大数跨境
0
0

SpringBoot AOP 9种切入点详细使用说明

SpringBoot AOP 9种切入点详细使用说明 Spring全家桶实战案例
2021-06-03
0
导读:SpringBoot AOP 9种切入点详细使用说明

环境:springboot2.3.10


表达式说明

这里以最常用的execution切入点指示符来说明表达式的组成

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

modifiers-pattern:修饰符;方法的修饰符,public, private, protected。可选

ret-type-pattern:返回值类型;方法的返回值。必选,一般用 * 号 任意类型,也可以是具体类型。

declaring-type-pattern:类信息;具体哪些类。可选

name-pattern:名称;方法的名称。必选

param-pattern:参数;方法的参数。必选

throws-pattern:异常;方法抛出的异常。可选

官方说明:

All parts except the returning type pattern (ret-type-pattern in the preceding snippet), the name pattern, and the parameters pattern are optional. The returning type pattern determines what the return type of the method must be in order for a join point to be matched. * is most frequently used as the returning type pattern. It matches any return type. A fully-qualified type name matches only when the method returns the given type. The name pattern matches the method name. You can use the * wildcard as all or part of a name pattern. If you specify a declaring type pattern, include a trailing . to join it to the name pattern component. The parameters pattern is slightly more complex: () matches a method that takes no parameters, whereas (..) matches any number (zero or more) of parameters. The (*) pattern matches a method that takes one parameter of any type. (*,String) matches a method that takes two parameters. The first can be of any type, while the second must be a String

翻译:除了返回类型模式(前面代码段中的ret-type模式)、名称模式和参数模式之外的所有部分都是可选的。返回类型模式确定方法的返回类型必须是什么,才能匹配连接点。*最常用作返回类型模式。它匹配任何返回类型。完全限定类型名仅在方法返回给定类型时匹配。名称模式与方法名称匹配。您可以使用*通配符作为名称模式的全部或部分。如果指定声明类型模式,请包含尾部。将其连接到名称模式组件。参数模式稍微复杂一些:()匹配一个不带参数的方法,而(..)匹配任意数量(零个或更多)的参数。(*)模式匹配采用任何类型的一个参数的方法。(*,字符串)匹配接受两个参数的方法。第一个可以是任何类型,而第二个必须是字符串

Spring AOP支持的切入点指示符如下:


接下来对spring aop支持的每一种切入点指示符进行详细使用说明

注备类

@Component
public class PersonDAOImpl implements PersonDAO{

private static Logger logger = LoggerFactory.getLogger(PersonDAOImpl.class) ;

public void save(Person person) {
logger.info("save method invoke...") ;
}

public Person findById(Integer id) {
logger.info("findById method invoke...") ;
return new Person(id, "姓名" + new Random().nextInt(10000));
}

public int countPersons() {
logger.info("countPersons method invoke...") ;
return new Random().nextInt(1000) ;
}

}
public interface PersonDAO {

void save(Person person) ;

Person findById(Integer id) ;

int countPersons() ;

}

Aspect类

@Aspect
@Component
public class PointcutAspect {

private static Logger logger = LoggerFactory.getLogger(PointcutAspect.class) ;

@Pointcut("execution(* com.pack.dao..*.*(..))")
private void log() {}

@Around("log()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
logger.info("执行前...") ;
pjp.proceed() ;
logger.info("执行后...") ;
}
}

自定义注解

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Pack {
}

execution指示符

匹配方法执行连接点。

@Pointcut("execution(* com.pack.dao..*.*(..))")

匹配com.pack.dao包下及子包下的任意类,任意方法,任意返回值,任意参数(0个或多个)

@Pointcut("execution(com.pack.domain.Person com.pack.dao..*.*(..))")

匹配返回值必须是com.pack.domain.Person。

@Pointcut("execution(* com.pack.dao..*.sa*(..))")

匹配方法名称必须是以sa开头的。

@Pointcut("execution(* com.pack.dao..*.*(com.pack.domain.Person))")

匹配所有参数类型是Person的。

@Pointcut("execution(public * com.pack.dao..*.*(..))")

匹配修饰符是public的方法。

@Pointcut("execution(* com.pack.dao.impl.*.*(..))")

匹配com.pack.dao.impl包下的任意类,任意方法,任意返回值,任意参数(0个或多个)

within指示符

