赞
踩
什么是拦截器?
拦截器是Spring框架提供的核心功能之一, 主要用来拦截用户的请求, 在指定方法前后, 根据业务需要执行预先设定的代码.
也就是说, 允许开发人员提前定义一些逻辑, 在用户的请求响应前执行. 也可以在用户请求前阻止其执行. 就比如我们要通过一个url访问一个页面, 但是这个页面只有登录后才能访问, 这时就需要拦截, 比如你们可以试着登入这个url: CSDN.
然后就会被传送到这:
在拦截器当中, 开发人员可以在应用程序中做一些通用性的操作, 比如通过拦截前端发来的请求, 判断Session中是否有登陆用户的信息. 如果有就可以放行, 如果没有就进行拦截.
比如抗日战争中, 想要在沦陷区进城, 就要向"太君"出示良民证(session),
出示了就可以放行, 放行之后还可能会有其它太君来对你搜身;
没有出示"太君"就会大大咧咧地骂"八嘎!你滴, 滚远地干活".
下面我们先来学习一下守城太君, 哎不对, 拦截器的基本使用.
拦截器的使用步骤分为两步:
1.定义拦截器(这个拦截器是用来干什么的, 比如守城太君不能让没带良民证的人进)
2.注册配置拦截器(拦截器要怎么做, 守城太君要查良民证)
自定义拦截器:实现HandlerInterceptor接口, 并重写其所有方法.
- @Slf4j
- @Component
- public class LoginInterceptorTest 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: 中断后续操作.
postHandle()方法: 目标方法执行后执行.
afterCompletion()方法: 视图渲染完毕后执行, 最后执行(后端开发现在几乎不涉及视图, 暂不了解).
注册配置拦截器: 实现WebMvcConfigurer接口, 并重写addInterceptors方法.
- @Configuration
- public class WebConfigTest implements WebMvcConfigurer {
- //自定义的拦截器对象
- @Autowired
- private LoginInterceptorTest loginInterceptorTest;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //注册自定义的拦截器对象
- registry.addInterceptor(loginInterceptorTest)
- .addPathPatterns("/**"); //设置拦截器拦截的请求路径(/**表示拦截所有路径)
- }
- }
启动服务, 访问任意请求, 观察后端日志:
可以看到preHandle方法执行之后就放行了, 开始执行目标方法, 目标方法执行完成之后执行postHandle和afterCompletion方法.
我们把拦截器中的preHandle方法的返回值改为false, 再观察运行结果:
可以看到, 拦截器拦截了请求, 没有进行响应.
拦截器的入门程序完成之后, 接下来我们来介绍拦截器的使用细节. 拦截器的使用细节我们主要介绍两个部分:
1.拦截器的拦截路径配置
2.拦截器的实现原理
拦截路径是指我们定义的这个拦截器, 对哪些请求生效.
我们在注册配置拦截器的时候, 通过addPathPatterns()方法指定要拦截哪些请求. 也可以通过excludePathPatterns()指定不拦截哪些请求.
在拦截器中除了/**表示拦截所有资源之外, 还有一些常见的拦截路径设置:
拦截路径 | 含义 | 举例 |
/* | 一级路径 | 能匹配/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 |
以上拦截规则可以拦截此项目中使用的URL, 包括静态文件.
正常的调用顺序:
有了拦截器之后就是这样的流程:
1.添加拦截器后, 执行Controller方法之前, 请求会先被拦截器拦截住. 执行preHandle()方法, 这个方法需要返回一个布尔类型的值. 如果返回true, 就表示放行本次操作, 继续访问controller中的方法. 如果返回false, 就不会放行. (controller中的方法也不会执行).
2.controller当中的方法执行完毕之后, 再回来执行postHandle()这个方法以及afterCompletion()方法, 执行完毕后, 最终给浏览器响应数据.
适配器模式, 也叫包装器模式. 将一个类的接口, 转换成客户希望的另一个接口, 适配器让原本接口不兼容的类可以合作无间.
简单来说就是目标类不能直接使用, 通过一个新类包装一下, 适配调用方使用. 把两个不兼容的接口通过一定的方式使之兼容.
比如下面两个接口, 本身是不兼容的(参数类型不一样, 参数个数不一样等等).
可以通过适配器, 使之兼容:
比如博主在大一时, 天气热了, 需要空调, 最后我们合资买了一个, 但是最后才知道学校规定, 必须要在指定平台租, 否则就不给办空调插口的电卡(非常sb的二求规定), 由于其它插口功率不高,不能直接给空调用, 我最后想了一个绝妙点子, 买了个功率转换头, 这里就可以通过其它非空调插口的插口直接使用. 这里这个功率转换头就是适配器.
Target: 目标接口(可以是抽象类或接口), 用户希望直接用的接口.(非空调插口的其它插口)
Adapee: 适配者, 但是与Target不兼容(空调)
Adapter: 适配器类, 通过继承或者引用适配者的对象, 把适配者转为目标接口.(功率转换头)
client: 需要使用适配器的对象.(劳累了一天非常热的灰灰)
一般来说, 适配器模式可以看作一种"补偿模式", 用于补救设计上的缺陷. 这种模式的应用算是"吾乃治具", 如果设计初期, 我们就能协调规避接口不兼容的问题, 就不需要使用适配器模式了.
所以适配器模式更多的应用场景主要是对正在运行的代码进行改造, 并且可以复用原有代码实现新功能.
统一数据返回格式是指在软件开发中约定一种统一的数据结构格式, 用于向客户端或者调用方返回数据. 这种格式通常包含固定的字段, 以便客户端能够统一地解析和处理返回的数据, 从而提高开发效率和降低沟通成本.
统一的数据返回格式使用@ControllerAdvice和ResponseBodyAdvice的方式实现@ControllerAdvice表示控制器通知类.
添加类ResponseAdvice, 实现ResponseBodyAdvice接口, 并在类上添加@ControllerAdvice注解.
- @ControllerAdvice
- public class ResponseAdviceTest implements ResponseBodyAdvice {
- @Override
- public boolean supports(MethodParameter returnType, Class converterType) {
- return true;
- }
-
- @Override
- public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectContentType,
- Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
- return Result.success(body);
- }
- }
supports方法: 判断是否要执行beforeBodyWrite方法. true为执行, false不执行. 通过该方法可以选择哪些类或哪些方法的response要进行处理, 其它的不进行处理.
beforeBodyWrite方法: 对response方法进行具体操作处理.
这里就直接开门见山: 首先提供以下代码:
- @RequestMapping("/test")
- @RestController
- public class TestController {
- @RequestMapping("/t1")
- public String t1() {
- return "t1";
- }
-
- @RequestMapping("/t2")
- public boolean t2() {
- return true;
- }
-
- @RequestMapping("/t3")
- public Integer t3() {
- return 200;
- }
- }
将以上代码的每一个入口都走一遍(先走t2t3,最后t1), 我们发现:
当返回类型为String时, 这里报错了.
结论: 当返回类型为String时, 就会发生错误.
解决方案:
- @ControllerAdvice //这时一个用于定义全局控制器通知的注解
- public class ResponseAdvice implements ResponseBodyAdvice {
- @Autowired
- //用于java对象和json格式之间的转换
- private ObjectMapper objectMapper;
-
- @Override
- //supports方法: 判断是否要执行beforeBodyWrite方法, true为执行, false为不执行.
- //通过该方法可以选择哪些类或哪些方法的response要进行处理, 其它的不处理
- 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) {
- //检查是否是Result类型, 如果是的话,则直接返回, 因为已经是统一的格式了.
- if(body instanceof Result) {
- return body;
- }
- //如果返回结果为String类型, 使用内部的SpringBoot内置提供的Jackson来实现信息的序列化
- if(body instanceof String) {
- return objectMapper.writeValueAsString(Result.success(body));
- }
- //如果既不是Result类型又不是String类型, 就将其包装成Result.success(body)的形式返回
- return Result.success(body);
- }
- }
再次运行返回为String的接口, 正常.
1.方便前端程序员更好地接受和解析后端数据接口返回的数据
2.降低前端程序员和后端程序员沟通成本, 按照某个格式实现即可, 因为所有的接口都是这样返回的
3.有利于项目统一数据的维护和修改.
4.有利于后端技术部门的统一规范的标准制定, 不会出现稀奇古怪的返回内容.
统一异常处理使用的是@ControllerAdvice + @ExceptionHandler来实现的, @ControllerAdvice表示控制器通知类, @ExeptionHandler是异常处理器, 两个结合表示当出现异常时执行某个通知, 也就是执行某个方法事件.
具体执行如下:
- @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()).
- public static Result fail(String msg) {
- Result result = new Result<>();
- result.setErrMsg(msg);
- result.setCode(ResultStatus.FAIL);
- return result;
- }
我们可以针对不同的异常, 返回不同的结果:
- @Slf4j
- @ResponseBody
- //控制器通知类
- @ControllerAdvice
- //统一处理异常
- public class ExceptionAdvice {
-
- @ExceptionHandler
- public Result handerException(Exception e) {
- log.error("发生异常, e:()", e);
- return Result.fail("内部错误");
- }
-
- @ExceptionHandler
- public Result handlerException(NullPointerException e) {
- log.error("发生异常: e", e);
- return Result.fail("发生空指针异常");
- }
-
- @ExceptionHandler
- public Result handlerException(ArithmeticException e) {
- log.error("发生异常, e:", e);
- return Result.fail("发生算术异常");
- }
- }
模拟制造异常:
- @RequestMapping("/test")
- @RestController
- public class TestController {
- @RequestMapping("/t1")
- public String t1() {
- return "t1";
- }
-
- @RequestMapping("/t2")
- public boolean t2() {
- int a = 10 / 0; // 抛出ArithmeticException
- return true;
- }
-
- @RequestMapping("/t3")
- public Integer t3() {
- String a = null;
- System.out.println(a.length()); // 抛出NullPointerException
- return 200;
- }
- }
运行t2:
运行t3:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。