springaop 简介
springaop 是 spring 对 AOP 技术的具体实现, 它是 spring 框架的核心技术. springaop 底层使用 JDK 动态代理或 CGLIB 动态代理技术实现.
应用场景:
在多个方法中执行相同操作且和当前业务没有直接关系的逻辑, 我们可以单独抽离出来. 并通过 aop 技术为目标方法扩展实现.
如:
1)记录总日志流水
2)权限控制
3)事物管理
...
一, springaop--aspectJ 详细讲解
1,aspectJ 常用注解
1)@Pointcut
切点: 简单理解就是一个匹配条件, 规则, 与切点函数组合使用.
2)@Before
前置增强: 在目标方法执行前, 增加执行逻辑.
为了方便理解 @Pointcut 及 @Before, 我们举个栗子:
2.1)定义一个 ZeroService 接口
- public interface ZeroService {void findOne();
- void addZero(String id);
- void deleteZero(String id);
- void updateZero(String id);
- void queryZero(String id);
- }
2.2)增加一个实现类 ZeroServiceImpl
- @Service
- public class ZeroServiceImpl implements ZeroService{
- @Override
- public void findOne() {
- LogUtil.info("execute findOne()~~~");
- }
- @Override
- public void addZero(String id) {
- LogUtil.info("execute addZero(id="+id+")~~~");
- if("123".equals(id)){
- throw new RuntimeException("模拟异常!");
- }
- }
- @Override
- public void deleteZero(String id) {
- LogUtil.info("execute deleteZero(id="+id+")~~~");
- if("123".equals(id)){
- throw new RuntimeException("模拟异常!");
- }
- }
- @Override
- public void updateZero(String id) {
- LogUtil.info("execute updateZero(id="+id+")~~~");
- if("123".equals(id)){
- throw new RuntimeException("模拟异常!");
- }
- }
- @Override
- public void queryZero(String id) {
- LogUtil.info("execute queryZero(id="+id+")~~~");
- if("123".equals(id)){
- throw new RuntimeException("模拟异常!");
- }
- }
- }
2.3)增加一个切点函数类
- @Aspect
- @Component
- public class ZeroAop {
- @Pointcut("execution(* com.lianjinsoft.springaop.service.ZeroService.findOne(..))")
- private void condition(){
- }
- @Before("condition()")
- private void before(){
- LogUtil.info("execute before method~~~");
- }
- }
condition 方法, 定义了一个切点, 匹配 com.lianjinsoft.springaop.service.ZeroService.findOne 方法.
before 方法, 定义了一个前置增强, 匹配条件引用了 condition 方法. 即为 ZeroService.findOne 方法, 增加一个前置执行方法.
2.4)通过 Junit 测试
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class ZeroServiceTests {
- @Autowired
- private ZeroService service;
- @Test
- public void test_before() {
- service.findOne();
- }
- }
2.4.1)执行 test_before()方法
- 15:01:12.237 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 15200 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:01:12.237 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:01:12.268 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:12 CST 2018]; root of context hierarchy
- 15:01:12.905 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.876 seconds (JVM running for 1.604)
- 15:01:12.938 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute before method~~~
- 15:01:12.938 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute findOne()~~~
- 15:01:12.941 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:12 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.findOne()方法之前, 先执行了 ZeroAop.before()方法.
3)@AfterReturning
后置增强: 在目标方法执行后, 增加执行逻辑(若目标方法抛出异常, 则不执行增强逻辑).
继续举栗子:
3.1)在 ZeroAop 类中增加切点
- @AfterReturning("execution(* com.lianjinsoft.springaop.service.ZeroService.addZero(..))")
- private void afterReturning(){
- LogUtil.info("execute afterReturning method~~~");
- }
3.2)在 ZeroServiceTests 类中增加测试方法
- @Test
- public void test_afterReturning() {
- service.addZero("666");
- }
- @Test
- public void test_afterReturning_exception() {
- service.addZero("123");
- }
3.2.1)执行 test_afterReturning 方法
- 15:01:43.617 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 1420 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:01:43.617 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:01:43.638 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:43 CST 2018]; root of context hierarchy
- 15:01:44.181 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.766 seconds (JVM running for 1.504)
- 15:01:44.215 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute addZero(id=666)~~~
- 15:01:44.216 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute afterReturning method~~~
- 15:01:44.219 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:43 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.addZero()方法之后, 执行了 ZeroAop.afterReturning()方法.
3.2.2)执行 test_afterReturning_exception 方法
- 15:01:53.984 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 19052 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:01:53.984 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:01:54.006 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:54 CST 2018]; root of context hierarchy
- 15:01:54.553 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.773 seconds (JVM running for 1.509)
- 15:01:54.586 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute addZero(id=123)~~~
- 15:01:54.593 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:01:54 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.addZero()方法之后, 没有输出增强方法中的信息, 即当目标方法抛出异常时, 不会执行增强方法中的逻辑.
4)@Around
环绕增强: 在目标方法执行前和执行后, 增加执行逻辑.
再举个例子:
4.1)在 ZeroAop 类中增加切点
- @Around("execution(* com.lianjinsoft.springaop.service.ZeroService.deleteZero(..))")
- private void around(ProceedingJoinPoint point) throws Throwable{
- LogUtil.info("execute around method start~~~");
- point.proceed();
- LogUtil.info("execute around method end~~~");
- }
4.2)在 ZeroServiceTests 类中增加测试方法
- @Test
- public void test_around() {
- service.deleteZero("666");
- }
- @Test
- public void test_around_exception() {
- service.deleteZero("123");
- }
4.2.1)执行 test_around 方法
- 15:02:07.355 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 12384 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:02:07.357 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:02:07.388 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:07 CST 2018]; root of context hierarchy
- 15:02:07.924 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.788 seconds (JVM running for 1.525)
- 15:02:07.960 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute around method start~~~
- 15:02:07.960 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute deleteZero(id=666)~~~
- 15:02:07.960 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute around method end~~~
- 15:02:07.964 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:07 CST 2018]; root of context hierarchy
从日志的第 5,6,7 行可以看出, 在执行 ZeroService.deleteZero()方法前后, 分别输出了 ZeroAop.around()增强方法中的调试信息.
4.2.2)执行 test_around_exception 方法
- 15:02:20.132 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 2056 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:02:20.132 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:02:20.156 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:20 CST 2018]; root of context hierarchy
- 15:02:20.698 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.774 seconds (JVM running for 1.525)
- 15:02:20.731 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute around method start~~~
- 15:02:20.732 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute deleteZero(id=123)~~~
- 15:02:20.738 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:20 CST 2018]; root of context hierarchy
从日志的第 5,6,7 行可以看出, 在执行 ZeroService.deleteZero()方法前, 输出了增强方法中的调试信息. 而在 ZeroService.deleteZero()方法执行后, 没有输出增强方法中的调试信息. 即说明当目标方法抛出异常时, 环绕增强方法中的后续逻辑不会执行.
如何保证让环绕增强后的逻辑继续执行? 手动加上异常处理即可.
5)@AfterThrowing
后置增强: what? 还有一个后置增强? 是的, 这个后置增强和 @AfterReturning 增强相对应. 只会在目标方法抛出异常时执行.
举个栗子:
5.1)在 ZeroAop 类中增加切点
- @AfterThrowing("execution(* com.lianjinsoft.springaop.service.ZeroService.updateZero(..))")
- private void afterThrowing(){
- LogUtil.info("execute afterThrowing method~~~");
- }
5.2)在 ZeroServiceTests 类中增加测试方法
- @Test
- public void test_afterThrowing() {
- service.updateZero("666");
- }
- @Test
- public void test_afterThrowing_exception() {
- service.updateZero("123");
- }
5.2.1)执行 test_afterThrowing 方法
- 15:02:33.418 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 4920 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:02:33.418 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:02:33.443 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:33 CST 2018]; root of context hierarchy
- 15:02:34.009 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.796 seconds (JVM running for 1.517)
- 15:02:34.042 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute updateZero(id=666)~~~
- 15:02:34.046 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:33 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.updateZero()方法后, 没有输出了 ZeroAop.afterThrowing()增强方法中的调试信息.
5.2.2)执行 test_afterThrowing_exception 方法
- 15:02:45.049 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 16904 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:02:45.049 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:02:45.079 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:45 CST 2018]; root of context hierarchy
- 15:02:45.643 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.802 seconds (JVM running for 1.549)
- 15:02:45.674 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute updateZero(id=123)~~~
- 15:02:45.675 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute afterThrowing method~~~
- 15:02:45.681 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:45 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.updateZero()方法后, 输出了 ZeroAop.afterThrowing()增强方法中的调试信息. 即当目标方法中抛出异常时, 才会触发 afterThrowing 后置增强.
6)@After
后置增强: 纳尼? 还有一个后置增强? 还有吗? 没有了, 这是终结版. 那和上面的两个后置增强又有什么区别呢?@After 增强始终会被执行(不管目标方法是否抛出异常).
再举个栗子:
6.1)在 ZeroAop 类中增加切点
- @After("execution(* com.lianjinsoft.springaop.service.ZeroService.queryZero(..))")
- private void after(){
- LogUtil.info("execute after method~~~");
- }
6.2)在 ZeroServiceTests 类中增加测试方法
- @Test
- public void test_after() {
- service.queryZero("666");
- }
- @Test
- public void test_after_exception() {
- service.queryZero("123");
- }
6.2.1)执行 test_after 方法
- 15:02:57.113 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 20348 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:02:57.114 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:02:57.139 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:57 CST 2018]; root of context hierarchy
- 15:02:57.677 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.771 seconds (JVM running for 1.499)
- 15:02:57.708 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute queryZero(id=666)~~~
- 15:02:57.709 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute after method~~~
- 15:02:57.713 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:02:57 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.queryZero()方法后, 输出了 ZeroAop.after()增强方法中的调试信息.
6.2.2)执行 test_after_exception 方法
- 15:03:06.697 [main] INFO c.l.springaop.aop.ZeroServiceTests - Starting ZeroServiceTests on LAPTOP-1DF7S904 with PID 11540 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 15:03:06.697 [main] INFO c.l.springaop.aop.ZeroServiceTests - No active profile set, falling back to default profiles: default
- 15:03:06.725 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:03:06 CST 2018]; root of context hierarchy
- 15:03:07.280 [main] INFO c.l.springaop.aop.ZeroServiceTests - Started ZeroServiceTests in 0.79 seconds (JVM running for 1.53)
- 15:03:07.313 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute queryZero(id=123)~~~
- 15:03:07.314 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute after method~~~
- 15:03:07.321 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c9d8e2: startup date [Mon Apr 16 15:03:06 CST 2018]; root of context hierarchy
从日志的第 5,6 行可以看出, 在执行 ZeroService.queryZero()方法后, 也输出了 ZeroAop.after()增强方法中的调试信息. 说明 @after 后置增强的方法, 始终会被执行.
2. 函数通配符
@AspectJ 支持三种通配符
*: 匹配任意字符, 但是只能匹配一层或一个元素.
..: 匹配任意字符, 可以匹配一个或多个元素. 在表示类时必须和 * 组合使用, 在表示方法入参时可以单独使用.
+: 匹配类型, 可以匹配指定类型及子类(实现类).
3. 逻辑运算符
&&(and): 与运算符, 计算切点的交集. 由于 & 是 xml 中的特殊字符, 所以需要使用转义字符 & amp; 表示. spring 为了方便使用, 增加了 and 运算符. 与 && 等价.
||(or): 或运算符, 计算切点的并集. spring 也增加了一个直观的运算符 or. 与 || 等价.
!: 非运算符, 计算切点的反集. spring 同样也增加了一个直观的运算符 not. 与! 等价.
4. 常用切点表达式函数
1)方法切点函数
1.1)execution(): 最常用的切点函数, 匹配方法层.
小二, 上一盘栗子! 好勒~
@Before("execution(* com.lianjinsoft.springaop.service.ExecutionService.find*(..))")
解释: 前置增强, 匹配 com.lianjinsoft.springaop.service.ExecutionService 类中, 所有以 find 开头的方法.
@Before("execution(* com..ExecutionService.add*(String))")
解释: 前置增强, 匹配 com 包及子包中, 所有 ExecutionService 类中包含 add 开头的方法, 并且方法的入参为 String 类型.
@Before("execution(* *.remove*(..))")
解释: 前置增强, 匹配所有包中, 包含所有 remove 开头的方法.
@Before("execution(* *.modify*(java.util.Map))")
解释: 前置增强, 匹配所有包中, 以 modify 开头的方法, 并且方法的入参为 java.util.Map
@Before("execution(* *.get*(java.util.Map+))")
解释: 前置增强, 匹配所有包中, 以 get 开头的方法, 并且方法的入参为 java.util.Map 或为 java.util.Map 的子类(实现类).
注意: 当需要匹配方法的入参类型时, 除了 jdk 自带的基本类型及 String 可以在匹配规则中使用简写. 其他类型, 必须使用类型的完整路径.
测试案例? 请执行 ExecutionServiceTests 类中的方法, 查看增强结果.
1.2)@annotation(): 注解切点函数, 匹配被标注指定注解的方法.
废话少说, 上栗子!
老板, 心急吃不了热栗子, 您容我再唠叨几句~
首先我们说一下使用场景, 通常使用该切点函数, 是我们需要对一些特定的方法或者没有太多规律的方法进行增强.
好, 那么我们可以通过在这些方法上增加注解(标记), 然后通过注解切点函数, 来匹配这些方法进行增强.
1.2.1)增加一个注解类(自定义注解)
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface VerifySign {
- String note();
- }
1.2.2)增加一个业务接口
- public interface AnnotationService {
- void paymentNotify(String msg);
- void refundNotify(String msg);
- }
1.2.3)增加一个业务实现类
- @Service
- public class AnnotationServiceImpl implements AnnotationService{
- @VerifySign(note="需要验证数据签名")
- @Override
- public void paymentNotify(String msg) {
- LogUtil.info("execute paymentNotify(msg="+msg+")~~~");
- }
- @Override
- public void refundNotify(String msg) {
- LogUtil.info("execute refundNotify(msg="+msg+")~~~");
- }
- }
1.2.4)增加一个注解切点函数类
- @Aspect
- @Component
- public class AnnotationAop {
- @Before("@annotation(com.lianjinsoft.springaop.annotation.VerifySign)")
- private void verify(){
- LogUtil.info("execute verify method~~~");
- }
- }
1.2.5)增加单元测试
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class AnnotationServiceTests {
- @Autowired
- private AnnotationService service;
- @Test
- public void test_paymentNotify() {
- service.paymentNotify("支付结果通知");
- }
- @Test
- public void test_refundNotify() {
- service.refundNotify("退货结果通知");
- }
- }
1.2.5.1)执行 test_paymentNotify 测试方法
- 10:13:38.536 [main] INFO c.l.s.aop.AnnotationServiceTests - Starting AnnotationServiceTests on LAPTOP-1DF7S904 with PID 15736 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 10:13:38.537 [main] INFO c.l.s.aop.AnnotationServiceTests - No active profile set, falling back to default profiles: default
- 10:13:38.562 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d34d211: startup date [Tue Apr 17 10:13:38 CST 2018]; root of context hierarchy
- 10:13:39.224 [main] INFO c.l.s.aop.AnnotationServiceTests - Started AnnotationServiceTests in 0.908 seconds (JVM running for 1.649)
- 10:13:39.274 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute verify method~~~
- 10:13:39.274 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute paymentNotify(msg = 支付结果通知)~~~
- 10:13:39.280 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d34d211: startup date [Tue Apr 17 10:13:38 CST 2018]; root of context hierarchy
从输出日志的第 5,6 行可以看出, 在执行 AnnotationService.paymentNotify()方法前, 执行了 AnnotationAop.verify()增强方法.
1.2.5.2)执行 test_refundNotify 测试方法
- 10:13:55.752 [main] INFO c.l.s.aop.AnnotationServiceTests - Starting AnnotationServiceTests on LAPTOP-1DF7S904 with PID 16200 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 10:13:55.752 [main] INFO c.l.s.aop.AnnotationServiceTests - No active profile set, falling back to default profiles: default
- 10:13:55.772 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d34d211: startup date [Tue Apr 17 10:13:55 CST 2018]; root of context hierarchy
- 10:13:56.437 [main] INFO c.l.s.aop.AnnotationServiceTests - Started AnnotationServiceTests in 0.884 seconds (JVM running for 1.635)
- 10:13:56.471 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute refundNotify(msg = 退货结果通知)~~~
- 10:13:56.475 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d34d211: startup date [Tue Apr 17 10:13:55 CST 2018]; root of context hierarchy
从输出日志的第 4,5 行可以看出, 在执行 AnnotationService.refundNotify()方法前, 没有执行 AnnotationAop.verify()增强方法.
2)方法入参切点函数
2.1)args(): 匹配目标方法的入参条件(即匹配方法入参类型, 又匹配入参类型的子类, 实现类).
小二, 快上栗子. 好的, 老板~
2.1.1)增加一个业务接口
- public interface ArgsService {
- void insertObject(Set<String> obj);
- void insertObject(List<String> obj);
- void insertObject(Queue<String> obj);
- void insertObject(Map<String,String> obj);
- }
2.1.2)增加一个业务实现类
- @Service
- public class ArgsServiceImpl implements ArgsService{
- @Override
- public void insertObject(Set<String> obj) {
- LogUtil.info("execute insertObject(Set<String> obj)~~~");
- }
- @Override
- public void insertObject(List<String> obj) {
- LogUtil.info("execute insertObject(List<String> obj)~~~");
- }
- @Override
- public void insertObject(Queue<String> obj) {
- LogUtil.info("execute insertObject(Queue<String> obj)~~~");
- }
- @Override
- public void insertObject(Map<String, String> obj) {
- LogUtil.info("execute insertObject(Map<String, String> obj)~~~");
- }
- }
2.1.3)增加一个切点函数类
本案例中使用了两个条件, 通过 and 逻辑运算符关联.
匹配 com.lianjinsoft 包及子包中, 所有类中的方法包含一个入参为 java.util.Collection 或 java.util.Collection 的子类(实现类)
- @Aspect
- @Component
- public class ArgsAop {
- @Before("execution(* com.lianjinsoft..*(..)) and args(java.util.Collection)")
- private void args(){
- LogUtil.info("execute args method~~~");
- }
- }
2.1.4)增加单元测试类
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class ArgsServiceTests {
- @Autowired
- private ArgsService service;
- @Test
- public void test_service() {
- Set<String> set = new HashSet<>();
- service.insertObject(set);
- List<String> list = new ArrayList<>();
- service.insertObject(list);
- Queue<String> queue = new LinkedBlockingQueue<>();
- service.insertObject(queue);
- Map<String,String> map = new HashMap<>();
- service.insertObject(map);
- }
- }
2.1.4.1)执行 test_service()测试方法
- 10:29:28.664 [main] INFO c.l.springaop.aop.ArgsServiceTests - Starting ArgsServiceTests on LAPTOP-1DF7S904 with PID 12536 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 10:29:28.665 [main] INFO c.l.springaop.aop.ArgsServiceTests - No active profile set, falling back to default profiles: default
- 10:29:28.689 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4009e306: startup date [Wed Apr 18 10:29:28 CST 2018]; root of context hierarchy
- 10:29:29.491 [main] INFO c.l.springaop.aop.ArgsServiceTests - Started ArgsServiceTests in 1.036 seconds (JVM running for 1.788)
- 10:29:29.543 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute args method~~~
- 10:29:29.543 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute insertObject(Set<String> obj)~~~
- 10:29:29.544 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute args method~~~
- 10:29:29.544 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute insertObject(List<String> obj)~~~
- 10:29:29.545 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute args method~~~
- 10:29:29.545 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute insertObject(Queue<String> obj)~~~
- 10:29:29.545 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute insertObject(Map<String, String> obj)~~~
- 10:29:29.554 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4009e306: startup date [Wed Apr 18 10:29:28 CST 2018]; root of context hierarchy
从输出日志的第 5~11 行可以看出, insertObject 入参为 Set,List,Queue 的方法都被增强了. 只有入参为 Map 的方法没有增强(因为 Map 没有继承 Collection 接口).
注意:
execution 切点函数匹配的入参条件默认是绝对匹配, 那想要实现和当前案例一样的效果, 需要怎么操作呢?
很简单, 通过函数通配符 "+" 实现:
@Before("execution(* com.lianjinsoft..*(java.util.Collection+))")
2.2)@args(): 注解匹配函数, 匹配目标方法的入参.
该函数与 args 使用方法类似, args 匹配方法入参的类型为普通类(接口, 抽象类). 而 @args 匹配的方法入参类型必须为注解类.
小二! 小二! 人呢?
咳咳.. 小二放假度蜜月去了..
3)目标类切点函数
3.1)within(): 类切点函数, 最小匹配层级为类
栗子在哪里? 来了, 老板.
3.1.1)增加一个业务接口
- public interface WithinService {
- void saveObject();
- void deleteObject();
- void updateObject();
- void queryObject();
- }
3.1.2)增加一个业务实现类
- @Service
- public class WithinServiceImpl implements WithinService{
- @Override
- public void saveObject() {
- LogUtil.info("execute saveObject()~~~");
- }
- @Override
- public void deleteObject() {
- LogUtil.info("execute deleteObject()~~~");
- }
- @Override
- public void updateObject() {
- LogUtil.info("execute updateObject()~~~");
- }
- @Override
- public void queryObject() {
- LogUtil.info("execute queryObject()~~~");
- }
- }
3.1.3)增加一个切点函数类
匹配 com.lianjinsoft 包及子包下, 所有 Within 开头的类中的方法.
- @Aspect
- @Component
- public class WithinAop {
- @Before("within(com.lianjinsoft..Within*)")
- private void within(){
- LogUtil.info("execute within method~~~");
- }
- }
3.1.4)增加一个测试类
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class WithinServiceTests {
- @Autowired
- private WithinService service;
- @Test
- public void test_service() {
- service.saveObject();
- service.deleteObject();
- service.updateObject();
- service.queryObject();
- }
- }
3.1.4.1)执行 test_service()测试方法
- 11:19:39.492 [main] INFO c.l.springaop.aop.WithinServiceTests - Starting WithinServiceTests on LAPTOP-1DF7S904 with PID 8612 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 11:19:39.492 [main] INFO c.l.springaop.aop.WithinServiceTests - No active profile set, falling back to default profiles: default
- 11:19:39.518 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@587e5365: startup date [Wed Apr 18 11:19:39 CST 2018]; root of context hierarchy
- 11:19:40.259 [main] INFO c.l.springaop.aop.WithinServiceTests - Started WithinServiceTests in 1.036 seconds (JVM running for 1.827)
- 11:19:40.321 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute within method~~~
- 11:19:40.321 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute saveObject()~~~
- 11:19:40.321 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute within method~~~
- 11:19:40.321 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute deleteObject()~~~
- 11:19:40.321 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute within method~~~
- 11:19:40.322 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute updateObject()~~~
- 11:19:40.322 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute within method~~~
- 11:19:40.322 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute queryObject()~~~
- 11:19:40.330 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@587e5365: startup date [Wed Apr 18 11:19:39 CST 2018]; root of context hierarchy
从输出的日志第 5~12 行可以看出, WithinService 类中的所有方法均被增强.
within 切点函数匹配的是类层面, 即会将命中目标类中的所有方法增强(除私有方法).
注意: 该函数只匹配目标对象, 不匹配运行时对象. 若匹配条件为父类, 那么调用子类方法时不会被增强.
3.2)@within() 和 @target(): 这两个切点函数也是注解切点函数, 但是匹配的注解是类的应用层.
@within(): 匹配被标注的指定类及子孙类 (实现类) 中所有的方法.
@target(): 匹配被标注的指定类中的所有方法(不包含子孙类).
如图:
图一为 @within 示例图, 当 T1 标注了 @M 注解, 那么 T1,T2(子类),T3(孙类)都被匹配.
图二为 @target 示例图, 当 T1 标注了 @M 注解, 那么只有 T1 类会被匹配.
3.3)target(): 类切点函数, 最小匹配层级为类.
那么 target()函数和 within()函数有什么区别?
target 函数不仅匹配目标类中的所有方法, 而且会匹配子孙类中的所有方法(除私有方法), 也就说会包含父类中没有的方法.
快! 快上栗子!! 喳~
3.3.1)增加两个业务接口, 并实现继承关系
- public interface TargetParentService {
- void saveObject();
- void deleteObject();
- void updateObject();
- void queryObject();
- }
- public interface TargetChildService extends TargetParentService{
- void queryAll();
- }
3.3.2)增加一个业务实现类
- @Service
- public class TargetServiceImpl implements TargetChildService{
- @Override
- public void saveObject() {
- LogUtil.info("execute saveObject()~~~");
- }
- @Override
- public void deleteObject() {
- LogUtil.info("execute deleteObject()~~~");
- }
- @Override
- public void updateObject() {
- LogUtil.info("execute updateObject()~~~");
- }
- @Override
- public void queryObject() {
- LogUtil.info("execute queryObject()~~~");
- }
- @Override
- public void queryAll() {
- LogUtil.info("execute queryAll()~~~");
- }
- }
3.3.3)增加一个切点函数类
注意: target 函数必须输入类的绝对路径, 不支持通配符.
- @Aspect
- @Component
- public class TargetAop {
- @Before("target(com.lianjinsoft.springaop.service.TargetParentService)")
- private void target(){
- LogUtil.info("execute target method~~~");
- }
- }
3.3.4)增加一个测试类
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class TargetServiceTests {
- @Autowired
- private TargetParentService service;
- @Test
- public void test_service() {
- service.saveObject();
- service.deleteObject();
- service.updateObject();
- service.queryObject();
- ((TargetChildService)service).queryAll();
- }
- }
3.3.4.1)执行 test_service()测试方法
- 12:30:40.431 [main] INFO c.l.springaop.aop.TargetServiceTests - Starting TargetServiceTests on LAPTOP-1DF7S904 with PID 11928 (started by lianjinsoft in E:\workspace\springboot\spring-aop)
- 12:30:40.432 [main] INFO c.l.springaop.aop.TargetServiceTests - No active profile set, falling back to default profiles: default
- 12:30:40.465 [main] INFO o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2de23121: startup date [Wed Apr 18 12:30:40 CST 2018]; root of context hierarchy
- 12:30:41.433 [main] INFO c.l.springaop.aop.TargetServiceTests - Started TargetServiceTests in 1.247 seconds (JVM running for 2.051)
- 12:30:41.497 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute target method~~~
- 12:30:41.498 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute saveObject()~~~
- 12:30:41.498 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute target method~~~
- 12:30:41.498 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute deleteObject()~~~
- 12:30:41.498 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute target method~~~
- 12:30:41.498 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute updateObject()~~~
- 12:30:41.499 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute target method~~~
- 12:30:41.499 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute queryObject()~~~
- 12:30:41.499 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute target method~~~
- 12:30:41.499 [main] INFO c.lianjinsoft.springaop.util.LogUtil - execute queryAll()~~~
- 12:30:41.536 [Thread-2] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2de23121: startup date [Wed Apr 18 12:30:40 CST 2018]; root of context hierarchy
从输出日志的第 5~14 行可以看出, TargetParentService 及 TargetChildService 类中的方法均被增强了.
总结:
springaop 为我们提供了很多增强函数, 通过不同的匹配规则, 组合方式可以实现我们日常需求.
本文采用 springboot 作为项目基础运行环境, 也是 spring 主推的开发模式.
本文演示源代码: https://gitee.com/skychenjiajun/spring-all
来源: https://www.cnblogs.com/skychenjiajun/p/8857713.html