Spring Boot AOP(面向切面编程)是一种面向对象编程技术,它可以使代码更加模块化和可重用。它通过将横切关注点(如日志记录、性能检测等)与应用程序的主要逻辑分离开来,实现代码的分离和复用。
Spring Boot AOP的主要组件是切点和通知。切点是指一个或多个方法,这些方法可以被通知拦截并执行额外的代码。通知是指在切点方法周围执行的代码,这些代码可以在切点方法之前、之后或周围执行。Spring Boot AOP支持不同类型的通知,例如前置通知、环绕通知、后置通知和异常通知。其中最常用的是环绕通知。
使用Spring Boot AOP有两种方式,分别是非注解的方式和使用注解的方式。
首先,需要在pom.xml文件中导入以下依赖;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
方式一:基于非注解的方式
@Aspect
@Component
public class MyAop {
//切入点:待增强的方法
@Pointcut("execution(public * com.learn.controller.TeacherController.*(..)) " +
"|| execution(public * com.learn.controller.LearnController.*(..))")
//切入点签名
public void log(){
System.out.println("pointCut签名。。。");
}
// 前置通知
@Before("log()")
public void deBefore(JoinPoint jp) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("CLASS_METHOD : " + jp);
System.out.println("ARGS : " + Arrays.toString(jp.getArgs()));
System.out.println("前置通知:方法执行前执行...");
}
//返回通知
@AfterReturning(returning = "ret", pointcut = "log()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
System.out.println("返回通知:方法的返回值 : " + ret);
}
//异常通知
@AfterThrowing(throwing = "ex", pointcut = "log()")
public void throwss(JoinPoint jp, Exception ex) {
System.out.println("异常通知:方法异常时执行.....");
System.out.println("产生异常的方法:" + jp);
System.out.println("异常种类:" + ex);
}
//后置通知
@After("log()")
public void after(JoinPoint jp){
System.out.println("后置通知:最后且一定执行.....");
}
@Around("log()")
public Object aroundAnnotationAdvice(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。前置");
rtValue = pjp.proceed(args);//明确调用切入点方法(切入点方法)
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。返回");
System.out.println("返回通知:"+rtValue);
return rtValue;
} catch (Throwable e) {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。异常");
throw new RuntimeException(e);
} finally {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。后置");
}
}
}
方式二:基于注解的方式
首先创建一个注解类:
//表示此注解可以标注在类和方法上
@Target({ElementType.METHOD, ElementType.TYPE})
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLogAnnotation {
String desc() default " ";
}
其次在需要增强的方法上进行注解
@MyLogAnnotation(desc = "测试注解类型:helloAnnotation")
最后创建一个java类
@Aspect
@Component
public class MyAopAnnotation {
//切入点:增强标有MyLogAnnotation注解的方法
@Pointcut(value="@annotation(com.wch.youxia.learn.myannotation.MyLogAnnotation)")
//切入点签名
public void logAnnotation(){
System.out.println("pointCut签名。。。");
}
//前置通知
//注意:获取注解中的属性时,@annotation()中的参数要和deBefore方法中的参数写法一样,即myLogAnnotation
@Before("logAnnotation() && @annotation(myLogAnnotation)")
public void deBefore(JoinPoint jp, MyLogAnnotation myLogAnnotation) throws Throwable {
System.out.println("前置通知:方法执行前执行...");
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取注解中的属性
System.out.println("deBefore===========>" + myLogAnnotation.desc());
// 记录下请求内容
System.out.println("URL : " + request.getRequestURL().toString());
}
//返回通知
@AfterReturning(returning = "ret", pointcut = "logAnnotation() && @annotation(myLogAnnotation)")
public void doAfterReturning(Object ret, MyLogAnnotation myLogAnnotation) throws Throwable {
// 获取注解中的属性
System.out.println("doAfterReturning===========>" + myLogAnnotation.desc());
// 处理完请求,返回内容
System.out.println("返回通知:方法的返回值 : " + ret);
}
//异常通知
@AfterThrowing(throwing = "ex", pointcut = "logAnnotation() && @annotation(myLogAnnotation)")
public void throwss(JoinPoint jp,Exception ex, MyLogAnnotation myLogAnnotation){
// 获取注解中的属性
System.out.println("throwss===========>" + myLogAnnotation.desc());
System.out.println("异常通知:方法异常时执行.....");
System.out.println("产生异常的方法:"+jp);
System.out.println("异常种类:"+ex);
}
//后置通知
@After("logAnnotation() && @annotation(myLogAnnotation)")
public void after(JoinPoint jp, MyLogAnnotation myLogAnnotation){
// 获取注解中的属性
System.out.println("after===========>" + myLogAnnotation.desc());
System.out.println("后置通知:最后且一定执行.....");
}
@Around("logAnnotation()&& @annotation(myLogAnnotation)")
public Object aroundAnnotationAdvice(ProceedingJoinPoint pjp,MyLogAnnotation myLogAnnotation) {
Object rtValue = null;
try {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。前置");
rtValue = pjp.proceed(args);//明确调用切入点方法(切入点方法)
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。返回");
System.out.println("返回通知:"+rtValue);
return rtValue;
} catch (Throwable e) {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。异常");
throw new RuntimeException(e);
} finally {
System.out.println("通知类中的aroundAnnotationAdvice方法执行了。。后置");
}
}
}
可以看到,两种方式运行方法的注解与参数列表有差异