springaop——AspectJ不可不知的细节
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中的特殊字符,所以需要使用转义字符&表示。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
springaop——AspectJ不可不知的细节的更多相关文章
- Spring-AOP @AspectJ切点函数之@annotation()
@annotation()概述@annotation表示标注了某个注解的所有方法. 下面通过一个实例说明@annotation()的用法. AnnotationTestAspect定义了一个后置切面增 ...
- Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)
Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚 使用场景: 1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动 ...
- Android任务和返回栈完全解析,细数那些你所不知道的细节
附:Android task详解 出处:http://blog.csdn.net/guolin_blog/article/details/41087993 原文: http://developer. ...
- Maven中央仓库——你可能不知道的细节
地址 —— 目前来说,http://repo1.maven.org/maven2/是真正的Maven中央仓库的地址,该地址内置在Maven的源码中,其它地址包括著名的ibiblio.org,都是镜像. ...
- Spring那些不得不知的细节
1.SpringMVC拦截器的url-pattern和RequestMapping 案例: url-pattern为/rest/* http请求为:/rest/query/id 那么requestMa ...
- 关于Istio 1.1,你所不知道的细节
本文整理自Istio社区成员Star在 Cloud Native Days China 2019 北京站的现场分享 第1则 主角 Istio Istio作为service mesh领域的明星项目,从2 ...
- Android Context完全解析,你所不知道的Context的各种细节
Context相信所有的Android开发人员基本上每天都在接触,因为它太常见了.但是这并不代表Context没有什么东西好讲的,实际上Context有太多小的细节并不被大家所关注,那么今天我们就来学 ...
- 关于 Spring AOP (AspectJ) 该知晓的一切
关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...
- Android开发——Context类的各种细节问题
0. 前言 Context相信所有的Android开发人员基本上每天都在接触,因为它太常见了.但实际上Context有太多小的细节并不被大家所关注,那么今天我们就来学习一下那些你所不知道的细节. ...
随机推荐
- django “如何”系列4:如何编写自定义模板标签和过滤器
django的模板系统自带了一系列的内建标签和过滤器,一般情况下可以满足你的要求,如果觉得需更精准的模板标签或者过滤器,你可以自己编写模板标签和过滤器,然后使用{% load %}标签使用他们. 代码 ...
- 网络通信 --> CRC校验
CRC校验 一.什么是CRC校验 循环校验码(Jyclic Redundancy Check,简称CRC码): 是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意 ...
- [poj2342]Anniversary party_树形dp
Anniversary party poj-2342 题目大意:没有上司的舞会原题. 注释:n<=6000,-127<=val<=128. 想法:其实就是最大点独立集.我们介绍树形d ...
- Divisor counting [线性筛积性函数]
Divisor counting 题目大意:定义f(n)表示整数n的约数个数.给出正整数n,求f(1)+f(2)+...+f(n)的值. 注释:1<=n<=1000,000 想法:我们再次 ...
- 程序猿媛 九:Adroid zxing 二维码3.1集成(源码无删减)
Adroid zxing 二维码3.1集成 声明:博文为原创,文章内容为,效果展示,思路阐述,及代码片段. 转载请保留原文出处“http://my.oschina.net/gluoyer/blog”, ...
- 大数据hadoop面试题2018年最新版(美团)
还在用着以前的大数据Hadoop面试题去美团面试吗?互联网发展迅速的今天,如果不及时更新自己的技术库那如何才能在众多的竞争者中脱颖而出呢? 奉行着"吃喝玩乐全都有"和"美 ...
- Item 15: 只要有可能,就使用constexpr
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果说C++11中有什么新东西能拿"最佳困惑奖" ...
- Git使用方法2.0
## Git来源: 最早开始是由Ruby程序员们发起的.Ruby是日本的家伙搞出来的,日本有个代码托管网站叫heroku,当时用这个的人比较多,现在这个网站还能打开,网址是www.heroku.com ...
- vue-router 组件实例被复用问题
最近在开发过程中遇到如下问题: 当前路由是这样的 http://127.0.0.1:3010/order?keywords=22 只改变keywords的值,路由不跳转 http://127.0.0. ...
- MyBatis 中使用数据库查询别名进行映射
方法1 XXMapper.xml <mapper namespace="com.hfepc.dao.andon.AndonExceptionKanbanVOMapper" & ...