SpringBoot统一功能处理(一)

简介: SpringBoot统一功能处理

一、用户登录权限效验🍭

1、最初用户登录验证🍉

用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统⼀的用户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。


@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 某⽅法 1
     */
    @RequestMapping("/m1")
    public Object method(HttpServletRequest request) {
        // 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
    /**
     * 某⽅法 2
     */
    @RequestMapping("/m2")
    public Object method2(HttpServletRequest request) {
        // 有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // 说明已经登录,业务处理
            return true;
        } else {
            // 未登录
            return false;
        }
    }
    // 其他⽅法。。。
}

从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:

  1. 每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也⼀样要传参调用和在方法中进行判断。
  2. 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些用户登录验证的方法和接下来要实现的业务几何没有任何关联,但每个方法中都要写⼀遍。 所以提供⼀个公共的 AOP 方法来进行统⼀的用户登录权限验证迫在眉睫。

2、Spring AOP 用户统⼀登录验证的问题🍉

说到统⼀的用户登录验证,我们想到的第⼀个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
    // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut() {
    }
    // 前置⽅法
    @Before("pointcut()")
    public void doBefore() {
    }
    // 环绕⽅法
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏拦截⽅法
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around 方法结束执行");
        return obj;
    }
}

如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:

  1. 定义拦截的规则(表达式)非常难。(我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样 的话排除方法的规则很难定义,甚至没办法定义)。
  2. 在切面类中拿到 HttpSession 比较难

那这样如何解决呢?

Spring 拦截器🍉

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理方法。
  2. 将自定义拦截器加入WebMvcConfigurer 的 addInterceptors 方法中。

具体实现如下:

目录结构:

image.png

Ⅰ、实现拦截器🍓

关键步骤:

a.实现 HandlerInterceptor 接口

b.重写 preHeadler 方法,在方法中编写自己的业务代码


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 LoginInterceptor implements HandlerInterceptor {
    /**
     * 此方法返回一个 boolean,如果为 true 表示验证成功,可以继续执行后续流程如果是 false 表示验证失败,后面的流程不能执行了
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @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;
        }
        // 可以调整到登录页面 or 返回一个 401/403 没有权限码
        response.sendRedirect("/login.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.WebMvcConfigurer;
@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");
    }
}

其中:

  • addPathPatterns:表示需要拦截的 URL,**表示拦截任意方法(也就是所有方法)。
  • excludePathPatterns:表示需要排除的 URL。

说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件(图片文件、JS 和 CSS 等文件)。

排除所有的静态资源:


// 拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有接⼝
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.jpg")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/**/login"); // 排除接⼝
    }

UserController:


package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
    /*    @GetMapping
        public String getMethod(){
            return "执行GET请求!";
        }
        @PostMapping
        public String postMethod(){
            return "执行POST请求!";
        }*/
    @RequestMapping("/getuser")
    public String getUser() {
        System.out.println("执行了 get User~");
        return "get user";
    }
    @RequestMapping("/login")
    public String login() {
        System.out.println("执行了 login~");
        return "login~";
    }
    @RequestMapping("/reg")
    public String reg() {
        System.out.println("执行了 reg~");
        return "reg~";
    }
}

Ⅲ、启动项目:🍓

不拦截:

image.png

image.png

拦截:http://localhost:8080/user/getuser

image.png

为什么会显示重定向次数过多?

这是因为这个login.html页面也被拦截了,所以它去访问时候就会一直重定向重定向去访问。

解决方法:

image.png

image.png

Ⅳ、拦截器实现原理🍓

正常情况下的调用顺序:

image.png

然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:

image.png

所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

image.png

而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 源码如下:


protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
        }
    }

SpringBoot统一功能处理(二)https://developer.aliyun.com/article/1393198


相关文章
|
8天前
|
JavaScript 前端开发 Java
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
|
8天前
|
存储 Java 数据库
|
8天前
|
存储 搜索推荐 Java
|
8天前
|
缓存 前端开发 Java
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
44 1
|
8天前
|
Java 测试技术 数据库
基于SpringBoot+HTML实现登录注册功能模块
基于SpringBoot+HTML实现登录注册功能模块
|
8天前
|
开发框架 监控 Java
深入探索Spring Boot的监控、管理和测试功能及实战应用
【5月更文挑战第14天】Spring Boot是一个快速开发框架,提供了一系列的功能模块,包括监控、管理和测试等。本文将深入探讨Spring Boot中监控、管理和测试功能的原理与应用,并提供实际应用场景的示例。
19 2
|
8天前
|
SQL Java 数据库连接
一文带你快速学会SpringBoot工程下MaBatis对数据的增删改查功能!
在SpringBoot项目中,已配置好Mybatis和Lombok,数据库tb_user有四条初始数据。需求是按ID删除用户。首先在UserMapper接口添加@Delete注解的删除方法,然后在单元测试类中测试此方法,成功删除ID为4的用户。删除方法可选返回影响的记录数,此处用void。参数名在#{...}内可自定义。通过配置mybatis日志在控制台显示SQL操作。
|
8天前
|
Java 容器
SpringBoot使用配置注解开启自动配置功能&整合spring-boot-configuration-processor
SpringBoot使用配置注解开启自动配置功能&整合spring-boot-configuration-processor
20 0
|
8天前
|
前端开发 关系型数据库 MySQL
springboot+jpa+tymeleaf实现分页功能
springboot+jpa+tymeleaf实现分页功能
13 0
|
8天前
|
存储 JavaScript 前端开发
Spring Boot + Vue: 实现文件导入导出功能
本文介绍了使用Spring Boot和Vue实现文件导入导出的步骤。在后端,Spring Boot通过`MultipartFile`接收上传文件,保存至服务器,并使用`ResponseEntity`提供文件下载。前端部分,Vue项目借助`axios`发送HTTP请求,实现文件选择、上传及下载功能。这种前后端分离的实现方式提高了应用的可维护性和可扩展性。
53 2