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代理侧重于类本身。

相关推荐
冰帝海岸4 小时前
01-spring security认证笔记
java·笔记·spring
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
代码小鑫8 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖8 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
哔哥哔特商务网10 小时前
一文探究48V新型电气架构下的汽车连接器
架构·汽车
007php00710 小时前
GoZero 上传文件File到阿里云 OSS 报错及优化方案
服务器·开发语言·数据库·python·阿里云·架构·golang
斗-匕10 小时前
Spring事务管理
数据库·spring·oracle
码上有前12 小时前
解析后端框架学习:从单体应用到微服务架构的进阶之路
学习·微服务·架构
Doker 多克13 小时前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt