Java框架-代理实现

代理在程序设计和开发中是一种常见的技术,特别是在Java语言中。其主要功能是作为中介,为其他对象提供一种代理以控制对这个对象的访问。代理模式是常用的Java设计模式,其特点在于代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

使用代理具有多种优势:首先,它可以在不修改原代码的基础上,扩展和增强实现,使代码解耦,并在代理中通过参数可以判断真实类,做出不同的响应或调用,从而更加灵活方便;其次,代理可以隐藏部分实现过程和细节,增加了代码的可读性和可维护性;最后,它可以起到保护目标对象的作用,使真实角色的操作更加纯粹,不用去关注一些公共的业务。

在实践中,根据创建代理的方式和时机,Java的代理模式可以被分为静态代理和动态代理。静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。而动态代理则可以在运行时生成代理对象。

以下文章主要针对如何实现代理来讲解

  • 静态代理

  • 动态代理

    • ByteBuddy实现
    • cglib实现
    • javassist实现
    • jdk实现

场景描述

早上起床,起床之前有个机器人说"早安"。晚上睡觉,睡觉之前有个机器人说"晚安"

抽象接口

抽象了两个接口

动物接口

java 复制代码
public interface Animal {
    
    void wakeup();

    void sleep();
}

这段代码定义了一个名为Animal的公共接口。这个接口包含两个方法:wakeup()sleep()

  • wakeup()方法用于唤醒动物,可能是通过发出声音或者其他方式来实现。
  • sleep()方法用于使动物进入睡眠状态,可能是通过停止活动或者其他方式来实现。

这个接口可以被其他类实现,以提供具体的动物行为。例如,可以创建一个名为Dog的类,实现Animal接口,并重写wakeup()sleep()方法,以提供狗的具体行为。

人接口

java 复制代码
public interface Person {

    void wakeup();

    void sleep();
}

这段代码定义了一个名为Person的公共接口。这个接口包含两个方法:wakeup()sleep()

  • wakeup()方法用于唤醒人,可能是通过发出声音或者其他方式来实现。
  • sleep()方法用于使人进入睡眠状态,可能是通过停止活动或者其他方式来实现。

这个接口可以被其他类实现,以提供具体的人的行为的抽象。例如,可以创建一个名为Student的类,实现Person接口,并重写wakeup()sleep()方法,以提供学生的具体行为。

实体

动物实体

小猫

java 复制代码
public class Cat implements Animal {

    private String name;

    public Cat() {
    }

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("小猫[{}]早晨醒来啦", name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("小猫[{}]晚上睡觉啦", name));
    }
}

这段代码定义了一个名为Cat的类,该类实现了Animal接口。Cat类有两个构造方法,一个是无参构造方法,另一个是带有一个字符串参数name的构造方法。在这两个构造方法中,都会将传入的name参数赋值给类的私有成员变量name

Cat类重写了Animal接口中的两个方法:wakeup()sleep()。当调用wakeup()方法时,会输出一条格式化的字符串,表示小猫已经醒来了。当调用sleep()方法时,也会输出一条格式化的字符串,表示小猫已经睡觉了。这里的StrUtil.format()方法用于格式化字符串,将小猫的名字插入到字符串中。

小狗

java 复制代码
public class Dog implements Animal {

    private String name;

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("小狗[{}]早晨醒来啦", name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("小狗[{}]晚上睡觉啦", name));
    }
}

这段代码定义了一个名为Dog的类,该类实现了Animal接口。Dog类有两个构造方法,一个是无参构造方法,另一个是带有一个字符串参数name的构造方法。在这两个构造方法中,都会将传入的name参数赋值给类的私有成员变量name

Dog类重写了Animal接口中的两个方法:wakeup()sleep()。当调用wakeup()方法时,会输出一条格式化的字符串,表示小狗已经醒来了。当调用sleep()方法时,也会输出一条格式化的字符串,表示小狗已经睡觉了。这里的StrUtil.format()方法用于格式化字符串,将小狗的名字插入到字符串中

人实体

学生

java 复制代码
public class Student implements Person{

    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
    }
}

这段代码定义了一个名为Student的类,该类实现了Person接口。Student类包含一个私有成员变量name,用于存储学生的名字。

Student类提供了两个构造方法:无参构造方法和带有一个字符串参数name的构造方法。无参构造方法没有为name赋值,而带有name参数的构造方法则将传入的name值赋给成员变量name

Student类重写了Person接口中的wakeup()sleep()方法。在这两个方法中,使用StrUtil.format()方法格式化输出字符串,表示学生已经醒来或睡觉。其中,{}占位符会被替换为学生的姓名。

