掌握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 {@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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代理对象@Overridepublic 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;}@Overridepublic 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相关推荐
- 【spring】springAop开发
xml开发方式 springboot的使用 1.声明一个parent 代码实现: <parent><groupId>org.springframework.boot</g ...
- Spring事务管理 与 SpringAOP
1,Spring事务的核心接口 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略. ...
- 关于Quartz的Job 不能被注入以及SpringAop对Job失效
关于Quartz的Job 不能被注入以及SpringAop对Job失效 Problem(问题) 最近在工作遇到需要对Quartz的Job进行异常后将异常记录到数据库的操作,第一反应就想到了使用Sp ...
- Spring AOP源码分析(八)SpringAOP要注意的地方
2019独角兽企业重金招聘Python工程师标准>>> SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来: (1)SpringAOP对于最外层的函数只拦截pub ...
- Spring-AOP的五种通知和切面的优先级、通知变量声明
SpringAOP的通知分为以下五种: 1前置通知(@before) 在连接点执行之前执行的代码 2后置通知(@after) 在连接点执行之后执行的代码,不管连接点执行后是否出现异常,后置通知都会执行 ...
- Spring系列之Spring框架和SpringAOP集成过程分析(十)
转载请注明出处:https://blog.csdn.net/zknxx/article/details/80724180 在开始这个系列之前大家先想一下我们是怎么在项目中使用SpringAOP的(这里 ...
- 一文带你学会基于SpringAop实现操作日志的记录
前言 大家好,这里是经典鸡翅,今天给大家带来一篇基于SpringAop实现的操作日志记录的解决的方案.大家可能会说,切,操作日志记录这么简单的东西,老生常谈了.不! 网上的操作日志一般就是记录操作人, ...
- Spring-AOP实践 - 统计访问时间
公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化.于是,首先就要搞清楚究竟是哪一步耗时太多. 我采用spring aop来统计各个阶段的用时,其中计时器工具为S ...
- 使用Spring-AOP
在开始使用Spring的AOP之前我们需要在bean.xml中引入aop约束,并在pom.xml导入依赖 <?xml version="1.0" encoding=" ...
- 注解的方式实现动态代理基于SpringAOP
1.配置spring容器 导入jar包 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.j ...
最新文章
- 启动Genymotion时报错Failed to initialize backend EGL display
- 如何在ubuntu上安装nvidia-docker同时与宿主共享GPU cuda加速
- 使用OUYA第一次启动OUYA
- python读excel字体颜色_无法使用python xlsxwri更改excel中的字体颜色
- DbHelperSQL 判断数据库表结构公用方法
- “约见”面试官系列之常见面试题之第七十四篇之v-if和v-for优先级(建议收藏)
- 如何在.NET Core中创建API
- 因 Cannot resolve com.lowagie:itext:2.1.7.js6,选择手动安装 Jar 包进 Maven 仓库
- 我学习图像处理的小结
- 这款Linux 恶意软件正在劫持全球的超级计算机
- 虚拟电脑键盘app_App发布倒计时
- 性能服务器可以同时标注吗,服务器上有内存标注
- sql语句之case when的用法
- 神策分析 1.17 重磅来袭,四大特性让数据治理更轻松,数据分析更深入
- Findbugs异常总汇
- dpbs和pbs的区别_PBS与TBS区别
- Windows 7 Service Pack 1已发布:但是您应该安装它吗?
- 微型计算机字,小型微型计算机系统
- P1478 陶陶摘苹果(升级版)(C++_贪心)
- PostgreSQL测试套-pg_regress使用
热门文章
- C++vector容器
- Oracle join多表查询
- VMware-vRealize-Log-Insight-8.2.0.0安装部署指南
- 如何解决常见的 Active Directory 复制错误
- word vba 点击任意域代码,刷新整个文档的域代码值
- Dubbo 学习总结(12)—— 十年再出发,Dubbo 3.0 Preview 即将在 3 月发布
- Spring Boot学习总结(27)—— Spring Boot中两个数据库迁移工具Liquibase和Flyway的比较
- Java基础学习总结(63)——Java集合总结
- “更高效 更自由 更开放”—— 艺赛旗RPA9.0重磅来袭
- Kubernetes资源分配(limit/request)