守成亦开疆 發表於 2025-5-8 22:34:00

SpringBoot三大组件之拦截器(Interceptor)

<h1 id="一概述">一、概述</h1>
<p>在<code>Spring Boot</code>中,拦截器是一种用于拦截和处理<code>HTTP</code>请求的机制。它是<code>Spring</code>框架提供的一种中间件,用于在请求到达控制器(<code>Controller</code>)之前或之后执行一些共享的逻辑。</p>
<p><code>Spring Boot</code>的拦截器基于<code>Spring MVC</code>框架中的<code>HandlerInterceptor</code>接口实现。通过创建一个自定义的拦截器类并实现<code>HandlerInterceptor</code>接口,可以定义拦截器要执行的逻辑和行为。</p>
<h1 id="二定义拦截器">二、定义拦截器</h1>
<p>在<code>Spring Boot</code>中定义拦截器十分的简单,只需要创建一个拦截器类,并实现<code>HandlerInterceptor</code>接口即可。</p>
<p><code>HandlerInterceptor</code>接口中定义以下<code>3</code>个方法,如下表:</p>
<table>
<thead>
<tr>
<th>返回值类型</th>
<th>方法声明</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>boolean</td>
<td>preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)</td>
<td>该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回true表示继续向下执行,返回false表示中断后续操作。</td>
</tr>
<tr>
<td>void</td>
<td>postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</td>
<td>该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。</td>
</tr>
<tr>
<td>void</td>
<td>afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</td>
<td>该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。</td>
</tr>
</tbody>
</table>
<p>它接收三个参数:</p>
<ul>
<li>HttpServletRequest request: 表示当前的HTTP请求</li>
<li>HttpServletResponse response: 表示当前的HTTP响应</li>
<li>Object handler: 表示被拦截的处理器(一般是Controller中的方法)</li>
</ul>
<h1 id="三使用拦截器">三、使用拦截器</h1>
<h2 id="31-自定义拦截器">3.1 自定义拦截器</h2>
<pre><code class="language-java">@Component
public class LoginInterceptor implements HandlerInterceptor {
    //调用目标方法之前执行的方法
    //如果返回ture表示拦截器验证成功,执行目标方法
    //如果返回false表示拦截器验证失败,不再继续执行后续业务
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler) throws Exception {
      //用户登录判断业务
      HttpSession session = request.getSession(false);
      if (session != null &amp;&amp; session.getAttribute("session_userinfo") != null) {
            //用户已登录
            return true;
      }
      response.setStatus(401);
      return false;
    }
}
</code></pre>
<p>代码中的<code>preHandle</code>方法是拦截器的主要方法,在目标方法调用之前执行。</p>
<p>在<code>preHandle</code>方法中,首先通过<code>request.getSession(false)</code>获取当前请求的<code>HttpSession</code>对象(如果存在的话),然后判断该<code>HttpSession</code>对象是否为<code>null</code>并且是否存在名为"session_userinfo"的属性。如果这个条件成立,说明用户已经登录,可以继续执行后续的业务,于是返回<code>true</code>,否则验证失败,将<code>HTTP</code>响应的状态码设置为<code>401</code>,表示未授权,然后返回<code>false</code>,不再继续执行后续业务。</p>
<h2 id="32-注册拦截器">3.2 注册拦截器</h2>
<pre><code class="language-java">@Configuration
public class MyConfig implements WebMvcConfigurer {

    //注入
    @Autowired
    private LoginInterceptor loginInterceptor;

