掌握SpringAOP
Posted 你这家伙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握SpringAOP相关的知识,希望对你有一定的参考价值。
我们在了解SpringMVC的时候,我们使用@ControllerAdvice来完成统一异常处理或响应的统一数据格式封装,这其实就是我们的AOP思想,AOP是面向切面编程的一种编程语言,但是和语言无关,那么今天就让你对AOP思想不在陌生
背景介绍
AOP(Aspect-Oriented Programming):面向切面编程。
AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、
继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。如传统的后端三层架构分
层设计模式为:Controller、Service、DAO三层,每一层对应不同的作用
- Conytroller 抽象了业务中,接受HTTP请求,解析/校验请求数据等操作,是前端控制器
- Service 抽象了业务中,比较复杂的业务逻辑,是业务处理类
- DAO 抽象了业务中,对数据的处理,是数据访问类
其调用次序为:Controller方法调用Service方法,Service方法调用DAO方法
假如有以下三个功能:付款、转账及贷款功能,要实现统一的业务,如下图
(单个业务纵向执行:Controller -> Service -> DAO)
要在以上所有功能中,都加入记录HTTP请求执行时间的功能,需要在以上三个Controller方法
中,以方法执行完毕的时间点,减去方法执行前的时间点,得到方法执行时间。这样的代码往往横向地
散布在所有对象层次中,而与它对应的对象的核心功能毫无关系。这样的编码设计会产生大量的重复代
码,且对原有代码的侵入性非常大,也就是我们说的代码耦合性很高。
这种散布在各处的无关的代码被称为横切(Cross Cutting),在OOP设计中,它导致了大量代码的重 复,而不利于各个模块的重用。
什么是AOP
AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面
上的问题(如日志,事务,权限等),利用一种称为"横切"的技术,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面,使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点
AOP的使用场景及作用
对于统一业务来说,常见切面业务如:
- 响应统一的数据格式
- 统一异常处理
- 统一日志记录,如跟踪用户访问信息
- 用户统一会话管理、权限管理
- 方法执行时间计算
- 事务管理
……
AOP技术解决了对横切业务的统一管理:
- 横切代码的高度复用,和业务代码互相独立,满足代码设计上的高内聚低耦合
- 系统更好的扩展性,可维护性
静态代理
例如现在有以下代码
public class AliPayService implements PayService
@Override
public void pay()
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//支付业务逻辑
System.out.println("支付宝支付...");
//4.时间记录结束
存在问题1:
比如现在有支付宝和微信两种支付手段,然后每种支付方式都有很多一样的逻辑,此时就会出现
- 每个实现类都有大量的重复代码
- 对原有代码的侵入性,必须修改原有代码
解决办法:
那肯定很多人都会把公共部分提取出模板或者公共的父类方法。然后让调用的类来使用公共模板或者继承公共的父类
存在问题二:
虽然问题一解决了代码的重复问题,但是是必须要求实现类继承统一的父类,会对实现类的代码造成侵入性。那么在不改动原有代码的基础如何实现呢?
解决办法:
使用代理设计模式,代理模式又分为静态代理和动态代理两种模式,关键在于:
- 被代理类:原始类不进行任何修改,但创建和使用时,不再使用原始的被代理类,而是设计一个原始类的代理类
- 代理类:基于被代理类,构造一个代理类,在使用时,也是使用代理类
静态代理
从代码设计上解决,可以使用静态代理设计模式。该设计模式可以采取两种手段:继承的方式,或聚合+接口的方式。
继承的方式
import org.example.demo.service.AliPayService;
//通过继承的方式实现静态代理类
class AliPayServiceStaticProxyByExtends extends AliPayService
@Override
public void pay()
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//支付业务逻辑:实际还是调用父类的业务
super.pay();
//4.时间统计结束
System.out.println("记录结束时间");
public static void main(String[] args) throws InterruptedException
//使用时,不再直接使用原有对象,而是使用代理对象,所以是new代理类
AliPayService aliPayService = new AliPayServiceStaticProxyByExtends();
//使用:发起支付业务
aliPayService.pay();
接口的方式
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
public class StaticProxyByImplements implements PayService
private PayService payService;
//构造方法中传入被代理的对象
public StaticProxyByImplements(PayService payService)
this.payService = payService;
@Override
public void pay()
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//支付业务逻辑:其实还是调用传入的被代理类的方法
payService.pay();
//4.时间统计结束
System.out.println("记录结束时间");
public static void main(String[] args)
//使用时,同样是使用代理类:通过被代理类创建代理类
PayService aliPayService = new StaticProxyByImplements(new
AliPayService());
//使用代理类完成支付业务
aliPayService.pay();
静态织入技术
从 AOP 的技术实现上看,可以通过 java 代码编译期及类加载期动态的织入字节码来解决。
java程序的开发、执行流程为:
使用一些静态织入的字节码技术,如 AspectJ,可以完成在真正运行前就织入字节码的内容。
- 编译期织入:在Java编译期,采用特殊的编译器,将切面的字节码织入到生成的 class 字节码文件中。可以在编译 java 文件为 class 文件时织入,也可以在已生成的 class 文件再次织入。
- 加载期织入:通过特殊的类加载器,在类被加载进虚拟机之前织入。
动态代理
上面的织入技术这种设计模式成为动态代理模式,都是在class代码运行期,动态的织入字节码
动态代理的实现方式:JDK 及 CGLIB 的方式,这两种方式的代理目标都是被代理类中的方法,在运行期,动态的织入字节码生成代理类
- CGLIB是Java中的动态代理框架,主要作用就是根据目标类和方法,动态生成代理类。
- Java中的动态代理框架,几乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
- 字节码框架是直接操作class字节码的框架。可以加载已有的class字节码文件信息,修改部分信息,或动态生成一个class
JDK实现
JDK实现时,先通过实现 InvocationHandler 接口创建方法调用处理器,再通过 Proxy 来创建代理
类。
//动态代理:使用JDK提供的api(InvocationHandler、Proxy实现),此种方式实现,要求被代理类必
须实现接口
public class PayServiceJDKInvocationHandler implements InvocationHandler
//目标对象即就是被代理对象
private Object target;
public PayServiceJDKInvocationHandler( Object target)
this.target = target;
//proxy代理对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过反射调用被代理类的方法
Object retVal = method.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
public static void main(String[] args)
PayService target= new AliPayService();
//方法调用处理器
InvocationHandler handler =
new PayServiceJDKInvocationHandler(target);
//创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]PayService.class,
handler);
proxy.pay();
CGLIB实现
public class PayServiceCGLIBInterceptor implements MethodInterceptor
//被代理对象
private Object target;
public PayServiceCGLIBInterceptor(Object target)
this.target = target;
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy
methodProxy) throws Throwable
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过cglib的代理方法调用
Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
public static void main(String[] args)
PayService target= new AliPayService();
PayService proxy= (PayService) Enhancer.create(target.getClass(),new
PayServiceCGLIBInterceptor(target));
proxy.pay();
JDK和GCLIB实现的区别
- JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy ,在运行时
动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实
现的方式),只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。 - CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象。(因此不能被final修饰,因为被final修饰就不能够实现继承)
SpringAOP的实现
Spring AOP由 spring-aop 、 spring-aspects 和 spring-instrument 3 个模块组成。
SpringAOP构建在动态代理基础上,因此Spring对AOP的支持局限于方法拦截。
SpringAOP支持 JDK 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使用 AOP 会基于JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类
注意:虽然Spring AOP由 spring-aop 、 spring-aspects 和 spring-instrument 3 个模块组成,但是SpringAOP值提供了AspectJ的注解语法支持,并没有真的实现AspectJ的编辑器,也就是说,加入spring-aspects依赖包,只是可以使用 AspectJ 的语法,运行时还是基于spring-aop 依赖包的动态代理实现
SpringAOP示例
AOP已经形成了自己的术语。描述切面的常用术语有通知(advice),切点(pointcut)和连接点(joinpoint) ![在这里插入图片描述](https://img-blog.csdnimg.cn/9aca0dc630de45ffbd7cb126ccef0cbc.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3draDE4ODkxODQzMTY1,size_16,color_FFFFFF,t_70)切面(Aspect)
切面(Aspect)由 切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。 切面由容器中的Bean使用@Aspect注解实现,如:@Aspect
@Component
public class PojoAspect
连接点(Join Point)
-
应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切
面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 -
在 Spring AOP 中,Join Point 总是方法的执行点, 即只有方法连接点。所以使用SpringAOP,无需使用
连接点的代码
切点(Pointcut)
在 Spring 中, 所有的方法都可以认为是 Join Point,但是我们并不希望在所有的方法上都添加 Advice, 而 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述)来匹配Join Point,给满足规则的 Join Point 添加 Advice。
如:
在切面类中的定义一个 public void 的普通方法,并使用切点注解@Pointcut
@Aspect
@Component
public class PojoAspect
/**
* 切点方法:查找org.example.demo.controller包下,以Test开头,Controller结尾的类,
* 在找到的类中,再查找以pojo开头的方法。
*/
@Pointcut("execution(* org.example.demo.controller.Test*Controller.pojo*
(..))")
public void pojoPointcut()
- :匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)
- … :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
- :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的所有
子类包括本身
通知(Advice)
切面也是有目标的 ——它必须完成的工作。在AOP术语中,切面的工作被称之为通知。 通知:定义了切面是什么,何时使用,其描述了切面要完成的工作,还解决何时执行这个工作的问题。Spring切面类中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:- 前置通知
使用@Before:通知方法会在目标方法调用之前执行。
后置通知 - 使用@After:通知方法会在目标方法返回或者抛出异常后调用。
- 返回之后通知
使用@AfterReturning:通知方法会在目标方法返回后调用。 - 抛异常后通知
使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。 - 环绕通知
使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行
为。
织入
织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:- 编译期:
切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。 - 类加载器:
切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving. LTW)就支持以这种方式织入切面。 - 运行期:
切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。SpringAOP就是以这种方式织入切面的。
常见面试题:
- AOP的概念、使用场景、优点
- springAOP的实现方式(JDK和CGLIB),以及两种方式的区别
- springAOP的注解(如:@Aspect(切面)、@Pointcut(切点)、以及通知的几种注解)
以上是关于掌握SpringAOP的主要内容,如果未能解决你的问题,请参考以下文章