AOP小记
编程思想演进:POP(Procedure Oriented Programming)-> OOP(Object Oriented Programming)-> AOP(Aspect Oriented Programming)
三者不是孤立的,往往互相配合使用。
1. AOP(Aspect Oriented Programming)
AOP即面向切面编程,一种编程思想。面向对象编程以纵向的编程方式对业务逻辑进行拆分,而面向切面编程则是以横向的方式将与核心业务逻辑关联小的业务独立出来并以很小的侵入性代价与核心业务整合,常用于性能检测、权限验证、日志记录、事务控制、异常处理等。示意:
2. 主要概念
- joinpoint(连接点):指定哪些目标函数可以被拦截
- pointcut(切入点):指定对哪些jointpoint进行拦截
- advice(通知/增强):指定在某些特定的pointcut上需要执行的动作(代码),如进行日志记录。通知的执行时机有:
- Before:前置通知。在目标函数执行前执行通知
- After:后置通知。在目标函数执行后执行通知,无论目标函数是否正常执行都会执行此通知
- AfterReturning:后置返回通知。在目标函数返回时执行通知
- AfterThrowing:异常通知。在目标函数抛出异常时执行通知
- Around:环绕通知。在目标函数执行时执行,可控制目标函数是否执行
- target:被拦截的目标对象
- proxy:被拦截的对象经AOP处理后产生新的对象,为代理对象
- aspect(切面):由pointcut和advice相结合而成,定义adcice应用到哪些pointcut上
- ewaving(织入):把切面代码应用(织入)到被拦截的对象的目标函数从而创建代理对象的过程。Spring是通过实现后置处理器BeanPostProcessor接口来实现织入的。也就是在bean完成初始化之后,通过给目标对象生成代理对象,并交由springIOC容器来接管,这样再去容器中获取到的目标对象就是已经增强过的代理对象。
3. 框架实现
原理
AOP的框架实现主要有Spring AOP、AspectJ,两者都遵循AOP规范且术语基本一致。区别:
- 前者动态织入(运行时动态生成代码应用到目标类):采用JDK动态代理、CGLIB代理等实现。功能没有后者全面(对大多数项目来说够用了),侧重于与Spring IOC整合。
- 后者静态织入(在编译期将将aspec代码编译织入到目标类):相当于静态代理。功能更全,但需要AspectJ特殊的编译器配合。(题外话:Python Numpy、JPython、AspectJ、IronPython作者均为美帝大牛Jim Hugunin,具体可参阅https://mp.weixin.qq.com/s/0lpsJgZkuFMOu-fsrGeY3Q)
Spring 2.0后使用了与AspectJ一样的注解,但底层仍是动态代理技术的实现,而没有依赖于 AspectJ 的编译器。
Spring AOP基于动态代理实现,原理图:
AspectJ基于静态代理实现,原理图:
3.1. Spring AOP示例
定义切点:@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing、@Around等。
定义切面:@Aspect
两个概念:目标对象(被代理的对象)、代理对象(目标对象的代理对象)
3.1.1. 示例
UserRepository及UserRepositoryImpl:
package com.marchon.learning.aspect; public interface UserRepository {
public Integer addUser(Integer val); void updateUser(); void deleteUser(); void findUser();
}
UserRepository.java
package com.marchon.learning.aspect; import org.springframework.stereotype.Repository; @Repository
public class UserRepositoryImpl implements UserRepository { @Override
public Integer addUser(Integer val) {
System.out.println("*add user...*");
return 10 / val;
} @Override
public void updateUser() {
System.out.println("*update user...*"); } @Override
public void deleteUser() {
System.out.println("*delet4e user...*"); } @Override
public void findUser() {
System.out.println("*update user...*"); } }
UserRepositoryImpl.java
切面定义与配置:
package com.marchon.learning.aspect; import java.util.Arrays; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; /**
* 在@Before等注解的value属性上指定pointcut,其他类似;各方法的参数模式应与下面一致(有的无参有的1参)、返回值则可以任意。
*/
@Aspect // 表示一个切面
@Component // 以让Spring IOC容器识别并生成动态代理
public class MyAspect {
private static final String CLASS_QUALIFIED_NAME = "com.marchon.learning.aspect.UserRepository"; @Before("execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))")
public void before() {
System.out.println("前置通知...");
} @After("execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))")
public void after() {
System.out.println("后置通知...");
} @AfterReturning(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))", returning = "retVal")
public void afterReturning(Object retVal) {
System.out.println("后置返回通知..." + retVal);
} @AfterThrowing(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))", throwing = "ex")
public void afterThrowing(Throwable ex) {// 若环绕通知中对目标函数产生的异常进行捕获处理则这里就不再可收到目标函数所抛异常
System.out.println("后置异常通知..." + ex.getMessage());
} @Around("execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前... ,参数:" + Arrays.asList(joinPoint.getArgs()));
Object ret = null;
ret = joinPoint.proceed();
System.out.println("环绕通知后..."); // try {//若这里进行异常捕获处理则后置通知将不再能收到目标方法执行产生的异常
// ret = joinPoint.proceed();
// System.out.println("环绕通知后...");
// } catch (Throwable e) {
// e.printStackTrace();
// }
return ret;
} // ============= 以下为与上面@Before等价的定义
@Pointcut("execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..))")
public void pointcut1() { } @Before("pointcut1()")
public void b1() {
System.out.println("等价的 前置通知...");
} // ============= 以下为@Before获取参数的示例,除了@Around外的其他通知获取参数的方式与下类似
// @Before(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..)) && args(theVal)", argNames = "theVal") // 两种写法均可
@Before(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..)) && args(theVal)") // 下面方法参数名须与此一致
public void before(Integer theVal) {
System.out.println("前置通知... ,参数:" + theVal);
} }
MyAspect.java
执行及结果:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class StudentTest {
@Autowired
private UserRepository userRepository; @Test
public void student() {
userRepository.addUser(2);
System.out.println(); userRepository.addUser(0);
} } //输出如下
环绕通知前... ,参数:[2]
等价的 前置通知...
前置通知... ,参数:2
前置通知...
*add user...*
环绕通知后...
后置通知...
后置返回通知...5 环绕通知前... ,参数:[0]
等价的 前置通知...
前置通知... ,参数:0
前置通知...
*add user...*
后置通知...
后置异常通知.../ by zero
可以看出无论是否出异常@After通知都会被执行
注:
- 定义切点的几个注解只能用在方法上。
- @Pointcut + @Before组合来定义切入点及通知,也可以直接只用@Before等来定义切入点及通知。前者使得在对同一切入点绑定多个通知时不需重复写长长的切入点、后者则可以减少一层代码,采用哪种视情况而定。
- 获取目标对象的方法参数:@Around可以直接通过ProceedingJoinPoint获取、其他Advice可以通过args参数获取。示例:
// ============= 以下为@Before获取参数的示例,除了@Around外的其他通知获取参数的方式与下类似
// @Before(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..)) && args(theVal)", argNames = "theVal") // 两种写法均可
@Before(value = "execution(* " + CLASS_QUALIFIED_NAME + ".addUser(..)) && args(theVal)") // 下面方法参数名须与此一致
public void before(Integer theVal) {
System.out.println("前置通知... ,参数:" + theVal);
}
- 在@Before等Advice或在@Pointcut中定义切入点。切入点表达式见下节。
3.1.2. 切入点表达式(切入点指示符)
(1) Wildcard(通配符): * 、 .. 、 +
* :匹配任意数量的字符。示例:
execution(* set*(int)) //匹配以set开头,参数为int类型,任意返回值的方法
.. :匹配方法中任意数量的参数、匹配指定包及任意子包。示例:
execution(public * *(..)) //匹配任意返回值,任意名称,任意参数的公共方法
within(com.marchon.learning.aspect..*) //匹配com.marchon.learning.aspect包及其子包中所有类中的所有方法
+ :匹配给定类的任意子类。示例:
within(com.marchon.learning.aspect.UserRepository+) //匹配实现了UserRepository接口的所有子类的方法
(2) Class Signature Expression(类签名表达式):匹配符合指定类型(包名、类名、接口)模式的方法。
语法: within(<type name>) ,示例:
@Pointcut("within(com.marchon.learning.aspect..*)") //匹配com.marchon.learning.aspect包及其子包中所有类中的所有方法
@Pointcut("within(com.marchon.learning.aspect.UserRepositoryImpl)") //匹配UserRepositoryImpl类中所有方法
@Pointcut("within(com.marchon.learning.aspect.UserRepositoryImpl+)") //匹配UserRepositoryImpl类及其子类中所有方法
@Pointcut("within(com.marchon.learning.aspect.UserRepository+)") //匹配所有实现UserRepository接口的类的所有方法
(3) Method Signature Expression(方法签名表达式):匹配符合指定方法签名模式的方法。
语法:
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters:方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
示例:
@Pointcut("execution(* com.marchon.learning.aspect.UserRepositoryImpl.*(..))") //匹配UserRepositoryImpl类中的所有方法
@Pointcut("execution(public * com.marchon.learning.aspect.UserRepositoryImpl.*(..))") //匹配UserRepositoryImpl类中的所有公共的方法
@Pointcut("execution(public int com.marchon.learning.aspect.UserRepositoryImpl.*(..))") //匹配UserRepositoryImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public * com.marchon.learning.aspect.UserRepositoryImpl.*(int , ..))") //匹配UserRepositoryImpl类中第一个参数为int类型的所有公共的方法
(4) bean:匹配指定名称的bean对象的方法(AspectJ中没有此指示符)。示例: @Pointcut("bean(*Service)") //匹配名称以Service结尾的bean中的所有方法
(5) target:匹配指定类型的被代理对象旳方法。示例: @Pointcut("target(com.marchon.learning.aspect.UserRepository)") //匹配实现了指定接口的所有被代理对象的方法
(6) this:匹配指定类型的代理对象的旳方法。示例: @Pointcut("this(com.marchon.learning.aspect.UserRepository)") //匹配实现了指定接口的所有代理对象的方法
(7) @within:匹配被指定注解修饰的类型中的方法。示例: @Pointcut("@within(com.marchon.spring.annotation.MarkerAnnotation)") //匹配被指定注解修饰的类中的方法
(8) @annotation:匹配被指定注解修饰的方法。示例: @Pointcut("@annotation(com.marchon.spring.annotation.MarkerAnnotation)") //匹配使用了指定注解的方法
(9) 逻辑运算符:and、or、not (或 &&、||、! )。示例: @Pointcut("bean(*Service) && within(com.marchon.learning) ") //匹配名以Service结尾且在com.marchon.learning包下的bean中的方法
3.1.3. Advice中获取目标对象方法参数
在Spring AOP中,除了execution和bean指示符不能传递参数给通知方法,其他指示符都可以将匹配的方法相应参数或对象自动传递给通知方法。
可通过args(xx)及argNames="xx"来获取目标对象的示例见3.1.1节获取目标对象参数部分。
对于@Around还可直接通过ProceedingJoinPoint获取目标对象的方法参数等信息。
3.1.4. 通知执行的优先级
同一个切面内的多个通知作用在同一个切点,或者不同切面中的多个通知作用在同一个切入点时,通知执行具有优先级。
(1) 多个切面应用到同一个切入点(目标函数)上时,对于目标函数之前的Advice优先级高的切面先执行、对于目标函数之后的Advice优先级高的后执行。
执行示意图(下图中切面1优先级比切面2高):
示例:定义了AspectOne、AspectTwo两个切面且前者优先级比后者高(优先级通过实现springframework Ordered接口指定),其相应执行结果如下:
@Aspect
@Component
class AspectOne implements Ordered {
private static final String CLASS_QUALIFIED_NAME = "com.marchon.learning.aspect.UserRepository"; @Pointcut("execution(* " + CLASS_QUALIFIED_NAME + ".updateUser(..))")
public void myPointcut() {
} @Before("myPointcut()")
public void before1() {
System.out.println("One before1");
} @Before("myPointcut()")
public void before2() {
System.out.println("One before2");
} @After("myPointcut()")
public void after1() {
System.out.println("One after1");
} @After("myPointcut()")
public void after2() {
System.out.println("One after2");
} @Override
public int getOrder() {
return 0;
}
} @Aspect
@Component
class AspectTwo implements Ordered {
private static final String CLASS_QUALIFIED_NAME = "com.marchon.learning.aspect.UserRepository"; @Pointcut("execution(* " + CLASS_QUALIFIED_NAME + ".updateUser(..))")
public void myPointcut() {
} @Before("myPointcut()")
public void before1() {
System.out.println("Two before1");
} @Before("myPointcut()")
public void before2() {
System.out.println("Two before2");
} @After("myPointcut()")
public void after1() {
System.out.println("Two after1");
} @After("myPointcut()")
public void after2() {
System.out.println("Two after2");
} @Override
public int getOrder() {
return 1;
}
} //执行userRepository.updateUser结果如下,说明了作用于同一个切点的不同切面的执行顺序
One before1
One before2
Two before1
Two before2
*update user...*
Two after1
Two after2
One after1
One after2
(2) 同一切面内的多个通知作用在同一切入点时,按书写的顺序执行,不过环绕通知(@Around)比较特殊,其始终比前置通知、后置通知等先执行。示例可见3.1.1中的示例。
4. 底层原理
根据切点找到目标对象(即被代理对象),通过静态代理或动态代理( JDK动态代理或CGLIB动态代理 )为目标对象生成代理对象。
5. 参考资料
https://blog.csdn.net/javazejian/article/details/56267036
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop
AOP小记的更多相关文章
- Spring AOP小记
一.概述 在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用dao来实现自身的业务逻辑,经常会导 ...
- TinyFrame再续篇:整合Spring AOP实现日志拦截
上一篇中主要讲解了如何使用Spring IOC实现依赖注入的.但是操作的时候,有个很明显的问题没有解决,就是日志记录问题.如果手动添加,上百个上千个操作,每个操作都要写一遍WriteLog方法,工作量 ...
- 展开说说,Spring Bean IOC、AOP 循环依赖
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 延迟满足能给你带来什么? 大学有四年时间,但几乎所有人都是临近毕业才发现找一份好工作 ...
- Java:动态代理小记
Java:动态代理小记 对 Java 中的 动态代理,做一个微不足道的小小小小记 概述 动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理.比如说加日志,加事务等.可以给这个类创建一个代理 ...
- 基于spring注解AOP的异常处理
一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...fin ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- 学习AOP之深入一点Spring Aop
上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...
- 学习AOP之认识一下Spring AOP
心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...
随机推荐
- Easyui datagrid扩展子网格detailview增删改查详解
话不多gang,先上代码,将以下三个属性插入主网格的初始化参数中: view : detailview, //1 detailFormatter : function(index, row) { // ...
- KVM学习笔记--静态迁移
.静态迁移过程如下 (1)确定虚拟机关闭状态 (2)准备迁移oeltest02虚拟机,查看该虚拟机配置的磁盘文件 (3)导入虚拟机配置文件 [root@node1~]# virsh dumpxml o ...
- 3-美团 HTTP 服务治理实践
参考: 美团 HTTP 服务治理实践 Oceanus:美团HTTP流量定制化路由的实践
- SPC软控件提供商NWA的产品在各行业的应用(包装行业)
Northwest Analytical (NWA)是全球领先的“工业4.0”制造分析SPC软件控件提供商.产品(包含: NWA Quality Analyst , NWA Focus EMI 和 N ...
- 英文DIAMAUND钻石DIAMAUND词汇
首先谈谈钻石和金刚石的名称.金刚石是一种天然矿物,是钻石的原石.习惯上人们常将加工过的金刚石称为钻石,而未加工过的称为金刚石(当然,有的金刚石不用加工便可应用).钻石是那些达到宝石级别的金刚石晶体切磨 ...
- 基于hashlib下的文件校验
hashlib不仅可以对密码进行加密也可以对文件内容进行校验,传统的小文件校验通过人为校验是不现实的,如果摸个文件里面的内容多出一个空格的话那么哦是根本就不知道的因此我们需要一个可以校验文件的方法,而 ...
- WIP表解析
1,WIP的作用 负责纪录生产相关信息,生产什莫--工单的制定,下达,生产步鄹--工序及其移动,投入什莫--组件需求和投料,资源投入入和费用吸收,负责纪录生产成本的归集和差异分析,投入多少组件,资 ...
- Tomcat部署项目时,发布的项目页面部分乱码,且页面渲染文件也是乱码。
catalina.bat中必须设置为UTF-8,如果我不设置为UTF-8,页面接收到的就是乱码了,尝试过各种UTF-8的调试,都无解,最后还是只能在catalina.bat的set "JAV ...
- Windows下面startup.bat启动Tomcat偶发死锁问题
Windows下面startup.bat启动Tomcat时,因为日志都打印到了cmd里面,存在偶发卡死Tomcat的问题,该问题确认是Windows系统的问题,而且一直没有解决.解决的办法是把日志重定 ...
- Gitlab批量迁移项目
最近接到一个需求,要把一个Gitlab上边的项目全部导入到另外一个Gitlab,借鉴了网上的一个方法,成功实现. 参考链接:https://segmentfault.com/a/11900000159 ...