在我们的日常开发工作中,Filter
(过滤器)、Interceptor
(拦截器)和 AOP
(面向切面编程)是非常常用的 3 种请求处理技术。在不同的应用场景中,使用它们都可以在不影响主业务逻辑的前提下为系统增加额外的功能。面试官去问这个问题的时候,一般是想考察求职者的技术深度和对框架机制的理解。本篇我们从 3 者的基本概念及使用来分析解答下这道面试题。
Filter
是 Java Servlet
规范的一部分,定义在 javax.servlet
包中,Filter
可以对 Servlet
容器的所有 HTTP 请求(HttpServletRequest
)和响应(HttpServletResponse
)进行预处理或后处理操作。例如,在请求到达目标资源之前执行身份验证或设置字符编码,或者在响应返回给客户端前修改其响应内容格式。
package javax.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
default public void destroy() {}
}
项目中自定义过滤器需实现该接口,接口中的 3 个方法就是 Filter
的整个生命周期。init()
-初始化、doFilter()
-执行过滤逻辑、destroy()
-销毁。
init方法
:Web 容器在启动时,会触发每个 Filter
实例的 init 方法调用并传递一个 FilterConfig
对象,该配置允许过滤器获取初始化参数以及 ServletContext
上下文对象,从而加载任何所需的资源。该方法在 Filter
的整个生命周期中仅会在初始化时被调用一次。
该方法如果抛出异常,Web 容器就会认为这个过滤器无法正常工作,因此不会将它加入到过滤器链中,无法提供后续的请求过滤工作。doFilter方法
:该方法为 Filter
的核心工作方法,每一次请求都会调用该方法。
FilterChain
接口参数由具体的 Servlet 容器实现并提供。每个过滤器的 doFilter 方法都会接收一个 FilterChain
对象作为参数。在这个方法内部,过滤器可以选择:chain.doFilter(request, response)
将请求传递给下一个过滤器或目标资源。destroy方法
:Web 容器在销毁时,会触发每个 Filter
实例的 destroy 方法调用,清理过滤器所有持有的资源(如内存、文件句柄、线程等)。该方法在 Filter
的整个生命周期中也仅会执行一次。在 SpringBoot
项目中可以使用如下几种配置方式:
@WebFilter
注解 + @ServletComponentScan
注解在过滤器类上使用 @WebFilter
注解来定义 URL 模式和其他属性
package com.example.filter;
@WebFilter(urlPatterns = "/*", filterName = "exampleFilter")
public class ExampleFilter 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() {
// 清理资源...
}
}
在启动类或者任意配置类上加上 @ServletComponentScan
注解来让 Spring Boot 自动扫描并注册这些过滤器。
@SpringBootApplication
@ServletComponentScan(basePackages = "com.example.filter") // 指定扫描包路径
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
FilterRegistrationBean
进行注册@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<ExampleFilter> customFilterRegistration() {
FilterRegistrationBean<ExampleFilter> registrationBean = new FilterRegistrationBean<>();
ExampleFilter customFilter = new ExampleFilter();
registrationBean.setFilter(customFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1); // 设置过滤器的执行顺序
// 添加初始化参数
registrationBean.addInitParameter("encoding", "UTF-8");
return registrationBean;
}
}
@Component
注解:这种方式可以让 Spring 自动将过滤器组件化,默认会应用到所有请求路径。@Component
public class ExampleFilter 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() {}
}
Interceptor
是 Spring MVC 框架的一部分,是位于 org.springframework.web.servlet
包中的 HandlerInterceptor
接口,用于在请求处理之前或之后执行特定逻辑。与 Filter
不同的是,Interceptor
不依赖于 Servlet
容器,它是 Spring 框架独有的。
/**
* A HandlerInterceptor gets called before the appropriate HandlerAdapter triggers the execution of the handler itself.
* This mechanism can be used for a large field of preprocessing aspects, e.g. for authorization checks,
* or common handler behavior like locale or theme changes.
* Its main purpose is to allow for factoring out repetitive handler code.
*/
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
接口注释意思大致是说,HandlerInterceptor
是在通过 HandlerAdapter
执行查找到的 handler
之前被调用,这种机制主要目的是为了减少重复代码,用于大量的程序预处理工作,比如授权检查等。
preHandle方法
:在 controller
方法调用之前,按照 Interceptor
链顺序执行,进行权限检查等请求前处理操作。如果该方法返回了 false
,那么不仅当前 Interceptor
会终止执行,整个拦截器链都会被终止。postHandle方法
:在 controller
方法调用之后返回 ModelAndView
之前执行,与 preHandle
不同的是,postHandle
是按照Interceptor
链逆序执行的。afterCompletion方法
:在整个请求完成后调用,通常用于资源清理或日志记录。执行顺序:
下面我们通过 Spring MVC 在实际分发处理请求时的源码具体看下 Interceptor
的执行情况(源码出自 spring-framework-5.0.x):
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exceptio {
// 此处用 processedRequest 接收了用户的 request 请求
HttpServletRequest processedRequest = request;
// HandlerExecutionChain 局部变量
HandlerExecutionChain mappedHandler = null;
// 标记一下是否解析了文件类型的数据,如果有最终需要清理操作
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// ModelAndView 局部变量
ModelAndView mv = null;
// 处理异常局部变量
Exception dispatchException = null;
try {
/**
* 判断一下是否是文件上传请求。
* 如果请求是 POST 请求,并且 Context-Type 是以 multipart/ 开头的就认为是文件上传的请求。
* 需要注意的是,若是这里被认定为文件上传请求,processedRequest 和 request 将不再指向同一对象
* 这里返回的是 MultipartHttpServletRequest。
*/
processedRequest = checkMultipart(request);
// 两个请求不再相同,进行文件上传标记,用于后续清理操作
multipartRequestParsed = (processedRequest != request);
/**
* 向 HandlerMapping 请求查找 HandlerExecutionChain
* 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404。
*/
mappedHandler = getHandler(processedRequest);
// 如果没找到对应的处理器,则抛出异常
// 相信大家都见过 'No mapping for GET /xxxx',就是这里抛出的
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根据查找到的 Handler 请求查找能够进行处理的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 判断自上次请求后是否有修改,没有修改直接返回响应
// 如果是GET请求,且内容没有变化的话,就直接返回
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (log.isDebugEnabled()) {
log.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
/**
* <<<<<<<<<这里到了我们本节内容,拦截器的执行了>>>>>>>>>
* 这里通过 applyPreHandle 方法,按顺序依次执行 HandlerInterceptor 的 preHandle 方法
* 可以看到,如果任一 HandlerInterceptor 的 preHandle 方法返回了 false, 则整个拦截器连
* 不再继续进行处理。
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
/**
* 通过 HandlerAdapter 执行查找到的 handler
* 这里真正执行我们 controller 中的方法逻辑,返回一个 ModelAndView
*/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
/**
* 检查是否已经开始处理并发请求,如果并发处理已经开始,那么当前的请求线程就可以返回了,而不会等待异步操 * 作的结果,也就不会再执行拦截器 PostHandle 之类的操作了。
*/
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果我们没有设置 viewName,就采用默认的,否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
// <<<<<<<<< 这里,通过 applyPostHandle 逆序执行 HandlerInterceptor 的 postHandle 方法>>>>>>>>>
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 渲染视图填充 Model,如果有异常渲染异常页面
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 如果有异常按倒序执行所有 HandlerInterceptor 的 afterCompletion 方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 如果有异常按倒序执行所有 HandlerInterceptor 的 afterCompletion 方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
// 倒序执行所有 HandlerInterceptor 的 afterCompletion 方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
// 如果请求包含文件类型的数据则进行相关清理工作
cleanupMultipart(processedRequest);
}
}
}
}
上述源码中,其实不仅仅是拦截器的执行顺序了,而是 Spring MVC 处理客户端请求的整个过程。如下图,可以很直观的看出拦截器的执行时机与顺序。
图片来源于网络,侵权请联系删除
自定义拦截器,实现 HandlerInterceptor
接口
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在这里添加拦截逻辑,例如身份验证或日志记录
System.out.println("MyInterceptor preHandle: " + request.getRequestURI());
return true; // 返回 true 继续处理请求,返回 false 中断请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在视图渲染之前执行的逻辑
System.out.println("MyInterceptor postHandle: " + request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成后的逻辑,比如资源清理
System.out.println("MyInterceptor afterCompletion: " + request.getRequestURI());
}
}
实现 WebMvcConfigurer
接口并重写 addInterceptors
方法,将拦截器注册到 Spring MVC 的拦截器链中
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截器,并指定拦截路径模式
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/static/**", "/login", "/register"); // 排除静态资源和某些 URL
}
}
AOP(Aspect-Oriented Programming),即面向切面编程,是一种编程范式,目的是通过分离横切关注点(如事务管理、日志记录)来提高代码的模块化程度。AOP 允许开发者定义“切面”(Aspects),通过这些切面可以在不改变业务逻辑的情况下增强现有业务功能。
AOP 是一种编程思想,Spring AOP 是 Spring 框架提供的 AOP 实现。
@Pointcut("execution(* com.example.service..*.*(..))")
Spring AOP 如何创建代理:
@EnableAspectJAutoProxy(proxyTargetClass = true)
注解即可。@EnableAspectJAutoProxy
注解。@Aspect
注解来标记这个类为一个切面,并使用 @Component
注解让 Spring 管理这个 Bean。@Pointcut
注解加 AspectJ
表达式定义切入点。Spring 提供的通知类型有如下几种:
启用 AOP 支持
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
定义切面类
@Slf4j
@Aspect
@Component
public class AllAdviceAspect {
// 定义切入点,匹配 service 包下的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
public void serviceLayerExecution() {}
// 前置通知,在方法调用前打印日志
@Before("serviceLayerExecution()")
public void logBefore(JoinPoint joinPoint) {
log.info("Before method execution: {}", joinPoint.getSignature().getName());
}
// 后置返回通知,在方法成功返回后打印日志
@AfterReturning(pointcut = "serviceLayerExecution()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("Method returned successfully: {}, Result: {}", joinPoint.getSignature().getName(), result);
}
// 抛出异常通知,在方法抛出异常后打印日志
@AfterThrowing(pointcut = "serviceLayerExecution()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
log.error("An exception was thrown in {}: {}", joinPoint.getSignature().getName(), ex.getMessage());
}
// 后置最终通知,在方法完成之后打印日志,无论是否抛出异常
@After("serviceLayerExecution()")
public void logAfter(JoinPoint joinPoint) {
log.info("After method execution: {}", joinPoint.getSignature().getName());
}
// 环绕通知,包围方法调用,控制方法执行流程
@Around("serviceLayerExecution()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
log.info("Starting around advice for method: {}", pjp.getSignature().getName());
long start = System.currentTimeMillis();
try {
// 执行目标方法
Object result = pjp.proceed();
log.info("Method {} took {} ms to execute", pjp.getSignature().getName(), System.currentTimeMillis() - start);
return result;
} catch (Throwable e) {
log.error("Error during method execution: {}", e.getMessage());
throw e; // 重新抛出异常以便后续处理
} finally {
log.info("Ending around advice for method: {}", pjp.getSignature().getName());
}
}
}
Filter
是 Java Servlet
规范的一部分,它工作在 Servlet
容器层面,是 Servlet
容器级别的,适用于所有进入应用的 HTTP 请求。Interceptor
是 Spring MVC 框架提供的一种请求处理机制,是属于框架级别的。通过 Interceptor
章节的源码可以看出,Interceptor
工作在 Spring MVC 分发处理请求时,而分发请求的类是 DispatcherServlet
,它是一个 Servlet
,根据 Servlet
规范,Filter
是先于 Servlet
执行的。所以 Filter
要比 Interceptor
优先执行。controller
中的某个方法时才会触发,而 Interceptor
在执行查找到的 handler
之前就已经被调用了,所以 Interceptor
要先于 Spring AOP 执行。执行顺序如下图:
本篇主要基于 SpringBoot 介绍了过滤器、拦截器和 Spring AOP,通过学习其基本知识了解到了它们工作时的执行顺序。实际上,其实无论是过滤器还是拦截器,都可以被视为 AOP 思想的具体实现形式,尽管它们各自工作在不同的层次上。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。