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上获取。


原始标题:How to Autowire a Spring Bean in a Servlet Filter