1.@AspectJ形式的Spring AOP
引入了AspectJ的Pointcut描述语言,直接使用POJO来定义Aspect以及相关的Advice。
Spring AOP使用AspectJ的类库进行Pointcut的解析和匹配,但最终的实现机制还是Spring AOP的架构,即使用代理模式处理横切逻辑的织入。
@AspectJ形式的Pointcut声明包含两部分:Pointcut Expression和Pointcut Signature。AspectJ的Pointcut表达式支持通过&&,||,!逻辑运算符,进行Pointcut表达式之间的逻辑运算。运算符可以应用于具体的Pointcut表达式以及相应的Pointcut Signature。
@Aspect
public class MyAspect{
@Pointcut("execution(void method1())*") //Pointcut Expression
public void method1Execution(){} //pointcut signature
@Pointcut("execution(void method2())*")
public void method2Execution(){}
@Pointcut("execution(void method1()) || execution(void method2())")
public void bothMethodExec(){}
@Pointcut("method1Execution() || method2Execution()")
public void bothMethodExec2(){}
}
2.@AspectJ形式Pointcut表达式的标识符
因为Spring AOP只支持方法级别的Joinpoint,所以只能使用AspectJ的Pointcut表述语言的少数几种标识符。
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
}
.....
}
以@开头的标识符,都只能指定注解类型参数。
3.@AspectJ形式的Pointcut在Spring AOP中的真实面目
实际上,@AspectJ形式声明的所有Pointcut表达式,在Spring AOP内部都会通过解析,转换为具体的Pointcut对象。因为Spring AOP有自己的Pointcut定义结构,所以,AspectJ形式声明的这些Pointcut表达式,最终会转化为一个专门面向AspectJ的Pointcut实现。
org.springframework.aop.aspectj.annotation.AspectJProxyFactory
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
这两个类会通过反射获取Aspect中的@Pointcut定义的AspectJ形式的Pointcut定义后,在Spring AOP框架内部构造一个对应的AspectJExpressionPointcut对象,然后AspectJExpressionPointcut会委托AspectJ类库中的PointcutParser来解析Pointcut表达式。
4.@AspectJ形式的Advice
@Before
@AfterReturning
如果需要访问方法的返回值,可以通过returning属性将返回值绑定到Advice定义所在的方法。
@AfterReturning(pointcut="execution(boolean *.execute(String,..))",returning="relValues")
public void taskExecutionCompleted(JoinPoint joinPoint,boolean relValues){}
@AfterThrowing
如果要访问具体的异常信息和其他信息,可以按如下定义:
@AfterThrowing(pointcut="execution(boolean *.execute(String,..))",throwing="e")
public void afterThrowing(JoinPoint joinPoint,RuntimeExecution e){ }
@After Finally-Advice
不管方法是正常执行返回,还是执行过程中抛出异常而非正常返回,都会触发其上的After Advice执行,,所以,After Advice适合用于释放某些系统资源的场景。
@Around
对于上面的四个Advice的方法定义的第一个参数可以是JoinPoint类型,且是可选的,但对于Around Advice的方法定义来说,它的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型(extends JoinPoint),且必须指定,通常情况下,需要通过ProceedingJoinPoint的proceed()方法继续调用链路的执行。
以上都是方法级别的注解,只能用于标注方法定义。
@DeclareParents 用于标注Introduction类型的Advice
某些情况下,需要在Advice定义中访问Joinpoint处的方法参数,可以通过org.aspectj.lang.JoinPoint获取。将方法的第一个参数声明为Joinpoint类型,借助于它的getSignature()方法取得当前Joinpoint处的方法签名,getTarget()获取当前目标对象,getArgs()访问相应Joinpoint处方法的参数值。还有一种那个方式也可以,通过args标识符绑定。
注意:除了Around Advice和Introduction不可以使用JoinPoint外,余下的Advice都可以使用。
Introduction
public interface ITask {}
public interface ICounter {
void resetCounter();
int getCounter();
}
public class TaskImpl implements ITask {}
public class CounterImpl implements ICounter{
private int counter;
@Override
public void resetCounter() {
counter = 0;
}
@Override
public int getCounter() {
counter++;
return counter;
}
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
/**
* <br>以AspectJava形式声明Introduction</br>
*/
@Aspect
public class IntroductionAspect {
/**
* @DeclareParents所归属的域定义类型是将为目标对象新增加的对象类型
* value指定将要应用到的目标对象,可以使用通配符指定一批目标对象
* defaultImpl 指定新增加的接口定义的实现类
*/
@DeclareParents(value = "com.hong.service.TaskImpl",defaultImpl = CounterImpl.class)
public ICounter counter;
}
<!--使用AspectJ形式声明Introduction,与上面使用ProxyFactoryBean和DelegatePerTargetObjectIntroductionInterceptor实现相同的效果-->
<!--开启AspectJ AOP自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="task" class="com.hong.service.TaskImpl" scope="prototype"/>
<bean id="counterIntroduction" class="com.hong.aspect.IntroductionAspect"/>
@Test
public void test3(){
Object task1 = container.getBean("task");
Object task2 = container.getBean("task");
//不同的目标对象对应不同的ICounter实例
System.out.println(((ICounter)task1).getCounter());//1
System.out.println(((ICounter)task1).getCounter());//2
System.out.println(((ICounter)task2).getCounter());//1
}
5.Advice的执行顺序
如果多个Advice引用的Pointcut定义恰好匹配同一个JoinPoint的时候, 在这同一个JoinPoint上,这些Advice该按照什么顺序执行?
(1)当这些Advice都声明在同一个Aspect内(使用@Aspect注解声明的POJO类)的时候,他们将按照声明顺序执行.
@Aspect
public class MultiAdviceAspect {
@Pointcut("execution(* com.hong.service..*.*(..))")
public void taskExecution(){ }
@Before("taskExecution()")
public void beforeOne(){
System.out.println("before one");
}
@Before("taskExecution()")
public void beforeTwo(){
System.out.println("before two");
}
@AfterReturning("taskExecution()")
public void afterReturningOne(){
System.out.println("after returning one");
}
@AfterReturning("taskExecution()")
public void afterReturningTwo(){
System.out.println("after returning two");
}
}
<bean id="counter" class="com.hong.service.CounterImpl"/>
<bean class="com.hong.aspect.MultiAdviceAspect"/>
@Test
public void test4() {
ICounter counter = (ICounter) container.getBean("counter");
counter.getCounter();
/**
* before one
before two
1
after returning one
after returning two
*/
}
(2)当这些Advice声明在不同的Aspect内的时候,需要用org.springframework.core.annotation.Order注解或者实现org.springframework.core.Ordered接口,标明执行顺序,否则,Advice的执行顺序是不确定的.
6.Aspect的实例化模式
对于注册到容器的各个Aspect,它们默认的实例化模式是singleton,除此外,Spring AOP还支持perthis,pertarget模式.
7.基于Schema的AOP
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--基于schema的AOP-->
<aop:config proxy-target-class="true">
<!--下面的3个子元素必须按照下面的顺序定义-->
<aop:pointcut id="" expression=""/>
<aop:advisor advice-ref=""/>
<aop:aspect/>
</aop:config>
</bean>