1. Introduction
1.导言
Servlet filters offer a powerful mechanism for intercepting and manipulating incoming requests. However, accessing Spring-managed beans within these filters can pose a challenge.
Servlet 过滤器为拦截和处理传入请求提供了强大的机制。但是,在这些过滤器中访问 Spring 管理的 Bean 可能会带来挑战。
In this tutorial, we’ll explore various approaches to seamlessly obtain Spring beans within a Servlet filter, which is a common requirement in Spring-based web applications.
在本教程中,我们将探讨在 Servlet 过滤器中无缝获取 Spring Bean 的各种方法,这是基于 Spring 的 Web 应用程序中的常见要求。
2. Understanding the Limitations of @Autowired in Servlet Filter
2.了解 Servlet 过滤器中 @Autowired 的限制
While Spring’s dependency injection mechanism, @Autowired, is a convenient way to inject dependencies into Spring-managed components, it doesn’t work seamlessly with Servlet filters. This is because Servlet filters are initialized by the Servlet container, typically before Spring’s ApplicationContext is fully loaded and initialized.
虽然 Spring 的依赖注入机制 @Autowired 是将依赖注入 Spring 管理组件的便捷方法,但它无法与 Servlet 筛选器无缝配合。这是因为 Servlet 筛选器由 Servlet 容器初始化,通常在 Spring 的 ApplicationContext 被完全加载和初始化之前。
As a result, when the container instantiates a Servlet filter, the Spring context may not yet be available, leading to null or uninitialized dependencies when attempting to use @Autowired annotations. Let’s explore alternative approaches for accessing Spring beans within Servlet filters.
因此,当容器实例化 Servlet 筛选器时,Spring 上下文可能尚未可用,从而导致在尝试使用 @Autowired 注释时,依赖关系为空或未初始化。让我们探讨一下在 Servlet 筛选器中访问 Spring Bean 的其他方法。
3. Setup
3.设置
Let’s create a common LoggingService that will be autowired into our filter:
让我们创建一个通用的 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);
}
}
We’ll then create our filter, which will intercept incoming HTTP requests to log the HTTP method and URI details using the LoggingService dependency:
然后,我们将创建过滤器,它将拦截传入的 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);
}
}
Let’s expose a RestController that returns a list of users:
让我们公开一个 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"));
}
}
We’ll set up our test to check if our LoggingService was autowired successfully into our filter:
我们将设置测试,以检查 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);
}
}
However, at this stage, it’s possible that the LoggingService won’t be injected into the LoggingFilter because the Spring context wasn’t available yet. We’ll explore the various options for resolving this issue in the following sections.
但是,在此阶段,LoggingService 有可能不会被注入到 LoggingFilter 中,因为 Spring 上下文尚未可用。我们将在接下来的章节中探讨解决这一问题的各种方案。
4. Using SpringBeanAutowiringSupport in Servlet Filter
4.在 Servlet 过滤器中使用 SpringBeanAutowiringSupport
Spring’s SpringBeanAutowiringSupport class provides support for dependency injection into non-Spring-managed classes such as Filters and Servlets. By using this class, Spring can inject dependencies, such as the LoggingService, which is a Spring-managed bean, into the LoggingFilter.
Spring的SpringBeanAutowiringSupport类支持将依赖关系注入到非 Spring 管理的类中,例如 Filters 和 Servlets 。通过使用该类,Spring 可以向 LoggingFilter 中注入依赖关系,例如 Spring 管理的 Bean LoggingService。
The init method is used to initialize a Filter instance, and we’ll override this method in the LoggingFilter to use SpringBeanAutowiringSupport:
init 方法用于初始化 Filter 实例,我们将在 LoggingFilter 中重载该方法以使用 SpringBeanAutowiringSupport :
@Override
public void init(FilterConfig filterConfig) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
filterConfig.getServletContext());
}
The processInjectionBasedOnServletContext method uses the ApplicationContext associated with the ServletContext to perform the autowiring. It first retrieves the ApplicationContext from the ServletContext and then uses it to autowire the dependencies into the target object. This process involves inspecting the target object’s fields for @Autowired annotations and then resolving and injecting the corresponding beans from the ApplicationContext.
processInjectionBasedOnServletContext 方法使用与 ServletContext 关联的 ApplicationContext 来执行自动连接。它首先从 ServletContext 中检索 ApplicationContext,然后使用它将依赖关系自动连接到目标对象。这一过程包括检查目标对象字段中的 @Autowired 注解,然后解析并从 ApplicationContext 中注入相应的 Bean。
This mechanism allows non-Spring-managed objects, like filters and servlets, to benefit from Spring’s dependency injection capabilities.
这种机制允许非 Spring 管理对象(如过滤器和 servlet)受益于 Spring 的依赖注入功能。
5. Using WebApplicationContextUtils in Servlet Filter
5.在 Servlet 过滤器中使用 WebApplicationContextUtils
WebApplicationContextUtils provides a utility method that retrieves the ApplicationContext associated with the ServletContext. The ApplicationContext contains all the beans managed by the Spring container.
WebApplicationContextUtils 提供了一个实用程序方法,用于检索与 ServletContext 关联的 ApplicationContext 。 ApplicationContext 包含 Spring 容器管理的所有 Bean。
Let’s override the init method of the LoggingFilter class:
让我们重载 LoggingFilter 类的 init 方法:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
loggingService = WebApplicationContextUtils
.getRequiredWebApplicationContext(filterConfig.getServletContext())
.getBean(LoggingService.class);
}
We retrieve an instance of LoggingService from the ApplicationContext and assign it to the loggingService field of the filter. This approach is useful when we need to access Spring-managed beans in a non-Spring-managed component, such as a Servlet or a Filter, and we cannot use annotation-based or constructor injection.
我们从 ApplicationContext 中检索 LoggingService 的实例,并将其分配给过滤器的 loggingService 字段。当我们需要在非 Spring 管理的组件(如 Servlet 或 Filter 中访问 Spring 管理的 Bean,并且无法使用基于注解或构造函数注入时,这种方法非常有用。
It should be noted that this approach tightly couples the filter with Spring, which may not be ideal in some cases.
需要注意的是,这种方法将过滤器与 Spring 紧密结合,在某些情况下可能并不理想。
6. Using FilterRegistrationBean in Configuration
6.在配置中使用 FilterRegistrationBean</em
FilterRegistrationBean is used to register Servlet filters in the servlet container programmatically. It provides a way to configure the filter registration dynamically in the application’s configuration classes.
FilterRegistrationBean 用于在 servlet 容器中以编程方式注册 Servlet 过滤器。它提供了一种在应用程序的配置类中动态配置过滤器注册的方法。
By annotating the method with @Bean and @Autowired, the LoggingService is automatically injected into the method, allowing it to be passed to the LoggingFilter constructor. Let’s set up the method for FilterRegistrationBean in our config class:
通过使用 @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;
}
We’ll then include a constructor in our LoggingFilter to support the above configuration:
然后,我们将在 LoggingFilter 中加入一个构造函数,以支持上述配置:
public LoggingFilter(LoggingService loggingService) {
this.loggingService = loggingService;
}
This approach centralizes the configuration of filters and their dependencies, making the code more organized and easier to maintain.
这种方法集中配置了过滤器及其依赖关系,使代码更有条理,更易于维护。
7. Using DelegatingFilterProxy in Servlet Filter
7.在Servlet过滤器中使用DelegatingFilterProxy</em
The DelegatingFilterProxy is a Servlet filter that allows passing control to Filter classes that have access to the Spring ApplicationContext.
DelegatingFilterProxy 是一个 Servlet 过滤器,允许将控制传递给可访问 Spring ApplicationContext 的 Filter 类。
Let’s configure the DelegatingFilterProxy to delegate to a Spring-managed bean named “loggingFilter”. FilterRegistrationBean is used by Spring to register the filter with the Servlet container when the application starts up:
让我们对 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;
}
Let’s use the same bean name for our filter defined earlier:
让我们为之前定义的过滤器使用相同的 bean 名称:
@Component("loggingFilter")
public class LoggingFilter implements Filter {
// standard methods
}
This approach allows us to use Spring’s dependency injection to manage the loggingFilter bean.
这种方法允许我们使用 Spring 的依赖注入来管理 loggingFilter Bean。
8. Comparing Dependency Injection Approaches in Servlet Filter
8.比较 Servlet 过滤器中的依赖注入方法
The DelegatingFilterProxy approach differs from both SpringBeanAutowiringSupport and the direct use of WebApplicationContextUtils in how it delegates the filter’s execution to a Spring-managed bean, allowing us to use Spring’s dependency injection.
DelegatingFilterProxy 方法不同于 SpringBeanAutowiringSupport 和直接使用 WebApplicationContextUtils 方法,它将过滤器的执行委托给 Spring 管理的 Bean,从而允许我们使用 Spring 的依赖注入。
The DelegatingFilterProxy is better aligned with the typical Spring application architecture and allows for a cleaner separation of concerns. The FilterRegistrationBean approach allows more control over the dependency injection of the filter and centralizes the configuration of dependencies.
DelegatingFilterProxy更符合典型的 Spring 应用程序架构,并允许更简洁地分离关注点。FilterRegistrationBean 方法允许对过滤器的依赖注入进行更多控制,并集中配置依赖关系。
In contrast, SpringBeanAutowiringSupport and WebApplicationContextUtils are more low-level approaches that can be useful in certain scenarios where we need more control over the filter’s initialization process or want to access the ApplicationContext directly. However, they require more manual setup and do not provide the same level of integration with Spring’s dependency injection mechanism.
相比之下,SpringBeanAutowiringSupport 和 WebApplicationContextUtils 是更底层的方法,在某些情况下可能非常有用,例如我们需要对过滤器的初始化过程进行更多控制,或希望直接访问 ApplicationContext 。但是,这些方法需要更多的手动设置,并且无法与 Spring 的依赖注入机制实现相同级别的集成。
9. Conclusion
9.结论
In this article, we’ve explored the different approaches to autowire Spring beans into Servlet Filters. Each approach has its advantages and limitations, and the choice of approach depends on the specific requirements and constraints of the application. Overall, they enable seamless integration of Spring-managed beans into Servlet filters, enhancing the flexibility and maintainability of applications.
在本文中,我们探讨了将 Spring Bean 自动连接到 Servlet 过滤器的不同方法。每种方法都有其优势和局限性,选择哪种方法取决于应用程序的具体要求和限制。总的来说,这些方法可将 Spring 管理的 Bean 无缝集成到 Servlet 过滤器中,从而提高应用程序的灵活性和可维护性。
As always, the code is available over on GitHub.
与往常一样,代码可在 GitHub 上获取。