【大家好,我是爱干饭的猿,本文重点介绍用户登录权限效验之Spring 拦截器、统一异常处理、统一数据返回格式。
后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关注一下吧】
上一篇文章:《【SSM】Bean 作用域和生命周期(重点:Bean 的生命周期5大部分)》
🤞目录🤞
用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统一的用户登录验证处理,它是一个逐渐完善和逐渐优化的过程。
最初用户登录验证的实现方法:
- @RestController
- public class IndexController {
- @RequestMapping("/method1")
- public String method1(HttpServletRequest request){
- User user = null;
- HttpSession session = request.getSession();
- if(session != null){
- user = (User) session.getAttribute("currentUser");
- }
- if(user == null){
- return "redirect:/login.html";
- }
- return "redirect:/";
- }
- @RequestMapping("/method2")
- public String method2(HttpServletRequest request){
- User user = null;
- HttpSession session = request.getSession();
- if(session != null){
- user = (User) session.getAttribute("currentUser");
- }
- if(user == null){
- return "redirect:/login.html";
- }
- return "redirect:/";
- }
- }
每个方法中都有相同的用户登录验证权限,它的缺点是:
我们想到的另一个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:
- @Aspect
- @Component
- public class UserAspect {
-
- @Pointcut("execution(* com.example.demo.controller..*.*(..))")
- public void pointCut(){}
-
- // 前置方法
- @Before("pointcut()")
- public void doBefore(){
- }
-
- // 环绕方法
- @Around("pointCut()")
- public Object doAround(ProceedingJoinPoint joinPoint){
- User user = null;
- System.out.println("Around 方法开始执行");
- try {
- // 执行拦截方法
- user = (User) joinPoint.proceed();
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
- System.out.println("Around 方法结束执行");
- return user;
- }
- }
如果要在 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:
对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下:
- @Slf4j
- @Component // 拦截器,如果用户未登录,则直接重定向到登录页,不会走到后续操作
- public class MyInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- log.info("MyInterceptor.preHandle()");
- User currentUser = null;
- HttpSession session = request.getSession();
- if(session != null){
- currentUser = (User)session.getAttribute("currentUser");
- }
- if(currentUser == null){
- // 说明用户未登录
- response.sendRedirect("/login.html");
- return false;
- }
- return true;
- }
-
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- log.info("MyInterceptor.postHandle()");
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- log.info("MyInterceptor.afterCompletion()");
- }
- }
将上一步中的自定义拦截器加入到系统配置信息中,具体实现代码如下:
- // 1. 必须是一个 Spring bean(否则没有机会调用)
- // 2. 必须实现了 WebMvcConfigurer 接口
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- private final MyInterceptor myInterceptor;
-
- @Autowired
- public WebConfig(MyInterceptor myInterceptor) {
- this.myInterceptor = myInterceptor;
- }
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // 可以添加多个拦截器,先添加的拦截器,先被应用
- registry.addInterceptor(myInterceptor)
- .addPathPatterns("/**") // 应用到所有 URL 上
- .excludePathPatterns("/error") // 只要有错误,都会到这
- .excludePathPatterns("/login.html") // 但不包括login.html
- .excludePathPatterns("/login.do"); // 但不包括login.do
- }
- }
说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。
正常情况下的调用顺序:

然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:
通过上面的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的, 大体的调用流程如下:

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的
两个结合表示当出现异常的时候执行某个通知,也 就是执行某个方法事件,具体实现代码如下:
- @ControllerAdvice
- @ResponseBody
- public class ExceptionAdvice {
- @ExceptionHandler(Exception.class)
- public Object exceptionAdvice(Exception e){
- HashMap
result = new HashMap<>(); - result.put("success", -1);
- result.put("message", "总的异常信息:" + e.getMessage());
- result.put("data", null);
- return result;
- }
- @ExceptionHandler(NullPointerException.class)
- public Object NullPointerExceptionAdvice(NullPointerException e) {
- HashMap
result = new HashMap<>(); - result.put("success", -1);
- result.put("message", "空指针异常:" + e.getMessage());
- result.put("data", null);
- return result;
- }
- }
统一数据返回格式的优点有很多,比如以下几个:
统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,具体实现代码如下:
- @ControllerAdvice
- public class ResBodyAdvice implements ResponseBodyAdvice {
- @Override
- public boolean supports(MethodParameter returnType, Class converterType) {
- // 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)
- // 返回 true 表示重写
- return false;
- }
-
- @Override
- public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
- // 构造统一返回对象
- HashMap
result = new HashMap<>(); - result.put("success", 1);
- result.put("message", "");
- result.put("data", body);
- return result;
- }
- }
分享到此,感谢大家观看!!!
如果你喜欢这篇文章,请点赞加关注吧,或者如果你对文章有什么困惑,可以私信我。
🏓🏓🏓