医生

java 复制代码
public class Doctor implements Person{

    private String name;

    public Doctor() {
    }

    public Doctor(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("医生[{}]早晨醒来啦", name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("医生[{}]晚上睡觉啦", name));
    }
}

这段代码定义了一个名为Doctor的类,该类实现了Person接口。Doctor类包含一个私有成员变量name,用于存储医生的名字。

Doctor类提供了两个构造方法:无参构造方法和带有一个字符串参数name的构造方法。无参构造方法没有为name赋值,而带有name参数的构造方法则将传入的name值赋给成员变量name

Doctor类重写了Person接口中的wakeup()sleep()方法。在这两个方法中,使用StrUtil.format()方法格式化输出字符串,表示医生已经醒来或睡觉。其中,{}占位符会被替换为医生的名字

静态代理

动物代理

java 复制代码
public class AnimalProxy implements Animal {

    private final Animal animal;

    public AnimalProxy(Animal animal) {
        this.animal = animal;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~~");
        animal.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~~");
        animal.sleep();
    }
}

这段代码定义了一个名为AnimalProxy的类,它实现了Animal接口。AnimalProxy类包含一个私有成员变量animal,该变量的类型为Animal接口。

AnimalProxy类的构造方法中,接收一个Animal类型的参数,并将其赋值给成员变量animal

AnimalProxy类重写了Animal接口中的两个方法:wakeup()sleep()。在这两个方法中,首先输出一条消息("早安"或"晚安"),然后调用animal对象的相应方法(wakeup()sleep())。

这段代码的作用是创建一个代理对象,用于控制对原始动物对象的访问。通过代理对象,可以在调用原始动物对象的方法之前或之后执行额外的操作。

人代理

java 复制代码
public class PersonProxy implements Person {

    private final Person person;

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~");
        person.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~");
        person.sleep();
    }
}

这段代码定义了一个名为PersonProxy的类,它实现了Person接口。PersonProxy类包含一个私有成员变量person,其类型为Person接口。

PersonProxy类的构造方法中,接收一个Person类型的参数,并将其赋值给成员变量person

PersonProxy类重写了Person接口中的两个方法:wakeup()sleep()。在这两个方法中,首先输出一条消息("早安~"或"晚安~"),然后调用person对象的相应方法(wakeup()sleep())。

这段代码的作用是创建一个代理对象,用于控制对原始Person对象的访问。通过代理对象,可以在调用原始Person对象的方法之前或之后执行额外的操作

测试

java 复制代码
public class StaticProxyTestMain {

    public static void main(String[] args) {
        Person student = new Student("王同学");
        PersonProxy studentProxy = new PersonProxy(student);
        studentProxy.wakeup();
        studentProxy.sleep();

        Person doctor = new Doctor("张医生");
        PersonProxy doctorProxy = new PersonProxy(doctor);
        doctorProxy.wakeup();
        doctorProxy.sleep();

        Animal dog = new Dog("小黑狗");
        AnimalProxy dogProxy = new AnimalProxy(dog);
        dogProxy.wakeup();
        dogProxy.sleep();

        Animal cat = new Cat("小猫咪");
        AnimalProxy catProxy = new AnimalProxy(cat);
        catProxy.wakeup();
        catProxy.sleep();
    }
}

这段代码是一个Java程序,用于测试静态代理模式。它创建了四个对象:学生、医生、狗和猫,并分别使用它们的代理类进行操作。

首先,通过调用Person student = new Student("王同学");创建一个学生对象,并将其赋值给变量student。然后,通过调用PersonProxy studentProxy = new PersonProxy(student);创建一个学生代理对象,并将其赋值给变量studentProxy。接下来,通过调用studentProxy.wakeup();studentProxy.sleep();分别调用学生代理对象的wakeup()sleep()方法,以模拟学生的行为。

同样地,代码创建了一个医生对象、一个狗对象和一个猫对象,并分别使用它们的代理类进行操作。每个代理类都实现了相同的接口(例如PersonAnimal),并在代理类中添加了一些额外的功能,例如日志记录或权限检查。

这段代码的目的是演示如何使用静态代理模式来控制对原始对象的访问。通过使用代理类,可以在不修改原始对象的情况下,为其添加额外的功能。

动态代理

ByteBuddy实现

java 复制代码
public class ByteBuddyProxy {

    private final Object bean;

