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 {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session=request.getSession(false);
if(session!=null && session.getAttribute("userinfo")!=null){
return true;
}
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;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterretcept())
.addPathPatterns("/**")
.excludePathPatterns("/**/*.html")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/user/*.login")
.excludePathPatterns("/user/reg");
}
@Override
public 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
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullException(NullPointerException e){
HashMap<String,Object> result=new HashMap<>();
result.put("succ",200);
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
public class ResponseAdvice implements ResponseBodyAdvice {
@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) {
HashMap<String,Object> result=new HashMap<>();
result.put("succ",200);
result.put("state",1);
result.put("data",body);
return result;
}
}
|