    //将拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有的 url
                .excludePathPatterns("/user/login")//排除url: /user/login (登录)
                .excludePathPatterns("/user/reg") //排除url: /user/reg   (注册)
                .excludePathPatterns("/image/**")//排除 image(图像) 文件夹下的所有文件
                .excludePathPatterns("/**/*.js")//排除任意深度目录下的所有".js"文件
                .excludePathPatterns("/**/*.css");
    }
}
</code></pre>
<p>UserController:</p>
<pre><code class="language-java">@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public String login(){
      return "login";
    }

    @RequestMapping("/index")
    public String index(){
      return "index";
    }

    @RequestMapping("/reg")
    public String reg(){
      return "reg";
    }
}
</code></pre>
<p>在配置类中,重写了<code>addInterceptors</code>方法,该方法用于注册拦截器。在这里,通过调用<code>InterceptorRegistry</code>的<code>addInterceptor</code>方法来添加拦截器,并设置拦截的路径和排除的路径。</p>
<p>具体地,通过调用<code>addInterceptor(loginInterceptor)</code>来添加<code>LoginInterceptor</code>拦截器。然后使用<code>addPathPatterns</code>方法指定需要拦截的<code>URL</code>路径模式,这里使用"/**"表示拦截所有的<code>URL</code>。使用<code>excludePathPatterns</code>方法来排除一些特定的<code>URL</code>路径,这些路径不会被拦截。</p>
<blockquote>
<p>"/<strong>/*.js","</strong>":表示零个或多个路径段(目录或文件夹),可以匹配任意深度的目录结构。<br>
"/*.js":表示以".js"结尾的文件名。</p>
</blockquote>
<p>常见的拦截配置:</p>
<table>
<thead>
<tr>
<th>拦截路径</th>
<th>含义</th>
<th>举例</th>
</tr>
</thead>
<tbody>
<tr>
<td>/*</td>
<td>拦截一级路径</td>
<td>能拦截 /user,/login,不能拦截 /user/login</td>
</tr>
<tr>
<td>/**</td>
<td>拦截任意路径</td>
<td>拦截所有</td>
</tr>
<tr>
<td>/user/*</td>
<td>拦截/user的下一级路径</td>
<td>能拦截 /user/getList,不能拦截 /user/getList/1 和 /user</td>
</tr>
<tr>
<td>/user/**</td>
<td>拦截/user下的所有路径</td>
<td>拦截/user下的所有路径,但是不能拦截 /book/getList</td>
</tr>
</tbody>
</table>
<p>此外拦截器不仅可以拦截项目中的<code>URL</code>,还可以拦截静态资源(<code>html</code>,图片等)。</p>
<h1 id="四执行流程">四、执行流程</h1>
<p><img src="https://img2024.cnblogs.com/blog/2745643/202505/2745643-20250514214353753-613588650.png" alt="" loading="lazy"></p>
<p>有了拦截器之后,会在调用<code>Controller</code>之前进行相应的业务处理,执行的流程如下图</p>
<p><img src="https://img2024.cnblogs.com/blog/2745643/202505/2745643-20250514214449256-1970086426.png" alt="" loading="lazy"></p>
<ol>
<li>添加拦截器后,执行<code>Controller</code>的方法之前,请求会先被拦截器拦截住。</li>
<li>执行<code>preHandle()</code>方法,这个方法需要返回一个布尔类型的值。</li>
<li>如果返回<code>true</code>,就表示放行本次操作, 继续访问<code>controller</code>中的方法。</li>
<li>如果返回<code>false</code>,则不会放行(<code>controller</code>中的方法也不会执行)。</li>
<li><code>controller</code>当中的方法执行完毕后,再回过来执行<code>postHandle()</code>这个方法以及<code>afterCompletion()</code>方法。</li>
<li>执行完毕之后,最终给浏览器响应数据。</li>
</ol>
<h1 id="五实际案例">五、实际案例</h1>
<h2 id="51-统一访问前缀添加">5.1 统一访问前缀添加</h2>
<p>所有请求地址添加<code>test</code>前缀:在<code>WebMvcConfigurer</code>接口中,<code>configurePathMatch</code>方法用于配置路径匹配规则。</p>
<pre><code class="language-java">@Configuration
public class MyConfig implements WebMvcConfigurer {
    //统一访问前缀的添加
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.addPathPrefix("test", new Predicate&lt;Class&lt;?&gt;&gt;() {
            @Override
            public boolean test(Class&lt;?&gt; aClass) {
                return true;
            }
      });
    }
}
</code></pre>
<p>在这个例子中,传递给<code>addPathPrefix</code>方法的前缀是"test",而<code>Predicate</code>对象是一个匿名内部类,实现了<code>Predicate&lt;Class&lt;?&gt;&gt;</code>接口。<code>Predicate</code>接口是<code>Java 8</code>中引入的函数式接口,它的test方法用于判断传入的类是否符合条件。</p>
<p>在这个匿名内部类中,test方法被重写为总是返回true,这意味着所有的类都符合条件,都会被添加统一访问前缀。</p>
<p>因此,通过这段代码的配置,所有的请求路径都会在前面添加"test"前缀。例如,原始路径为"/example",添加了前缀后的路径就变为"/test/example"。这样可以实现对请求路径的统一处理。</p>
<p>注意:如果加了前缀,拦截器的排除路径也要跟着改动:</p>
<pre><code class="language-java">//将拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**") //拦截所有的 url
            .excludePathPatterns("/**/user/login")//排除url: /user/login (登录)
            .excludePathPatterns("/**/user/reg") //排除url: /user/reg   (注册)
            .excludePathPatterns("/**/image/**")//排除 image(图像) 文件夹下的所有文件
            .excludePathPatterns("/**/*.js")//排除任意深度目录下的所有".js"文件
            .excludePathPatterns("/**/*.css");
}
</code></pre>
<h2 id="52-配置本地资源映射路径">5.2 配置本地资源映射路径</h2>
<p>实现<code>WebMvcConfigurer</code>,重写<code>addResourceHandlers(ResourceHandlerRegistry registry)</code>方法</p>
<ul>
<li>addResourceHandler():添加的是访问路径</li>
<li>addResourceLocations(): 添加的是映射后的真实路径,映射的真实路径末尾必须加<code>/</code>,不然映射不到,<code>/</code>适用于<code>windows</code>和<code>linux</code></li>
</ul>
<p>代码:</p>
<pre><code class="language-java">@Configuration
public class MyWebMVCConfig implements WebMvcConfigurer {

