环绕通知
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点。甚至可以控制是否执行连接点。类似于动态代理。
环绕通知 连接点的参数类型必须是 ProceedingJoinPoint ,它是 JoinPoint 的子接口,,允许控制何时执行, 是否执行连接点。
环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法,如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
环绕通知的方法需要有返回值,返回目标方法执行之后的结果, 即调用 joinPoint.proceed() 的返回值, 否则会出现空指针异常。
四大通知(前置,后置,异常,最终)在用注解的时候会有顺序问题,所以尽量不要用注解方式配置四大通知,注解配置AOP时候应该用环绕的通知.
示例:
/**
* 定义注解切点
*/
//@Pointcut("@annotation(AroundDemo.annotation.InterceptClass)") 拦截注解标注的方法的(如果注解放在类上面就失效)
@Pointcut("@within(AroundDemo.annotation.InterceptClass)")//拦截注解标注的类上的所有方法
public void tokenIntercept() {
}
//@Around(value="execution(public int com.wf.springaopImpl.ArithmeticCalculatorImpl.*(int , int)))")
@Around("tokenIntercept()")
public Object aroundlogging(ProceedingJoinPoint pjd) {
Object result = null;
String methodName = pjd.getSignature().getName();
List<Object> args = Arrays.asList(pjd.getArgs());
try {
// 前置通知
System.out.println("前置通知" + methodName + " begin with" + args);
result = pjd.proceed();
// 返回通知
System.out.println("返回通知 " + methodName + " end with " + result);
} catch (Throwable e) {
System.out.println("异常通知 " + methodName + " occurs excetion: " + e);
// 异常通知
e.printStackTrace();
}
//后置通知
System.out.println("后置通知 " + methodName + " end");
return result;
}
@Around注解用法
传递@Pointcut
可以给多个环绕通知来使用
/**
* 拦截类的切点
*/
@Pointcut("@within(aop.aroundDemo.annotation.InterceptClass)")
public void InterceptClass() {
}
@Around(value = "InterceptClass()")
public Object InterceptClassAround(ProceedingJoinPoint pjd) {
@Around(value = "InterceptClass()")
public Object InterceptClassAround(ProceedingJoinPoint pjd) {
两个环绕通知的执行顺序:
我是第二个环绕通知的前置通知
第一个环绕通知前置通知ceui begin with[]
TestController.ceui (目标方法)
第一个环绕通知返回通知 ceui end with null
第一个环绕通知后置通知 ceui end
我是第二个环绕通知的返回通知
我是第二个环绕通知的后置通知
传递表达式
/**
* 切入点表达式
*/
@Around("execution(* aop.aroundDemo.controller.execution.*.*(..))")
public Object InterceptMethodAround(ProceedingJoinPoint pjd) {
传递注解
@Around(value = “@annotation(aop.aroundDemo.annotation.InterceptMethod)”)
当方法上面或者类上面有这个注解就可以被拦截,详细情况需要参考自定义注解的Target属性
@Around参数可以写@PointCut定义的注解
@Around是环绕通知注解
可以传入表达式
@Around(“execution( com.company.controller..*(..))”)
ProceedingJoinPoint
ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法:
public interface ProceedingJoinPoint extends JoinPoint {
public Object proceed() throws Throwable; //执行目标方法
public Object proceed(Object[] args) throws Throwable;//传入的心的参数去指定目标方法
}
其它四大通知
前置通知
前置通知:在目标 方法开始之前进行执行的通知。
前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值。
value属性值:切入点表达式,匹配与之对应的目标。利用【】可以进行那匹配不同的目标,参数只需要传入类型即可。
方法体JoinPoint 参数:用来连接当前连接点的连接细节,该参数可以获取目标对象的信息,如类名称,方法参数,方法名称等。【org.aspectj.lang.JoinPoint】包
表达式写法
@Before( value=**”execution(public int com.wf.springaopImpl.ArithmeticCalculatorImpl.(int , int))”**)
/**
* 前置通知
* @param joinPoint
*/
@Before("@within(aop.TheBigFourNotice.annotation.InterceptClass)")
public void before(JoinPoint joinPoint) { //注意 JoinPoint 的包
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The 前置通知 方法" + methodName + " begin with" + args);
}
返回通知@AfterReturning
返回通知: 在目标方法正常结束时,才执行的通知 ,如果方法发生异常则没有返回通知
返回通知使用@AfterReturning注解,并将切入点表达式的值作为注解值。
返回通知可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。如下
returning属性值:声明该方法可以存在返回值,该属性的值即为用来传入返回值的参数名称。
方法体Object参数 :需要使用与returning同名参数名称,用来接收方法的返回值。
表达式写法
@AfterReturning(value=“execution(public int com.wf.springaopImpl.ArithmeticCalculatorImpl.*(int , int)))”,returning=“result”)
/**
* 返回通知
* @param joinPoint
* @param result
*/
@AfterReturning(value = "@within(aop.TheBigFourNotice.annotation.InterceptClass))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The 返回通知方法" + methodName + " end with " + result);
}
异常通知@AfterThrowing
异常通知 :在目标方法出现异常时才会进行执行的代码,如果方法没有异常,则不执行异常通知
异常通知使用@AfterThrowing注解,并将切入点表达式的值作为注解值。
throwing属性:访问连接点抛出的异常。
方法体Exception参数:用来接收连接点抛出的异常。Exception类匹配所有的异常,可以指定为特定的异常 例如NullPointerException类等
表达式写法
@AfterThrowing(value=“execution(public int com.wf.springaopImpl.ArithmeticCalculatorImpl.*(int , int)))”,throwing=“ex”)
/**
* 异常通知
* @param joinPoint
* @param ex
*/
@AfterThrowing(value = "@within(aop.TheBigFourNotice.annotation.InterceptClass))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The 异常通知方法" + methodName + " occurs excetion: " + ex);
}
后置通知
后置通知: 在目标方法执行之后,无论是否发生异常,都进行执行的通知。 该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。
后置通知使用@After注解, 并将切入点表达式的值作为注解值。
在后置通知中,不能访问目标方法的执行结果。原因可能在执行过程中发生异常而无法得到结果。
@After(“execution(public int com.wf.springaopImpl.ArithmeticCalculatorImpl.*(int , int)))”)
/**
* 后置通知
* @param joinPoint
*/
@After("@within(aop.TheBigFourNotice.annotation.InterceptClass)")
public void after(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The 后置通知方法 " + methodName + " end");
}
五大通知执行时机
要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理,AOP的原理其实就是利用了动态代理,将动态代理进行了封装。
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类,通知则是标注有某种注解的简单的 Java 方法。
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行 (如果方法发生异常则没有返回通知)
@AfterThrowing: 异常通知, 在方法抛出异常之后 (如果方法没有异常,则不抛出异常通知)
@Around: 环绕通知, 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。