Spring框架中的动态代理

在Spring框架中,动态代理主要分为两种类型:JDK动态代理和CGLIB动态代理。这两种代理机制在Spring的AOP(面向切面编程)和事务管理等功能中起到关键作用。

1.JDK动态代理

在Spring框架中,JDK动态代理主要用于实现AOP(面向切面编程),特别是在处理基于接口的代理时。

1. 实现原理

JDK动态代理利用了Java的反射机制。其核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

  • Proxy类 :用于在运行时动态创建代理对象。它的newProxyInstance方法可以创建实现了指定接口的代理对象。

  • 创建代理实例: 这个方法需要三个参数:

    • 类加载器(ClassLoader):用于定义代理类。
    • 接口数组(Class<?>[] interfaces):代理类需要实现的接口列表。
    • 调用处理器(InvocationHandler):当代理对象的方法被调用时,将会执行调用处理器的invoke方法。
    java 复制代码
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) 
        throws IllegalArgumentException {
        // 核心逻辑:创建代理类并返回其实例

    上边的核心逻辑补充:ProxyClassFactory是一个私有的静态内部类,位于Proxy类中。它的作用是生成代理类的字节码。它是通过Java的字节码操作完成的,这部分工作实际上是由Java核心库中的sun.misc.ProxyGenerator类完成的,ProxyClassFactory会为指定的接口生成一个新的类(代理类)。这个代理类会实现所有提供的接口,并将所有方法调用转发到一个InvocationHandler。

  • InvocationHandler接口 :用于定义代理对象的方法调用的行为。当代理对象的方法被调用时,实际上是调用InvocationHandlerinvoke方法。 调用处理逻辑: invoke`方法接收三个参数:

    • 代理对象(Object proxy):调用该方法的代理实例。
    • 方法对象(Method method):对应于在代理实例上调用的接口方法。
    • 参数数组(Object[] args):传递给方法的参数。
    java 复制代码
    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }

2. 实际代码案例

假设你有一个接口和一个实现类,你想通过JDK动态代理来创建代理对象:

  • 接口(MyService.java):

    java 复制代码
    public interface MyService {
        void performAction();
    }
  • 实现类(MyServiceImpl.java):

    java 复制代码
    public class MyServiceImpl implements MyService {
        @Override
        public void performAction() {
            System.out.println("Action performed");
        }
    }
  • 创建代理

    java 复制代码
    public class MyServiceProxy {
        public static void main(String[] args) {
            MyService original = new MyServiceImpl();
            MyService proxy = (MyService) Proxy.newProxyInstance(
                    MyService.class.getClassLoader(),
                    new Class[]{MyService.class},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // 添加代理逻辑(例如日志记录、安全检查等)
                            System.out.println("Before invoking " + method.getName());
                            
                            // 调用实际对象的方法
                            Object result = method.invoke(original, args);
                            
                            System.out.println("After invoking " + method.getName());
                            return result;
                        }
                    });
            
            // 使用代理对象
            proxy.performAction();
        }
    }

在上述示例中,MyServiceProxy类创建了MyService的一个代理实例。在代理实例上调用performAction方法时,实际上会先执行InvocationHandlerinvoke方法,然后才调用真实对象MyServiceImplperformAction方法。

2.CGLIB动态代理

在Spring Boot中,CGLIB(Code Generation Library)动态代理是一种基于类的代理机制,用于创建目标类的子类作为代理。它在代理没有实现接口的类或者需要通过类代理而非接口代理的场景中特别有用。

1. 实现原理

CGLIB代理通过在运行时动态生成被代理类的子类来实现代理功能。它不依赖于接口。

  • Enhancer类 :CGLIB的核心是Enhancer类,用于生成代理对象。它可以设置超类(被代理的类),并将方法调用重定向到MethodInterceptor

  • Enhancer的创建过程

    java 复制代码
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(MyClass.class); // 设置被代理类
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            // 在调用方法前后可以执行的代码
            return proxy.invokeSuper(obj, args); // 调用原始对象的方法
        }
    });
    MyClass proxy = (MyClass) enhancer.create(); // 创建代理对象

    上边的创建代理对象补充:CGLIB在运行时使用ASM(一个Java字节码操作和分析框架)动态生成代理类的字节码,并加载到JVM中。生成的字节码实际上扩展了被代理类,并在运行时作为该类的子类实例化。

  • MethodInterceptor接口 :这是一个回调接口,用于拦截对原始类方法的调用。通过实现MethodInterceptor,可以在方法调用前后添加自定义行为。

  • MethodInterceptor

    java 复制代码
    public interface MethodInterceptor extends Callback {
        Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
    }

2. 实际代码案例

假设有一个没有实现任何接口的类,你想通过CGLIB动态代理来创建代理对象:

  • 目标类(MyClass.java):

    java 复制代码
    public class MyClass {
        public void performAction() {
            System.out.println("Action performed in MyClass");
        }
    }
  • 创建代理

    java 复制代码
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class MyClassCglibProxy {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MyClass.class);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("Before method " + method.getName());
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("After method " + method.getName());
                    return result;
                }
            });
    
            MyClass myClassProxy = (MyClass) enhancer.create();
            myClassProxy.performAction();
        }
    }

在上述示例中,MyClassCglibProxy类使用Enhancer创建了MyClass的代理。代理对象的performAction方法被调用时,实际上会先执行MethodInterceptorintercept方法,然后再调用真实对象的performAction方法。

3.在Spring Boot中的使用动态代理

在Spring Boot中,当使用@Transactional注解时,Spring Boot框架会根据目标类是否实现了接口自动选择使用JDK动态代理还是CGLIB动态代理。这一过程涉及到Spring AOP的配置和代理创建机制。

1. 引入依赖

首先,确保Spring Boot项目中包含了事务管理和Spring AOP的依赖:

xml 复制代码
<!-- Spring Boot Starter AOP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Spring Boot Starter Data JPA (如果使用JPA) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2. 使用@Transactional

在服务类或方法上使用@Transactional注解。Spring将基于此注解创建代理来管理事务。

示例代码

假设有一个服务接口和一个实现类:

java 复制代码
public interface MyService {
    void performAction();
}

@Service
public class MyServiceImpl implements MyService {
    @Override
    @Transactional
    public void performAction() {
        // 业务逻辑
    }
}

3. 代理的选择

Spring Boot如何决定使用哪种代理方式:

  • 如果目标类实现了接口 :Spring Boot默认使用JDK动态代理。在上面的例子中,由于MyServiceImpl实现了MyService接口,Spring将默认使用JDK动态代理。
  • 如果目标类没有实现接口 :Spring Boot将使用CGLIB代理。如果MyServiceImpl没有实现任何接口,Spring将自动使用CGLIB来创建代理。

4. 代理的创建过程

Spring使用ProxyFactory来创建代理。这个过程涉及以下步骤:

  1. 检查目标类:确定目标类是否实现了接口。
  2. 选择代理策略:基于检查结果选择JDK代理或CGLIB代理。
  3. 配置代理 :根据@Transactional等注解配置代理,如拦截器TransactionInterceptor,它管理事务的边界和行为。

5. 事务的管理

当调用被@Transactional注解的方法时,事务拦截器会根据注解的配置管理事务。这包括:

  • 开启新事务或加入现有事务。
  • 在方法正常返回时提交事务。
  • 在方法抛出异常时回滚事务。

4.总结

JDK动态代理适用于代理实现了接口的类,依赖Java自带的反射机制,而CGLIB代理可以代理没有实现接口的类,通过运行时生成类的子类来实现代理。JDK代理侧重于接口,CGLIB代理侧重于类本身。

相关推荐
稻草人222217 小时前
java Excel 导出 ,如何实现八倍效率优化,以及代码分层,方法封装
后端·架构
数据智能老司机18 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机19 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
程序员鱼皮1 天前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码
SimonKing1 天前
接口调用总失败?试试Spring官方重试框架Spring-Retry
java·后端·程序员
bobz9651 天前
k8s svc 实现的技术演化:iptables --> ipvs --> cilium
架构
云舟吖1 天前
基于 electron-vite 实现一个 RPA 网页自动化工具
前端·架构
brzhang1 天前
当AI接管80%的执行,你“不可替代”的价值,藏在这20%里
前端·后端·架构
Lei活在当下1 天前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计