通过一个多月的 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 的切入机制

  1. 总的来说,是 LogAspect 这个 bean 切入了 XuanTiServiceImpl 这个 bean

  2. 在 spring-common.xml 配置文件中,声明了 XuanTiServiceImpl 和 LogAspect 2个 bean
    <bean class="com.tgb.service.impl.XuanTiServiceImpl" />
    <bean id="logAspect" class="com.tgb.utils.LogAspect" />

  3. 在 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 配置,即每个通知要切入到哪里

  4. AOP 的实现方式是通过代理实现的。所以在 spring-common.xml 中指定了 autoproxy,为 XuanTiServiceImpl 创建代理,如下所示:
    <aop:aspectj-autoproxy />
    注释:如果Spring决定一个bean要被切入,那么Spring就会为这个 bean 自动生成代理来插入外来的方法,保证通知被执行

  5. 在 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 相关的过程最小子集

  1. XuanTiController 被调用了,具体来说,其中的 xuanTiService.save(xuanTi); 被调用了

  2. 因为在 XuanTiController 有标注为 @Autowired 的 xuanTiService,所以 Application 会去查找 xuanTiService 这个 bean

  3. 至于为什么会去查找 xuanTiService 这个 bean 呢,是因为 spring-common.xml 中的配置
    <context:annotation-config />
    注释:启用注解配置方式,比如说启用了 @Autowired 的识别。

  4. XuanTiService 只是一个 interface,实现它的是 XuanTiServiceImpl

  5. XuanTiServiceImpl 这个 bean 找到了,因为在 spring-common.xml 中有配置
    <bean class="com.tgb.service.impl.XuanTiServiceImpl" />

  6. xuanTiService.save(xuanTi); 被执行了,具体来说,是 XuanTiServiceImpl 中的 public void save(XuanTi xuanTi) {…} 被执行了

  7. 根据上一章节“切入机制”的描述,在执行 public void save(XuanTi xuanTi) {...} 的时候,会伴随着执行 LogAspect 中的 saveLogInsert 方法

  8. 在 LogAspect 中,有标注为 @Autowired 的 logService,所以 Application 会去查找 logService 这个 bean

  9. 查找的原因,就是第3点所描述的

  10. LogService 只是一个 interface,实现它的是 LogServiceImpl

  11. XuanTiServiceImpl 这个 bean 找到了,因为在 spring-common.xml 中有配置
    <bean id="LogService" class="com.tgb.service.impl.LogServiceImpl" />
    注释:日志记录业务逻辑对象

  12. 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 动态代理。

参考资料

  1. 10. Aspect Oriented Programming with Spring (这是总纲,可以作为知识点的引入)
  2. AOP 那点事儿 (这篇文章写得并不好,太随性,思路不清楚)
  3. Spring AOP Example Tutorial – Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration (Spring AOP demo)
  4. Spring AOP 完成日志记录 (PressSystem 的日志功能实现,参考了这篇文章)
  5. Spring 容器AOP的实现原理——动态代理 (Spring AOP 动态代理的学习)
  6. Spring AOP的底层实现技术—JDK动态代理(Spring AOP 动态代理的学习)

创作时间:2016-05-14 星期六 6:32:24 PM

Spring AOP 知识整理的更多相关文章

  1. Spring Ioc知识整理

    Ioc知识整理(一): IoC (Inversion of Control) 控制反转. 1.bean的别名 我们每个bean元素都有一个id属性,用于唯一标识实例化的一个类,其实name属性也可用来 ...

  2. Spring AOP使用整理:各种通知类型的介绍

    2.PersonImpl类的源码 public class PersonImpl implements Person { private String name; private int age; p ...

  3. Spring框架知识整理

    Spring框架主要构成 Spring框架主要有7个模块: 1.Spring AOP:面向切面编程思想,同时也提供了事务管理. 2.Spring ORM:提供了对Hibernate.myBatis的支 ...

  4. Spring AOP使用整理:自动代理以及AOP命令空间

    三.自动代理的实现 1.使用BeanNameAutoProxyCreator 通过Bean的name属性自动生成代理Bean. <bean class="org.springframe ...

  5. Spring AOP使用整理:使用@AspectJ风格的切面声明

    要启用基于@AspectJ风格的切面声明,需要进行以下的配置: <!-- 启用@AspectJ风格的切面声明 --> <aop:aspectj-autoproxy proxy-tar ...

  6. Spring AOP详细介绍

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

  7. AOP面试知识整理,^_^-包括spring Aop

    讲到java企业级开发框架,就不可避免的讲到 IOC,AOP,MCV 今天面试时被问到AOP,讲的很乱,这里整理笔记,包括AOP,spring-AOP的部分知识,错误的地方请小伙伴指出来. 谈谈你对A ...

  8. Spring AOP四种实现方式Demo详解与相关知识探究

    一.前言 在网络上看到一篇博客Spring实现AOP的4种方式,博主写的很通俗易懂,但排版实在抓狂,对于我这么一个对排版.代码格式有强迫症的人来说,实在是不能忍受~~~~(>_<)~~~~ ...

  9. Spring AOP基础知识

    Spring AOP使用动态代理技术在运行期织入增强的代码,两种代理机制包括:一是基于JDK的动态代理,另一种是基于CGLib的动态代理.之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的 ...

随机推荐

  1. Hbase入门(四)——表结构设计-RowKey

    Hbase的表结构设计与关系型数据库有很多不同,主要是Hbase有Rowkey和列族.timestamp这几个全新的概念,如何设计表结构就非常的重要. 创建 Hbase就是通过 表 Rowkey 列族 ...

  2. 整理基础的CentOS常用命令

    如何知道apache装在哪里? which httpd 1.查看系统使用端口并释放端口 [root@my_nn_01 WEB-INF]# lsof -w -n -i tcp:80 COMMAND    ...

  3. (八十二)c#Winform自定义控件-穿梭框

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...

  4. Thinkphp5.0第三篇

    批量插入数据 //新增一条数据的方法 public function add() { /*$user =new UserModel(); $user->id=1; $user->name= ...

  5. Network in Network(2013),1x1卷积与Global Average Pooling

    目录 写在前面 mlpconv layer实现 Global Average Pooling 网络结构 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 <Net ...

  6. 服务器时间误差导致的google sign-in后台验证错误(远程调试java程序)

    https://developers.google.com/identity/sign-in/web/backend-auth import com.google.api.client.googlea ...

  7. Java 爬虫服务器被屏蔽,不要慌,咱们换一台服务器

    这是 Java 爬虫系列博文的第四篇,在上一篇 Java 爬虫遇上数据异步加载,试试这两种办法! 中,我们从内置浏览器内核和反向解析法两个角度简单的聊了聊关于处理数据异步加载问题.在这篇文章中,我们简 ...

  8. 某CTF平台一道PHP代码审计

    这道题不是说太难,但是思路一定要灵活,灵活的利用源码中给的东西.先看一下源码. 首先要理解大意. 这段源码的大致的意思就是,先将flag的值读取放在$flag里面. 后面再接受你输入的值进行判断(黑名 ...

  9. python自动化测试三部曲之request+django实现接口测试

    国庆期间准备写三篇博客,介绍和总结下接口测试,由于国庆期间带娃,没有按照计划完成,今天才完成第二篇,惭愧惭愧. 这里我第一篇博客的地址:https://www.cnblogs.com/bainianm ...

  10. RF中for循环

    robotframework支持FOR循环语句,语法和Python的语法基本相同,但robotframework中,“FOR”关键字前面需要增加一个“:”,写成“:FOR”,其它与Python的语法相 ...