• Spring——AOP用到的代理模式


    AOP,面向切面编程,是Spring框架中的核心思想之一;在Spring中是通过动态代理来实现的,在目标类的基础上增加切面逻辑,对原对象进行增强;

    SpringAOP的源码中用到了两种动态代理来实现拦截切入功能:JDK动态代理和CGlib动态代理,两种方法的适用条件和效率各有不同,各有优劣;

    本来准备写一篇关于SpringAOP相关的文章的,这篇文章介绍,作为Spring AOP的基础知识,介绍代理设计模式以及Java中的几种不同的代理模式的实现:静态代理、JDK动态代理和CGlib代理;

    1. 代理模式

    代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能;简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式;代理模式UML类图如下(来自维基百科代理模式);

    [delegate]:授(权),把……委托给他人;委派……为代表,任命;

    关于代理模式,如果觉得"代理"这个词难以理解,可以把它翻译成"代表";即原本由A做的事doAction,改为了由proxy$A代表A来做;总结下来关注几个点即可:

    1. 为什么proxy$A可以代表A?

    ——因为他们的类型(父类/接口)都是IA;

    2. 为什么需要proxy$A来代表A?

    ——因为需要在A执行某件事doAction的前后,再插入一些其他的事宜,让整件事做的更加完善,proxy$A可以看做是IA类型对外的对象,核心是执行A的doAction方法;

    3. 如何让proxy$A能在A.doAction前后执行其他方法?

    ——(1)通过持有A对象;(2)通过继承A对象获得其doAction方法;

    实际生产中,这个"在A执行某件事doAction的前后,再插入一些其他的事宜",一般是一些通用的处理逻辑,它不是某个特定类独有的,例如:日志打印、异常捕获、接口出入参统一处理等;接下来用几个例子分别介绍Java的3种代理模式的实现,包括:静态代理、动态代理和CGlib代理

    2. 静态代理

    这种代理方式需要代理对象和原对象实现一样的接口,而代理对象需要持有原对象,一般通过属性注入;在代理对象实现的同一接口的抽象方法中,包括两部分:(1)自己的增强逻辑和(2)原对象的方法调用;

    优点:可以在不修改原对象所在类的前提下扩展(增强)原对象的功能,因为代理对象和原对象实现相同的接口,只需要将对原对象的调用换成代理对象即可;

    缺点:
    1. 冗余,由于代理对象要实现与原对象一样的接口,会产生过多的代理类,这些类都需要我们手动去编写,编译完成后每个代理类对应1个class文件;——这也就是静态的由来,代理类的代码必须提前写好
    2. 不易维护,一旦接口增加抽象方法,原对象与代理对象所在的类都要进行修改;

    给个示例:

    (1)接口

    1. public interface IUserDAO {
    2. void save(UserDO user);
    3. }

    (2)原对象的类

    1. public class UserDAO implements IUserDAO {
    2. @Override
    3. public void save(UserDO user) {
    4. System.out.println("UserDAO-插入/更新用户");
    5. }
    6. }

    (3)代理类

    1. public class UserDAOTransactionProxy implements IUserDAO {
    2. private IUserDAO realMapper;
    3. public UserDAOTransactionProxy(IUserDAO realMapper) {
    4. this.realMapper = realMapper;
    5. }
    6. @Override
    7. public void save(UserDO user) {
    8. // 增强逻辑:执行前处理
    9. System.out.println("proxy-开启事务");
    10. // 调用被代理对象的方法
    11. try {
    12. realMapper.save(user);
    13. } catch (Exception e) {
    14. // 增强逻辑:执行异常时处理
    15. System.out.println("proxy-异步通知");
    16. }
    17. // 增强逻辑:执行后处理
    18. System.out.println("proxy-提交事务");
    19. }

    测试:

    1. public static void main(String[] args) {
    2. // 真实对象(原对象)
    3. IUserDAO realMapper = new UserDAO();
    4. // 代理对象 持有原对象
    5. IUserDAO mapper = new UserDAOTransactionProxy(realMapper);
    6. // 执行IUserDAO接口对象的方法
    7. mapper.save(new UserDO());
    8. }
    1. // 测试结果
    2. proxy-开启事务
    3. UserDAO-插入/更新用户
    4. proxy-提交事务

    3. 动态代理

    动态代理包括两种:JDK动态代理和CGlib代理;

    3.1 JDK动态代理

    JDK动态代理基于JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能,又被称为接口代理;

    静态代理与动态代理的区别主要在

    1. 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件;
    2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中;

    特点:代理对象不需要实现接口,但是要求原对象必须实现接口,否则不能使用JDK动态代理,就需要考虑使用CGlib代理了;

    执行代理包括2个步骤:(1)通过原对象获取其代理对象,定义被代理增强的方法逻辑;(2)执行代理对象的方法;这两个步骤分别对应JDK API中2个关键的类;

    1. java.lang.reflect.Proxy#newProxyInstance

    1. public static Object newProxyInstance(ClassLoader loader, // 指定当前目标对象使用类加载器
    2. Class<?>[] interfaces, //目标对象实现的接口的类型
    3. InvocationHandler h //事件处理器
    4. )
    5. throws IllegalArgumentException {...}

    2. java.lang.reflect.InvocationHandler#invoke

    1. public Object invoke(Object proxy, // 代理对象
    2. Method method, // 目标方法
    3. Object[] args // 方法参数
    4. )
    5. throws Throwable;

    示例如下:

    先定义代理工厂,根据目标对象获取其代理对象;

    1. public class ProxyFactory {
    2. // (原)目标对象
    3. private Object target;
    4. public ProxyFactory(Object target) {
    5. this.target = target;
    6. }
    7. public Object getProxyInstance() {
    8. // 为目标对象生成代理对象
    9. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
    10. new InvocationHandler() {
    11. @Override
    12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    13. System.out.println("当前类实现了IUserDAO接口:" + (proxy instanceof IUserDAO));
    14. System.out.println("JdkProxy-执行前增强");
    15. // 执行目标对象方法
    16. Object returnValue = method.invoke(target, args);
    17. System.out.println("JdkProxy-执行后增强");
    18. return returnValue;
    19. }
    20. });
    21. }
    22. }

    测试:

    1. public static void main(String[] args) {
    2. // 真实对象(原对象)
    3. IUserDAO realMapper = new UserDAO();
    4. // 从代理工厂获取代理对象 类型强转
    5. IUserDAO mapper = (IUserDAO) new ProxyFactory(realMapper).getProxyInstance();
    6. // 执行IUserDAO接口对象的方法
    7. mapper.save(new UserDO());
    8. }
    1. // 测试结果
    2. 当前类实现了IUserDAO接口:true
    3. JdkProxy-执行前增强
    4. UserDAO-插入/更新用户
    5. JdkProxy-执行后增强

    3.2 CGlib动态代理

    CGlib(Code Generation Library)是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展;

    JDK动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口;如果想代理没有实现接口的类,就可以使用CGlib实现;

    CGlib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口;它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截);

    CGlib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类;
    不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉;

    cglib与动态代理最大的区别就是:使用JDK动态代理的对象必须实现一个或多个接口;而使用CGlib代理的对象则无需与被代理对象共同实现同一接口,可以达到对被代理类的无侵入;

    使用CGlib代理需要引入CGlib的jar包,如果已经引入spring-core的jar包,则无需单独引入,因为spring-core中包含了CGlib;

    单独引入CGlib的jar包:

    1. <dependency>
    2. <groupId>cglib</groupId>
    3. <artifactId>cglib</artifactId>
    4. <version>3.3.0</version>
    5. </dependency>

    给个基于CGlib的动态代理示例:

    1. public class CglibProxyFactory implements MethodInterceptor {
    2. // (原)目标对象
    3. private Object target;
    4. public CglibProxyFactory(Object target) {
    5. this.target = target;
    6. }
    7. public Object getProxyInstance() {
    8. // 工具类 用来创建代理对象
    9. Enhancer en = new Enhancer();
    10. // 设置父类
    11. en.setSuperclass(target.getClass());
    12. // 设置回调函数
    13. en.setCallback(this);
    14. // 创建子类对象代理
    15. return en.create();
    16. }
    17. @Override
    18. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    19. System.out.println("当前代理类继承了UserDAO类:" + (obj instanceof UserDAO));
    20. System.out.println("CglibProxy-执行前增强");
    21. // 执行目标对象的方法
    22. Object returnValue = method.invoke(target, args);
    23. System.out.println("CglibProxy-执行后增强");
    24. return returnValue;
    25. }
    26. }

    测试:

    1. public static void main(String[] args) {
    2. // 目标对象
    3. UserDAO target = new UserDAO();
    4. // cglib代理对象
    5. UserDAO proxy = (UserDAO) new CglibProxyFactory(target).getProxyInstance();
    6. // 执行代理对象方法
    7. proxy.save(new UserDO());
    8. }
    1. // 测试结果
    2. 当前代理类继承了UserDAO类:true
    3. CglibProxy-执行前增强
    4. UserDAO-插入/更新用户
    5. CglibProxy-执行后增强

    小结

    实现上:

    • 静态代理简单,只要代理对象对目标对象进行包装,通过持有原对象,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类;
    • JDK动态代理需要目标对象实现某接口;
    • CGlib代理无需要求目标对象实现某接口;

    性能上:

    • 静态代理在编译时产生class字节码文件,可以直接使用,效率高;
    • JDK动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活;
    • CGlib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但CGlib会继承目标对象,需要重写方法,所以目标对象不能为final修饰的类;

    参考:Java三种代理模式 - SegmentFault 思否

  • 相关阅读:
    web自动化基础
    ElasticSearch快速入门
    如何在Linux系统中安装MySQL数据库
    asp.net城市公交线路查询系统sqlserver
    K8S:本质
    接上篇文章,完成Hadoop集群部署实验
    VUE路由总结
    GIS工具maptalks开发手册(四)01——渲染地图信息框之添加绘制工具、获取点的坐标数据信息框进行展示
    《微积分的力量》读书摘记
    从零实现深度学习框架——Seq2Seq从理论到实战【实战】
  • 原文地址:https://blog.csdn.net/minghao0508/article/details/125528481