在 Servlet Filter 中自动装配 Spring Bean
1、简介
Servlet Filter(过滤器)为拦截和处理传入的请求提供了强大的机制。
本文将会带你了解在 Servlet Filter 中无缝获取 Spring Bean 的各种方法,这种需求在 Spring Web 应用中很常见。
2、Servlet Filter 中 @Autowired 的限制
Spring 的依赖注入机制 @Autowired
,是一种方便的方式来将依赖注入到由 Spring 管理的组件中。但它无法与 Servlet Filter 完美配合。这是因为 Servlet Filter 是由 Servlet 容器初始化的,通常是在 Spring 的 ApplicationContext
完全加载和初始化之前 。
因此,当容器实例化 Servlet Filter 时 ,Spring Context 可能尚未可用,从而导致在尝试使用 @Autowired
注解时 ,依赖为 null
或未初始化。
3、项目设置
创建一个通用的 LoggingService
,它将自动装配到 Filter
中:
@Service
public class LoggingService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public void log(String message,String url){
logger.info("Logging Request {} for URI : {}",message,url);
}
}
然后,创建 Filter
,拦截传入的 HTTP 请求,并使用 LoggingService
依赖记录 HTTP 方法和 URI 的详细信息:
@Component
public class LoggingFilter implements Filter {
@Autowired
LoggingService loggingService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
loggingService.log(httpServletRequest.getMethod(),httpServletRequest.getRequestURI());
filterChain.doFilter(servletRequest,servletResponse);
}
}
暴露一个返回用户列表的 RestController
:
@RestController
public class UserController {
@GetMapping("/users")
public List<User> getUsers(){
return Arrays.asList(new User("1","John","john@email.com"),
new User("2","Smith","smith@email.com"));
}
}
测试,检查 LoggingService
是否已成功自动装配到 Filter:
@RunWith(SpringRunner.class)
@SpringBootTest
public class LoggingFilterTest {
@Autowired
private LoggingFilter loggingFilter;
@Test
public void givenFilter_whenAutowired_thenDependencyInjected() throws Exception {
Assert.assertNotNull(loggingFilter);
Assert.assertNotNull(getField(loggingFilter,"loggingService"));
}
private Object getField(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(target);
}
}
然而,在此阶段,由于 Spring Context 尚未可用,LoggingService
有可能无法注入 LoggingFilter
。
接下来,来看看各种方解决案。
4、在 Servlet Filter 中使用 SpringBeanAutowiringSupport
Spring 的 SpringBeanAutowiringSupport
类支持将依赖注入到 Filter
和 Servlet 等非 Spring 管理的类中 。通过使用该类,Spring 可以将依赖注入到 LoggingFilter
中,如 LoggingService
(它是一个 Spring 管理的 Bean)。
Filter
的 init
方法用于初始化 Filter
实例,我们可以在 LoggingFilter
中覆写此方法,以使用 SpringBeanAutowiringSupport
:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
filterConfig.getServletContext());
}
processInjectionBasedOnServletContext
方法使用与 ServletContext
关联的 ApplicationContext
来执行自动装配。它首先从 ServletContext
获取 ApplicationContext
,然后使用它将依赖自动装配到目标对象。这一过程包括检查目标对象字段中的 @Autowired
注解,然后解析并从 ApplicationContext
中注入相应的 Bean。
这种机制允许非 Spring 管理对象(如 Filter 和 Servlet)受益于 Spring 的依赖注入功能。
5、在 Servlet Filter 中使用 WebApplicationContextUtils
WebApplicationContextUtils
提供了一种工具方法,用于检索与 ServletContext
关联的 ApplicationContext
。ApplicationContext
包含 Spring 容器管理的所有 Bean。
覆写 LoggingFilter
类的 init
方法:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
loggingService = WebApplicationContextUtils
.getRequiredWebApplicationContext(filterConfig.getServletContext())
.getBean(LoggingService.class);
}
我们从 ApplicationContext
中获取 LoggingService
的实例,并将其赋值给过 Filter
的 loggingService
字段。当我们需要在非 Spring 管理的组件(如 Servlet 或 Filter)中访问 Spring 管理的 Bean ,而又无法使用注解或构造函数注入时,这种方法就非常有用。
值得注意的是,这种方法将 Filter 与 Spring 耦合在一起,在某些情况下可能并不理想。
6、在配置类中使用过 FilterRegistrationBean
FilterRegistrationBean
用于在 Servlet 容器中以编程式注册 Servlet Filter。它提供了一种在应用的配置类中动态配置 Filter 注册的方法。
通过使用 @Bean
和 @Autowired
对该方法进行注解,LoggingService
会自动注入到该方法中,从而将其传递给 LoggingFilter
构造函数。
在配置类中配置 FilterRegistrationBean
:
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilterRegistration(LoggingService loggingService) {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter(loggingService));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
然后,需要在 LoggingFilter
中加入一个构造函数,以支持上述配置:
public LoggingFilter(LoggingService loggingService) {
this.loggingService = loggingService;
}
这种方法集中了 Filter
及其依赖的配置,使代码更有条理,更易于维护。
7、在 Servlet Filter 中使用 DelegatingFilterProxy
DelegatingFilterProxy
是一个 Servlet Filter,允许将控制权传递给能访问Spring ApplicationContext
的 Filter
类。
配置 DelegatingFilterProxy
,将其委托给名为 loggingFilter
的 Spring 管理的 Bean。当应用启动时,Spring 会使用 FilterRegistrationBean
向 Servlet 容器注册 Filter:
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> loggingFilterRegistration() {
FilterRegistrationBean<DelegatingFilterProxy> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DelegatingFilterProxy("loggingFilter"));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
为之前定义的 Filter
使用相同的 Bean 名称:
@Component("loggingFilter")
public class LoggingFilter implements Filter {
// Filter 方法
}
这种方法允许我们使用 Spring 的依赖注入来管理 loggingFilter
Bean。
8、各种方式之间的比较
DelegatingFilterProxy
方法既不同于 SpringBeanAutowiringSupport
,也不同于直接使用 WebApplicationContextUtils
,它将 Filter
的执行委托给 Spring 管理的 Bean,从而允许我们使用 Spring 的依赖注入。
DelegatingFilterProxy
更符合典型的 Spring 应用架构,可以更简洁地分离关注点。FilterRegistrationBean
方法允许对 Filter
的依赖注入进行更多控制,并集中配置依赖。
相比之下,SpringBeanAutowiringSupport
和 WebApplicationContextUtils
是更底层的方法,在某些情况下非常有用,比如我们需要更多地控制 Filter
的初始化过程或者直接访问 ApplicationContext
。然而,它们需要更多的手动设置,并且无法提供与 Spring 的依赖注入机制相同的集成级别。
9、总结
本文介绍了将 Spring Bean 自动装配到 Servlet Filter 的不同方法。
每种方法都有其优势和局限性,选择哪种方法取决于应用的具体要求和限制。总的来说,这些方法可以将 Spring 管理的 Bean 无缝集成到 Servlet Filter 中,从而提高应用的灵活性和可维护性。
Ref:https://www.baeldung.com/spring-autowire-bean-servlet-filter