经过一个多月的 Spring AOP 的学习,掌握了 Spring AOP 的基本概念。AOP 是面向切面的编程(Aspect-Oriented Programming),是基于 OOP(面向对象的编程,Object-Oriented Programming)开发的一套程序架构。
微服务架构体系下,有时候要求一段通用性的事务使用在软件中的许多模块,好比日志模块,外部业务逻辑等。以日志模块举例,根据 OOP 的思想,我能够创建一个日志类,而后在每个须要记录日志的类中初始化日志类,达到日志记录的目的。缺点是,你必须在全部即将使用的业务代码中,实例化日志类,而后执行日志类的方法。若是采用 AOP 的思想,能够将日志类做为切面,而后使用代理的方式将这个日志类切入到须要记录日志的类中。日志类方法的执行只须要写一次,代码更容易维护。
在实践的过程当中,使用 Spring AOP 的思想,为 PressSystem 项目加入了日志记录模块。日志采用 MySQL 数据库表进行记录,数据库链接采用 MyBatis。html
下面给出 AOP 中本身理解后的基本概念。若是想看原文,能够点击文末的 Spring AOP 官方文档。java
JoinPoint(链接点):能够理解为一个方法,就是切面要切入的那个方法。spring
Advice(通知):通知就是切面中的方法,这个方法将从JoinPoint切入。数据库
Pointcut(切点):切点就是表达式,通知将会切入符合切点表达式的JoinPoint 中。编程
Introduction(引入):引入机制能够向一个能被切入的对象发明新的接口,并实现它。其实就是把能被切入的对象强制转换成另外一个类,这样就能够执行另外一个类的方法了。架构
Target Object(目标对象):就是能被切入的对象。Spring AOP 中,目标对象永远是可被代理的对象。框架
AOP Proxy(AOP 代理):AOP Proxy 就是 经过 AOP 框架生成出来的一个对象,这个对象实现了切面的功能,是目标类被切入之后的结果。Spring AOP 代理分两种:ssh
Weaving(织入):织入就是把切面与目标类链接的过程。过程的产物就是一个被切入的目标。异步
简单地来讲,通知能够有前置通知、后置通知、环绕通知等等。好比,前置通知就是通知在切入点执行以前执行;后置通知就是在切入点执行以后执行。在个人 Spring AOP 示例程序中,列举了如下几个通知的实现方式。jsp
Before advice(前置通知):在链接点以前执行。除非是前置通知抛出了异常,不然前置通知没有能力影响执行流流向链接点。
After returning advice(返回后通知):在链接点的方法正常执行完以后再执行的通知。所谓的正常执行完,好比说链接点方法没有抛出异常。
After throwing advice(抛出后通知):若是方法由于抛出了异常而终止,那么就执行抛出后通知。
After (finally) advice(后置通知):无论链接点方法的退出状况如何,是正常仍是有异常,后置通知都会执行。
Around advice(环绕通知):环绕通知是包围住链接点的通知,能够在链接点的前面或后面执行自定义的方法。环绕通知能够决定是否执行链接点的方法,或者绕过链接点的方法。绕过的方式是返回环绕通知本身的值或者抛出一个异常。
如下提到的组件,是实现 Spring AOP 的组件最小子集。
XuanTiController.java
在 PressSystem 项目中,用户操做的是 jsp 页面,jsp 页面会发送一个异步请求给 XuanTiController,在 XuanTiController 中,会调用切入点的方法。因此能够把 XuanTiController 看做入口。
XuanTiService.java
采用接口与实现分离的设计原则,XuanTiService 就是接口,它的实现是 XuanTiServiceImpl,XuanTiServiceImpl 就是切入点。
XuanTiServiceImpl.java
XuanTiServiceImpl 就是切入点,其中的函数被通知切入了。
LogAspect.java
LogAspect 就是切面,其中有一些通知,这些通知将会切入到切入点中。
spring-common.xml
这是切入点和切面的配置文件。
总的来讲,是 LogAspect 这个 bean 切入了 XuanTiServiceImpl 这个 bean
在 spring-common.xml 配置文件中,声明了 XuanTiServiceImpl 和 LogAspect 2个 bean<bean class="com.tgb.service.impl.XuanTiServiceImpl" />
<bean id="logAspect" class="com.tgb.utils.LogAspect" />
在 LogAspect 中,有不少配置,看以下的3行。这些配置指定了
saveLogInsert
updateLogInsert
deleteLogInsert
这三个通知要切入 XuanTiServiceImpl@AfterReturning(pointcut="execution(* com.tgb.service.impl.*.save(..))", argNames="returnValue", returning="returnValue")
@AfterReturning(pointcut="execution(* com.tgb.service.impl.*.update(..))", argNames="returnValue", returning="returnValue")
@Around("execution(* com.tgb.service.impl.*.delete(..)) && args(id, table_name)")
注意,每一个通知,有不一样类型的通知的 @AspectJ 方式的标记,例如 @AfterReturning, @Around
另外,每一个通知,能够有 pointcut 配置,即每一个通知要切入到哪里
AOP 的实现方式是经过代理实现的。因此在 spring-common.xml 中指定了 autoproxy,为 XuanTiServiceImpl 建立代理,以下所示:<aop:aspectj-autoproxy />
注释:若是Spring决定一个bean要被切入,那么Spring就会为这个 bean 自动生成代理来插入外来的方法,保证通知被执行
在 XuanTiServiceImpl 中有以下3个方法,这3个方法符合 pointcut 的描述,所以具体地来讲,这3个方法就是切入点,它们被顺利地切入了public void save(XuanTi xuanTi) {...}
public boolean update(XuanTi xuanTi) {...}
public boolean delete(String id, String table_name) {...}
如下描述,主要涉及2点,能够归纳为 @Aspect 和 @Autowired
@Aspect 就是“切入机制”,@AfterReturning, @Around, pointcut,<aop:aspectj-autoproxy />
等知识点都属于这个知识体系
@Autowired 是“自动绑定机制”,@Component 注解,广义的 bean 等知识点属于 @Autowired 的基础知识体系另外,下面所提到的调用过程,是 Spring AOP 相关的过程最小子集
XuanTiController 被调用了,具体来讲,其中的 xuanTiService.save(xuanTi);
被调用了
由于在 XuanTiController 有标注为 @Autowired 的 xuanTiService,因此 Application 会去查找 xuanTiService 这个 bean
至于为何会去查找 xuanTiService 这个 bean 呢,是由于 spring-common.xml 中的配置<context:annotation-config />
注释:启用注解配置方式,好比说启用了 @Autowired 的识别。
XuanTiService 只是一个 interface,实现它的是 XuanTiServiceImpl
XuanTiServiceImpl 这个 bean 找到了,由于在 spring-common.xml 中有配置<bean class="com.tgb.service.impl.XuanTiServiceImpl" />
xuanTiService.save(xuanTi); 被执行了,具体来讲,是 XuanTiServiceImpl 中的 public void save(XuanTi xuanTi) {…} 被执行了
根据上一章节“切入机制”的描述,在执行 public void save(XuanTi xuanTi) {...}
的时候,会伴随着执行 LogAspect 中的 saveLogInsert 方法
在 LogAspect 中,有标注为 @Autowired 的 logService,因此 Application 会去查找 logService 这个 bean
查找的缘由,就是第3点所描述的
LogService 只是一个 interface,实现它的是 LogServiceImpl
XuanTiServiceImpl 这个 bean 找到了,由于在 spring-common.xml 中有配置<bean id="LogService" class="com.tgb.service.impl.LogServiceImpl" />
注释:日志记录业务逻辑对象
logService.log(log); 被执行了,具体来讲,是 LogServiceImpl 中的 public void log(Log log) {…} 被执行了
在本章节,我将会介绍另一些 Spring AOP Docs 中提到的基础知识。这些基础知识没有使用在 PressSystem 项目中,可是做为 Spring AOP Docs 中的基础知识,应当有所了解。
在前面的章节中,提到过五种类型的通知。这五种类型的通知,在个人 aop_demo 示例程序中都有使用。注意,声明通知的方法有如下两种方式。在个人示例程序中,都有展现:
声明一个通知的时候,是能够传入切入点的参数,在通知中使用这个参数的,例如上文中提到的 delete 通知:
@Around("execution(* com.tgb.service.impl.*.delete(..)) && args(id, table_name)")
当我传入了 (id, table_name)
参数后,能够在通知中使用 id 和 table_name。delete 通知的目的是,当 delete 方法执行的时候,写下日志。在日志中我要知道删除的 id 和 table_name。传入了这两个参数以后,就能够记录了。
通知的参数能够很复杂,用于知足实际须要。想要了解更多通知参数的使用方法,能够查看 Spring AOP Docs。
Pointcut
是 Advice
的具体配置,指定了一个 Advice 将会切入到哪些切入点中,这是由切点表达式决定的。Pointcut 是一种十分重要的机制,在 PressSystem 的 LogAspect 中有简单的应用。想要知道更多使用方法,能够参考 Spring AOP Docs。
如下是 Spring AOP Docs 中的原版描述:
Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a given interface, and to provide an implementation of that interface on behalf of those objects.
相关的代码,能够参看 com.spring.demo08 和 com.spring demo14。其中:
根据个人理解,Introduction 的意义就是接口类型强转,可是看到一句注释说,这是“接口动态实现”,以为里面还有其余的花头我没有理解。。。
Spring 的动态代理有两种:一是 JDK 的动态代理;另外一个是 cglib 动态代理(经过修改字节码来实现代理)。能够以 JDK 动态代理的方式为例,来粗浅地探知 AOP Proxy 的究竟。
JDK的代理方式主要就是经过反射跟动态编译来实现的,主要涉及到java.lang.reflect包中的两个类:Proxy
和 InvocationHandler
。其中 InvocationHandler
是一个接口,能够经过实现该接口定义横切逻辑,在并经过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一块儿。 将会在另外一篇文章中,重点来谈一下 Spring AOP 的底层实现技术:JDK 动态代理。
这篇博客写了好久,一方面是因为差很少是利用业余时间在学习,另外一方面是因为 Spring AOP 的知识点有不少。
在面向切面的编程的概念中,比较重要的是切面类和目标类。切面类中有切面方法,目标类中有切入点。经过正确的配置,可使切面方法切入到目标类的切面点中。
这篇博客首先介绍了 AOP 的概念,而后介绍了通知的5种类型。接下来,以 PressSystem 为例,讲述了 PressSystem 中的 AOP 的基本组件,切入机制和调用过程。
最后,在知识拓展中,十分简单地提到了五种类型的通知的实现方式,通知参数的使用方法,Pointcut - 切点表达式的使用,Introductions 简单介绍。最重要的一点,我认为是 AOP Proxy 原理简介。有关 AOP Proxy 原理简介,请参看另外一篇博客:Spring AOP 中的 JDK 动态代理。
创做时间:2016-05-14 星期六 6:32:24 PM