Spring AOP 知识整理
通过一个多月的 Spring AOP 的学习,掌握了 Spring AOP 的基本概念。AOP 是面向切面的编程(Aspect-Oriented Programming),是基于 OOP(面向对象的编程,Object-Oriented Programming)开发的一套程序架构。
微服务架构体系下,有时候要求一段通用性的事务使用在软件中的许多模块,比如日志模块,外部业务逻辑等。以日志模块举例,根据 OOP 的思想,我可以建立一个日志类,然后在每一个需要记录日志的类中初始化日志类,达到日志记录的目的。缺点是,你必须在所有即将使用的业务代码中,实例化日志类,然后执行日志类的方法。如果采用 AOP 的思想,可以将日志类作为切面,然后使用代理的方式将这个日志类切入到需要记录日志的类中。日志类方法的执行只需要写一次,代码更容易维护。
在实践的过程中,使用 Spring AOP 的思想,为 PressSystem 项目加入了日志记录模块。日志采用 MySQL 数据库表进行记录,数据库连接采用 MyBatis。
AOP 基本概念
下面给出 AOP 中自己理解后的基本概念。如果想看原文,可以点击文末的 Spring AOP 官方文档。
- Aspect(切面):一种切入多个类的模块化机制。Spring AOP 中,aspect 有两种实现方式:
- 使用常规的类,然后使用 基于 XML Schema 方式
- 使用常规的类,然后加上 @Aspect 注解,也就是 @AspectJ 方式
JoinPoint(连接点):可以理解为一个方法,就是切面要切入的那个方法。
Advice(通知):通知就是切面中的方法,这个方法将从JoinPoint切入。
Pointcut(切点):切点就是表达式,通知将会切入符合切点表达式的JoinPoint 中。
Introduction(引入):引入机制可以向一个能被切入的对象发明新的接口,并实现它。其实就是把能被切入的对象强制转换成另一个类,这样就可以执行另一个类的方法了。
Target Object(目标对象):就是能被切入的对象。Spring AOP 中,目标对象永远是可被代理的对象。
AOP Proxy(AOP 代理):AOP Proxy 就是 通过 AOP 框架生成出来的一个对象,这个对象实现了切面的功能,是目标类被切入以后的结果。Spring AOP 代理分两种:
- JDK 动态代理
- CGLIB 代理
Weaving(织入):织入就是把切面与目标类连接的过程。过程的产物就是一个被切入的目标。
通知的类型
简单地来说,通知可以有前置通知、后置通知、环绕通知等等。比如,前置通知就是通知在切入点执行之前执行;后置通知就是在切入点执行之后执行。在我的 Spring AOP 示例程序中,列举了以下几个通知的实现方式。
Before advice(前置通知):在连接点之前执行。除非是前置通知抛出了异常,否则前置通知没有能力影响执行流流向连接点。
After returning advice(返回后通知):在连接点的方法正常执行完之后再执行的通知。所谓的正常执行完,比如说连接点方法没有抛出异常。
After throwing advice(抛出后通知):如果方法因为抛出了异常而终止,那么就执行抛出后通知。
After (finally) advice(后置通知):不管连接点方法的退出情况如何,是正常还是有异常,后置通知都会执行。
Around advice(环绕通知):环绕通知是包围住连接点的通知,可以在连接点的前面或后面执行自定义的方法。环绕通知可以决定是否执行连接点的方法,或者绕过连接点的方法。绕过的方式是返回环绕通知自己的值或者抛出一个异常。
PressSystem 项目中 Spring AOP 的基本组件
以下提到的组件,是实现 Spring AOP 的组件最小子集。
XuanTiController.java
在 PressSystem 项目中,用户操作的是 jsp 页面,jsp 页面会发送一个异步请求给 XuanTiController,在 XuanTiController 中,会调用切入点的方法。所以可以把 XuanTiController 看作入口。XuanTiService.java
采用接口与实现分离的设计原则,XuanTiService 就是接口,它的实现是 XuanTiServiceImpl,XuanTiServiceImpl 就是切入点。XuanTiServiceImpl.java
XuanTiServiceImpl 就是切入点,其中的函数被通知切入了。LogAspect.java
LogAspect 就是切面,其中有一些通知,这些通知将会切入到切入点中。spring-common.xml
这是切入点和切面的配置文件。
PressSystem 项目中 Spring AOP 的切入机制
总的来说,是 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) {...}
PressSystem 项目中 Spring AOP 机制的调用过程
以下描述,主要涉及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 示例程序中都有使用。注意,声明通知的方法有以下两种方式。在我的示例程序中,都有展示:
- @Aspectj
- Schema-based
通知参数的使用方法
声明一个通知的时候,是可以传入切入点的参数,在通知中使用这个参数的,例如上文中提到的 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 - 切点表达式的使用
Pointcut
是 Advice
的具体配置,指定了一个 Advice 将会切入到哪些切入点中,这是由切点表达式决定的。Pointcut 是一种十分重要的机制,在 PressSystem 的 LogAspect 中有简单的应用。想要知道更多使用方法,可以参考 Spring AOP Docs。
Introductions 介绍
以下是 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。其中:
- com.spring.demo08 是 Schema 方式实现的
- com.spring.demo14 是 @AspectJ 方式实现的
根据我的理解,Introduction 的意义就是接口类型强转,但是看到一句注释说,这是“接口动态实现”,觉得里面还有其他的花头我没有理解。。。
AOP Proxy 原理简介
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 动态代理。
参考资料
- 10. Aspect Oriented Programming with Spring (这是总纲,可以作为知识点的引入)
- AOP 那点事儿 (这篇文章写得并不好,太随性,思路不清楚)
- Spring AOP Example Tutorial – Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration (Spring AOP demo)
- Spring AOP 完成日志记录 (PressSystem 的日志功能实现,参考了这篇文章)
- Spring 容器AOP的实现原理——动态代理 (Spring AOP 动态代理的学习)
- Spring AOP的底层实现技术—JDK动态代理(Spring AOP 动态代理的学习)
创作时间:2016-05-14 星期六 6:32:24 PM
Spring AOP 知识整理的更多相关文章
- Spring Ioc知识整理
Ioc知识整理(一): IoC (Inversion of Control) 控制反转. 1.bean的别名 我们每个bean元素都有一个id属性,用于唯一标识实例化的一个类,其实name属性也可用来 ...
- Spring AOP使用整理:各种通知类型的介绍
2.PersonImpl类的源码 public class PersonImpl implements Person { private String name; private int age; p ...
- Spring框架知识整理
Spring框架主要构成 Spring框架主要有7个模块: 1.Spring AOP:面向切面编程思想,同时也提供了事务管理. 2.Spring ORM:提供了对Hibernate.myBatis的支 ...
- Spring AOP使用整理:自动代理以及AOP命令空间
三.自动代理的实现 1.使用BeanNameAutoProxyCreator 通过Bean的name属性自动生成代理Bean. <bean class="org.springframe ...
- Spring AOP使用整理:使用@AspectJ风格的切面声明
要启用基于@AspectJ风格的切面声明,需要进行以下的配置: <!-- 启用@AspectJ风格的切面声明 --> <aop:aspectj-autoproxy proxy-tar ...
- Spring AOP详细介绍
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...
- AOP面试知识整理,^_^-包括spring Aop
讲到java企业级开发框架,就不可避免的讲到 IOC,AOP,MCV 今天面试时被问到AOP,讲的很乱,这里整理笔记,包括AOP,spring-AOP的部分知识,错误的地方请小伙伴指出来. 谈谈你对A ...
- Spring AOP四种实现方式Demo详解与相关知识探究
一.前言 在网络上看到一篇博客Spring实现AOP的4种方式,博主写的很通俗易懂,但排版实在抓狂,对于我这么一个对排版.代码格式有强迫症的人来说,实在是不能忍受~~~~(>_<)~~~~ ...
- Spring AOP基础知识
Spring AOP使用动态代理技术在运行期织入增强的代码,两种代理机制包括:一是基于JDK的动态代理,另一种是基于CGLib的动态代理.之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的 ...
随机推荐
- Spring Boot 监听 Activemq 中的特定 topic ,并将数据通过 RabbitMq 发布出去
1.Spring Boot 和 ActiveMQ .RabbitMQ 简介 最近因为公司的项目需要用到 Spring Boot , 所以自学了一下, 发现它与 Spring 相比,最大的优点就是减少了 ...
- 带你入门SpringCloud 之 通过SpringCloud Bus 自动更新配置
前言 在<带你入门SpringCloud统一配置 | SpringCloud Config>中通过 SpringCloud Config 完成了统一配置基础环境搭建,但是并没有实现配置修改 ...
- 高效解决「SQLite」数据库并发访问安全问题,只这一篇就够了
Concurrent database access 本文译自:https://dmytrodanylyk.com/articles/concurrent-database/ 对于 Android D ...
- php EOF(heredoc)使用
PHP EOF(heredoc)是一种在命令行shell(如sh.csh.ksh.bash.PowerShell和zsh)和程序语言(像Perl.PHP.Python和Ruby)里定义一个字符串的方法 ...
- else块的用途
除了在if...else...中使用,else块还可以在for循环.while循环以及try...except中使用. 在for循环中使用: my_list = ['a','b','c','d'] f ...
- 【MySQL】 用户授权
启动mysql命令符 grant all privileges on mysql.* to 'root'@'%' identified by '123456'; 给mysql用户root授权,'%'表 ...
- SpringBootSecurity学习(22)前后端分离版之OAuth2.0自定义授权码
使用JDBC维护授权码 前面的代码中,测试流程第一步都是获取授权码,然后再携带授权码去申请令牌,授权码示例如下: 产生的授权码默认是 6 位的,产生以后并没有做任何管理,可以说是一个临时性的授权码,o ...
- idea 新建项目 coding上新建项目 idea推送到coding
1. 注册coding a. 首先在(https://coding.net)上创建项目 ps:跳过注册 ![file](https://img2018.cnblogs.com/blog/1416679 ...
- oracle查询当前用户下所有的表,包括所有的字段
oracle查询当前用户下所有的表,包括所有的字段 背景: 前两天接到一个需求,做一个展示所有表名,表备注,表数据,表字段数,点击查看按钮查看字段名和注释,支持导出. 在Oracle中,可用使用视 ...
- webstrom 永久激活方法 ,长期可用
打开hosts文件:C:\Windows\System32\drivers\etc 在最后一行添加 0.0.0.0 account.jetbrains.com 打开webstorm,选择Activat ...