目录
一、拦截器
拦截器是spring框架提供的核心功能之一,它可以拦截用户请求,在指定方法前后执行预先设定的代码。
比如我们可以通过拦截器拦截前端发来的请求,判断session中是否有用户的登录信息,如果用户登录则放行,如果没有就进行拦截
拦截器的使用分为两步:
1、定义拦截器
2、注册配置拦截器
自定义拦截器:
需要实现HandlerInterceptor接口,并重新其方法。
java
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
}
}
我们一般主要关注preHandle方法,它是在目标方法执行前执行,这个方法返回true就放行,返回false就进行拦截。
我们可以在preHandle方法中进行用户登录的判断,用户没有登录就进行拦截,不执行目标方法,
比如:
java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//System.out.println("目标方法执行前......");
log.info("登录拦截器进行拦截校验...");
//判断用户是否登录
HttpSession session = request.getSession(false);//false表示request中没有session不会创建一个session
if(session == null){
response.setStatus(401);//http状态码,不是业务码
response.setContentType("text/html;charset=utf-8");//设置编码
response.getOutputStream().write("用户未登录".getBytes(StandardCharsets.UTF_8));
return false;
}
//true 放行,false 拦截
return true;
}
注册配置拦截器:
实现WebMvcConfigurer 接口,并重写addInterceptors方法
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所
有请求)
.excludePathPatterns("/user/login")//表示排除这个路径,允许放行
.excludePathPatterns("/**/*.js") //排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}
|------------|-------------|-------------------------------------------|
| 路径 | 含义 | 举例 |
| /* | 一级路径 | 能匹配/book, /user, 不能匹配/book/list |
| /** | 任意级路径 | 匹配/book, /book/list, /user/login, /user |
| /book/* | book下的一级路径 | 匹配/book/add, /book/list, 不能匹配/book/add/1 |
| /book/** | book下的任意级路径 | 匹配/book/add, /book/add/1, 不能匹配/user/login |
[拦截器路径设置]
拦截
拦截器执行流程
用户发送请求,在执行controller中的目标方法之前先调用拦截器中的preHandle方法,
preHandle方法返回true就放行 执行目标方法,preHandle方法返回false就拦截 不执行目标方法。
然后执行拦截器中的postHandle、afterCompletion方法。
二、统一数据返回格式
java
import com.example.demo.model.Result;
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 {
@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) {
return xxx;//根据需求统一返回数据
}
}
@ControllerAdvice是控制器通知类,有这个注解的类对象也是交由spring loc容器进行管理,它是派生于@Component注解的
通知类需要实现ResponseBodyAdvice接口,重写它的supports和beforeBodyWrite方法,并添加@ControllerAdvice注解。
supports方法返回true表示执行beforeBodyWrite方法,返回false表示不执行。
响应正文写入之前执行beforeBodyWrite方法,可以在这做统一处理,统一返回数据格式。beforeBodyWrite方法的参数body是指目标方法的返回值,returnType是目标方法的返回类型。
因此可以通过supports方法来确定哪些方法的返回值需要在beforeBodyWrite中进行统一处理,哪些不需要处理。
比如:
java
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//是否要执行下面的方法(beforeBodyWrite),可以做不同处理
//比如:
if(returnType.getDeclaringClass().equals(UserController.class)){
return false;
}
//false:不执行下面的方法
//true:执行下面的方法
return true;
}
这个代码就表示如果目标方法的返回值类型是UserController类就不执行beforeBodyWrite方法,其他的返回值类型都执行beforeBodyWrite方法。
java
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof Result<?>){
return body;
}
//返回数据body类型是String需要处理
if(body instanceof String){
try {
return objectMapper.writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
return Result.success(body);
}
这个重写的beforeBodyWrite方法中统一返回的类型都是Result,第一个if语句表示如果目标方法返回值类型就是Result,就直接return。
后面的就是对返回值进行统一处理:
当控制器方法返回String类型时 ,Spring 默认会将其视为视图名称或直接文本响应,而不是 JSON 格式。如果不手动转换为 JSON,客户端可能会收到纯文本而非结构化的 JSON 数据。
(简言之,beforeBodyWrite方法的参数body如果是String类型,那么必须手动将要return的内容转换成json格式)
下面这个代码就是我们定义的统一数据返回的格式:
java
package com.bit.book.model;
import com.bit.book.enums.CodeStatus;
import lombok.Data;
@Data
public class Result<T> {
//业务状态码
//自己定义
//200正常 -1 未登录 -2 异常 ...
private CodeStatus code;
private String errorMsg;
private T data;
public static <T> Result<T> success(T data){
Result result = new Result();
result.setCode(CodeStatus.SUCCESS);
result.setData(data);
return result;
}
public static <T> Result<T> fail(String errMsg){
Result result = new Result();
result.setCode(CodeStatus.ERROR);
result.setErrorMsg(errMsg);
return result;
}
public static Result unLogin(){
Result result = new Result();
result.setCode(CodeStatus.UNLOGIN);
result.setErrorMsg("用户未登录");
return result;
}
public static <T> Result<T> unLogin(T data){
Result result = new Result();
result.setCode(CodeStatus.UNLOGIN);
result.setErrorMsg("用户未登录");
result.setData(data);
return result;
}
}
统一数据返回格式有利于前端更好地接收、分析数据,有利于项目统一数据的维护、修改。
三、统一异常处理
统一异常处理是使用@ControllerAdvice和@ExceptionHandler来实现的。
@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,
这两个注解结合就表示当出现异常的时候执行某个方法。
类名、方法名、返回值可以自定义。
比如:
java
import com.bit.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
@ResponseBody//这个注释一定要加,表示返回的是数据,不加返回的就是页面 会出错
public class ResponseExceptionHandle {
@ExceptionHandler
public Result exceptionHandler(Exception e){
log.error("发生异常,e:",e);
return Result.fail("内部错误,e: " + e);
}
@ExceptionHandler
public Result exceptionHandler(NullPointerException e){
log.error("空指针异常, e:",e);
return Result.fail("空指针异常,e:"+ e);
}
@ExceptionHandler
public Result exceptionHandler(ArithmeticException e){
log.error("算术运算异常,e:",e);
return Result.fail("算术运算异常,e:"+ e);
}
@ExceptionHandler
public Result exceptionHandler(IndexOutOfBoundsException e){
log.error("数组越界异常,e:",e);
return Result.fail("数组越界异常,e:"+ e);
}
}
还有另一种写法:
java
package com.bit.book.advice;
import com.bit.book.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
@ResponseBody//这个注释一定要加,表示返回的是数据,不加返回的就是页面 会出错
public class ResponseExceptionHandle {
//第二种
@ExceptionHandler(Exception.class)
public Result exceptionHandler1(Exception e){
log.error("发生异常,e:",e);
return Result.fail("内部错误,e: " + e);
}
@ExceptionHandler(NullPointerException.class)
public Result exceptionHandler2(Exception e){
log.error("空指针异常, e:",e);
return Result.fail("空指针异常,e:"+ e);
}
@ExceptionHandler(ArithmeticException.class)
public Result exceptionHandler3(Exception e){
log.error("算术运算异常,e:",e);
return Result.fail("算术运算异常,e:"+ e);
}
@ExceptionHandler(IndexOutOfBoundsException.class)
public Result exceptionHandler4(Exception e){
log.error("数组越界异常,e:",e);
return Result.fail("数组越界异常,e:"+ e);
}
}
当出现对应的异常时,前端就会收到我们处理的异常数据,这里的Result.fail()就是前面Result类中的静态方法
public static <T> Result<T> fail(String errMsg){
Result result = new Result();
result.setCode(CodeStatus.ERROR);
result.setErrorMsg(errMsg);
return result;
}
比如我们写一个TestController,来测试一下算术运算异常:
java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public int t1(){
return 10/0;
}
}
运行程序,在浏览器上访问接口(如果配置了拦截器,记得放行这个路径):
{
"code": "ERROR",
"errorMsg": "算术运算异常,e:java.lang.ArithmeticException: / by zero",
"data": null
}