Spring AOP是一个实现面向切面编程思想的框架,可以实现对功能统一且使用地方较多的功能进行统一处理。
AOP(Aspect Oriented Programming):面向切面编程,是一种思想,是对某一类事情的集中处理。Spring AOP是一个框架,是对AOP思想的具体实现。类似于IoC和DI的关系。
主要学习:
1.AOP是如何组成的。
2.Spring AOP的使用。
3.Spring AOP的实现原理。
切面由切点和连接点组成。在程序中就是一个处理某方面具体问题的一个类,类里面包含了很多方法,这些方法就是切点和通知。
用来进行主动拦截的规则(配置)。
切面的工作,程序中被拦截请求触发的具体动作,就是在通知中实现的具体业务代码。
(1)前置通知:在执行目标方法之前执行的方法就叫做前置通知。
(2)后置通知:在执行了目标方法之后执行的方法就叫做后置通知。
(3)异常通知:在执行了目标方法出现异常时,执行的通知。
(4)返回通知:目标方法执行了返回数据(return)时,执行的通知。
(5)环绕通知:在目标方法执行的周期范围内(执行之前、执行中、执行后)都可以执行的方法叫做环绕通知。
应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
可能会触发AOP规则的所有点(所有请求)。
1.添加Spring AOP依赖;
创建Spring Boot项目时是没有Spring AOP框架可以选择的,创建好项目之后,再在pom.xml中添加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.7.9</version>
</dependency>
2.定义切面(创建切面类);
//环绕通知
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint jointPoint) throws Throwable {
System.out.println("进入环绕通知了~");
Object obj = null;
//执行目标方法
obj = jointPoint.proceed();
System.out.println("退出环绕通知了~");
return obj;
}
Spring AOP是构建在动态代理基础上,因此Spring对AOP的支持局限于方法级别的拦截。
Spring的切面包裹了目标对象的代理类实现,代理类处理方法的调用,执行额外的切面逻辑,并调用额外方法。
(1)原生Spring AOP实现统一拦截的难点:
a.定义拦截的规则(表达式)非常难;
b.在切面类中拿到HttpSession比较难。
(2)实现一个普通拦截器
关键步骤:
a.实现HandlerInterceptor接口;
b.重写preHeadler方法,在方法中编写自己的业务代码。
public class LoginInterceptor 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;
}
//可以调整到登录页面 或者 返回一个 401/403 没有权限码
response.sendRedirect("/login.html");
return false;
}
}
(3)将拦截器添加到配置文件中,并且设置拦截规则
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/user/login") //排除URL地址(不拦截的URL地址)
.excludePathPatterns("/user/reg")
.excludePathPatterns("/**/*.html");
}
}
给当前项目配置统一的前缀:
(1)在系统的配置文件中设置;
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/zhangsan",c->true);
}
(2)在application.properties/.yml中配置。
异常统一封装:
(1)创建一个类,并在类上标识@ControllerAdvice;
(2)添加方法@ExceptionHandler来订阅异常。
@ControllerAdvice
public class MyExHandler {
//拦截所有的空指针异常,并统一处理
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullException(NullPointerException e) {
HashMap<String,Object> result = new HashMap<>();
result.put("code","-1");
result.put("msg","空指针异常:"+e.getMessage());
result.put("data",null);
return result;
}
}
优点:
(1)方便前端更好地接收和解析后端数据接口返回的数据;
(2)降低前端与后端的沟通成本,按照某个格式实现就行;
(3)有利于项目统一数据的维护和修改;
(4)有利于后端技术的统一规范的标准制定。
实现:
(1)创建一个类,并添加@ControllerAdvice;
(2)实现ResponseBodyAdvice接口,并重写supports和beforeBodyWrite(统一对象就是此方法实现的)
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return false;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","");
result.put("data",body);
//String在转换时会出错 需要特殊处理,
if (body instanceof String) {
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return result;
}
}