一、简介
Spring 的拦截器(HandlerInterceptor)和过滤器(Filter)都是用于请求处理的组件,但拦截器更适合于Spring MVC的应用场景,提供了更丰富的Spring特性支持;而过滤器则更通用,适用于需要在Web容器层面上进行处理的场景。
二、拦截器与过滤器的区别
总结来说,拦截器和过滤器都可以用来对请求进行预处理和后处理,以下是他们在设计目的、工作机制和使用场景上的不同点:
- 设计目的:
- 拦截器:主要用于处理Spring MVC框架内部的请求处理流程,例如在控制器方法执行前后添加业务逻辑。
- 过滤器:主要用于在Web容器层面上对请求和响应进行预处理和后处理,可以处理所有类型的请求,不仅限于Spring MVC。
- 工作机制:
- 拦截器:基于Spring的代理机制,拦截器可以访问Spring的IoC容器,因此可以访问到其他的bean。
- 过滤器:基于Servlet规范的过滤器链,每个过滤器都是独立工作的,不依赖于Spring的容器。
- 使用场景:
- 拦截器:
- 预处理请求,例如检查用户权限、记录日志等。
- 处理请求,例如修改模型数据。
- 后处理响应,例如添加响应头、处理异常等。
- 过滤器:
- 跨域请求处理(CORS)。
- 请求数据的编码解码。
- 请求和响应的压缩和解压。
- 安全性控制,例如防止SQL注入、XSS攻击等。
- 请求的性能统计。
- 拦截器:
- 生命周期:
- 拦截器:在Spring MVC的DispatcherServlet中处理请求时被调用。
- 过滤器:在Servlet容器处理请求之前或之后被调用,这意味着在Spring MVC的上下文中,过滤器可能先于拦截器被调用。
- 性能:
- 拦截器:因为依赖于Spring的容器,所以可能比过滤器有更多的开销。
- 过滤器:作为Servlet规范的一部分,通常性能开销较小。
- 配置:
- 拦截器:通过Spring的配置文件或者Java配置类来定义和注册。
- 过滤器:通过web.xml或者使用Servlet 3.0的注解来配置。
三、拦截器
拦截器是一种基于Java的编程机制,用于拦截和处理HTTP请求,其实现原理主要基于Java的动态代理机制和AOP(面向切面编程)概念,并结合了责任链和适配器设计模式。它允许开发者在请求到达控制器之前或响应被发送回客户端之后,插入自定义的业务逻辑。
3.1 拦截器工作原理
- 拦截器接口:Spring MVC提供了一个HandlerInterceptor接口,该接口包含了三个回调方法:preHandle、postHandle和afterCompletion。开发者需要创建一个类并实现这个接口,以便在请求处理的不同阶段执行自定义逻辑。
- 拦截器链:当Spring MVC应用启动时,或者通过配置动态添加拦截器时,Spring会构建一个拦截器链。这个链由多个拦截器组成,每个拦截器按照一定顺序链接在一起。当一个请求到达Spring MVC时,它会依次通过这些拦截器,每个拦截器都有机会对请求进行处理。
- 适配器模式:为了让拦截器能够以统一的方式被调用,Spring MVC使用了适配器模式。HandlerExecutionChain类就是一个适配器,它包装了一个或多个拦截器以及一个处理器(Controller)。
- 动态代理:Spring MVC使用动态代理技术来创建拦截器的代理对象。当请求到达时,实际上是通过这个代理对象来调用preHandle、postHandle和afterCompletion方法的。如果拦截器实现了HandlerInterceptor接口,那么代理对象会调用这些方法;如果没有,代理对象会直接调用处理器的方法。
- 预处理(preHandle):在请求处理器被调用之前,每个拦截器都会调用preHandle方法。如果某个拦截器的preHandle方法返回false,那么请求处理将停止,不会继续传递给链中的后续拦截器和处理器。
- 请求处理器调用:如果所有拦截器的preHandle方法都返回true,则请求会被传递给请求处理器执行。
- 后置处理(postHandle):在请求处理器执行完成后,每个拦截器会按照逆序调用postHandle方法。这个方法允许在视图渲染之前对模型数据进行修改。
- 清理(afterCompletion):在整个请求处理完毕后,无论是否发生异常,每个拦截器都会调用afterCompletion方法。这个方法通常用于资源清理和日志记录等操作。
- DispatcherServlet处理:DispatcherServlet作为前端控制器,负责协调拦截器链和请求处理器之间的交互,确保请求正确地经过拦截器链,并最终由合适的处理器处理。
3.2 拦截器使用步骤
在Spring MVC中,使用拦截器涉及到以下几个步骤:
- 实现HandlerInterceptor接口:创建一个类并实现HandlerInterceptor接口,覆盖其中的preHandle、postHandle和afterCompletion方法。
bash
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在控制器处理请求之前执行的逻辑
// 如果返回false,则后续的拦截器和处理器都不会被执行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在控制器处理请求之后、视图渲染之前执行的逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求处理完成之后执行的逻辑
}
}
- 配置拦截器:在Spring配置文件中注册拦截器。可以通过XML配置或Java配置来实现。
bash
## 在spring-mvc.xml配置示例:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 匹配所有路径 -->
<mvc:exclude-mapping path="/login"/> <!-- 排除特定路径 -->
<bean class="com.example.MyInterceptor"/> <!-- 拦截器的Bean类 -->
</mvc:interceptor>
</mvc:interceptors>
bash
## Java配置示例:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
- 使用拦截器:配置完成后,当HTTP请求到达Spring MVC时,拦截器会自动工作。拦截器会根据配置的路径模式拦截请求,并执行相应的拦截逻辑。
以上步骤完成后,MyInterceptor类中实现的拦截逻辑就会按照配置的规则对请求进行拦截。通过这种方式,可以在不修改现有控制器代码的情况下,统一处理请求前、后以及处理完成后的逻辑。
四、过滤器
在Spring框架中,过滤器是Java Servlet API的一部分,它主要用于在请求到达Servlet之前和响应返回客户端之前对请求和响应进行预处理和后处理。过滤器是通过实现javax.servlet.Filter接口来创建的,并且必须在web.xml中配置,或者在基于Java的配置中声明。
4.1 过滤器工作原理
- 定义过滤器:开发者首先定义一个类,实现javax.servlet.Filter接口或者继承org.springframework.web.filter.GenericFilterBean。后者提供了Spring的集成特性,如依赖注入。
- 注册过滤器:在Spring配置中,可以通过XML配置或者Java配置来注册过滤器。使用Java配置时,可以使用@Bean注解创建过滤器的Bean,并使用FilterRegistrationBean来注册。
- 配置过滤器顺序:在注册过滤器时,需要指定过滤器的URL模式,以及它在过滤器链中的顺序。过滤器链是按照注册顺序来处理请求的。
- 初始化:当Web应用启动或者过滤器被首次请求时,Spring容器会创建过滤器的实例,并调用其init方法进行初始化。
- 拦截请求:当请求匹配过滤器的URL模式时,Spring容器会创建一个FilterChainProxy对象,并调用其doFilter方法。FilterChainProxy是Spring提供的一个代理类,它封装了真实的过滤器链。
- 执行过滤器逻辑:FilterChainProxy会遍历注册的过滤器列表,并按顺序调用每个过滤器的doFilter方法。在每个doFilter方法中,过滤器可以对ServletRequest和ServletResponse对象进行检查和修改。
- 调用下一个过滤器:在doFilter方法中,过滤器可以选择调用FilterChain对象的doFilter方法,将请求传递给下一个过滤器;如果当前过滤器是链中的最后一个过滤器,则请求会传递给Spring MVC的DispatcherServlet。
- 处理响应:在DispatcherServlet处理完请求并生成响应后,响应会沿着过滤器链逆向返回。每个过滤器都有机会对响应进行最后的修改。
- 销毁:当Web应用停止或者过滤器不再需要时,Spring容器会调用过滤器的destroy方法,以便释放资源。
4.2 过滤器使用步骤
在Spring Boot中使用过滤器通常涉及以下步骤:
- 创建过滤器类:实现javax.servlet.Filter接口或继承org.springframework.web.filter.GenericFilterBean。并覆盖init、doFilter和destroy三个方法。
bash
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 在请求到达目标资源之前的处理逻辑
chain.doFilter(request, response);
// 在响应返回给客户端之后的处理逻辑
}
@Override
public void destroy() {
// 过滤器销毁时的清理代码
}
}
- 注册过滤器:在Spring配置文件中注册过滤器,或者使用Java配置。
bash
## 在web.xml配置文件注册:
<filter>
<filter-name>exampleFilter</filter-name>
<filter-class>com.example.filter.ExampleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exampleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
bash
## 使用Java配置注册:
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<ExampleFilter> exampleFilter() {
FilterRegistrationBean<ExampleFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ExampleFilter());
registrationBean.addUrlPatterns("/*"); // 设置过滤器适用的URL模式
return registrationBean;
}
}
- 设置过滤器顺序:可以通过setOrder方法为过滤器设置排序权重,数字越小优先级越高。
bash
registrationBean.setOrder(1); // 设置过滤器顺序
- 启动应用程序:部署并启动应用程序。Spring容器会自动检测到过滤器并进行注册,无需手动在web.xml中配置。
- 测试过滤器:通过发送HTTP请求来测试过滤器是否按预期工作。
以上步骤完成后,过滤器就会按照指定的URL模式介入请求处理流程,执行doFilter方法中定义的业务逻辑。如果需要,还可以在init方法中进行初始化操作,或者在destroy方法中进行清理操作。