    @Value("${file.location}") //         D:/test/
    String fileLocation;// 这两个是路径

    @Value("${file.path}") //         /file/**
    String filePath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
      //匹配到resourceHandler,将URL映射至location,也就是本地文件夹
      registry.addResourceHandler(filePath)
                //这里最后一个/必须写
                .addResourceLocations("file:///" + fileLocation);
    }
}
</code></pre>
<p>这段代码意思就配置一个拦截器,如果访问路径是<code>addResourceHandler</code>中的<code>filePath</code>这个路径那么就映射到访问本地的<code>addResourceLocations</code>的参数的这个路径上,这样就可以让别人访问服务器的本地文件了,比如本地图片或者本地音乐视频什么的。</p>
<h1 id="六应用场景">六、应用场景</h1>
<p>拦截器在<code>SpringBoot</code>中扮演着多面手的角色,适用于多种场景,有助于增强应用程序的功能和安全性。以下是拦截器的一些典型应用:</p>
<ul>
<li>
<p>身份验证与授权<br>
使用拦截器检查用户的身份验证状态和权限级别,确保只有经过验证且有适当权限的用户能够访问特定资源。这为应用程序的安全性提供了第一道防线。</p>
</li>
<li>
<p>日志记录与审计:<br>
在请求处理的不同阶段(如请求到达前、响应发送后)插入日志记录逻辑,帮助开发者跟踪系统行为、调试问题,并满足审计需求。</p>
</li>
<li>
<p>性能分析与监控:<br>
拦截器可用于测量请求处理时间,收集性能指标,识别潜在的性能瓶颈,从而优化系统的响应速度和服务质量。</p>
</li>
<li>
<p>跨域资源共享(CORS)配置:<br>
当构建<code>RESTful API</code>时,通过拦截器设置适当的HTTP响应头来允许或限制跨域请求,简化前端与后端之间的交互。</p>
</li>
<li>
<p>异常捕捉与友好错误处理:<br>
捕获请求处理过程中抛出的异常,提供统一的错误处理机制,确保最终用户接收到格式化良好且友好的错误信息,而不是直接暴露底层技术细节</p>
</li>
<li>
<p>数据预处理与后处理:<br>
对进入的数据进行验证或转换,确保其符合预期格式;同时,在响应生成之后对输出数据进行格式化或加密等操作,以满足业务逻辑要求。</p>
</li>
<li>
<p>流量控制与限流策略:<br>
实施流量控制措施,例如限制同一IP地址在一定时间内发起的请求数量,或者控制并发请求的数量,以保护系统免受过载影响。</p>
</li>
</ul>
<p>通过合理运用拦截器,开发人员可以在不改变原有业务逻辑的前提下,轻松地向应用程序添加这些额外的功能,提高系统的灵活性、安全性和可维护性。</p>
<h1 id="七拓展">七、拓展</h1>
<h2 id="71-过滤器和拦截器的区别">7.1 过滤器和拦截器的区别</h2>
<p>过滤器和拦截器非常相似,但是它们有很大的区别</p>
<ol>
<li>过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入<code>servlet</code>之前进行预处理的。请求结束返回也是,是在<code>servlet</code>处理完后,返回给前端之前。</li>
<li>拦截器可以获取<code>IOC</code>容器中的各个<code>bean</code>,而过滤器就不行,因为拦截器是<code>spring</code>提供并管理的,<code>spring</code>的功能可以被拦截器使用,在拦截器里注入一个<code>service</code>,可以调用业务逻辑。而过滤器是<code>JavaEE</code>标准,只需依赖<code>servlet api</code>,不需要依赖<code>spring</code>。</li>
<li>过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射。</li>
<li><code>Filter</code>是依赖于<code>Servlet</code>容器,属于<code>Servlet</code>规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。</li>
<li><code>Filter</code>的执行由<code>Servlet</code>容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。</li>
<li><code>Filter</code>的生命周期由<code>Servlet</code>容器管理,而拦截器则可以通过<code>IoC</code>容器来管理,因此可以通过注入等方式来获取其他<code>Bean</code>的实例,因此使用会更方便。</li>
</ol>
<p>最简单明了的区别就是</p>
<ol>
<li>过滤器可以修改<code>request</code>,而拦截器不能。过滤器需要在<code>servlet</code>容器中实现,拦截器可以适用于<code>javaEE</code>,<code>javaSE</code>等各种环境</li>
<li>拦截器可以调用<code>IOC</code>容器中的各种依赖,而过滤器不能</li>
<li>过滤器只能在请求的前后使用,而拦截器可以详细到每个方法</li>
</ol><br><br>
来源:https://www.cnblogs.com/ciel717/p/18806305
頁: [1]
查看完整版本: SpringBoot三大组件之拦截器(Interceptor)