文章目录
- 一、拦截器
- 1.1 拦截器入门
- 1.1.1 拦截器介绍
- 1.1.2 自定义拦截器
- 1.1.3 注册配置拦截器
- 1.2 拦截器详解
- 1.2.1 拦截路径
- 1.2.2 拦截器执行流程
- 二、统一数据返回格式
- 2.1 实现方式
- 2.2 可能出现的问题
- 三、统一异常处理
一、拦截器
1.1 拦截器入门
1.1.1 拦截器介绍
拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码。
在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息.。如果有就可以放行,如果没有就进行拦截。
拦截器的使用步骤分为两步:
- 定义拦截器
- 注册配置拦截器
1.1.2 自定义拦截器
实现HandlerInterceptor接口,并重写其所有方法
@Slf4j@ComponentpublicclassLoginInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{log.info("LoginInterceptor 目标方法执行前执行..");returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{log.info("LoginInterceptor 目标方法执行后执行");}}- preHandle()方法:目标方法执行前执行。返回true:继续执行后续操作;返回false:中断后续操作。
- postHandle()方法:目标方法执行后执行。
- afterCompletion()方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,可以忽略)。
1.1.3 注册配置拦截器
实现WebMvcConfigurer接口,并重写addInterceptors方法
@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{//自定义的拦截器对象@AutowiredprivateLoginInterceptorloginInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){//注册自定义拦截器对象registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)}}启动服务,试试访问任意请求,观察后端日志。
可以看到preHandle 方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行 postHandle。
1.2 拦截器详解
1.2.1 拦截路径
拦截路径是指定义的这个拦截器,对哪些请求生效。
在注册配置拦截器的时候,通过addPathPatterns() 方法指定要拦截哪些请求。也可以通过 excludePathPatterns() 指定不拦截哪些请求。
上述代码中,配置的是 /** ,表示拦截所有的请求。
比如用户登录校验,可以对除了登录之外所有的路径生效。
@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{//自定义的拦截器对象@AutowiredprivateLoginInterceptorloginInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){//注册自定义拦截器对象registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login");//设置拦截器拦截的请求路径 (/** 表示拦截所有请求)}}在拦截器中除了可以设置/** 拦截所有资源外,还有一些常见拦截路径设置:
| 拦截路径 | 含义 |
|---|---|
| /* | 一级路径 |
| /** | 任意级路径 |
| /book/* | /book下的一级路径 |
| /book/** | /book下的任意级路径 |
以上拦截规则可以拦截项目中的使用 URL,包括静态文件(图片文件, JS 和 CSS 等文件)。
1.2.2 拦截器执行流程
正常的调用顺序:
后端程序 -> Controller 控制器层 -> 服务层 Service -> 数据持久层 Mapper -> 调用数据库
有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下:
后端程序 -> 拦截器 -> 控制器层 Controller -> 服务层 Service -> 数据持久层 Mapper -> 数据库
- 添加拦截器后, 执行Controller的方法之前,请求会先被拦截器拦截住。执行preHandle() 方法,这个方法需要返回一个布尔类型的值。如果返回true, 就表示放行本次操作,继续访问controller中的方法。如果返回false,则不会放行(controller中的方法也不会执行)。
- controller当中的方法执行完毕后,再回过来执行postHandle() 这个方法,执行完毕之后,最终给浏览器响应数据。
二、统一数据返回格式
2.1 实现方式
统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现
@ControllerAdvice 表示控制器通知类
添加类ResponseAdvice ,实现ResponseBodyAdvice 接口,并在类上添加 @ControllerAdvice 注解
@ControllerAdvicepublicclassResponseAdviceimplementsResponseBodyAdvice{@Overridepublicbooleansupports(MethodParameterreturnType,ClassconverterType){returntrue;}@OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){returnResult.success(body);}}supports方法:判断是否要执行beforeBodyWrite方法。true为执行,false不执行。通过该方法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理。
beforeBodyWrite方法:对response方法进行具体操作处理。
2.2 可能出现的问题
问题现象:
在测试修改图书的接口: http://127.0.0.1:8080/book/updateBook
结果显示,发生内部错误。查看日志,日志报错:java.lang.ClassCastException: com.example.demo.model.Result cannot be cast to java.lang.String
多测试几种不同的返回结果,发现只有返回结果为String类型时才有这种错误发生。
解决方案:将返回数据转换为String 类型返回。
@SneakyThrows@OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){//返回结果更加灵活if(bodyinstanceofResult){returnbody;}//如果返回结果为String类型, 使用SpringBoot内置提供的Jackson来实现信息的序列化if(bodyinstanceofString){returnmapper.writeValueAsString(Result.success(body));}returnResult.success(body);}统一数据返回格式优点:
- 方便前端程序员更好的接收和解析后端数据接口返回的数据
- 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了, 因为所有接口都是这样返回的.
- 有利于项目统一数据的维护和修改.
- 有利于后端技术部门的统一规范的标准制定, 不会出现稀奇古怪的返回内容.
三、统一异常处理
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
具体代码如下:
@ControllerAdvice@ResponseBodypublicclassErrorAdvice{@ExceptionHandlerpublicObjecthandler(Exceptione){returnResult.fail(e.getMessage());}}类名,方法名和返回值可以自定义,重要的是注解。
接口返回为数据时,需要加@ResponseBody 注解。
以上代码表示,如果代码出现Exception异常(包括Exception的子类),就返回一个 Result的对象,Result 对象的设置参考 Result.fail(e.getMessage())。
publicstaticResultfail(Stringmsg){Resultresult=newResult();result.setStatus(ResultStatus.FAIL);result.setErrorMessage(msg);result.setData("");returnresult;}可以针对不同的异常,返回不同的结果。
@Slf4j@ResponseBody@ControllerAdvicepublicclassExceptionAdvice{@ExceptionHandlerprivateResulthandler(Exceptione){log.error("发生异常 e",e);returnResult.fail("内部错误请联系管理员");}@ExceptionHandlerprivateResulthandler(NullPointerExceptione){log.error("发生异常 e",e);returnResult.fail(-2,"发生空指针异常");}@ExceptionHandlerprivateResulthandler(ArithmeticExceptione){log.error("发生异常 e",e);returnResult.fail(-3,"发生算数异常");}@ExceptionHandlerprivateResulthandler(IndexOutOfBoundsExceptione){log.error("发生异常 e",e);returnResult.fail(-4,"发生数组越界异常");}}模拟制造异常:
@RequestMapping("/test")@RestControllerpublicclassTestController{@RequestMapping("/t1")publicStringt1(){inta=10/0;return"t1";}@RequestMapping("/t2")publicStringt2(){Stringa=null;System.out.println(a.length());return"t2";}}当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配。
/test/t1 抛出ArithmeticException, 运行结果如下:
/test/t2 抛出NullPointerException, 运行结果如下: