于AOP的原理网上有很多教程,此处不再赘述,只是通过具体的案例来记录如何使用。
首先从start.spring.io
上下载一个Spring Boot工程,只需要引入web依赖即可。然后我们创建一个Controller:
@RestController
public class HelloController {
/*@AspectAction(name = "loglog")*/
@GetMapping("/getHello")
public String getHello(){
System.out.println("执行getHello!");
return "hello";
}
}
然后创建一个切面:
@Aspect
@Component
public class LogAspect {
/**
* 前置通知
* 在方法执行之前执行
*/
@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void before() {
System.out.println("before");
}
}
这个切面中最重要的就是@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
,它说明了这是一个前置通知,并且切入点为HelloController类中的getHello方法。
到这里一个最简单的AOP例子就能运行了,启动程序后,访问入口,打印信息如下:
before
执行getHello!
总共支持五种通知类型:
@Aspect
@Component
public class LogAspect {
/**
* 前置通知
* 在方法执行之前执行
*/
@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void before() {
System.out.println("before");
}
/**
* 后置通知
* 在方法执行之后
*/
@After("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void after() {
System.out.println("after");
}
/**
* 返回通知
* 在方法执行之后,返回之前执行
*/
@AfterReturning("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void afterReturing(){
System.out.println("afterReturning");
}
/**
* 异常通知
* 在方法抛出异常之后执行
*/
@AfterThrowing("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void afterThrowing(){
System.out.println("afterThrowing");
}
}
执行结果可以表现各个通知的执行顺序:
before
执行getHello!
afterReturning
after
还有一个环绕通知,可以代替其它四个通知,环绕通知可以实现其它四个通知。
/**
* 环绕通知
* 能够代替其它四种通知使用
*/
@Around("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
// 执行目标方法中的逻辑
joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("异常通知!");
} finally {
System.out.println("后置通知!");
}
}
假设同时设置了四种通知,还有环绕通知,那么执行的顺序是怎样的呢?
前置通知!
before
执行getHello!
afterReturning
after
返回通知!
后置通知!
需要注意的是,环绕通知中的joinPoint.proceed();
如果不写的话,就会跳过切入点目标方法的逻辑执行,直接执行环绕通知后续的通知逻辑:
前置通知!
返回通知!
后置通知!
在如上的案例中,我们发现如果定义了很多通知,在定义切入点表达式时写了很多一样的东西:
"execution(* com.example.aopdemo.controller.HelloController.getHello(..))"
其实可以在切面类中先定义一个切点表达式,然后其它通知定义的时候引用这个切点表达式即可:
@Aspect
@Component
public class LogAspect {
/**
* 声明切点表达式
*/
@Pointcut("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
public void pointCut(){}
/**
* 环绕通知
* 能够代替其它四种通知使用
*/
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
//joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("异常通知!");
} finally {
System.out.println("后置通知!");
}
}
}
以上面的切点表达式为例:
execution
表示匹配执行目标方法,还有其它类型的指示器,但用的不多;*
表示不关心方法的返回值;..
表示不关心方法的参数;&& || !
来表示且、或、非的关系;
@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")
其中within是和execution平行的指示器,表示匹配的切入点类型;bean是实例匹配器,只有遇到girl这个实例的时候才会执行AOP;
有的时候我们希望在声明自定义注解的方法上执行AOP的逻辑,而不是如上指定具体的位置。
首先我们需要先定义自己的注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAction {
String name() default "default";
}
然后,在切面中定义切入点为标注这个注解的地方。
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.example.aopdemo.annotation.AspectAction)")
public void pointCut(){}
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("前置通知!");
try {
joinPoint.proceed();
System.out.println("返回通知!");
} catch (Throwable throwable) {
System.out.println("异常通知!");
} finally {
System.out.println("后置通知!");
}
}
}
最后,只要在想使用AOP的方法之上加上自定义注解即可:
@AspectAction(name = "loglog")
@GetMapping("/getHello")
public String getHello(){
System.out.println("执行getHello!");
return "hello";
}
我们在环绕通知中,可以通过ProceedingJoinPoint来获取如下各种信息,ProceedingJoinPoint继承自JoinPoint,所以在其它类型的通知中也可以获得如下内容:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。