限制匹配到特定类型内的连接点。within只能指定到类。

@Pointcut("within(com.pack..*)")

匹配com.pack包下及其子包下的所有类中的任何方法。

@Pointcut("within(com.pack.dao.impl.PersonDAOImpl)")

匹配PersonDAOImpl类中的任何方法。

@Pointcut("within(com.pack.dao.impl.*)")

匹配com.pack.dao.impl包下所有类中的任何方法。

this指示符

当前执行的AOP代理对象是this中指定的类型的。匹配的是AOP代理对象,也就是说当前执行的AOP代理对象是this中指定的类型那么就进行织入。

语法:this(全限定类名)。如:this("com.pack.dao.PersonDAO") ;

@Pointcut("this(com.pack.dao.PersonDAO)")

匹配所有当前执行的AOP对象是PersonDAO类型的类。

@Pointcut("this(com.pack.dao.impl.PersonDAOImpl)")

在做这个测试的时候需要配合 如下配置

spring:
aop:
proxy-target-class: false

false:使用jdk代理。
true:使用cglib代理;默认为true。

当设置为false时,织入失败。


当设置为true时,织入成功。


cglib是通过继承来实现的,jdk是通过实现接口来实现的。这里this指向的是当前的这个代理对象,也就是说当前代理对象是通过继承PersonDAOImpl来实现的。


看到这里也就清楚了this指向的是当前生成的这个代理对象,cglib是通过继承,jdk是实现对应的接口这也就是为何this中全限定为PersonDAOImpl对象时不能织入成功,压根就不匹配(当使用jdk代理时,代理对象实现了PersonDAO,而限定中是PersonDAOImpl)。

target指示符

匹配当前目标对象类型的执行方法;也就是说当前对象是target中指定的类型。

语法:target(全限定类名)。如:this("com.pack.dao.PersonDAO") ;

@Pointcut("target(com.pack.dao.PersonDAO)")

匹配当前对象是PersonDAO类型的所有类中的方法。

如上this的指示符,不管全限定类是接口还是实现类,不管是jdk还是cglib都会成功。因为targe指向的是当前的对象。

args指示符

匹配当前执行的方法传入的参数为指定类型的执行方法。

@Pointcut("execution(* com.pack..*.*(..)) && args(com.pack.domain.Person)")

匹配参数为Person对象的方法。

@Pointcut("execution(* com.pack..*.*(..)) && args(java.lang.String, ..)")

匹配第一个参数是String类型的方法。

@within指示符

匹配所有类上具体指定的注解的类内的所有方法。

语法:@within("全限定类名") ,完整的包名加类名

@Pointcut("@within(com.pack.dao.Pack)")
@Component
@Pack
public class PersonDAOImpl implements PersonDAO{
}

@target指示符

匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解

@Pointcut("execution(* com.pack..*.*(..)) && @target(com.pack.dao.Pack)")

@args指示符

匹配传入方法的参数带有指定的注解

@Pointcut("execution(* com.pack..*.*(..)) && @args(com.pack.dao.Pack)")
@Pack
public class Person {
// 省略
}

上面的save方法接收参数Person,只要调用save方法传入的这个参数具有指定的注解就匹配。

@annotation指示符

匹配当前执行的方法具有指定的注解

@Pointcut("@annotation(com.pack.dao.Pack)")
@Pack
public void save(Person person) {
}

只要当前的执行方法上有@Pack注解那么就匹配。

各种通知

  • 前置通知

@Before("log()")  
public void deBefore(JoinPoint joinPoint) throws Throwable {
}
  • 返回通知

@AfterReturning(returning = "ret", pointcut = "log()")  
public void doAfterReturning(Object ret) throws Throwable {
System.out.println("返回值:" + ret) ;
}

执行结果:


  • 抛出异常后通知

@AfterThrowing("log()")  
public void throwss(JoinPoint jp){
System.out.println("抛出异常了") ;
}
@Pack
public int countPersons()
{
logger.info("countPersons method invoke...") ;
System.out.println(1 / 0) ;
return new Random().nextInt(1000) ;
}
  • 后置通知

@After("log()")  
public void after(JoinPoint jp){
}
  • 环绕通知

@Around("log()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
logger.info("执行前...") ;
Object result = pjp.proceed() ;
logger.info("执行后...") ;
return result ;
}

完毕!!!

给个关注+转发谢谢


【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832