目录
一、Spring 拦截器
1.1、背景
1.2、实现步骤
1.3、拦截原理
二、 统一url前缀路径
2.1、方法一:在系统的配置文件中设置
2.2、方法二:在 application.properies 中配置
三、统一异常处理
四、统一返回数据返回格式处理
4.1、背景
4.2、具体实现
4.3、实际项目中的写法
在原生的 Spring AOP 中实现统一的拦截的难点在于:1.定义拦截规则(表达式)很难,2.在切面类中拿到 HttpSession 比较难;如何解决这两个难点呢?使用拦截器!
实现一个普通的拦截器关键在于以下两步:
具体的,首先步骤一,例如要实现一个用户登录判断,就需要创建一个类,这里起名叫LoginInterceptor 类,实现 HandlerInterceptor 接口,重写 preHeadler 方法(此方法返回的是以个 boolean 类型,如果为 true 表示验证成功,可以继续执行后面的流程,若是 false 表示验证失败,后面的流程就不执行了),通过是否可以获取到 Session 信息判断用户是否已经登陆,来返回 true 或 false。
a)实现 HandlerInterceptor 接口,重写 preHeadler 方法,如下代码:
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 表示验证成功,否则验证失败,后面的流程不能执行了
* @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;
}
//可以调整登录页面,或者 返回一个 401/403 没有权限
response.sendRedirect("/login.html");
return false;
}
}
b)将拦截器添加到配置文件中,设置拦截规则
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
.excludePathPatterns("/user/reg")
.excludePathPatterns("/**/*.html"); //不拦截所有的页面
}
}
注意:
addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。 excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)
具体的,重写 WebMvcConfigurer 接口下的 configurePathMatch 方法,例如修改所有请求url添加前缀 /zhangsan ,如下代码:
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;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/zhangsan", c -> true);
}
}
例如修改所有请求url添加前缀 /zhangsan,如下代码:
server.servlet.context-path=/zhangsan
统一异常处理是通过如下两个注解结合实现的:
两个结合表示出现异常的时候执行某个通知方法,具体的步骤如下:
如下代码:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@ControllerAdvice
@ResponseBody
public class MyExHandler {
/**
* 拦截所有的空指针异常,进行统一的数据返回
* @param e
* @return
*/
@ExceptionHandler(Exception.class) //这里也可以根据实际情况,填写不同的异常
public HashMap nullException(NullPointerException e) {
HashMap reslut = new HashMap<>();
reslut.put("code", "-1");
reslut.put("msg", "空指针异常" + e.getMessage());
reslut.put("data", null);
//这里返回 HashMap ,就相当于项前端返回了一个 JSON 格式的数据
return reslut;
}
}
为什么要统一数据返回格式处理?例如以下几个原因:
统一数据格式返回的实现需要以下两个步骤:
Ps:
1、 supports 方法不用编写业务逻辑,而是像一个控制器一样,返回 true 则执行 beforeBodyWrite 方法,反之则不执行。
2、beforeBodyWrite 方法就是用来实现统一对象的。
具体的如下:
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;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
//将 java 对象转化成 JSON 格式
@Autowired
private ObjectMapper objectMapper;
/**
* 此方法返回 true 则执行下面的 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) {
HashMap result = new HashMap<>();
result.put("code", 200);
result.put("msg", "");
result.put("data",body);
// 这里需要进行特殊处理,因为 String 在转换的时候报错
if(body instanceof String) {
try {
return objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return result;
}
}
代码中为什么要进行特殊处理?(最易出错!)
在 java 程序中, String 是一个最特殊的类型(既不是基础类型也不是对象),并且在重写方法时也很特殊,除了 String 其他的都是用一个格式化工具,而 String 用的是自己的一套格式化工具,因此在转换成 HashMap 的时候还没有被加载好,而其他的转换器已经加载好了,最后就会引发如下异常:
因此就要判断 body 是否为 String ,若为 String 类型,就要进行特殊处理,使用 JSON 的writeValueAsString 方法将 Java 对象转换成 JSON 格式再返回。
实际的项目中,统一数据格式的返回我们因该这样写:
具体的如下代码:
a)AjaxResult 类
import lombok.Data;
import java.io.Serializable;
/**
* JSON统一数据格式返回
*/
@Data
public class AjaxResult implements Serializable {
//状态码
private Integer code;
//状态码描述信息
private String msg;
//返回的数据
private Object data;
/**
* 操作成功返回的结果
*/
public static AjaxResult success(Object data) {
AjaxResult result = new AjaxResult();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(int code, Object data) {
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(int code, String msg, Object data) {
AjaxResult result = new AjaxResult();
result.setCode(200);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 返回失败结果
*/
public static AjaxResult fail(int code, String msg) {
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(null);
return result;
}
public static AjaxResult fail(int code, String msg, Object data) {
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
b)ResponseAdvice 类
import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
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 ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
/**
* 开关
* @param returnType
* @param converterType
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
* 对数据格式进行校验和封装
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof AjaxResult) {
return body;
}
//String比较特殊,要单独处理
if(body instanceof String) {
return objectMapper.writeValueAsString(AjaxResult.success(body));
}
return AjaxResult.success(body);
}
}