目录
本篇文章介绍 Spring Boot 的统一功能处理模块,也就是 AOP 的实战环节。
没有登录的情况下,会跳转到登录页面。
Spring 提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
1. 创建自定义拦截器,实现 HandlerInterceptor 接口,重写 preHandle 方法。(执行具体方法之前的预处理)。
2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。
- package com.example.demo.configuration;
-
- 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
- */
- @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;
- }
- }
- package com.example.demo.common;
-
- /**
- * 全局变量
- */
- public class AppVar {
- // Session Key
- public static final String SESSION_KEY = "SESSION_KEY";
- }

- package com.example.demo.configuration;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- /**
- * 将自定义拦截器加入到系统配置
- * 有两种写法
- * 一是 new 一个 UserInterceptor 对象
- * 二是 注入的方式
- */
- @Configuration
- public class AppConfig implements WebMvcConfigurer {
-
- @Autowired
- private UserInterceptor userInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // registry.addInterceptor(new UserInterceptor());
-
- registry.addInterceptor(userInterceptor)
- .addPathPatterns("/**") // 拦截所有请求
- .excludePathPatterns("/user/reg") // 登录页面不拦截
- .excludePathPatterns("/user/login") // 注册页面不拦截
- ;
- }
- }
* 一级路由,** 所有路由。
/user 就是一级路由,/user/reg 是二级路由。
addPathPatterns 表示需要拦截的 URL,** 表示拦截所有方法
excludePathPattern 表示需要排除的 URL
- 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("/getuser")
- public String getUser(){
- System.out.println("do getUser()");
- return "getuser";
- }
-
- @RequestMapping("/reg")
- public String reg(){
- System.out.println("do reg()");
- return "reg";
- }
-
- @RequestMapping("/login")
- public String getlogin(){
- System.out.println("do login()");
- return "login";
- }
- }
输出:
do UserInterceptor
do reg()
do login()
正常情况下的调用顺序:

有了拦截器之后,在调用 controller 之前,会执行拦截器,如果为 true,则继续执行后续程序,如果为 false,则跳转相关页面。

- package com.example.demo.configuration;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- /**
- * 将自定义拦截器加入到系统配置
- * 有两种写法
- * 一是 new 一个 UserInterceptor 对象
- * 二是 注入的方式
- */
- @Configuration
-
- public class AppConfig implements WebMvcConfigurer {
-
- @Autowired
- private UserInterceptor userInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // registry.addInterceptor(new UserInterceptor());
-
- 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") // 注册页面不拦截
- .excludePathPatterns("/image/**") // image 文件下所有的图片格式拦截
- ;
- }
- }
除了注册页面、登录页面之外,其余页面都会跳转到百度。
- @RequestMapping("/reg")
- public String reg(){
- System.out.println("do reg()");
- Object obj = null;
- System.out.println(obj.hashCode());
- return "reg";
- }


如果每个页面都出现异常,可不可以统一处理呢?
出现的所有异常按照统一的格式返回:
- package com.example.demo.common;
-
- import lombok.Data;
-
- /**
- * 统一返回对象
- */
- @Data
- public class ResultAjax {
- private int code; // 状态码
- private String msg; // 状态码的描述信息
- private Object data; // 返回数据
- }
异常统一处理时,需要两个注解。一个是 @ControllerAdvice / @RestControllerAdvice ,另一个是 @ExceptionHandler(Exception.class) 统一返回对象。
- package com.example.demo.configuration;
-
- 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(NullPointerException.class)
- public ResultAjax doNullPointerException(NullPointerException e){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(-1);
- resultAjax.setMsg("空指针异常:"+e.getMessage());
- resultAjax.setData(null);
- return resultAjax;
- }
- }

再举一个算术异常的例子:
由于所有的异常都继承自 Exception,所以 @ExceptionHandler(Exception.class) 里面的异常类不用写那么详细也可以:
- @RequestMapping("/login")
- public String login(){
- System.out.println("do login()");
- int num = 10 / 0;
- return "login";
- }
- @ExceptionHandler(Exception.class)
- public ResultAjax doException(Exception e){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(-1);
- resultAjax.setMsg("异常:"+e.getMessage());
- resultAjax.setData(null);
- return resultAjax;
- }

统⼀数据返回格式的优点有很多,⽐如以下⼏个:
1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。
2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回 的。
3. 有利于项⽬统⼀数据的维护和修改。
4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。
对 ResultAjax 这个类进行改造,添加两种方法,一是成功之后返回的数据,二是失败之后返回的数据。
- 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 success(Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg("");
- resultAjax.setData(data);
- return resultAjax;
- }
-
- public static ResultAjax success(String msg, Object data){
- ResultAjax resultAjax = new ResultAjax();
- resultAjax.setCode(200);
- resultAjax.setMsg(msg);
- resultAjax.setData(data);
- return resultAjax;
- }
-
- 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.controller;
-
- import com.example.demo.common.ResultAjax;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @RequestMapping("/getuser")
- public ResultAjax getUser(){
- System.out.println("do getUser()");
- return ResultAjax.success("getuser");
- }
-
- @RequestMapping("/reg")
- public ResultAjax reg(){
- System.out.println("do reg()");
- return ResultAjax.success("reg");
- }
-
- @RequestMapping("/login")
- public ResultAjax login(){
- System.out.println("do login()");
- return ResultAjax.success("login");
- }
- }

如果就是有人不按要求返回 ResultAjax 这一格式呢?比如下面这样:
- @RequestMapping("getnum")
- public int getNum(){
- System.out.println("getNum()");
- return 1;
- }

这时就可以对返回的数据进行统一处理,这是强制执行的。
使用:
1. @ControllerAdvice
2. 实现 ResponseBodyAdvice 接口,并重写它的两个方法,supports 必须返回 true,beforeBodyWrite 方法中进行重新判断和重写操作。
- package com.example.demo.configuration;
-
- import com.example.demo.common.ResultAjax;
- 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;
-
- @ControllerAdvice
- public class ResponAdvice implements ResponseBodyAdvice {
- @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) {
- // 已经包装好的对象
- // body 是 ResultAjax 的格式
- if(body instanceof ResultAjax){
- return body;
- }
-
- // 对字符串进行判断和处理
- // 重新封装成 ResultAjax 的格式
- return ResultAjax.success(body);
-
- }
- }

- @RequestMapping("/getstr")
- public String getStr(){
- System.out.println("getStr()");
- return "whoooooo~~";
- }
如果返回的是 String,而不是 int 类型,会报错。在把 String 转化成 json格式的时候报错了。所以对于返回类型是 String 的话,需要单独处理。不使用 String 解析引擎,而是手动转成 json。

- package com.example.demo.configuration;
-
- import com.example.demo.common.ResultAjax;
- 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;
-
- @ControllerAdvice
- public class ResponAdvice implements ResponseBodyAdvice {
- // springboot 框架自动注入
- @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) {
- // 已经包装好的对象
- // body 是 ResultAjax 的格式
- if(body instanceof ResultAjax){
- return body;
- }
-
- // 对字符串进行判断和处理
- // 手动转换成 json 格式
- if(body instanceof String){
- ResultAjax resultAjax = ResultAjax.success(body);
- try {
- return objectMapper.writeValueAsString(resultAjax);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
-
- }
-
- return ResultAjax.success(body);
-
- }
- }

如果返回的是对象呢?
- @RequestMapping("/usermsg")
- public User usermsg(){
- User user = new User();
- user.setId(263);
- user.setName("柳飘飘");
- user.setPassword("96134");
- return user;
- }

