Spring中将bean自动装配到ServletFilter的4种方法

Servlet过滤器提供了一种强大的机制来拦截和操作传入请求。然而,在这些过滤器中访问 Spring 管理的 bean 可能会带来挑战。

在本教程中,我们将探索在Servlet过滤器中无缝获取 Spring bean 的各种方法,这是基于 Spring 的 Web 应用程序中的常见要求。

@Autowired
虽然 Spring 的依赖注入机制@Autowired是将依赖项注入到 Spring 管理的组件中的一种便捷方法,但它不能与Servlet过滤器无缝配合。这是因为Servlet过滤器是由Servlet容器初始化的,通常是在 Spring 的ApplicationContext完全加载和初始化之前。

因此,当容器实例化Servlet过滤器时,Spring 上下文可能尚不可用,从而在尝试使用@Autowired注释时导致 null 或未初始化的依赖项。让我们探索在Servlet过滤器中访问 Spring bean 的替代方法。

设置
让我们创建一个通用的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@email.com"),
          new User(
"2","Smith","smith@email.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 上下文尚不可用。我们将在以下部分中探讨解决此问题的各种选项。

1、在Servlet Filter中使用SpringBeanAutowiringSupport
Spring 的SpringBeanAutowiringSupport类提供对非 Spring 管理的类(例如Filter和Servlet)的依赖注入的支持。通过使用此类,Spring 可以将依赖项(例如LoggingService(Spring 管理的 bean))注入到LoggingFilter中。

init方法用于初始化Filter实例,我们将在 LoggingFilter 中重写此方法以使用SpringBeanAutowiringSupport:

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
      filterConfig.getServletContext());
}

processInjectionBasedOnServletContext方法使用与ServletContext关联的ApplicationContext来执行自动装配。它首先从ServletContext检索 ApplicationContext ,然后使用它将依赖项自动装配到目标对象中。此过程涉及检查目标对象的字段中是否有@Autowired注释,然后从ApplicationContext解析并注入相应的 beans 。

该机制允许非 Spring 管理的对象(如过滤器和 servlet)从 Spring 的依赖注入功能中受益。

2、在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 紧密耦合,在某些情况下可能并不理想。

3、在配置中使用FilterRegistrationBean
FilterRegistrationBean用于以编程方式在 servlet 容器中注册Servlet过滤器。它提供了一种在应用程序的配置类中动态配置过滤器注册的方法。

通过使用@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;
}

这种方法集中了过滤器及其依赖项的配置,使代码更有组织性并且更易于维护。

4、在Servlet Filter中使用DelegatingFilterProxy
DelegatingFilterProxy 是一个Servlet过滤器,它允许将控制传递给有权 访问 Spring ApplicationContext的Filter类。

让我们配置DelegatingFilterProxy以委托给名为“loggingFilter”的 Spring 管理的 bean 。Spring 使用FilterRegistrationBean在应用程序启动时向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 {
   
// standard methods
}

这种方法允许我们使用Spring的依赖注入来管理loggingFilter bean。

比较Servlet Filter中的依赖注入方法
DelegatingFilterProxy方法与SpringBeanAutowiringSupport和直接使用WebApplicationContextUtils的不同之处在于它如何将过滤器的执行委托给 Spring 管理的 bean,从而允许我们使用 Spring 的依赖注入。

DelegatingFilterProxy与典型的 Spring 应用程序架构更好地保持一致,并允许更清晰地分离关注点。FilterRegistrationBean方法允许对过滤器的依赖项注入进行更多控制,并集中依赖项的配置。

相比之下,SpringBeanAutowiringSupport和WebApplicationContextUtils是更底层的方法,在我们需要对过滤器的初始化过程进行更多控制或想要直接访问ApplicationContext 的某些场景中非常有用。然而,它们需要更多的手动设置,并且不提供与 Spring 依赖注入机制相同级别的集成。