阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

使用Interceptor

269次阅读
没有评论

共计 5244 个字符,预计需要花费 14 分钟才能阅读完成。

在 Web 程序中,注意到使用 Filter 的时候,Filter 由 Servlet 容器管理,它在 Spring MVC 的 Web 应用程序中作用范围如下:

         │   ▲
         ▼   │
       ┌───────┐
       │Filter1│
       └───────┘
         │   ▲
         ▼   │
       ┌───────┐
┌ ─ ─ ─│Filter2│─ ─ ─ ─ ─ ─ ─ ─ ┐
       └───────┘
│        │   ▲                  │
         ▼   │
│ ┌─────────────────┐           │
  │DispatcherServlet│◀───┐
│ └─────────────────┘    │      │
   │              ┌────────────┐
│  │              │ModelAndView││
   │              └────────────┘
│  │                     ▲      │
   │    ┌───────────┐    │
│  ├───▶│Controller1│────┤      │
   │    └───────────┘    │
│  │                     │      │
   │    ┌───────────┐    │
│  └───▶│Controller2│────┘      │
        └───────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

上图虚线框就是 Filter2 的拦截范围,Filter 组件实际上并不知道后续内部处理是通过 Spring MVC 提供的 DispatcherServlet 还是其他 Servlet 组件,因为 Filter 是 Servlet 规范定义的标准组件,它可以应用在任何基于 Servlet 的程序中。

如果只基于 Spring MVC 开发应用程序,还可以使用 Spring MVC 提供的一种功能类似 Filter 的拦截器:Interceptor。和 Filter 相比,Interceptor 拦截范围不是后续整个处理流程,而是仅针对 Controller 拦截:

       │   ▲
       ▼   │
     ┌───────┐
     │Filter1│
     └───────┘
       │   ▲
       ▼   │
     ┌───────┐
     │Filter2│
     └───────┘
       │   ▲
       ▼   │
┌─────────────────┐
│DispatcherServlet│◀───┐
└─────────────────┘    │
 │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ┐
 │                     │
 │ │            ┌────────────┐ │
 │              │   Render   │
 │ │            └────────────┘ │
 │                     ▲
 │ │                   │       │
 │              ┌────────────┐
 │ │            │ModelAndView│ │
 │              └────────────┘
 │ │                   ▲       │
 │    ┌───────────┐    │
 ├─┼─▶│Controller1│────┤       │
 │    └───────────┘    │
 │ │                   │       │
 │    ┌───────────┐    │
 └─┼─▶│Controller2│────┘       │
      └───────────┘
   └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

上图虚线框就是 Interceptor 的拦截范围,注意到 Controller 的处理方法一般都类似这样:

@Controller
public class Controller1 {@GetMapping("/path/to/hello")
    ModelAndView hello() {...}
}

所以,Interceptor 的拦截范围其实就是 Controller 方法,它实际上就相当于基于 AOP 的方法拦截。因为 Interceptor 只拦截 Controller 方法,所以要注意,返回 ModelAndView 并渲染后,后续处理就脱离了 Interceptor 的拦截范围。

使用 Interceptor 的好处是 Interceptor 本身是 Spring 管理的 Bean,因此注入任意 Bean 都非常简单。此外,可以应用多个 Interceptor,并通过简单的 @Order 指定顺序。我们先写一个LoggerInterceptor

