在我们开发的过程中经常会有这样的需求,每一个请求需要判断权限,校验参数,打印日制等,但是每一个方法都写这样重复的代码无疑会让代码很冗余,于是可以想到用AOP切面的方式可以一次性解决所有的烦劳。
其实我们通过AOP机制可以实现:Authentication 权限检查、Caching 缓存、参数校验、Context passing 内容传递、Error handling 错误处理、日志打印等功能,这里我们讲一下怎么从0开始手写一个Spring AOP来实现前置通知或后置通知。
一:添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二:新建一个aop包,定义一个切面
一般会专门新建一个包用来处理aop,我这里新建一个package org.sang.org.sang.advice包,并且新建一个LogAdvice类。
package org.sang.org.sang.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* create by 86159 on 2020/11/2
*/
@Aspect
@Component
public class LogAdvice {
}
这里注意要加上注解@Aspect,申明是个切面类,@Component能够扫描到。
三:定义一个切面
package org.sang.org.sang.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* create by 86159 on 2020/11/2
*/
@Aspect
@Component
public class LogAdvice {
//定义一个切点,所有被PostMapping注解修饰的方法会织入advice
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
private void logAdvicePoint(){
}
}
定义切面是用@Pointcut注解,括号里为织入切面的条件,或者范围,里面可以是包,类,也可以是注解,我这里用的是注解,表示凡是被PostMapping注解修饰的方法会织入advice切面。logAdvicePoint为切面名称。
四:前置通知
package org.sang.org.sang.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* create by 86159 on 2020/11/2
*/
@Aspect
@Component
public class LogAdvice {
//定义一个切点,所有被PostMapping注解修饰的方法会织入advice
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
private void logAdvicePoint(){
}
@Before("logAdvicePoint()")
public void beforeAdvice(){
System.out.println("post请求advice被触发了。。。。。。。。。。。。,校验权限,参数有效性");
}
}
在切面的基础上构建前置通知,前置通知是用@Before注解,括号里的参数为切面的名称,前置通知的方法体内为在调用方法前要处理的业务逻辑,比如权限校验,参数校验等等。
五:后置通知
package org.sang.org.sang.advice;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* create by 86159 on 2020/11/2
*/
@Aspect
@Component
public class LogAdvice {
//定义一个切点,所有被PostMapping注解修饰的方法会织入advice
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
private void logAdvicePoint(){
}
@Before("logAdvicePoint()")
public void beforeAdvice(){
System.out.println("post请求advice被触发了。。。。。。。。。。。。,校验权限,参数有效性");
}
@After("logAdvicePoint()")
public void afterAdvice(){
System.out.println("请求完毕,打印日制记录");
}
}
后置通知用@After注解,同样括号里为切面名称,方法体内为调用方法完成后需要处理的业务逻辑,如打印日志记录。
下面我们写个方法测试下效果:
package org.sang.controller;
import org.sang.model.entity.User;
import org.sang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 新增用户
* @param user
* @return
*/
@PostMapping("/insert")
@ResponseBody
public User insert(@RequestBody @Validated(User.valid1.class) User user){
System.out.println("保存用户开始执行。。。");
return userService.insert(user);
}
我们用postman调用方法测试结果如下:
post请求advice被触发了。。。。。。。。。。。。,校验权限,参数有效性
保存用户开始执行。。。
请求完毕,打印日制记录
可以看到,在调用方法前和方法后都执行相应的前置通知和后置通知的逻辑代码,这样一个简单的aop就实现了。
上面定义@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")就是将所有标有@PostMapping注解的请求方法织入切面,也可以定义类,例如将所有contoller类织入切面。
@Pointcut("@annotation(org.sang.controller.*)") 将所有controller包下的类都织入切面,也就可以处理所有的web请求。
另外还有其他的通知:@Around,@AfterReturning,@Afterthrowing
这里简单介绍一下,切面的执行方法和其执行顺序:
- @Around 通知方法将目标方法封装起来
- @Before 通知方法会在目标方法调用之前执行
- @After 通知方法会在目标方法返回或者异常后执行
- @AfterReturning 通知方法会在目标方法返回时执行
- @Afterthrowing 通知方法会在目标方法抛出异常时执行