Spring4.x 笔记(12):Spring Aop 基础

AOP概述

AOP 场景概述

  1. 在OOP面对对象以及软件重构的理念,如果多个类中出现相同的代码,则应该考虑定义父类。如Pig、Horse这些对象都有run()和eat()方法,则可以通过定义一个包含着两个方法的Animal父类,Pig、Horse 继承这个父类实现两个方法

  2. 有一些情况的公共方法或者相同的代码没有办法使用OOP的思路抽象解决,如:下面的代码,所有的业务实现类方法都需要加入监控方法。

# 监控类
public class AopMonitor {
    public static void start() {
        System.out.println("~~ 监控启动 ~~");
    }
    public static void end() {
        System.out.println("++ 监控结束 ++");
    }
}

# 业务接口
public interface AopService {
    void add();
    void update();
}

# 业务实现类
public class AopServiceImpl implements AopService {
    
    @Override
    public void add() {
        AopMonitor.start(); # 加入监控 start
        System.out.println("** 操作新增的业务** ");
        AopMonitor.end(); # 加入监控 end
    }

    @Override
    public void update() {
        AopMonitor.start(); # 加入监控 start
        System.out.println("** 操作修改的业务** ");
        AopMonitor.end(); # 加入监控 end
    }
}
  1. AOP 面向切面编程。其另辟蹊径,通过横向抽取机制解决无法纵向继承的问题。抽取抽象一部分内容是比较很容易的,困难在于怎么把这些抽取的部分集成到业务逻辑中以完成和原来一样的业务流程,这才是关键。而这也就时AOP需要完成的内容。
# 把这部分抽取出来,然后集成到需要的业务代码中
AopMonitor.start();
......
......
AopMonitor.end();

AOP 实现者

  1. AspectJ:声明式AOP,语言级的AOP实现,扩展了Java语言。在【编译期】提供横切代码织入。
  2. Spring AOP:称为编程式AOP,纯Java实现,【运行期】通过代理的方式实现。
  3. AspectWerkz
  4. JBoss AOP
  5. AOP 联盟: aopalliance 是众多开源AOP项目的联合组织,定义了一套AOP规范与标准接口(如 org.aopalliance.aop.Advice

AspectJ 是目前用的最多,一般spring项目的日常也会使用声明式的实现。Spring的最大的价值并不是提供最好的AOP实现以及最完整AOP功能,其侧重于提供一种和IOC整合的AOP实现,使得在Spring中可以无缝的将Spring AOP、IOC和AspectJ整合在一起。

AOP 术语

  1. 连接点-Joinpoint:指程序执行的某个位置,如类初始化前、类初始化后、方法调用前/调用后、方法抛出异常后等。

  2. 切点-Pointcut:一个类会有多个连接点,如上面的AopServiceImpl类,每个方法都是一个连接点。AOP通过切点定位特定的连接点,如通过名字匹配其中一个连接点add()方法,只对这个方法进行织入,忽略其他方法。

  3. 增强-Advice:是指织入到连接点上的一段程序代码。如上例中的监控类AopMonitor中的start()、end()方法,被织入到业务方法的调用前后。在Spring中定义了5个增强,如前置(BeforeAdvice)、后置(AfterAdvice)、引介等

  4. 目标-Target:把Advice织入的目标类,如上例中的业务处理类AopServiceImpl

  5. 引介-Introduction:一种特殊的Advice,可以为目标类添加一些属性和方法。如业务类原本没有实现某个接口,通过引介功能,可以动态的为改业务类添加接口的实现逻辑,增强业务类的功能

  6. 织入-Weaving:将Advice添加到目标(Target)类的具体连接点上的过程。

  • 编译期织入
  • 类装载期织入
  • 动态代理织入(运行期)

spring 采用动态代理织入(运行期),AspectJ采用编译期织入和类装载期织入

  1. 代理-Proxy:一个类被AOP织入Advice后,就产生了一个结果类,其是融合了原类和Advice增强逻辑的代理类

  2. 切面-Aspect:由切点(Pointcut)和Advice组成,即包含横切逻辑的定义,也包括切点的定义。

Spring AOP 原理

具体的例子还是上面监控的例子,如果没有AOP,需要硬编码实现,每个业务(AopServiceImpl)方法中都需要手动调用监控类(AopMonitor)的方法。Spring Aop 使用JDK的动态代理和CGLib设计这一块,改善其实现。

JDK 动态代理

动态代理详解

详细的动态代码不赘述,详见博客其他相关文章 JDK进阶(7):动态代理

使用动态代理改造
  1. 从业务代码中移除监控的横切部分,使得 AopServiceProxyImpl 只负责业务。代码如下:
# 监控类
public class AopMonitor {
    public static void start() {
        System.out.println("~~ 监控启动 ~~");
    }
    public static void end() {
        System.out.println("++ 监控结束 ++");
    }
}

# 业务接口
public interface AopService {
    void add();
    void update();
}

# 业务实现类,只负责业务(去掉监控代码)
public class AopServiceImpl implements AopService {
    
    @Override
    public void add() {
        System.out.println("** 操作新增的业务** ");
    }

    @Override
    public void update() {
        System.out.println("** 操作修改的业务** ");
    }
}
  1. 把横切的监控部分,放入动态代理的InvocationHandlerinvoke()中,如新增类ProxyHandler。横切代码只出现了一次,这样通过invoke()就把业务diamante与横切代码编织在一起。
# 动态代理
public class ProxyHandler implements InvocationHandler {

    # 希望被代理的目标对象
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        # 横切放入这边
        AopMonitor.start();

        // 业务方法调用,这边生成的为代理实例
        Object invoke = method.invoke(target, args);
        
        # 横切放入这边
        AopMonitor.end();

        # 返回代理实例
        return invoke;
    }
}
  1. 通过动态代理的 Proxy 工具类创建业务的代理类,代码如下
    # 目标对象
    AopService target = new AopServiceProxyImpl();
    # InvocationHandler 实现类实例,需要传入目标对象
    ProxyHandler proxyHandler = new ProxyHandler(target);
    
    # 创建代理。从这边的实现也可以看出,JDK的动态代理针对接口编程
    AopService proxy = (AopService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(), // 目标类实现的接口
            proxyHandler);
    
    # 调用代理对象的 add() 方法
    proxy.add();

