【Spring】静态代理、动态代理

Java中,代理模式是一种设计模式,用于通过代理对象控制对目标对象的访问。代理可以分为静态代理动态代理,其中动态代理又包括JDK动态代理CGLIB动态代理。这些机制在Spring框架中广泛用于AOP(面向切面编程)、事务管理等场景,尤其与Bean的创建和循环依赖解决密切相关。以下是详细说明,包括代码示例、优缺点、与Spring Bean的关联,以及在循环依赖中的表现。


1. 静态代理

  • 定义:静态代理是在编译期手动创建代理类,代理类和目标类实现相同的接口,代理类持有目标对象的引用,并在方法调用前后添加额外逻辑。

  • 特点

    • 代理类和目标类需实现同一接口或继承同一父类。
    • 代理类在代码中硬编码,针对每个目标类需编写特定代理类。
    • 逻辑固定,扩展性较差。
  • 代码示例

    // 公共接口
    public interface UserService {
        void save();
    }
    
    // 目标类
    public class UserServiceImpl implements UserService {
        @Override
        public void save() {
            System.out.println("Saving user...");
        }
    }
    
    // 静态代理类
    public class UserServiceProxy implements UserService {
        private UserService target;
    
        public UserServiceProxy(UserService target) {
            this.target = target;
        }
    
        @Override
        public void save() {
            System.out.println("Before save: Logging...");
            target.save();
            System.out.println("After save: Logging...");
        }
    }
    
    // 使用
    public class Main {
        public static void main(String[] args) {
            UserService target = new UserServiceImpl();
            UserService proxy = new UserServiceProxy(target);
            proxy.save();
        }
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 优点

    • 实现简单,逻辑清晰。
    • 适合目标类较少、代理逻辑固定的场景。
  • 缺点

    • 扩展性差:每个目标类需编写一个代理类,代码重复。
    • 维护困难:如果接口方法变更,所有代理类需修改。
  • 适用场景

    • 简单场景,目标类和代理逻辑固定。
    • 学习代理模式或临时实现。
  • 与Spring Bean的关系

    • 静态代理不常用于Spring容器,因为Spring更倾向于动态代理来实现AOP。
    • 如果在Spring中使用静态代理,需手动创建代理Bean并注入目标Bean,可能影响单例Bean的循环依赖解决(需显式管理代理和目标Bean)。

2. 动态代理

动态代理在运行时动态生成代理类,无需为每个目标类手动编写代理类。Java中主要有两种实现:JDK动态代理CGLIB动态代理

2.1 JDK动态代理
  • 定义:基于Java反射API(java.lang.reflect.Proxy),在运行时动态生成代理类,代理类实现目标对象实现的接口。

  • 特点

    • 要求目标类实现至少一个接口,代理类通过接口动态生成。
    • 使用InvocationHandler处理方法调用,代理逻辑集中在invoke方法中。
    • 代理对象是接口的实现,调用接口方法时转发到InvocationHandler
  • 代码示例

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    // 公共接口
    public interface UserService {
        void save();
    }
    
    // 目标类
    public class UserServiceImpl implements UserService {
        @Override
        public void save() {
            System.out.println("Saving user...");
        }
    }
    
    // InvocationHandler
    public class LoggingInvocationHandler implements InvocationHandler {
        private final Object target;
    
        public LoggingInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before save: Logging...");
            Object result = method.invoke(target, args);
            System.out.println("After save: Logging...");
            return result;
        }
    }
    
    // 使用
    public class Main {
        public static void main(String[] args) {
            UserService target = new UserServiceImpl();
            InvocationHandler handler = new LoggingInvocationHandler(target);
            UserService proxy = (UserService) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    handler
            );
            proxy.save();
        }
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 优点

    • 动态生成代理类,无需手动编写。
    • 代理逻辑集中于InvocationHandler,易于复用。
    • 符合接口规范,适合基于接口的开发。
  • 缺点

    • 必须实现接口:目标类若无接口,无法使用JDK动态代理。
    • 性能略低于CGLIB(因反射调用)。
  • 适用场景

    • 目标类实现了接口(如Spring的AOP默认实现)。
    • 需要动态增强接口方法(如日志、事务)。
  • 与Spring Bean的关系

    • Spring AOP默认使用JDK动态代理(当目标Bean实现接口时)。
    • 单例Bean的代理对象由Spring容器管理,注入时返回代理对象而非原始Bean。
    • 循环依赖:Spring通过三级缓存支持代理对象的循环依赖。例如,BeanABeanB循环依赖,Spring在三级缓存中存储ObjectFactory,在需要时生成代理对象,确保循环依赖正确解析。

