Spring中將bean自動裝配到ServletFilter的4種方法

banq發表於2024-03-17

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(<font>"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(<font>"/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,<font>"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(<font>"/*");
    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(<font>"loggingFilter"));
    registrationBean.addUrlPatterns(
"/*");
    return registrationBean;
}

讓我們為之前定義的過濾器使用相同的 bean 名稱:

@Component(<font>"loggingFilter")
public class LoggingFilter implements Filter {
   
// standard methods<i>
}

這種方法允許我們使用Spring的依賴注入來管理loggingFilter bean。

比較Servlet Filter中的依賴注入方法
DelegatingFilterProxy方法與SpringBeanAutowiringSupport和直接使用WebApplicationContextUtils的不同之處在於它如何將過濾器的執行委託給 Spring 管理的 bean,從而允許我們使用 Spring 的依賴注入。

DelegatingFilterProxy與典型的 Spring 應用程式架構更好地保持一致,並允許更清晰地分離關注點。FilterRegistrationBean方法允許對過濾器的依賴項注入進行更多控制,並集中依賴項的配置。

相比之下,SpringBeanAutowiringSupport和WebApplicationContextUtils是更底層的方法,在我們需要對過濾器的初始化過程進行更多控制或想要直接訪問ApplicationContext 的某些場景中非常有用。然而,它們需要更多的手動設定,並且不提供與 Spring 依賴注入機制相同級別的整合。

 

相關文章