CGLib 实现

  1. 如果目标类没有实现接口,JDK动态代理没有办法帮其创建代理,实现织入,需要 CGLib 的支持。CGLib 采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并织入横切逻辑
  2. 编写一个可以为任何类创建织入性能监控横切逻辑代理对象的创建器:实现 MethodInterceptor 接口
public class CglibHandler implements MethodInterceptor {

    # 获取代理
    public Object getProxy(Class clazz) {
        Enhancer enhancer = new Enhancer();
        # 设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        # 通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    /**
     * 拦截父类所有方法
     * @param o 目标类实例
     * @param method 目标类方法的反射对象
     * @param objects 参数
     * @param methodProxy 代理实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        # 织入横切监控
        AopMonitor.start();

        // 业务方法调用,返回代理
        Object invoke = methodProxy.invokeSuper(o, objects);

        AopMonitor.end();

        return invoke;

    }
}
  1. 创建代理类实现
    CglibHandler cglibHandler = new CglibHandler();
    # 创建代理类
    AopServiceCglib proxy = (AopServiceCglib) cglibHandler.getProxy(AopServiceCglib.class);
    proxy.add();
    proxy.update();

总结

  1. JDK 动态代理与 CGLib 性能
  • CGLib创建的代理类执行效率比动态代理创建的代理对象性能高
  • CGLib创建代理对象所花费的时间比动态代理高
  • 结果:创建次数少的使用CGLib,如Spring中的单例bean;需多次创建的使用动态代理。
  1. 问题

就以上的实现,有一些问题需要解决:

  1. Advice 增强没有分类。通过硬编码的方式指定织入横切逻辑,在目标方法的开始前、开始后织入代码,没有灵活实现各个不同的横切,如方法前、方法后、异常等。
  2. Pointcut 不灵活。目标类的所有方法都添加了监控的横切逻辑,要不所有方法都监控,要不都不监控,没有实现灵活的切点。
  3. 代理创建麻烦。手工编写代码获取实例的过程,对于不同的类创建代理时,需要使用不同的方式(动态代理和 CGLib )

Spring 编程式 AOP 的主要工作就是围绕以上三点问题展开的。

  1. 通过 Advice 描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)
  2. 通过 Pointcut 指定在哪些类的哪些方法上织入 Advice
  3. 通过 Advisor 切面将 Pointcut 和 Advice 组装起来

参考

  1. 源码:Spring-AOP
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值