动态代理-整理

1. JDK 动态代理

JDK 自带的动态代理基于 接口 实现。它主要依赖 java.lang.reflect.Proxy 类和 InvocationHandler 接口。

  • InvocationHandler:用于定义代理类中方法的处理逻辑。代理对象的方法调用会被重定向到 InvocationHandlerinvoke 方法。
  • Proxy.newProxyInstance:创建代理对象,接受目标类的类加载器、接口列表、InvocationHandler 实例作为参数。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Service {
    void execute();
}

class ServiceImpl implements Service {
    public void execute() {
        System.out.println("Service is executing...");
    }
}

class ServiceInvocationHandler implements InvocationHandler {
    private final Object target;

    public ServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before executing service...");
        Object result = method.invoke(target, args);
        System.out.println("After executing service...");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        Service proxyInstance = (Service) Proxy.newProxyInstance(
                service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                new ServiceInvocationHandler(service)
        );
        proxyInstance.execute();
    }
}

在这个例子中:

  • ServiceInvocationHandler 是代理类,实现了 InvocationHandler 接口。
  • 代理对象 proxyInstance 会拦截 execute 方法的调用,并执行自定义的前后逻辑。

2. CGLIB 动态代理

CGLIB(Code Generation Library)基于 子类 来实现动态代理。与 JDK 动态代理不同,CGLIB 允许为没有实现接口的类创建代理类。

CGLIB 通过生成目标类的子类,并在子类中拦截方法调用(使用字节码增强技术),添加代理逻辑。Spring AOP 在没有接口的情况下,通常会使用 CGLIB 来生成代理

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

class Service {
    public void execute() {
        System.out.println("Service is executing...");
    }
}

class ServiceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before executing service...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After executing service...");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service.class);
        enhancer.setCallback(new ServiceMethodInterceptor());
        
        Service proxy = (Service) enhancer.create();
        proxy.execute();
    }
}

在这个例子中:

  • ServiceMethodInterceptor 实现了 MethodInterceptor 接口,用于拦截方法调用。
  • Enhancer 类用于生成代理对象,并将拦截器应用到代理对象的所有方法。

总结

  • JDK 动态代理:适用于有接口的类,基于接口实现代理。
  • CGLIB 动态代理:适用于无接口的类,基于子类继承实现代理。

在 Spring 中,默认使用 JDK 动态代理。如果目标类没有接口,则自动切换到 CGLIB 动态代理。

什么叫动态代理

动态代理是一种在程序运行时动态生成代理对象的技术。它的核心概念是:通过代理对象拦截方法调用,并在方法执行前后插入额外的操作,比如日志记录、事务管理、权限检查等。

静态代理不同,动态代理在编译期并不知道代理的对象,而是通过运行时生成的方式实现。这使得动态代理更加灵活,适用于许多不同的类或接口,而不需要在每个类上重复编写代理代码。

动态代理的核心特点

  1. 动态生成代理对象:不需要提前定义代理类,在运行时生成代理对象。这使得代理可以应用到不同的类或接口。
  2. 拦截方法调用:代理对象在方法被调用时可以拦截该方法,从而在方法执行前后加入额外的逻辑。
  3. 对目标对象透明:代理对象和目标对象对外表现出一致的接口,调用者通常不需要知道自己调用的是代理对象还是原始对象。

动态代理的典型实现方式

  1. JDK 动态代理

    • JDK 动态代理只能代理实现了接口的类,因为它通过接口创建代理对象。
    • 使用 Java 的 Proxy 类和 InvocationHandler 接口来实现。
  2. CGLIB 动态代理

    • CGLIB 是基于字节码生成技术实现的,能够代理没有实现接口的类(基于继承的方式生成子类)。
    • 使用 Enhancer 类和 MethodInterceptor 接口来创建代理对象。

为什么使用动态代理?

动态代理的主要目的是分离业务逻辑与通用功能,从而实现面向切面编程(AOP)。例如,日志记录、事务管理、缓存控制等功能往往是应用程序中的通用需求,但将这些逻辑写在业务代码中会造成重复和耦合。动态代理提供了一种方式,在不修改业务代码的情况下实现这些功能,提升代码的清晰性和维护性。

在 Spring 和其他框架中,有许多常用的注解是基于动态代理实现的。这些注解通常用于实现横切关注点(如事务、缓存、异步调用等),通过动态代理拦截方法调用,插入相应的逻辑。以下是一些常见的基于动态代理的注解:

1. @Transactional(事务管理)

  • 用途:用于方法或类上,用于声明一个方法或类中的方法需要事务管理。
  • 实现方式:Spring 使用动态代理在方法执行前开启事务,执行完后提交或回滚事务。
  • 代理逻辑:拦截器会在方法执行前开始事务,方法执行后根据结果决定提交还是回滚事务。

2. @Cacheable@CachePut@CacheEvict(缓存管理)

  • 用途:用于缓存数据。@Cacheable 会在方法执行前检查缓存是否存在,如果存在则直接返回缓存中的结果;@CachePut 用于更新缓存;@CacheEvict 用于清除缓存。
  • 实现方式:使用动态代理在方法执行前后检查或操作缓存,以避免重复计算和加快数据读取。
  • 代理逻辑:在方法调用前检查缓存,如果有缓存数据则直接返回结果,否则执行方法并将结果存入缓存。