2.2 CGLIB动态代理
  • 定义:基于CGLIB(Code Generation Library),通过字节码生成技术在运行时动态创建目标类的子类作为代理类。

  • 特点

    • 不要求目标类实现接口,代理类通过继承目标类实现。
    • 使用MethodInterceptor拦截方法调用,代理逻辑在intercept方法中定义。
    • 代理类是目标类的子类,覆盖父类方法以添加额外逻辑。
  • 代码示例

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    // 目标类(无接口)
    public class UserService {
        public void save() {
            System.out.println("Saving user...");
        }
    }
    
    // MethodInterceptor
    public class LoggingInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("Before save: Logging...");
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("After save: Logging...");
            return result;
        }
    }
    
    // 使用
    public class Main {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserService.class);
            enhancer.setCallback(new LoggingInterceptor());
            UserService proxy = (UserService) enhancer.create();
            proxy.save();
        }
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 依赖

    • 需要引入CGLIB库(Spring Boot中通过spring-core已包含CGLIB)。
    • 示例依赖(Maven):
      <dependency>
          <groupId>cglibgroupId>
          <artifactId>cglibartifactId>
          <version>3.3.0version>
      dependency>
      
  • 优点

    • 不需要接口,适用范围更广(可代理普通类)。
    • 性能略高于JDK动态代理(因字节码生成优于反射)。
    • 支持继承关系,代理类可调用父类的非final方法。
  • 缺点

    • 无法代理final类或final方法(因无法继承或覆盖)。
    • 依赖外部库(CGLIB)。
    • 生成的代理类是子类,可能引发继承相关问题。
  • 适用场景

    • 目标类无接口(如Spring AOP中无接口的Bean)。
    • 需要高性能动态代理。
  • 与Spring Bean的关系

    • Spring AOP在目标Bean无接口时使用CGLIB动态代理。
    • 代理对象作为单例Bean存储在Spring容器中,注入时返回代理对象。
    • 循环依赖:与JDK动态代理类似,CGLIB代理对象通过三级缓存的ObjectFactory生成,支持单例Bean的循环依赖解决。

3. 静态代理 vs JDK动态代理 vs CGLIB动态代理

