深入了解Spring之AOP

GitHub:https://github.com/JDawnF/learning_note

目录

1、简介

1.1 应用场景

2、Aspect简介

3、AOP相关术语

Joinpoint(连接点):

Pointcut(切入点):

Advice(通知/增强):

Introduction(引介):

Target(目标对象):

Weaving(织入):

Proxy(代理):

Aspect(切面):

4、AOP实现方式

5、AspectJ

切点的定义:

AspectJ表达式:

AspectJ增强:


1、简介

AOP(Aspect-Oriented Programming),即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角。

  • 在 OOP 中,以类( Class )作为基本单元

  • 在 AOP 中,以切面( Aspect )作为基本单元。

所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

1.1 应用场景

  1. Authentication 权限

  2. Caching 缓存

  3. Context passing 内容传递

  4. Error handling 错误处理

  5. Lazy loading 懒加载

  6. Debugging 调试

  7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准

  8. Performance optimization 性能优化

  9. Persistence 持久化

  10. Resource pooling 资源池

  11. Synchronization 同步

  12. Transactions 事务

2、Aspect简介

Aspect 由 PointCutAdvice 组成。

  • 它既包含了横切逻辑的定义,也包括了连接点的定义。

  • Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中。

AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:

  1. 如何通过 PointCut 和 Advice 定位到特定的 JoinPoint 上。

  2. 如何在 Advice 中编写切面代码。

可以简单地认为, 使用 @Aspect 注解的类就是切面

3、AOP相关术语

Joinpoint(连接点):

所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。(实际上连接点还可以是字段或者构造器)

Pointcut(切入点):

所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。

两者区别:

首先,Advice 通过 PointCut 查询需要被织入的 JoinPoint 。

然后,Advice 在查询到 JoinPoint 上执行逻辑。

Advice(通知/增强):

所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

  • Before - 这些类型的 Advice 在 JoinPoint 方法之前执行,并使用 @Before 注解标记进行配置。

  • After Returning - 这些类型的 Advice 在连接点方法正常执行后执行,并使用 @AfterReturning 注解标记进行配置。

  • After Throwing - 这些类型的 Advice 仅在 JoinPoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。

  • After Finally - 这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。

  • Around - 这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。

Introduction(引介):

引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。

Target(目标对象):

织入 Advice 的目标对象,代理的目标对象

  • 因为 Spring AOP 使用运行时代理的方式来实现 Aspect ,因此 Advised Object 总是一个代理对象(Proxied Object) 。

  • 注意, Advised Object 指的不是原来的对象,而是织入 Advice 后所产生的代理对象

  • Advice + Target Object = Advised Object = Proxy 。

Weaving(织入):

是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。

Proxy(代理):

一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面):

是切入点和通知(引介)的结合

4、AOP实现方式

实现 AOP 的技术,主要分为两大类:

  • 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强。

    • 编译时编织(特殊编译器实现)

    • 类加载时编织(特殊的类加载器实现)。

  • 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。目前 Spring 中使用了两种动态代理库:

    • JDK 动态代理

    • CGLIB

From 《Spring 源码深度解析》P172

Spring AOP 部分使用 JDK 动态代理或者 CGLIB 来为目标对象创建代理。(建议尽量使用 JDK 的动态代理)

如果被代理的目标对象实现了至少一个接口,则会使用 JDK 动态代理。所有该目标类型实现的接口都将被代理。 

若该目标对象没有实现任何接口,则创建一个 CGLIB 代理。

如果你希望强制使用 CGLIB 代理,(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下两个方法: 1> 无法通知(advise) Final 方法,因为它们不能被覆盖。 2> 你需要将 CGLIB 二进制发型包放在 classpath 下面。

为什么 Spring 默认使用 JDK 的动态代理呢?笔者猜测原因如下: 1> 使用 JDK 原生支持,减少三方依赖 2> JDK8 开始后,JDK 代理的性能差距 CGLIB 的性能不会太多。可参见:https://www.cnblogs.com/haiq/p/4304615.html

或者,我们来换一个解答答案:

Spring AOP 中的动态代理主要有两种方式,

  • JDK 动态代理(接口)

    JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是 InvocationHandler 接口和 Proxy 类。

    JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。

  • CGLIB 动态代理(类)

    如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。当然,Spring 也支持配置,强制使用 CGLIB 动态代理,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。

    CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final ,那么它是无法使用 CGLIB 做动态代理的。

5、AspectJ

切点的定义:

@Pointcut("execution(* cn.itcast.spring3.demo1.UserDao.find(..))")
     private void myPointcut(){}

AspectJ表达式:

* 语法:execution(表达式)

execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)

* execution(“* cn.itcast.spring3.demo1.dao.*(..)”) ---只检索当前包

* execution(“* cn.itcast.spring3.demo1.dao..*(..)”) ---检索包及当前包的子包.

* execution(* cn.itcast.dao.GenericDAO+.*(..)) ---检索GenericDAO及子类

AspectJ增强:

@Before 前置通知,相当于BeforeAdvice

@AfterReturning 后置通知,相当于AfterReturningAdvice

@Around 环绕通知(前后到通知),相当于MethodInterceptor

@AfterThrowing 抛出通知,相当于ThrowAdvice

@After 最终final通知,不管是否异常,该通知都会执行

@DeclareParents 引介通知,相当于IntroductionInterceptor

具体例子可参照:彻底征服 Spring AOP 之 实战篇

@Aspect
     public class TransactionDemo {
         @Pointcut(value = "execution(* com.yangxin.core.service.*.*.*(..))")
         public void point() {
         }
         @Before(value = "point()")
         public void before() {
             System.out.println("transaction begin");
         }
         @AfterReturning(value = "point()")
         public void after() {
             System.out.println("transaction commit");
         }
         @Around("point()")
         public void around(ProceedingJoinPoint joinPoint) throws Throwable {
             System.out.println("transaction begin");
             joinPoint.proceed();
             System.out.println("transaction commit");
         }
     }

 

参照:芋道源码

黑马

posted @ 2019-05-27 08:42  白晨冬阳  阅读(128)  评论(0编辑  收藏  举报