3. @Async(异步调用)

  • 用途:用于将某个方法异步执行,即方法在后台执行,调用者不必等待方法完成。
  • 实现方式:Spring 使用动态代理将该方法放入线程池异步执行。
  • 代理逻辑:拦截方法调用,将方法提交到线程池执行,并返回 Future 对象或者不等待结果。

4. @Scheduled(任务调度)

  • 用途:用于定时执行某个方法。
  • 实现方式:Spring 使用动态代理结合线程池或定时任务调度器,在指定的时间或周期执行方法。
  • 代理逻辑:将方法注册到调度器中,按指定时间间隔或 cron 表达式执行。

5. @PreAuthorize@PostAuthorize@Secured(权限控制)

  • 用途:用于方法级的权限控制,在方法执行前或执行后验证用户权限。
  • 实现方式:Spring Security 使用动态代理在方法执行前后检查用户是否有权限执行该操作。
  • 代理逻辑:拦截方法调用,根据注解配置的权限规则检查用户权限,若无权限则抛出异常。

6. @Retryable(重试机制)

  • 用途:用于在方法执行失败时自动重试,通常用于网络请求、数据库连接等不稳定操作。
  • 实现方式:Spring Retry 使用动态代理在方法调用失败时进行重试。
  • 代理逻辑:拦截方法调用,在失败时按配置的重试规则(重试次数、延迟时间等)重新执行方法。

7. @RestController / @Controller(控制层代理)

  • 用途:用于标记 Spring MVC 的控制器,处理 HTTP 请求。
  • 实现方式:Spring 使用动态代理生成控制器对象,并管理控制器的生命周期和请求响应处理。
  • 代理逻辑:在请求处理前后可以插入过滤器、拦截器等,统一管理请求、响应、异常处理等逻辑。

8. @Around(AOP 切面注解)

  • 用途:用于定义切面方法,在目标方法执行前后加入额外逻辑。
  • 实现方式:Spring AOP 使用动态代理创建代理对象,将切面方法插入目标方法的执行链。
  • 代理逻辑:拦截方法调用,将切面逻辑插入到方法前、后,甚至替换方法的实际调用。

总结

这些注解大部分通过动态代理来实现方法的拦截和控制逻辑,避免了在业务代码中混入横切逻辑,使代码更清晰、可维护。在使用这些注解时,Spring 自动生成代理对象并处理复杂的底层实现,我们只需简单的注解配置即可享受代理带来的功能。

记忆已更新

在 Java 开发中,关于动态代理的问题通常围绕其原理、使用场景、优缺点和实现方式展开。以下是一些常见的问题和可能的答案方向

1. 什么是动态代理?

  • :动态代理是在运行时创建代理对象的机制。Java 动态代理允许在不修改原始类的情况下,为目标对象添加额外的功能,通常用于拦截方法调用。JDK 动态代理依赖接口,CGLIB 动态代理可以代理类。

2. Java 中有哪些实现动态代理的方式?

  • :主要有两种方式:
    1. JDK 动态代理:通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口实现,适用于实现了接口的类。
    2. CGLIB 动态代理:基于字节码生成的代理库,通过继承目标类来创建代理对象,适用于没有接口的类。

3. 动态代理的应用场景有哪些?

  • :动态代理通常用于:
    • AOP(面向切面编程):如日志、事务管理、权限控制等。
    • RPC 框架:远程调用时拦截方法,实现通信和序列化。
    • 代理模式和装饰器模式的实现:动态增加功能而不修改原有代码。

4. JDK 动态代理的原理是什么?

  • :JDK 动态代理使用 Proxy.newProxyInstance 创建代理对象,需要一个 InvocationHandler 实现。每次调用代理对象的方法时,都会转发给 InvocationHandler.invoke 方法,通过反射执行目标方法。

5. CGLIB 动态代理的原理是什么?

  • :CGLIB 通过继承目标类并重写其方法实现代理。它基于 ASM 框架操作字节码,因此能代理没有接口的类,但不能代理 final 类和 final 方法。

6. 动态代理与静态代理有什么区别?

  • :静态代理需要手动实现代理类,代码量较大,代理逻辑是编译期决定的;动态代理在运行时生成代理类,灵活性高,代码简洁。

7. Spring AOP 是如何实现动态代理的?

  • :Spring AOP 默认使用 JDK 动态代理,如果目标类没有实现接口,则使用 CGLIB 动态代理。Spring 会为 @Around@Before 等切面方法创建代理对象,并在调用链中添加 MethodInterceptor 来实现切面逻辑。

8. 如何选择 JDK 动态代理和 CGLIB?

  • :如果目标类实现了接口,优先选择 JDK 动态代理,效率较高;如果没有接口,选择 CGLIB,但要注意不能代理 final 类或方法。

9. 可以解释一下 InvocationHandler 的作用吗?

  • InvocationHandler 是 JDK 动态代理的核心接口,每次代理对象的方法被调用时都会调用 invoke 方法,开发者可以在 invoke 方法中编写额外的逻辑,比如日志、事务管理等。
  • 总结一下选择规则
  • 没有实现接口的类:只能使用 CGLIB 动态代理

  • 实现了接口的类

    • 默认使用 JDK 动态代理
    • 如果强制使用 CGLIB(例如,Spring 中设置 proxyTargetClass=true),那么即使类实现了接口,也会使用 CGLIB。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值