    public ByteBuddyProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws Exception {
        return new ByteBuddy().subclass(bean.getClass())
                .method(ElementMatchers.any())
                .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
                .make()
                .load(ByteBuddyProxy.class.getClassLoader())
                .getLoaded()
                .getDeclaredConstructor()
                .newInstance();
    }

    public static class AopInvocationHandler implements InvocationHandler {

        private final Object bean;

        public AopInvocationHandler(Object bean) {
            this.bean = bean;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if ("wakeup".equals(methodName)) {
                System.out.println("早安~~~");
            } else if ("sleep".equals(methodName)) {
                System.out.println("晚安~~~");
            }
            method.invoke(bean, args);
            return "method.invoke(bean, args)";
        }
    }
}

这段代码是一个使用Byte Buddy库创建代理对象的示例。它定义了一个名为ByteBuddyProxy的类,该类有一个私有成员变量bean,用于存储要代理的对象。

ByteBuddyProxy类的构造函数中,将传入的bean对象赋值给成员变量bean

getProxy()方法用于创建代理对象。它使用Byte Buddy库来动态生成代理类,并返回一个代理对象实例。具体步骤如下:

  1. 使用new ByteBuddy().subclass(bean.getClass())创建一个子类,该子类继承自传入的bean对象的类。
  2. 使用method(ElementMatchers.any())匹配所有方法,即拦截所有方法调用。
  3. 使用intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))设置拦截器为AopInvocationHandler实例,并将bean对象传递给它。
  4. 使用make()方法生成代理类。
  5. 使用load(ByteBuddyProxy.class.getClassLoader())加载代理类。
  6. 使用getLoaded()获取已加载的代理类。
  7. 使用getDeclaredConstructor()获取代理类的默认构造函数。
  8. 使用newInstance()创建代理对象实例。

AopInvocationHandler类实现了InvocationHandler接口,用于处理代理对象的方法调用。它有一个私有成员变量bean,用于存储要代理的对象。

invoke()方法中,首先获取被调用方法的名称。如果方法名称是"wakeup",则输出"早安";如果方法名称是"sleep",则输出"晚安"。然后,使用method.invoke(bean, args)调用原始对象的方法,并返回结果。

这段代码的作用是创建一个代理对象,当调用代理对象的"wakeup"或"sleep"方法时,会输出相应的问候语,并调用原始对象的对应方法。

java 复制代码
public class DynamicByteBuddyProxyTestMain {

    public static void main(String[] args) throws Exception {
        ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("王同学"));
        Student student = (Student) proxy.getProxy();
//        Object result = Student.class.getMethod("wakeup", null).invoke(student, null);
//        System.out.println("最后结果" + result);
        student.wakeup();
        student.sleep();
//
        proxy = new ByteBuddyProxy(new Doctor("张医生"));
        Doctor doctor = (Doctor) proxy.getProxy();
        doctor.wakeup();
        doctor.sleep();
//
        proxy = new ByteBuddyProxy(new Dog("小黑狗"));
        Dog dog = (Dog) proxy.getProxy();
        dog.wakeup();
        dog.sleep();

        proxy = new ByteBuddyProxy(new Cat("小猫咪"));
        Cat cat = (Cat) proxy.getProxy();
        cat.wakeup();
        cat.sleep();
    }
}

这段代码是使用Byte Buddy库创建代理对象并调用其方法的示例。首先,它创建了一个Student对象,然后使用ByteBuddyProxy类创建了一个代理对象,并通过代理对象调用了Student类的wakeup()和sleep()方法。接下来,它对Doctor、Dog和Cat类执行相同的操作。

cglib实现

java 复制代码
public class CglibProxy implements MethodInterceptor {

    private final Enhancer enhancer = new Enhancer();

    private final Object bean;

    public CglibProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy(){
        //设置需要创建子类的类
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if ("wakeup".equals(methodName)){
            System.out.println("早安~~~");
        }else if("sleep".equals(methodName)){
            System.out.println("晚安~~~");
        }
        //通过代理类调用父类中的方法
        //return proxy.invokeSuper(obj, args);

        //调用原bean的方法
        return method.invoke(bean,args);
    }
}

这段代码是一个使用CGLIB库创建代理对象的示例。它实现了一个名为CglibProxy的类,该类实现了MethodInterceptor接口。在构造函数中,它接收一个bean对象作为参数,并将其存储在一个私有变量中。

getProxy()方法用于创建代理对象。它首先设置需要创建子类的类为传入的bean对象的类,然后设置回调函数为当前类的实例。最后,通过调用enhancer的create()方法来动态创建子类实例并返回。

