maven需要引入依赖,aspectjweaver。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
AspectJ提供了面向切面编程的实现,AspectJ有三个核心概念:
Join Point
Advice
Pointcut
Aspect(切面): 通过横切多个对象来做模块化的关联,每个切面侧重一个特定的横切功能。
Join point(执行点): 脚本执行过程中的一个点,例如方法的执行或属性的访问。
Advice(通知): 切面在特定执行点采取的行动。
Pointcut(与执行点相切的点): 使用规则表达式与执行点相切的点。advice通过pointcut的规则表达式,可在任意与执行点相切的点运行。
Pointcut
注解的源码,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
/**
* @return the pointcut expression
* We allow "" as default for abstract pointcut
*/
String value() default "";
/**
* When compiling without debug info, or when interpreting pointcuts at runtime,
* the names of any arguments used in the pointcut are not available.
* Under these circumstances only, it is necessary to provide the arg names in
* the annotation - these MUST duplicate the names used in the annotated method.
* Format is a simple comma-separated list.
*
* @return argNames the argument names (should match those in the annotated method)
*/
String argNames() default "";
}
Pointcut规则表达式可以是@pointcut
注解的value。
最主要的Spring PCD是execution
,通过method的执行点切入。
@Pointcut("execution(public String com.pingpongx.pointcutadvice.dao.InboundDao.findById(Long))")
上述的示例,InboundDao类的findById方法作为切点。但是这种方式不够灵活。
可以使用通配符,使规则表达式能表达的Pointcut更灵活。
@Pointcut("execution(* com.pingpongx.pointcutadvice.dao.InboundDao.*(..))")
第一个通配符*
表示任意的return的结果。
第二个通配符*
表示任意的方法名称。
第三个(..)
表示任意个数的参数(包含0个)。
通过某种type的执行点切入。
@Pointcut("within(com.pingpongx.pointcutadvice.dao.InboundDao)")
当然也可以是某个package或者sub-package里面的任意类型。
@Pointcut("within(com.pingpongx..*)")
this limits matching to join points where the bean reference is an instance of the given type, while target limits matching to join points where the target object is an instance of the given type.
this
在Spring AOP创建基于cglib的代理时有效,target
在创建基于jdk的代理时使用。
假设target
类XxxDao
实现了一个接口
public class XxxDao implements YyyDao {
...
}
上述例子,Spring AOP会使用基于jdk代理,因此需要使用target
,因为代理的对象会是Proxy
类的实例,且实现了YyyDao
接口。
@Pointcut("target(com.pingpongx.pointcutadvice.dao.YyyDao)")
换言之,如果XxxDao
没有实现任何接口,或者proxyTargetClass
的属性是true
,那么被代理的对象会是XxxDao
的子类,需要使用this
。
@Pointcut("this(com.pingpongx.pointcutadvice.dao.XxxDao)")
通过特定方法的参数作为执行点切入。
@Pointcut("execution(* *..find*(Long))")
如果需要任意个数参数,但保证一个参数是Long类型。
@Pointcut("execution(* *..find*(Long,..))")
执行切入点是执行对象的类具有给定类型的注解。
@Pointcut("@target(org.springframework.stereotype.Repository)")
执行切入点是传递的实际参数是运行时类型,且具有给定类型的注释。
比如,追溯所有方法,方法满足接收的bean参数具备@Entity
注解。
@Pointcut("@args(com.pingpongx.pointcutadvice.annotations.Entity)")
public void methodsAcceptingEntities() {}
要访问参数,我们应该为通知提供一个 JoinPoint
参数。
@Before("methodsAcceptingEntities()")
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]);
}
执行切入点是具有给定注解的类型内的类型。
@Pointcut("@within(org.springframework.stereotype.Repository)")
等价于
@Pointcut("within(@org.springframework.stereotype.Repository *)")
执行切入点是执行点的主体具有给定的注解。
@Pointcut("@annotation(com.pingpongx.pointcutadvice.annotations.Loggable)")
public void loggableMethods() {}
@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("Executing method: " + methodName);
}
支持&&
、||
、!
操作符。
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}
@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}
@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}
Advice
有around
、before
、after
。
对于configuration类使用@EnableAspectJAutoProxy
注解,对于component类使用@Aspect
注解。
通知执行在执行点之前。除非抛出异常,否则它不会阻止它通知的方法的继续执行。
在切点的方法执行后执行,无论是否引发异常。
ProceedingJoinPoint
参数控制before和after功能效果,pjp.proceed()
执行前是before,pjp.proceed()
执行后是after,Object retval = pjp.proceed();
,return retval;
是结束(或者抛出异常)。如果没有进行retval接收,直接return pjp.proceed();
,就没有after了。