Spring AOP

  • 1.什么是Spring AOP?
  • 2.为什么要用Spring AOP?
  • 3.AOP的组成
  • 4.Spring AOP实现
  • 5.Spring AOP实现原理
  • 6.JDK和CGLIB实现的区别
  • 7.Spring AOP用户统一登录验证的问题
  • 8.统一异常处理
  • 9.统一数据返回格式

1.什么是Spring AOP?

AOP:面向切面编程,它是一种思想,对某一类事情集中处理。
OOP:面向对象编程。
AOP是一种思想,Spring AOP是一个框架,提供了一种对AOP思想的实现。

2.为什么要用Spring AOP?

举例:一个系统中,很多页面都需要先验证用户登录,这种情况下,如果采用都写一遍用户登录验证,当功能越来越多,登录验证也越来越多,代码修改和维护成本就比较高了,对于功能统一且使用地方较多的,就可以考虑AOP统一处理。
AOP可以实现:
1.统一的用户登录判断;
2.统一方法执行时间统计;
3.统一日志记录;
4.统一的返回格式设置;
5.统一的异常处理;
6.事务的开启和提交等;
AOP可以扩充多个对象的某个能力,AOP可以说是OOP的补充和完善。

3.AOP的组成

AOP的组成:
1.切面:针对与某一个功能的具体定义,某一个功能可能是登录验证功能,也有可能是日志记录功能。
2.切点:切面中的某一个方法。
3.连接点:用来匹配切面的多个点。
4.通知:在AOP的术语当中,切面的工作被称为通知。
通知可以分为:
前置通知使用@Before:通知方法会在目标方法调用之前执行;
后置通知使用@After:通知方法会在目标方法返回或者抛出异常后调用。
返回之后通知使用@AfterReturning:通知方法会在目标方法返回后调用。
抛出异常后通知使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。
环绕通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。

4.Spring AOP实现

Spring AOP实现步骤:
1.添加Spring AOP框架支持;
2.定义切面和切点;
3.定义通知。

1.添加Spring AOP框架支持;

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.6.6</version></dependency>

2.定义切面和切点

在一个类上面加注解@Aspect:表明当前类是一个切面


切点表达式说明:
AspectJ支持三种通配符
1*:匹配任意字符,只匹配一个元素(包、类、方法、参数)
2 … :匹配任意字符,可以匹配多个元素,在表示类时,必须和*联合使用。
3 +:表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
切点表达式由切点函数组成,其中execution()是最常用的切点函数,用来匹配方法,语法为:
execution()
修饰符和异常可以省略

3.定义通知(前置通知、后置通知、环绕通知)

5.Spring AOP实现原理

1.Spring AOP是构建在动态代理基础上,因此Spring对AOP的支持局限于方法级别的拦截。
2.动态代理分为两类:
1).JDK Porxy:JDK动态代理机制
2).CGLIB动态代理
3.代理的生成时机:
a)编译期;
b)类加载期;
c)运行期.

6.JDK和CGLIB实现的区别

1.JDK实现,要求被代理类必须实现接口,之后是通过InvocationHandler及Proxy,在运行时动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现,只是在该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。
2.CGLIB实现,被代理类可以不实现接口,通过实现被代理类,在运行时动态的生成代理类对象。

7.Spring AOP用户统一登录验证的问题

用户登录拦截器实现:
1.创建一个拦截器:(判断用户是否登录,实现HandlerInterceptor接口)并重写preHandle

package com.example.demo.config;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;/** * 登录拦截器 */public class LoginInterretcept implements HandlerInterceptor {/** * 拦截方法 */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session=request.getSession(false);if(session!=null && session.getAttribute("userinfo")!=null){//已经登录return true;}//401表示没有权限response.setStatus(401);return false;}}

2.配置拦截器和拦截规则

package com.example.demo.config;/** * 全局配置文件 */import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class AppConfig implements WebMvcConfigurer {/** * 配置拦截器和拦截规则 * @param registry */@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterretcept()) //创建一个拦截器.addPathPatterns("/**")//拦截所有请求.excludePathPatterns("/**/*.html").excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.js").excludePathPatterns("/user/*.login").excludePathPatterns("/user/reg");}//设置api统一的访问前缀@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("api",c->true);}}

8.统一异常处理

统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。

1.先给类上添加一个@ControllerAdvice注解,标识当前的类为一个控制器的增强类。
2.添加具体的异常返回业务代码,并标识为@ExceptionHandler为异常统一处理方法。

import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;/** * 统一异常处理 */@ControllerAdvice//1.标识为控制器增强类@ResponseBodypublic class ErrorAdvice {//2.添加异常统一处理方法@ExceptionHandler(NullPointerException.class)public HashMap<String,Object> nullException(NullPointerException e){HashMap<String,Object> result=new HashMap<>();result.put("succ",200); //http请求成功了,(大状态)result.put("state",-1); //报错了(业务状态,小状态)result.put("message",e.toString());return result;}@ExceptionHandler(Exception.class)public HashMap<String,Object> exception(Exception e){HashMap<String,Object> result=new HashMap<>();result.put("succ",200);result.put("state",-1);result.put("message",e.toString());return result;}}

9.统一数据返回格式

1.为什么需要统一数据返回格式?
a)方便前端程序员更好的接收和解析后端数据接口返回的数据;
b)减低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的;
c)有利于项目统一数据的维护和修改;
d)有利于后端技术部门的统一规范的标准制定。
2.统一数据返回格式的实现:@ControllerAdvice+ResponseBodyAdvice的方式实现

package com.example.demo.config;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.HashMap;/** * 统一数据格式处理 */@ControllerAdvice //1.public class ResponseAdvice implements ResponseBodyAdvice {//是否要进行内容重写@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {//统一数据格式的封装HashMap<String,Object> result=new HashMap<>();result.put("succ",200); //返回大状态result.put("state",1);result.put("data",body);return result;}}