intercept()方法是实现MethodInterceptor接口的方法。它接收四个参数:obj(被代理的对象),method(被拦截的方法),args(方法的参数数组)和proxy(代理对象)。在这个方法中,首先获取被拦截方法的名称,并根据名称执行相应的操作。如果方法名为"wakeup",则输出"早安";如果方法名为"sleep",则输出"晚安"。

接下来,注释掉的代码表示通过代理类调用父类中的方法,但实际上并没有调用。而下面的代码则是直接调用原bean的方法,并将结果返回。

总之,这段代码的作用是创建一个代理对象,当调用其方法时,会根据方法名执行相应的操作,并最终调用原bean的方法。

java 复制代码
public class DynamicCglibProxyTestMain {

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy(new Student("王同学"));
        Student student = (Student) proxy.getProxy();
        student.wakeup();
        student.sleep();

        proxy = new CglibProxy(new Doctor("张医生"));
        Doctor doctor = (Doctor) proxy.getProxy();
        doctor.wakeup();
        doctor.sleep();

        proxy = new CglibProxy(new Dog("小黑狗"));
        Dog dog = (Dog) proxy.getProxy();
        dog.wakeup();
        dog.sleep();

        proxy = new CglibProxy(new Cat("小猫咪"));
        Cat cat = (Cat) proxy.getProxy();
        cat.wakeup();
        cat.sleep();
    }
}

这段代码是使用CGLIB库创建动态代理对象,并调用其方法的示例。首先创建一个CglibProxy类的对象,然后传入一个实现了接口的实例(如Student、Doctor、Dog、Cat等)。接着通过getProxy()方法获取代理对象,并将其强制转换为对应的接口类型。最后调用代理对象的方法(如wakeup、sleep等)。

  1. 创建一个CglibProxy类的对象,传入一个实现了接口的实例(如Student、Doctor、Dog、Cat等)。
  2. 通过getProxy()方法获取代理对象,并将其强制转换为对应的接口类型。
  3. 调用代理对象的方法(如wakeup、sleep等)。

javassist实现

java 复制代码
public class JavassitProxy {

    private final Object bean;

    public JavassitProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws IllegalAccessException, InstantiationException {
        ProxyFactory f = new ProxyFactory();
        f.setSuperclass(bean.getClass());
        f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));

        Class c = f.createClass();
        MethodHandler mi = (self, method, proceed, args) -> {
            String methodName = method.getName();
            if ("wakeup".equals(methodName)){
                System.out.println("早安~~~");
            }else if("sleep".equals(methodName)){
                System.out.println("晚安~~~");
            }
            return method.invoke(bean, args);
        };
        Object proxy = c.newInstance();
        ((Proxy)proxy).setHandler(mi);
        return proxy;
    }
}

这段代码是一个Javassit库的代理类,用于创建动态代理对象。它包含一个构造函数和一个getProxy()方法。

构造函数接受一个Object类型的参数bean,并将其存储在类的私有变量中。

getProxy()方法用于创建代理对象。首先,创建一个ProxyFactory对象f,并设置其超类为bean的类。然后,使用setFilter()方法设置过滤器,只允许调用名为"wakeup"或"sleep"的方法。

接下来,通过调用f.createClass()方法生成代理类的字节码,并将其转换为Class对象c。然后,定义一个MethodHandler对象mi,用于处理代理对象上的方法调用。在mi的invoke()方法中,根据方法名执行相应的操作,并调用原始对象的对应方法。

最后,通过调用c.newInstance()方法创建代理对象proxy,并使用((Proxy)proxy).setHandler(mi)将MethodHandler对象mi设置为代理对象的处理器。最终返回代理对象proxy。

这段代码的作用是创建一个代理对象,当调用该代理对象的"wakeup"或"sleep"方法时,会输出相应的问候语,并调用原始对象的对应方法。

java 复制代码
public class DynamicJavassistProxyTestMain {

    public static void main(String[] args) throws Exception{
        JavassitProxy proxy = new JavassitProxy(new Student("王同学"));
        Student student = (Student) proxy.getProxy();
        student.wakeup();
        student.sleep();

        proxy = new JavassitProxy(new Doctor("张医生"));
        Doctor doctor = (Doctor) proxy.getProxy();
        doctor.wakeup();
        doctor.sleep();

        proxy = new JavassitProxy(new Dog("小黑狗"));
        Dog dog = (Dog) proxy.getProxy();
        dog.wakeup();
        dog.sleep();

        proxy = new JavassitProxy(new Cat("小猫咪"));
        Cat cat = (Cat) proxy.getProxy();
        cat.wakeup();
        cat.sleep();
    }
}

