目录
简略介绍
AOP是如何实现的
实现时机
实现原理
-
简略介绍
- AOP(Aspect-Oriented Programming),即面向切面编程,用人话说就是把公共的逻辑抽出来,让开发者可以更专注于业务逻辑开发
- 和IOC一样,AOP也指的是一种思想
- AOP思想是OOP(Object-Oriented Programming)的补充
- OOP是面向类和对象的,但是AOP则是面向不同切面的
- 一个切面可以横跨多个类和对象去操作,极大的丰富了开发者的使用方式,提高了开发效率
- 譬如,一个订单的创建,可能需要以下步骤:
- 1. 权限校验
- 2. 事务管理
- 3. 创建订单
- 4. 日志打印
- 如果使用AOP思想,我们就可以把这四步当成四个“切面”,让业务人员专注开发第三个切面,其他三个切面则是基础的通用逻辑,统一交给AOP封装和管理
- 使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点
- 业务处理的主要流程是核心关注点(业务的主要功能),与之关系不大的部分是横切关注点(非核心、额外增加的功能)
- 横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物
- 用户下单为例子
- 核心关注点:创建订单
- 横切关注点:记录日志、控制事务
- AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来
- Spring AOP有如下概念:
- 对于通知类型来说:
- 1. 前置通知(Before):在目标方法被调用之前调用通知功能
- 2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 3. 返回通知(After-returning):在目标方法成功执行之后调用通知
- 4. 异常通知(After-throwing):在目标方法抛出异常后调用通知
- 5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
- 同一个aspect,不同advice的执行顺序:
- 1-没有异常情况下的执行顺序:
- around before advice
- before advice
- target method执行
- around after advice
- after advice
- afterReturning
- 2-有异常情况下的执行顺序:
- around before advice
- before advice
- target method执行
- around after advice
- after advice
- afterThrowing:异常发生
- java.lang.RuntimeException:异常发生
- 1-没有异常情况下的执行顺序:
-
AOP是如何实现的
-
实现时机
- 参考Bean的初始化流程-SpringBean的初始化流程-CSDN博客
- 从Bean的初始化流程中来讲,Spring的AOP会在bean实例的初始化已完成,进行初始化后置处理即AbstractAutoProxyCreator#postProcessBeforeInstantiation中,生成代理:
-
实现原理
- Spring AOP 是通过代理模式实现的,具体有两种实现方式,一种是基于Java原生的动态代理,一种是基于cglib的动态代理
- Spring AOP 默认使用标准的JDK动态代理进行AOP代理;这使得任何接口都可以被代理
- 但是JDK动态代理有一个缺点,就是它不能代理没有接口的类
- 所以Spring AOP就使用CGLIB代理没有接口的类
- 默认情况下,如果一个业务对象没有实现一个接口,就会使用CGLIB
- 当然代理这种设计模式也有动态代理和静态代理之分,可以参考这篇文章:Java基础---动态代理-CSDN博客
- Spring AOP and AspectJ AOP
- AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理
- 什么是静态代理
- 这种代理方式需要代理对象和目标对象实现一样的接口
- 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class文件就已经存在
- 通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的
- 什么是动态代理
- 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
- 不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
- 静态代理的代表为AspectJ;动态代理则以Spring AOP为代表
- (1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象
- (2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
- JDK动态代理和CGLIB动态代理
- Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
- 具体使用哪种方式生成代理对象由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定
- 默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理
- JDK动态代理只提供接口的代理,不支持类的代理
- 又被称为接口代理
- 动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用JDK动态代理
- JDK中生成代理对象主要涉及的类有java.lang.reflect Proxy,主要方法为
- 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】
- 用户调用代理对象的什么方法,都是在调用处理器的invoke方法【被拦截】
- 使用JDK动态代理必须要有接口【参数二需要接口】
- 核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
- 接着,Proxy利用 InvocationHandler 动态创建一个符合某一接口的的实例,生成目标类的代理对象
- 静态代理和动态代理的区别
- 静态代理需要自己写代理类-->代理类需要实现与目标对象相同的接口
- 而动态代理不需要自己编写代理类--->(是动态生成的)
- 使用静态代理时:如果目标对象的接口有很多方法的话,那我们还是得一一实现,这样就会比较麻烦
- 使用JDK动态代理时:代理对象的生成,是利用JDKAPI,动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型),并且会默认实现接口的全部方法
- 为什么jdk动态代理必须基于接口;原因如下:
- 1、生成的代理类继承了Proxy,由于java是单继承,所以只能实现接口,通过接口实现方法
- 2、从代理模式的设计来说,充分利用了java的多态特性,也符合基于接口编码的规范
- 3、动态代理是通过反射来实现的,而且接口不依赖实现,才能体现动态代理的优点啊;如果是一个实体类怎么能是动态代理呢?
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类
- CGLIB(Code GenerationLibrary),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP
- 也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能
- 使用cglib就是为了弥补JDK动态代理的不足【动态代理的目标对象一定要实现接口】
- 如果想代理没有实现接口的类,就可以使用CGLIB实现
- CGLIB是通过继承的方式做的动态代理,因此如果存在某个类被标记为final,private方法和static方法,那么它是无法使用CGLIB做动态代理的
- 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理
- 小结:
- 0. 通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过 SpringAOP 框架触发的代码段;目标对象是被一个或者多个切面所通知的对象,也指被通知(advised)对象;代理是通知目标对象后创建的对象;从客户端的角度看,代理对象和目标对象是一样的
- 1. 静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类
- 2. JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口
- 3. JDK动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为classcom.cglib.UserDao$$EnhancerByCGLIB$$552188b6
- 4. 静态代理在编译时产生class字节码文件,可以直接使用,效率高
- 5. JDK动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活
- 6. cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类