目录
前言
1. Spring AOP 用户统⼀登录验证的问题
1.1 自定义拦截器
1.2 配置拦截器并配置拦截的规则
1.3 拦截器的原理源码分析
2. 统一异常处理
2.1 实现统一异常处理
2.2 测试统一异常处理
3. 统一的数据格式返回
3.1 统⼀数据返回格式的实现
3.2 测试统一的数据返回
Spring AOP是一个面向切面编程的框架,可以同设置要做处理的类为切面,设置切点进行设置拦截规则,然后定义通知,实现一定的拦截规则.但是我们原生的Spring AOP的框架使用起来比较繁琐.为了解决如此之类的Spring AOP的问题.对于Spring AOP的实战,本文将介绍三种功能.
1. 统一用户登录权限验证
2. 统一数据返回格式
3. 统一异常处理
假设我们要实现这样一个功能,在多个页面进行判断用户的登录状态,然后发现当前用户的登录状态为空的时候进行跳转到登录页.
我们之前的做法是,在每个页面的路由中进行判断用户的登录状态,显然这些操作是重复的,后来我们又学习了Spring AOP,我们试想可以将需要进行验证的类进行设置为切面,在需要地方进行配置拦截的规则,然后进行设置前置通知.这个思想是可以的,但是我们实现起来会遇到两个问题.
1. 没办法获取到 HttpSession 对象(我们需要对Session进行判断当前的用户登录状态)
2. 我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册⽅法和登录方法是不拦截的,这样的话排除⽅法的规则很难定义,甚至没办法定义。
针对一以上的问题,Spring 框架为我们提供了一个具体实现的拦截器:HandlerInterceptor
而我们要使用这个拦截器实现自己的功能,可以具体分为两步:
下面我们使用代码进行实现上述的功能:
具体代码:
public class LoginInterceptor implements HandlerInterceptor {
/**
* 此方法返回一个boolean类型的值,返回true表示验证成功,继续执行后续流程
*/
// 重写preHandle方法实现自己的业务代码
@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("/login1.html");
// response.setStatus(403);
// 上面两者取一个即可
return false;
}
}
红色部分就是将自定义的拦截器添加到项目中,然后绿色是一个扩展,就是在路由前面加前缀.
具体代码:
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;
/**
* Created with IntelliJ IDEA.
* Description:将自定义拦截器加入到WebMvcConfigurer中,重写addInterceptors方法进行设置拦截规则
* User: YAO
* Date: 2023-07-14
* Time: 15:04
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截规则设置,此处进行拦截所有请求
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg")
.excludePathPatterns("/user/get-num")
.excludePathPatterns("/user/get-string")
.excludePathPatterns("/img/**")
.excludePathPatterns("/**/*.html");
}
// 所有的接⼝添加 api 前缀
// 给路由进行添加前缀 ,设置为 true 表示启动前缀
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// configurer.addPathPrefix("api", c -> true);
// }
}
我们对上述实现的拦截器进行验证
当进行点击的时候,因为没有Session所以直接跳转到我们设置的登录页面了.
可以看出没有进行设置过Cookie的,就是没有Session连接.
所有的方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,观察源码发现:
点击这个applyPreHandle,applyPreHandle 中会获取所有拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这与之前我们实现拦截器的步骤对应.
统一异常处理是指 在应用程序中定义一个公共的异常处理机制,用来处理所有的异常情况。 这样可以避免在应用程序中分散地处理异常,降低代码的复杂度和重复度,提高代码的可维护性和可扩展性.\
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件.
我们自己创建一个路由,其中内容出现一个空指针异常,我们查看是否能进行返回给前端处理.
我们设置的异常处理起到作用了.那么如果不设置,异常的处理,会出现什么样的现象?
就会直接将控制台打印的异常返回给前端,前端拿到数据就很懵.所以统一的异常处理还是很有必要的.
下述是我们一般的统一返回的数据的格式
实现统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,具体步骤如下:
其中需要注意的是,当返回的类型为String类型的时候,我们需要使用Jackson将String转换成json再进行返回.也就是绿色的部分.
package com.example.demo.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: YAO
* Date: 2023-07-14
* Time: 17:25
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
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) {
HashMap result = new HashMap<>();
result.put("state",1);
result.put("msg", "");
result.put("data", body);
if (body instanceof String){
// 需要特殊进行处理,因为String比较特殊,既不是基础类型也不是对象,进行重写的时候String用的格式化工具是自己独有的
// 在body返回之前没有加载好.需要将String进行单独进行处理.使用Jackson将String转换成json再进行返回.
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return result;
}
}
可以看出返回的格式是我们规定好的格式.这就变得更加统一.
好啦本文就总结到这了,点个赞吧,谢谢!!!