Spring 统一处理(JavaEE进阶系列6)

目录

前言:

1.用户登录权限的校验

2.Spring拦截器

2.1自定义拦截器

2.2将自定义拦截器加入到系统配置

2.3拦截器练习

2.4拦截器的实现原理

3.统一异常处理

4.统一数据的返回格式

结束语:


前言:

接下来就是Spring Boot中的统一功能处理模块了,也就是AOP的实战环节,这节中我们主要学习的有以下三点:1.统一用户登录权限验证。2.统一数据格式返回。3.统一异常处理。话不多说我们一起来了解吧!

1.用户登录权限的校验

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

最初我们实现用户登录功能的时候会有很多缺点,在每个方法中都有相同的用户登录验证权限:

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

在我们之前学习过Spring AOP之后,如果再次提到要实现统一用户登录验证的时候,我们的第一想法应该就是Spring AOP的前置通知或通过环绕通知来实现。

但是使用我们之前的的方法来实现的话,会发现存在两个问题:

  • 没有办法获取到HttpSession对象。
  • 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则就很难定义,甚至没有办法定义。

所以这针对于上述的问题我们就提出一个新的解决方案使用Spring中的拦截器。

2.Spring拦截器

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

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

2.1自定义拦截器

具体的实现如下所示:

代码展示:

config层代码:

package com.example.demo.config;

import com.example.demo.common.AppVar;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 自定义拦截器
 */
@Component
public class UserInterceptor implements HandlerInterceptor {
    /**
     * 返回true -> 拦截器验证成功,继续执行后续的方法
     * 返回false -> 拦截器验证失败,不会执行后续的目标方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("do UserInterceptor");
        //业务方法
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {
            //用户已经登录
            return true;//继续执行后续的流程
        }
        response.sendRedirect("https://www.baidu.com");
        return false;
    }
}

common代码:

package com.example.demo.common;

/**
 * 全局变量
 */
public class AppVar {
    //Session Key
    public static final String SESSION_KEY = "SESSION_KEY";
}

Controller层代码:

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/reg")
    public String reg() {
        System.out.println("do reg()");
        Object obj = null;
        System.out.println(obj.hashCode());
        return "reg";
    }

    @RequestMapping("/login")
    public String login() {
        System.out.println("do login()");
        return "login";
    }
}

结果展示:

如果已经登录:

Spring 统一处理(JavaEE进阶系列6)_第1张图片

如果没有登录:

这里我们使用以下的方式来模拟没有登录的场景:

Spring 统一处理(JavaEE进阶系列6)_第2张图片

Spring 统一处理(JavaEE进阶系列6)_第3张图片

Spring 统一处理(JavaEE进阶系列6)_第4张图片 

2.2将自定义拦截器加入到系统配置

上述自定好了一个拦截器之后接下来我们就需要将自定义的拦截器设置到当前项目的配置文件中,并设置拦截的规则。

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private UserInterceptor userInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/**") //拦截所有的请求
                .excludePathPatterns("user/reg") //除去拦截reg注册请求
                .excludePathPatterns("user/login"); //除去拦截login登录请求
    }
}
其中:
  • addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
  • excludePathPatterns:表示需要排除的URL。

​​​​​​​2.3拦截器练习

接下来我们来实现一下面的这个登录拦截器的练习题:

  • 登录、注册⻚⾯不拦截,其他⻚⾯都拦截。
  • 当登录成功写⼊ session 之后,拦截的⻚⾯可正常访问。

首先我们先来导入一个博客系统的静态页面:

大家可以从我的Gitee中进行下载☞博客系统静态页面: 博客系统的静态页面

导入静态页面之后我们就可以开始编写拦截器的规则了。

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;

import javax.annotation.Resource;

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Resource
    private UserInterceptor userInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/**") //拦截所有的请求
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/user/login");
    }
}

接下来就可以正常访问页面了。

Spring 统一处理(JavaEE进阶系列6)_第5张图片

2.4拦截器的实现原理

正常情况下的调用顺序:

Spring 统一处理(JavaEE进阶系列6)_第6张图片

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

Spring 统一处理(JavaEE进阶系列6)_第7张图片 

首先所有的Controller执行都会通过一个调度器DispathcherServlet来实现,这一点可以在Spring Boot的控制台打印信息中看出来。

Spring 统一处理(JavaEE进阶系列6)_第8张图片

而所有的方法都会执行 DispathcherServlet 中的doDispatch调度方法,通过分析doDispatch源码我们可以看出在开始执行Controller之前,会先调用预处理方法applyPreHandle,在applyPreHandle中会获取所有的拦截器HandleInterceptor并执行拦截器中的preHandle方法,这样就和我们之前定义的拦截器可以对应上了。这就是拦截器的实现原理。

3.统一异常处理

统一异常处理使用的是@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler来实现的,@ControllerAdvice表示控制器通知类@ExceptionHandler是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。

代码如下所示:

package com.example.demo.common;

import lombok.Data;

/**
 * 统一对象
 */
