Spring AOP
AOP
- 静态AOP=》直接对字节码进行更改,对切面做任何的更改都需要重新编译程序
- 动态AOP=》在运行时织入,性能不如静态AOP
Spring AOP包含
- AOP 内核
- 一组框架服务
AOP Alliance是由多个开源AOP共同制定的一组AOP标准接口
AOP的流程:
- 创建切面类
- 创建ProxyFactory实例
- 调用addAdvice(),为实例添加切面类
- 使用setTarget()为代理实例添加目标类
- 使用getProxy()得到代理后的目标实例
- 调用实例方法
AOP的优点:可以在不考虑AOP的情况下创建了类,也可以为任何类提供通知
Spring AOP中不能通知final修饰的类,因为他们不能被覆盖
Spring AOP的架构:
spring AOP的核心是基于代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GjwlI94y-1583159134320)(en-resource://database/1164:1)]
在spring中有两个代理实现
- JDK代理实现(动态代理,只支持代理接口)
- CGLIB代理
在spring中,连接点只支持方法调用,在实际使用中,方法调用时最有用的。
创建的通知类型有:
- 前置通知:继承MethodBeforeAdvice,实现before()方法。通过使用前置通知,可以在连接点执行之前完成自定义处理。因为Spring 的 连接点就是方法调用,所以通常允许方法执行之前执行预处理。虽然前置通知可以完全访问方法调用的目标以及传递给方法的参数,但却无法控制方法本身的执行。如果前置通知抛出异常,那么拦截器链(以及目标方法)的进一步执行将被中止, 并且异常将传回拦截器链
- 后置返回通知,继承AfterReturnAdvice,在连接点的方法调用完成执行并返回一个值后执行后置返回通知。后置返回通知可以访问方法调用的目标、传递给方法的参数以及返回值。由于方法在调用后置返回通知时已经执行, 因此根本无法控制方法调用。如果目标方法抛出异常,则不会运行后置返回通知,并且异常将照常传回调用堆栈
- 环绕通知,继承MethodInterceptor,在Spring 中,环绕通知使用方法拦截器的AOP ALLiance 标准进行建模。环绕通知允许在方法调用之前和之后执行, 并且可以控制允许进行方法调用的点。如果需要,可以选择完全绕过方法,从而提供自己的逻辑实现
- 异常通知
- 后置返回通知无法修改返回值,但是环绕通知允许修改返回值
切入点
建议避免将硬编码的方法检查放入通知,尽可能的使用切入点通知对目标方法的适用性。
切入点实现了Pointcut接口,包含了getClassFilter()和getMethodMatcher(),Spring 支持两种类型的MethodMatcher一静态和动态MethodMatcher。
在spring中提供了8个Pointcut接口,2个用于创建静态和动态的切入点的抽象类,以及6个具体类。
- StaticMethodMatcherPointcut,用于创建静态的切入点
- DynamicMethodMatcherPointcut,用于创建动态的切入点
- NameMatchMethodPointcut,使用简单的名字匹配,不考虑方法的签名
- JdkRegexpMethodPointcut,使用正则匹配
- AspectJExpressionPointcut,使用AspectJ匹配
- AnnotationmatchingPointcut,使用注解匹配
- ControlFlowPointcut,控制流切入点
- ComposePointcut,组合切入点
在使用Pointcut之前,需要先实现PointcutAdvisor接口
代理
代理的核心是拦截方法调用,并在必要时执行适用于特定方法的通知链。
1、JDK动态代理
jdk代理实spring中基本的代理类型,代理过程:
- 所有的方法被JVM拦截并路由到代理的invoke()方法
- 有invoke()方法确定是否通知有关方法(根据切入点制定的规则)
- 如果通知,则通过使用反射调用通知链,然后调用方法本身
在调用invoke()之前, JDK 代理无法区分被通知方法和未被通知方法。这意味着对于代理上的未被通知方法,invoke()方法仍然会被调用,所有检查仍然会执行,并且仍然可以通过使用反射进行调用会导致运行时开销,即使代理不会执行额外的处理,而只是通过反射调用未被通知的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfdlvD2P-1583159134339)(en-resource://database/1166:1)]
2、CGLIB代理
CGLIB 会为每个代理动态生成新类的字节码,并尽可能重用己生成的类,因此代理类型是目标对象的子类。
当首次创建CGLIB 代理时, CGLIB 会询问Spring 如何处理每个方法,CGLIB 代理可以确定一个方法是否返回代理: 如果不返回,则允许直接调用方法调用,从而进一步减少运行时间开销。
还以不同于JDK 代理的方式处理固定通知链。固定通知链是在代理生成后不会更改的,从而减少执行通知链的运行时间开销。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVDRNAQq-1583159134341)(en-resource://database/1168:1)]
决定使用哪个代理通常很容易。CGLIB 代理可以代理类和接口,而JDK 代理只能代理接口。在性能方面,除非在冻结模式下使用CGLIB , 否则JDK 和CGLIB 标准模式之间没有显著差异(至少在运行被通知和未被通知方法时没有显著差异)。在这种情况下,通知链不能更改且CGLIB 在冻结模式下会进行进一步优化。当需要代理类时, CGLIB代理是默认选择,因为它是唯一能够生成类代理的代理。如果想要在代理接口时使用CGLIB 代理,必须使用setOptimize()方法将ProxyFactory 中的optimize 标志的值设置为true。