特性 静态代理 JDK动态代理 CGLIB动态代理
实现方式 手动编写代理类 基于反射(Proxy 基于字节码(子类生成)
目标类要求 需实现接口或继承 必须实现接口 无需接口(但不能是final类)
代理对象类型 实现接口或继承目标类 实现接口 目标类的子类
性能 高(无运行时开销) 中等(反射调用) 较高(字节码生成)
扩展性 差(需为每个类写代理) 高(动态生成) 高(动态生成)
依赖 JDK内置 CGLIB库
循环依赖支持 手动管理,可能复杂 通过三级缓存支持 通过三级缓存支持
Spring使用场景 极少使用 默认(有接口时) 无接口时或强制指定

4. 代理与Spring Bean循环依赖

  • 单例Bean与代理

    • Spring AOP使用动态代理(JDK或CGLIB)为Bean生成代理对象,增强功能(如事务、日志)。
    • 代理对象存储在Spring的一级缓存(singletonObjects)中,客户端注入的是代理对象而非原始Bean。
    • 三级缓存的singletonFactories在循环依赖时生成代理对象。例如:
      • BeanABeanB循环依赖,BeanA启用了AOP。
      • Spring在三级缓存中存储BeanAObjectFactory,在BeanB需要BeanA时生成代理对象。
      • 确保循环依赖正确解析,注入的BeanA是代理对象。
  • 循环依赖示例

    @Component
    public class BeanA {
        @Autowired
        private BeanB beanB;
    
        @Transactional // 启用AOP,生成代理
        public void doSomething() {
            System.out.println("BeanA doSomething");
        }
    }
    
    @Component
    public class BeanB {
        @Autowired
        private BeanA beanA;
    
        public void doSomething() {
            System.out.println("BeanB doSomething");
        }
    }
    
    • 解决流程
      1. 创建BeanA,实例化后放入三级缓存(ObjectFactory)。
      2. BeanA注入beanB,触发BeanB创建,放入三级缓存。
      3. BeanB需要BeanA,从三级缓存获取BeanA的代理对象(因@Transactional)。
      4. BeanB完成,放入一级缓存;BeanA继续注入beanB,完成并放入一级缓存。
    • 结果BeanA的代理对象和BeanB成功注入,循环依赖解决。
  • 静态代理与循环依赖

    • 静态代理需手动创建代理Bean并注入目标Bean,可能干扰Spring的三级缓存机制。
    • 如果代理Bean和目标Bean都是单例,需确保Spring正确管理依赖关系,否则可能导致循环依赖失败。

5. Spring中代理的选择

  • 默认行为
    • 如果目标Bean实现接口,Spring使用JDK动态代理
    • 如果目标Bean无接口,Spring使用CGLIB动态代理
  • 强制CGLIB
    • @EnableAspectJAutoProxy中设置proxyTargetClass = true
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      @Configuration
      public class AppConfig {}
      
    • 或在application.properties中:
      spring.aop.proxy-target-class=true
      
  • 注意事项
    • JDK动态代理生成接口代理,注入的Bean只能通过接口类型访问。
    • CGLIB生成子类代理,注入的Bean可以通过类类型访问,但需注意final方法不可代理。
    • 循环依赖中,Spring确保代理对象正确生成,客户端无需关心代理类型。

6. 注意事项

  • 性能
    • 静态代理性能最高(无运行时开销)。
    • CGLIB略快于JDK动态代理,但初始化时字节码生成有一定开销。
  • 线程安全
    • 代理对象(如单例Bean)在多线程环境下共享,需确保代理逻辑线程安全。
    • Spring的AOP代理通常无状态,天然线程安全。
  • 循环依赖
    • 动态代理(JDK/CGLIB)通过三级缓存无缝支持单例Bean的循环依赖。
    • 静态代理需手动管理,可能需@Lazy或Setter注入解决循环依赖。
  • AOP与代理
    • Spring AOP依赖动态代理实现切面(如@Transactional@Around)。
    • 三级缓存的ObjectFactory确保AOP代理在循环依赖中正确生成。
  • 限制
    • JDK动态代理要求接口,CGLIB无法代理final类/方法。
    • 静态代理扩展性差,不适合复杂系统。

7. 总结

  • 静态代理
    • 手动编写代理类,简单但扩展性差,适合简单场景。
    • 不常用于Spring,循环依赖需手动管理。
  • JDK动态代理
    • 基于接口,动态生成代理类,Spring AOP默认使用。
    • 支持循环依赖,适合接口驱动开发。
  • CGLIB动态代理
    • 基于子类,适用于无接口的类,性能略优。
    • 支持循环依赖,Spring在无接口时使用。
  • Spring中的应用
    • 动态代理是Spring AOP的核心,结合三级缓存解决单例Bean的循环依赖。
    • 推荐动态代理(JDK或CGLIB)而非静态代理,以支持复杂场景和AOP。

你可能感兴趣的:(Spring,spring,代理模式,java)