动态代理
Spring AOP概论
AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理。而Spring AOP是AOP具体的实现。
比如:写博客系统时,用户在进行观看和书写的时候,需要服务器去验证用户的登录状态,以前的写法是在每个执行程序的加入判断用户状态,现在用AOP就可以做到。集中处理这些。
之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。
对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP
来统⼀处理了。
AOP可以实现。
- 统⼀⽇志记录
- 统⼀⽅法执⾏时间统计
- 统⼀的返回格式设置
- 统⼀的异常处理
- 事务的开启和提交等
AOP的组成
1.切面(Aspect):定义的是事件(AOP是做啥的):比如用户登录校验(定义方向)
2.切点(Pointcut):定义具体规则:比如定义用户登录拦截规则,那些接口判断用户登录权限?哪些不判断(定制具体方案)
3.通知(Advice):AOP执行的具体方法:比如获取用户登录信息,如果获取到说明已经登录,否则未登录。(具体业务执行者)
分类:前置通知,后置通知,环绕通知,异常通知,返回通知
4.连接点(join poit):有可能触发切点的所有点:比如所有的接口,链接所有可能触发条件 的接口
Spring AOP实现步骤:
(拦截)
1.添加Spring AOP依赖
2.定义切面
3.定义切点
4.实现通知
添加依赖
面板中不存在,不热门的插件,所以这个需要手动添加到pom.xml中
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面
使用注解:@Aspect
定义切点
使用注解 @Pointcut
AspectJ ⽀持三种通配符
- ( * ) :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
- (. .) :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
- (+) :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身
实现通知
- 前置通知使⽤@Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
- 后置通知使⽤@After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
- 返回之后通知使⽤@AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
- 抛异常后通知使⽤@AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
- 环绕通知使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执
每一次在前端调用方法时都会,被AOP拦截,执行完AOP中的方法后,在返回给Controller
@Aspect
@Component
public class UserAspect {
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void doBefore(){
System.out.println("执行了前置通知");
}
@After("pointcut()")
public void doAfter(){
System.out.println("执行了后置通知");
}
@AfterReturning("pointcut()")
public void doAfterReturning(){
System.out.println("执行了返回之后通知");
}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行之前");
//执行目标方法
Object result=joinPoint.proceed();
System.out.println("环绕通知执行之后");
return result;
}
}
环绕通知: AOP 统计 UserController 每个⽅法的执⾏时间。
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知执行之前");
//执行目标方法
Object result=joinPoint.proceed();
System.out.println("环绕通知执行之后");
return result;
}
Spring AOP 实现原理—》动态代理
动态代理组成:
1.JDK Proxy-------》代理对象必须实现接口,才能使用JDK Proxy。
2.CGLIB-------》通过是实现代理类的子类来实现动态代理(被final修饰的类是不能被代理)
JDK Proxy vs CGLIB 的区别
1.出身不同:
JDK Proxy出身于JDK
CGLIB出身第三方库
2.实现不同:
- JDK Proxy 要求代理类实现接口才能实现dialing;
- CGLIB 是通过实现代理类的子类完成动态代理。
3.性能不同:
- JDK 7+ JDK Proxy性能是略高于 CGLIB;
- JDK 7 之前CGLIB 性能远远高于JDK Proxy
AOP的实战环节
- 统一的登录权限校验
- 统一异常处理
- 统一数据格式返回
Spring 拦截器:
HandlerInterceptor,拦截器的实现分为以下两个步骤:
- 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
- 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中
用户登录拦截器
创建⾃定义拦截器
重写:preHandle
/*
* 自定义拦截器
* */
public class UserInterceptor implements HandlerInterceptor {
//返回 true -》拦截器校验成功,继续执行后续的方法
//返回false -> 拦截器校验失败,不会执行后续的目标方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//业务方法
HttpSession session=request.getSession();
if (session != null&&session.getAttribute(AppVar.SESSION_KEY)!=null) {
return true;
}
return false;
}
}
将⾃定义拦截器加⼊ WebMvcConfigurer
全局配置文件
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private UserInterceptor userInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/reg")//拦截所有的请求,然后在选择放权限
.excludePathPatterns("/user/login")
;
}
}
控制层Controller
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getuser")
public String getUser(){
System.out.println("do getUser");
return "get user";
}
@RequestMapping("/getUser")
public String delUser(){
System.out.println("do getUser");
return "del user";
}
@RequestMapping("/reg")
public String doReg(){
System.out.println("do reg");
return "get reg";
}
@RequestMapping("/login")
public String dologin(){
System.out.println("do login");
return "get login";
}
}
设置的全局变量
//全局变量
public class AppVar {
//session key值
public static final String SESSION_KEY="SESSION_KEY";
}
- addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
- excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。
统一异常处理
1.@ControllerAdvice/@RestControllerAdvice
2.@ExceptionHandler(Exception.class)统一返回对象。
//一个特定的异常处理
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(NullPointerException.class)
public ResultAjax doNullPointerException(NullPointerException e){
ResultAjax resultAjax=new ResultAjax();
resultAjax.setCode(-1);
resultAjax.setMsg("空指针异常:"+e.getMessage());
resultAjax.setObject(null);
return resultAjax;
}
}
//全部的异常处理
@ExceptionHandler(Exception.class)
public ResultAjax doException(Exception e){
ResultAjax resultAjax=new ResultAjax();
resultAjax.setCode(-1);
resultAjax.setMsg("空指针异常:"+e.getMessage());
resultAjax.setObject(null);
return resultAjax;
}
统一数据格式的返回
统⼀的数据返回格式可以使⽤ @ControllerAdvice +
@ResponseBodyAdvice 的⽅式实现.
后置执行,无论如何都得执行
- @ControllerAdvice
- 实现@ResponseBodyAdvice接口,并重写他的两个方法,supports
必须返回true,beforeBodyWrite方法中进行重写判断和重写操作。
这个呢,也叫保底策略。
/*
* 实现返回值的保底实现类
*
* */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/*
* true-->才会调用beforBody方法
* false-->不会会调用beforBody方法
* */
@Resource
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
//已经包装好的对象
if (body instanceof ResultAjax){
return body;
}
//无法接收String类型
//对字符串进行单独的判断和数据
if (body instanceof String){
ResultAjax resultAjax=ResultAjax.succ(body);
try {
return objectMapper.writeValueAsString(resultAjax);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return ResultAjax.succ(body);
}
}
总结:
- 统⼀⽤户登录权限的效验使⽤ WebMvcConfigurer+ HandlerInterceptor来实现
- 统⼀异常 处理使⽤ @ControllerAdvice + @ExceptionHandler 来实现
- 统⼀返回值处理使⽤ @ControllerAdvice + ResponseBodyAdvice 来处理