如何在 Servlet 過濾器中自動組裝 Spring Bean
一、簡介
Servlet
過濾器提供了一個強大的機制來攔截和操作傳入請求。然而,在這些過濾器中存取 Spring 管理的 bean 可能會帶來挑戰。
在本教程中,我們將探索在Servlet
過濾器中無縫取得 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","[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.在Servlet
Filter中使用SpringBeanAutowiringSupport
Spring 的SpringBeanAutowiringSupport
類別提供對非 Spring 管理的類別(例如Filters
和Servlets
的依賴注入的支援。透過使用此類,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 的依賴注入功能中受益。
5. 在Servlet
Filter中使用WebApplicationContextUtils
WebApplicationContextUtils
提供了一個實用方法,用於擷取與 ServletContext 關聯的ApplicationContext
ServletContext. The
包含 Spring 容器管理ApplicationContext
所有 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
和@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;
}
這種方法集中了過濾器及其依賴項的配置,使程式碼更有組織性並且更易於維護。
7. 在Servlet
Filter中使用DelegatingFilterProxy
DelegatingFilterProxy
是一個Servlet
過濾器,它允許將控制權傳遞給有權存取 Spring ApplicationContext
的Filter
類別。
讓我們設定DelegatingFilterProxy
以委託給名為“loggingFilter”. FilterRegistrationBean
Spring 使用“loggingFilter”. 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。
8. 比較Servlet
Filter 中的依賴注入方法
DelegatingFilterProxy
方法與SpringBeanAutowiringSupport
和直接使用WebApplicationContextUtils
的不同之處在於它如何將過濾器的執行委託給 Spring 管理的 bean,從而允許我們使用 Spring 的依賴注入。
DelegatingFilterProxy
與典型的 Spring 應用程式架構更好地保持一致,並允許更清晰地分離關注點。 FilterRegistrationBean
方法允許對過濾器的依賴項注入進行更多控制,並集中依賴項的配置。
相較之下, SpringBeanAutowiringSupport
和WebApplicationContextUtils
是更底層的方法,在我們需要對過濾器的初始化過程進行更多控製或想要直接存取ApplicationContext
某些場景中非常有用。然而,它們需要更多的手動設置,並且不提供與 Spring 依賴注入機制相同層級的整合。
9. 結論
在本文中,我們探討了將 Spring bean 自動組裝到Servlet
過濾器中的不同方法。每種方法都有其優點和局限性,方法的選擇取決於應用程式的特定要求和約束。總的來說,它們能夠將 Spring 管理的 bean 無縫整合到Servlet
過濾器中,從而增強應用程式的靈活性和可維護性。
與往常一樣,程式碼可以在 GitHub 上取得。