在 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)。

Filterinit 方法用于初始化 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 关联的 ApplicationContextApplicationContext 包含 Spring 容器管理的所有 Bean。

覆写 LoggingFilter 类的 init 方法:

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    loggingService = WebApplicationContextUtils
      .getRequiredWebApplicationContext(filterConfig.getServletContext())
      .getBean(LoggingService.class);
}

我们从 ApplicationContext 中获取 LoggingService 的实例,并将其赋值给过 FilterloggingService字段。当我们需要在非 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 ApplicationContextFilter 类。

配置 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 的依赖注入进行更多控制,并集中配置依赖。

相比之下,SpringBeanAutowiringSupportWebApplicationContextUtils 是更底层的方法,在某些情况下非常有用,比如我们需要更多地控制 Filter 的初始化过程或者直接访问 ApplicationContext。然而,它们需要更多的手动设置,并且无法提供与 Spring 的依赖注入机制相同的集成级别。

9、总结

本文介绍了将 Spring Bean 自动装配到 Servlet Filter 的不同方法。

每种方法都有其优势和局限性,选择哪种方法取决于应用的具体要求和限制。总的来说,这些方法可以将 Spring 管理的 Bean 无缝集成到 Servlet Filter 中,从而提高应用的灵活性和可维护性。


Ref:https://www.baeldung.com/spring-autowire-bean-servlet-filter