@Order(1)
@Component
public class LoggerInterceptor implements HandlerInterceptor {final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {logger.info("preHandle {}...", request.getRequestURI());
        if (request.getParameter("debug") != null) {PrintWriter pw = response.getWriter();
            pw.write("<p>DEBUG MODE</p>");
            pw.flush();
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {logger.info("postHandle {}.", request.getRequestURI());
        if (modelAndView != null) {modelAndView.addObject("__time__", LocalDateTime.now());
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {logger.info("afterCompletion {}: exception = {}", request.getRequestURI(), ex);
    }
}

一个 Interceptor 必须实现 HandlerInterceptor 接口,可以选择实现 preHandle()postHandle()afterCompletion()方法。preHandle()是 Controller 方法调用前执行,postHandle()是 Controller 方法正常返回后执行,而 afterCompletion() 无论 Controller 方法是否抛异常都会执行,参数 ex 就是 Controller 方法抛出的异常(未抛出异常是null)。

preHandle() 中,也可以直接处理响应,然后返回 false 表示无需调用 Controller 方法继续处理了,通常在认证或者安全检查失败时直接返回错误响应。在 postHandle() 中,因为捕获了 Controller 方法返回的 ModelAndView,所以可以继续往ModelAndView 里添加一些通用数据,很多页面需要的全局数据如 Copyright 信息等都可以放到这里,无需在每个 Controller 方法中重复添加。

我们再继续添加一个 AuthInterceptor,用于替代上一节使用AuthFilter 进行 Basic 认证的功能:

@Order(2)
@Component
public class AuthInterceptor implements HandlerInterceptor {final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {logger.info("pre authenticate {}...", request.getRequestURI());
        try {authenticateByHeader(request);
        } catch (RuntimeException e) {logger.warn("login by authorization header failed.", e);
        }
        return true;
    }

    private void authenticateByHeader(HttpServletRequest req) {String authHeader = req.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Basic")) {logger.info("try authenticate by authorization header...");
            String up = new String(Base64.getDecoder().decode(authHeader.substring(6)), StandardCharsets.UTF_8);
            int pos = up.indexOf(':');
            if (pos > 0) {String email = URLDecoder.decode(up.substring(0, pos), StandardCharsets.UTF_8);
                String password = URLDecoder.decode(up.substring(pos + 1), StandardCharsets.UTF_8);
                User user = userService.signin(email, password);
                req.getSession().setAttribute(UserController.KEY_USER, user);
                logger.info("user {} login by authorization header ok.", email);
            }
        }
    }
}

这个 AuthInterceptor 是由 Spring 容器直接管理的,因此注入 UserService 非常方便。

最后,要让拦截器生效,我们在 WebMvcConfigurer 中注册所有的 Interceptor:

@Bean
WebMvcConfigurer createWebMvcConfigurer(@Autowired HandlerInterceptor[] interceptors) {return new WebMvcConfigurer() {public void addInterceptors(InterceptorRegistry registry) {for (var interceptor : interceptors) {registry.addInterceptor(interceptor);
            }
        }
        ...
    };
}

注意

如果拦截器没有生效,请检查是否忘了在 WebMvcConfigurer 中注册。

处理异常

在 Controller 中,Spring MVC 还允许定义基于 @ExceptionHandler 注解的异常处理方法。我们来看具体的示例代码:

@Controller
public class UserController {@ExceptionHandler(RuntimeException.class)
    public ModelAndView handleUnknowException(Exception ex) {return new ModelAndView("500.html", Map.of("error", ex.getClass().getSimpleName(), "message", ex.getMessage()));
    }
    ...
}

异常处理方法没有固定的方法签名,可以传入 ExceptionHttpServletRequest 等,返回值可以是 void,也可以是ModelAndView,上述代码通过@ExceptionHandler(RuntimeException.class) 表示当发生 RuntimeException 的时候,就自动调用此方法处理。

注意到我们返回了一个新的ModelAndView,这样在应用程序内部如果发生了预料之外的异常,可以给用户显示一个出错页面,而不是简单的 500 Internal Server Error 或 404 Not Found。例如 B 站的错误页:

使用 Interceptor

可以编写多个错误处理方法,每个方法针对特定的异常。例如,处理 LoginException 使得页面可以自动跳转到登录页。

使用 ExceptionHandler 时,要注意它仅作用于当前的 Controller,即 ControllerA 中定义的一个 ExceptionHandler 方法对 ControllerB 不起作用。如果我们有很多 Controller,每个 Controller 都需要处理一些通用的异常,例如LoginException,思考一下应该怎么避免重复代码?

练习

使用 Interceptor。

下载练习

小结

Spring MVC 提供了 Interceptor 组件来拦截 Controller 方法,使用时要注意 Interceptor 的作用范围。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计5244字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19348
评论数
4
阅读量
7821975
文章搜索
热门文章
开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南 大家好,我是星哥。之前介绍了腾讯云的 Code...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板 1Panel:小白轻松构建 Web 服务与面板加固指南 哈喽,我是星哥,经常有人问我不...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
让微信公众号成为 AI 智能体:从内容沉淀到智能问答的一次升级

让微信公众号成为 AI 智能体:从内容沉淀到智能问答的一次升级

让微信公众号成为 AI 智能体:从内容沉淀到智能问答的一次升级 大家好,我是星哥,之前写了一篇文章 自己手撸一...
三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

  三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Andr...
开发者福利:免费 .frii.site 子域名,一分钟申请即用

开发者福利:免费 .frii.site 子域名,一分钟申请即用

  开发者福利:免费 .frii.site 子域名,一分钟申请即用 前言 在学习 Web 开发、部署...
使用1Panel面板搭建属于你的AI项目环境

使用1Panel面板搭建属于你的AI项目环境

使用 1Panel 面板搭建属于你的 AI 项目环境 在 AI 项目越来越火的今天,很多朋友都想自己动手搭建一...
每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站 - 手机博物馆 -CHAZ 3D Experience 一句话介绍:一个用 3D 方式重温...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装 Black 群晖 DSM7.2 系统安装教程(在 Vmware 虚拟机中、实体机均可)! 前言 大家好,...
星哥带你玩飞牛NAS硬件02:某鱼6张左右就可拿下5盘位的飞牛圣体NAS

星哥带你玩飞牛NAS硬件02:某鱼6张左右就可拿下5盘位的飞牛圣体NAS

星哥带你玩飞牛 NAS 硬件 02:某鱼 6 张左右就可拿下 5 盘位的飞牛圣体 NAS 前言 大家好,我是星...
你的云服务器到底有多强?宝塔跑分告诉你

你的云服务器到底有多强?宝塔跑分告诉你

你的云服务器到底有多强?宝塔跑分告诉你 为什么要用宝塔跑分? 宝塔跑分其实就是对 CPU、内存、磁盘、IO 做...
国产开源公众号AI知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率

国产开源公众号AI知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率

国产开源公众号 AI 知识库 Agent:突破未认证号限制,一键搞定自动回复,重构运营效率 大家好,我是星哥,...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...