@Data
public class ResultAjax {
    private int code; // 状态码
    private String msg; // 状态码的描述信息
    private Object data; // 返回数据

    /**
     * 返回成功对象
     * @param data
     * @return
     */
    public static ResultAjax succ(Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg("");
        resultAjax.setData(data);
        return resultAjax;
    }

    public static ResultAjax succ(String msg, Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }

    /**
     * 返回失败对象
     * @param code
     * @param msg
     * @return
     */
    public static ResultAjax fail(int code,String msg){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(null);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg,Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }

}
package com.example.demo.config;

import com.example.demo.common.ResultAjax;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice //表示控制器的通知类
public class ExceptionAdvice {
    @ExceptionHandler //统一返回的对象
    public ResultAjax doNullPointerException(NullPointerException e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("空指针异常:" + e.getMessage());
        resultAjax.setData(null);
        return resultAjax;
    }
}

这里需要将Controller里面的返回值进行修改。

Spring 统一处理(JavaEE进阶系列6)_第9张图片

结果如下所示:

Spring 统一处理(JavaEE进阶系列6)_第10张图片

这样当出现异常的时候前端拿到的就不是之前报一大堆的错误了,就是可以直接在页面中显示出错误的信息了。

当然上述过程中我们只是处理了空指针异常,那么异常那么多难道要将没一种情况都写进去吗?

当然不是我们可以使用Exception来进行异常处理。如下代码所示:

Spring 统一处理(JavaEE进阶系列6)_第11张图片

4.统一数据的返回格式

为什么我们需要统一数据的返回格式呢?统一数据的返回格式优点有以下几点:

  • 方便前端程序员更好的接收和解析后端数据接口返回的数据。
  • 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。
  • 有利于项目统一数据的维护和修改。
  • 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。

统一的数据返回格式可以使用@ControllerAdvice + ResponseBodyAdvice的方式来实现,具体实现代码如下所示:

package com.example.demo.config;

import com.example.demo.common.ResultAjax;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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 javax.annotation.Resource;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Resource
    private ObjectMapper objectMapper;

    /**
     * true -> 才会调用 beforeBodyWrite 方法,
     * 反之则永远不会调用 beforeBodyWrite 方法
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @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) {
        //已经包装好的对象
        //instanceof - 判断类型
        if (body instanceof ResultAjax) {
            return body;
        }
        //对字符串进行判断和处理
        if(body instanceof String) {
            ResultAjax resultAjax = ResultAjax.succ(body);
            try {
                return objectMapper.writeValueAsString(resultAjax);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return ResultAjax.succ(body);
    }
}

结束语:

好了这节小编就给大分享到这里啦,希望这节对大家有关于Spring中的统一处理的基础知识的了解有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

你可能感兴趣的:(JavaEE进阶,spring,java-ee,java)