这段代码是使用Javassist库创建动态代理的示例。它定义了一个名为DynamicJavassistProxyTestMain的类,其中包含一个main方法。在main方法中,首先创建了一个JavassitProxy对象,然后通过调用getProxy()方法获取代理对象。接着,将代理对象强制转换为相应的接口类型(如Student、Doctor、Dog和Cat),并调用其wakeup()和sleep()方法。这个过程对每个接口类型的对象都进行了重复。

jdk实现

java 复制代码
public class JdkProxy implements InvocationHandler {

    private final Object bean;

    public JdkProxy(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("wakeup".equals(methodName)){
            System.out.println("早安~~~");
        }else if("sleep".equals(methodName)){
            System.out.println("晚安~~~");
        }

        return method.invoke(bean, args);
    }
}

这段代码是一个Java的动态代理实现。它定义了一个名为JdkProxy的类,该类实现了InvocationHandler接口。

JdkProxy类中,有一个私有成员变量bean,用于存储被代理的对象。构造函数接受一个对象作为参数,并将其赋值给bean

invoke方法是InvocationHandler接口的核心方法,它接收三个参数:proxy表示代理对象本身,method表示要调用的方法,args表示方法的参数。

invoke方法中,首先获取方法的名称,然后根据方法名称执行相应的操作。如果方法名称为"wakeup",则输出"早安~~~";如果方法名称为"sleep",则输出"晚安~~~"。

最后,使用method.invoke(bean, args)调用原始对象的相应方法,并将结果返回。

这段代码的作用是创建一个动态代理对象,当调用代理对象的"wakeup"或"sleep"方法时,会输出相应的问候语,并调用原始对象的对应方法。

java 复制代码
public class DynamicJdkProxyTestMain {

    public static void main(String[] args) {
        JdkProxy proxy = new JdkProxy(new Student("王同学"));
        Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
        student.wakeup();
        student.sleep();

        proxy = new JdkProxy(new Doctor("张医生"));
        Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
        doctor.wakeup();
        doctor.sleep();

        proxy = new JdkProxy(new Dog("小黑狗"));
        Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
        dog.wakeup();
        dog.sleep();

        proxy = new JdkProxy(new Cat("小猫咪"));
        Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
        cat.wakeup();
        cat.sleep();
    }
}

这段代码是使用Java的动态代理机制创建了四个不同的对象:Student、Doctor、Dog和Cat。每个对象都有一个对应的代理对象,通过代理对象可以调用这些对象的方法。

具体来说,代码首先创建了一个JdkProxy对象,然后使用Proxy.newProxyInstance方法创建了一个代理对象。这个方法需要三个参数:类加载器、接口数组和InvocationHandler对象。类加载器用于加载代理对象的类,接口数组指定了代理对象需要实现的接口,InvocationHandler对象定义了当代理对象的方法被调用时的行为。

在这个例子中,代理对象实现了Person接口,所以它可以调用Student、Doctor等对象的方法。同样,代理对象也实现了Animal接口,所以它可以调用Dog、Cat等对象的方法。

最后,代码调用了代理对象的方法,如student.wakeup()和student.sleep(),这些方法实际上是调用了原始对象的对应方法。

代码链接 gitee.com/youhei/prox...

相关推荐
code.song1 小时前
电影评论|基于springBoot的电影评论网站设计与实现(附项目源码+论文+数据库)
数据库·spring boot·后端
Dovir多多2 小时前
渗透测试入门学习——php与mysql数据库连接、使用session完成简单的用户注册、登录
前端·数据库·后端·mysql·安全·html·php
Flying_Fish_roe2 小时前
Spring Boot-WebSocket相关问题
spring boot·后端·websocket
骆晨学长2 小时前
基于springboot学生健康管理系统的设计与实现
java·开发语言·spring boot·后端·spring
骆晨学长2 小时前
基于Springboot的医疗健康助手开题报告
java·spring boot·后端
我是小酒2 小时前
掌握 Spring:从新手到高手的常见问题汇总
java·后端·spring·springboot
if时光重来2 小时前
springboot项目实现导出excel动态设置表头
spring boot·后端·excel
code.song2 小时前
校园社团|基于springBoot的校园社团信息管理系统设计与实现(附项目源码+论文+数据库)
数据库·spring boot·后端
白总Server2 小时前
php语言基本语法
开发语言·ide·后端·golang·rust·github·php
捕风捉你2 小时前
Spring 源码解读:手动实现Environment抽象与配置属性
后端·spring