1. 引言
Servlet过滤器(Filter)提供了拦截和处理请求的强大机制。但在这些过滤器中访问Spring管理的Bean时,可能会遇到一些挑战。本文将探讨几种在Servlet Filter中无缝获取Spring Bean的方法,这是Spring Web应用中的常见需求。
2. 理解Servlet Filter中使用@Autowired的限制
虽然Spring的依赖注入机制(如@Autowired)是向Spring托管组件注入依赖的便捷方式,但它与Servlet过滤器的配合并不完美。这是因为Servlet过滤器由Servlet容器初始化,通常在Spring的ApplicationContext完全加载和初始化之前。
结果就是:当容器实例化Servlet过滤器时,Spring上下文可能尚未可用,导致使用@Autowired注解时依赖为null或未初始化。下面我们探讨在Servlet过滤器中访问Spring Bean的替代方案。
3. 项目设置
首先创建一个将被注入到过滤器中的通用LoggingService:
@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);
}
}
然后创建过滤器,它将拦截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@example.com"),
new User("2","Smith","smith@example.com"));
}
}
最后编写测试用例验证LoggingService是否成功注入到过滤器中:
@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);
}
}
⚠️ 在这个阶段,LoggingService可能无法注入到LoggingFilter中,因为Spring上下文尚未可用。接下来我们将探讨解决这个问题的几种方案。
4. 在Servlet Filter中使用SpringBeanAutowiringSupport
Spring的SpringBeanAutowiringSupport类支持向非Spring管理的类(如Filters和Servlets)进行依赖注入。通过这个类,Spring可以将Spring管理的Bean(如LoggingService)注入到LoggingFilter中。
我们重写LoggingFilter的init方法来使用SpringBeanAutowiringSupport:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
filterConfig.getServletContext());
}
processInjectionBasedOnServletContext
方法使用与ServletContext关联的ApplicationContext执行自动装配。它首先从ServletContext获取ApplicationContext,然后用它来自动装配目标对象的依赖。这个过程包括检查目标对象字段上的@Autowired注解,然后从ApplicationContext解析并注入相应的Bean。
这种机制使非Spring管理的对象(如过滤器和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实例,并将其赋值给过滤器的loggingService字段。当我们需要在非Spring管理的组件(如Servlet或Filter)中访问Spring管理的Bean,且无法使用基于注解或构造函数注入时,这种方法很有用。
需要注意的是,这种方法会使过滤器与Spring紧密耦合,在某些情况下可能不是理想选择。
6. 在配置类中使用FilterRegistrationBean
FilterRegistrationBean用于以编程方式在Servlet容器中注册Servlet过滤器。它提供了一种在应用配置类中动态配置过滤器注册的方法。
通过在方法上使用@Bean注解,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;
}
这种方法集中管理过滤器及其依赖的配置,使代码更有条理且更易于维护。
7. 在Servlet Filter中使用DelegatingFilterProxy
DelegatingFilterProxy是一个Servlet过滤器,允许将控制权传递给可以访问Spring ApplicationContext的Filter类。
我们配置DelegatingFilterProxy将其委托给名为"loggingFilter"的Spring管理Bean。FilterRegistrationBean被Spring用来在应用启动时向Servlet容器注册过滤器:
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> loggingFilterRegistration() {
FilterRegistrationBean<DelegatingFilterProxy> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DelegatingFilterProxy("loggingFilter"));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
为之前定义的过滤器使用相同的Bean名称:
@Component("loggingFilter")
public class LoggingFilter implements Filter {
// 标准方法
}
这种方法允许我们使用Spring的依赖注入来管理loggingFilter Bean。
8. Servlet Filter中依赖注入方法对比
DelegatingFilterProxy方法与SpringBeanAutowiringSupport和直接使用WebApplicationContextUtils的不同之处在于:它将过滤器的执行委托给Spring管理的Bean,使我们能够使用Spring的依赖注入。
DelegatingFilterProxy更符合典型的Spring应用架构,允许更清晰的关注点分离。FilterRegistrationBean方法则提供了对过滤器依赖注入的更多控制,并集中管理依赖配置。
相比之下,SpringBeanAutowiringSupport和WebApplicationContextUtils是更底层的方案,在需要更多控制过滤器初始化过程或直接访问ApplicationContext的场景中很有用。但它们需要更多手动设置,且与Spring依赖注入机制的集成程度较低。
9. 结论
本文探讨了在Servlet Filter中自动装配Spring Bean的几种方法。每种方法都有其优缺点,具体选择取决于应用的需求和约束。总体而言,这些方法实现了Spring管理的Bean与Servlet过滤器的无缝集成,增强了应用的灵活性和可维护性。
本文代码可在GitHub上获取。