Spring 常用的三種攔截器詳解

程序员博博發表於2024-07-27

前言

在開發過程中,我們常常使用到攔截器來處理一些邏輯。最常用的三種攔截器分別是 AOP、 Interceptor 、 Filter,但其實很多人並不知道什麼時候用AOP,什麼時候用Interceptor,什麼時候用Filter,也不知道其攔截順序,內部原理。今天我們詳細介紹一下這三種攔截器。

攔截器順序

我們現在有一個controller介面,叫做test,現在我們在專案有定義了三個攔截器,其順序如下

Filter(before) > Interceptor(before) > AOP(before) > test方法 > AOP(after) > Interceptor(after) > Filter(after)

具體流程如下兩幅圖片。

所以有時候我們使用不了AOP/Interceptor, 只能使用Filter。

  • 比如我們現在是一個Get請求,但是別人卻傳送了一個Post請求,這時候只有Filter才能攔截,只能使用Filter.
  • 又比如我們在Interceptor獲取了請求引數以後,因為是一個流,後面controller就會獲取不到,我們一般會採用包裝類來實現重複讀取。但假如我們直接使用AOP就完全可以避免這個問題了。

類似的例子比較多,所以我們應該知其然,也要知其所以然。下面我們介紹一下三種攔截器的具體用法。

Filter攔截器

filter是servlet層面的提供攔截器,和spring無關。只是說現在spring/springboot一統江湖,很多專案在spring的基礎上面使用filter。那我們如果在spring專案中使用filter攔截器呢

@Slf4j
public class FirstFilter implements Filter {
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

		log.info("FirstFilter before doFilter");
		filterChain.doFilter(servletRequest, servletResponse);
	}
}

@Slf4j
public class SecondFilter implements Filter {
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

		log.info("SecondFilter before doFilter");
		filterChain.doFilter(servletRequest, servletResponse);
	}
}

@Slf4j
@Configuration
public class FilterConfig {

	@Bean
	public FilterRegistrationBean firstFilter() {
		FilterRegistrationBean registration = new FilterRegistrationBean();
		registration.setFilter(new FirstFilter());
		registration.addUrlPatterns("/*");
		registration.setName("FirstFilter");
		// 數字越小,優先順序越高
		registration.setOrder(1);
		return registration;
	}

	@Bean
	public FilterRegistrationBean secondFilter() {
		FilterRegistrationBean registration = new FilterRegistrationBean();
		registration.setFilter(new SecondFilter());
		registration.addUrlPatterns("/*");
		registration.setName("SecondFilter");
		// 數字越大,優先順序越低
		registration.setOrder(2);
		return registration;
	}

}

輸出結果

FirstFilter before doFilter

SecondFilter before doFilter

Interceptor攔截器

Interceptor是springmvc給我們提供的攔截器,只有在sotingmvc中才可以使用

@Slf4j
@Component
public class FirstInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		log.info("FirstInterceptor preHandle");
		return true;
	}
}

@Slf4j
@Component
public class SecondInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		log.info("SecondInterceptor preHandle");
		return true;
	}

}

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

	@Autowired
	private FirstInterceptor firstInterceptor;

	@Autowired
	private SecondInterceptor secondInterceptor;

	// 配置攔截規則
	public void addInterceptors(InterceptorRegistry registry) {

		// 按照註冊的順序,依次執行
		registry.addInterceptor(firstInterceptor)
				.addPathPatterns("/**")
				.excludePathPatterns("/task/**");

		registry.addInterceptor(secondInterceptor)
				.addPathPatterns("/**")
				.excludePathPatterns("/spring/**");
	}
}

輸出結果

FirstInterceptor preHandle

SecondInterceptor preHandle

AOP攔截器

AOP是我們非常常用的攔截器,織入點,有before,after,around等,我們今天以around為例

@Aspect
@Component
public class LoggerAOP {

	@Pointcut("execution (public * com.xxx.controller..*(..))")
	public void pointcutLogger() {}

	@Around("pointcutLogger()")
	public Object methodAround(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			logger.info("request className = {}, method = {}, ip = {}, param = {}", className, methodName, ip, param);

			resp = joinPoint.proceed();

			long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
			logger.info("response className = {}, method = {}, resp = {}, cost = {}ms", className, methodName, buildResp(resp), duration);
		}

		catch (Throwable e) {
			logger.error("logger request className = {}, method = {} fail message = {} ",
					className, methodName, e.getMessage(), e);
			throw e;
		}
		return resp;
	}
}

最後

我們介紹了一下spring中,常用的三種攔截器,以及他們在springboot中如何使用。

最後給大家出一個小作業,如果把上面三個攔截器放到一個專案中,他會輸出什麼順序呢?

[INFO  2024-07-27 16:01:52.146] [http-nio-8099-exec-2] [] - [FirstFilter.doFilter:17] [FirstFilter before doFilter]
[INFO  2024-07-27 16:01:52.146] [http-nio-8099-exec-2] [] - [SecondFilter.doFilter:17] [SecondFilter before doFilter]
[INFO  2024-07-27 16:01:52.148] [http-nio-8099-exec-2] [] - [FirstInterceptor.preHandle:18] [FirstInterceptor preHandle]
[INFO  2024-07-27 16:01:52.148] [http-nio-8099-exec-2] [] - [SecondInterceptor.preHandle:18] [SecondInterceptor preHandle]
[INFO  2024-07-27 16:01:52.148] [http-nio-8099-exec-2] [cdc644d0-afdf-4283-bf52-fc5fdd217746] - [LoggerAOP.methodAround:52] [request className = TestController, method = testController, ip = 0:0:0:0:0:0:0:1, param = [null]]

[INFO  2024-07-27 16:01:52.149] [http-nio-8099-exec-2] [cdc644d0-afdf-4283-bf52-fc5fdd217746] - [TestController.testController:57] [main process]

[INFO  2024-07-27 16:01:52.149] [http-nio-8099-exec-2] [cdc644d0-afdf-4283-bf52-fc5fdd217746] - [LoggerAOP.methodAround:62] [response className = TestController, method = testController, resp = "ok", cost = 0ms]

相關文章