1. 簡介
Servlet 過濾器提供了一種強大的機制,用於攔截和修改傳入的請求。但是,在這些過濾器中訪問 Spring 管理的 Bean 可能會帶來挑戰。
在本教程中,我們將探索在Servlet 過濾器中無縫獲取 Spring Bean 的各種方法,這在基於 Spring 的 Web 應用程序中是一個常見的需求。
2. 理解 @Autowired 在 Servlet 過濾器中的侷限性
雖然 Spring 的依賴注入機制,@Autowired,是一種方便的方式來注入依賴到 Spring 管理的組件中,但它與 Servlet 過濾器並不無縫集成。這是因為 Servlet 過濾器由 Servlet 容器初始化,通常在 Spring 的 ApplicationContext 完全加載和初始化之前。
Servlet 過濾器在容器實例化時,Spring 容器可能尚未完全可用,導致嘗試使用 @Autowired 註解時出現空指針或未初始化依賴。讓我們探討在 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","[email protected]"),
new User("2","Smith","[email protected]"));
}
}
我們將會設置測試,以檢查我們的 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. 使用 <em>SpringBeanAutowiringSupport</em> 在 <em>Servlet</em> 過濾器中
Spring 的 SpringBeanAutowiringSupport 類提供對將依賴注入到非 Spring 管理的類(如 過濾器 和 Servlet)的支持。 通過使用該類,Spring 可以將依賴(例如 LoggingService,這是一個 Spring 管理的 Bean)注入到 LoggingFilter 中。
init 方法用於初始化一個 過濾器 實例,並且我們將覆蓋該方法在 LoggingFilter 中使用 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. 使用 WebApplicationContextUtils 在 Servlet 過濾器中
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 實例,並將其分配給 filter 的 loggingService 字段。 這種方法在我們需要在非 Spring 管理的組件(如 Servlet 或 Filter)中訪問 Spring 管理的 Bean,並且無法使用註解式或構造器注入時非常有用。
需要注意的是,這種方法緊密地將 Filter 與 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. 使用 <em>DelegatingFilterProxy</em> 在 <em>Servlet</em> 過濾器中
<em>DelegatingFilterProxy</em> 是一個 <em>Servlet</em> 過濾器,它允許將控制傳遞給具有訪問 Spring <em>ApplicationContext</em> 權限的 <em>Filter</em> 類。
讓我們配置 <em>DelegatingFilterProxy</em> 以委託到名為“loggingFilter”的 Spring 管理 Bean。FilterRegistrationBean 用於 Spring 在應用程序啓動時將過濾器註冊到 <em>Servlet</em> 容器中:
@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。
8. 比較 Servlet 過濾器中依賴注入方法
DelegatingFilterProxy 方法與 SpringBeanAutowiringSupport 以及直接使用 WebApplicationContextUtils 的方式不同,它通過委託執行過濾器的過程,將過濾器執行委託給 Spring 管理的 Bean,從而允許我們利用 Spring 的依賴注入。
DelegatingFilterProxy 更符合典型的 Spring 應用架構,並允許更清晰地分離關注點。 FilterRegistrationBean 方法則提供了對過濾器依賴注入的更多控制,並集中了依賴項的配置。
相比之下,SpringBeanAutowiringSupport 和 WebApplicationContextUtils 則是更底層的方案,在我們需要對過濾器的初始化過程進行更多控制或直接訪問 ApplicationContext 時,它們可能仍然有用。 但是,它們需要更多的手動設置,並且不提供與 Spring 依賴注入機制相同的集成程度。
9. 結論
在本文中,我們探討了將 Spring Bean 自動注入到 Servlet 過濾器中的不同方法。每種方法都有其自身的優勢和侷限性,選擇哪種方法取決於應用程序的特定要求和約束。總的來説,它們使 Spring 管理的 Bean 與 Servlet 過濾器無縫集成,從而增強了應用程序的靈活性和可維護性。