一、拦截器
1.1 什么是拦截器?
拦截器是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方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行
postHandle和afterCompletion方法.
将拦截器中preHandle方法的返回值改为false,再观察运行结果
运行程序 观察日志,拦截器拦截了请求,没有进行响应.
1.2 拦截路径
我们在注册配置拦截器的时候,通过addPathPatterns()方法指定要拦截哪些请求.也可以通过
excludePathPatterns()指定不拦截哪些请求.
如用户登录校验,我们希望可以对除了登录之外所有的路径生效.
java">@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
// 自定义拦截器对象
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
//拦截所有请求 (/** 拦截所有请求)
.addPathPatterns("/**")
.excludePathPatterns("/user/login"); //(/user/login不拦截)
}
}
在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
/* | 一级路径 | 能匹配/user,/book,/login,不能匹配/user/login |
/** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
/book/* | /book下的一级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,book |
/book/** | /book下的任意级路径 | 能配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |
1.3 拦截器执行流程
普通调用顺序:
添加拦截器后的调用顺序:
1添加拦截器后,执行Controller的方法之前,请求会先被拦截器拦截住.执行preHandle()方法,
这个方法需要返回一个布尔类型的值.如果返回true,就表示放行本次操作,继续访问controller中的方法.如果返回false,则不会放行(controller中的方法也不会执行).
2.controller当中的方法执行完毕后,再回过来执行postHandle()和afterCompletion()方法,执行完毕之后,最终给浏览器响应数据.
1.4 定义拦截器
从session中获取用户信息,如果session中不存在,则返回false,并设置http状态码为401,否则返回true.
java">@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession();
if (session != null && session.getAttribute(Constants.SESSION_USER_KEY) != null) {
return true;
}
response.setStatus(401);
return false;
}
}
http状态码401:Unauthorized 未经过认证.指示⾝份验证是必需的,没有提供⾝份验证或⾝份验证失败.如果请求已经包 含授权凭据,那么401状态码表示不接受这些凭据。
1.5 注册配置拦截器
java">@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
private List<String> excludePaths = Arrays.asList(
"/user/login",
"/**/*.js",
"/**/*.css",
"/**/*.png",
"/**/*.html"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")// 设置拦截器拦截的请求路径 (/**表⽰拦截所有请求)
.excludePathPatterns(excludePaths);//设置拦截器排除拦截的路径
}
}
java">@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getBookListByPage")
public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
log.info("查询翻页信息, pageRequest:{}",pageRequest);
//校验成功
if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
return Result.fail("参数校验失败");
}
PageResult<BookInfo> bookInfoPageResult = null;
try {
bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
return Result.success(bookInfoPageResult);
}catch (Exception e){
log.error("查询翻页信息错误,e:{}",e);
return Result.fail(e.getMessage());
}
}
}
运行程序,通过Postman进行测试
1、未登录的情况下进行图书列表的查看
http://127.0.0.1:8080/book/getListByPage
2、登录的情况下再次进行图书列表的查看
java">@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getBookListByPage")
public Result getBookListByPage(PageRequest pageRequest, HttpSession session){
log.info("查询翻页信息, pageRequest:{}",pageRequest);
//用户登录校验
UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
if (userInfo==null|| userInfo.getId()<=0 || "".equals(userInfo.getUserName())){
//用户未登录
return Result.unlogin();
}
//校验成功
if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){
return Result.fail("参数校验失败");
}
PageResult<BookInfo> bookInfoPageResult = null;
try {
bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);
return Result.success(bookInfoPageResult);
}catch (Exception e){
log.error("查询翻页信息错误,e:{}",e);
return Result.fail(e.getMessage());
}
}
}
启动程序,在Postman中进行登录
http://127.0.0.1:8080/user/login?name=admin&password=admin
Postman返回正确的数据
二、统一数据返回格式
强制登录案例中,我们共做了两部分工作
1.通过Session来判断用户是否登录
2.对后端返回数据进行封装,告知前端处理的结果
java">@Data
public class Result<T> {
private int status;
private String errorMessage;
private T data;
}
java">@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest) {
log.info("获取图书列表, pageRequest:{}", pageRequest);
//⽤⼾登录, 返回图书列表
PageResult<BookInfo> pageResult =
bookService.getBookListByPage(pageRequest);
log.info("获取图书列表222, pageRequest:{}", pageResult);
return Result.success(pageResult);
}
2.1 快速入门
统一的数据返回格式使用@ControllerAdvice和ResponseBodyAdvice的方式实现
@ControllerAdvice表示控制器通知类
添加类 ResponseAdvice,实现 ResponseBodyAdvice接口,并在类上添加
@ControllerAdvice 注解
java">@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 Result.success(body);
}
}
supports方法:判断是否要执行beforeBodyWrite方法.true为执行,false不执行.通过该方法可以
选择哪些类或哪些方法的response要进行处理,其他的不进行处理.






java">@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result<?>){
return body;
}
if (body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
再次运行返回正常结果:
2.2 使用统一数据返回格式的优点
三 、统一异常处理
统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的,
@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,两个结合表
示当出现异常的时候执行某个通知,也就是执行某个方法事件
具体代码如下:
java">@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
}
接口返回为数据时,需要加@ResponseBody注解
以上代码表示,如果代码出现Exception异常(包括Exception的子类),就返回一个Result的对象,Result对象的设置参考Result.fail(e.getMessage())
java">public static Result fail(String msg) {
Result result = new Result();
result.setStatus(ResultStatus.FAIL);
result.setErrorMessage(msg);
result.setData("");
return result;
}
java">@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("发生异常, e: ", e);
return Result.fail();
}
// @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public Object handler(ArrayIndexOutOfBoundsException e){
log.error("发生异常, e:{} ", e.getMessage());
return Result.fail("发生数组越界异常");
}
@ExceptionHandler
public Object handler(NullPointerException e){
log.error("发生异常, e: ", e);
return Result.fail("发生NullPointerException 异常");
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler
public Object handler(NoResourceFoundException e){
log.error("发生异常, e: {}, path:{}", e.getDetailMessageCode(), e.getResourcePath());
return Result.fail("发生NoResourceFoundException 异常");
}
}
java">@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public String t1(){
int a = 10/0;
log.info("执行T1方法");
return "t1";
}
@RequestMapping("/t2")
public int t2(){
String aa = "abcdef";
log.info("执行T2方法");
return aa.length();
}
@RequestMapping("/t3")
public int t3(){
int[] a = {1,2,3};
int val = a[4];
log.info("执行T3方法");
return val;
}
}
访问:http://127.0.0.1:8080/test/t1
异常
访问:http://127.0.0.1:8080/test/t2
正常返回
访问:http://127.0.0.1:8080/test/t3
异常
