在嵌入式 Jetty 9 或無 web.xml 的 Spring Boot 應用中,需以程式化方式註冊 `HttpSessionListener` 與 `Filter`。本文提供完整代碼範例、註冊 API 解析、生命週期觸發條件與常見坑點,助你快速掌握 Jetty 組件配置。
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("sessionCreated: " + se.getSession().getId());
// 可在此初始化 Session 屬性、計數在線用戶等
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("sessionDestroyed: " + se.getSession().getId());
// 清理資源、下線統計等
}
}
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.SessionHandler;
public class JettyServer {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// 關鍵:獲取 SessionHandler 並添加 EventListener
context.getSessionHandler().addEventListener(new MyHttpSessionListener());
// 添加 Servlet 測試
context.addServlet(TestServlet.class, "/test");
server.start();
server.join();
}
}
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 初始化: " + filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
if (req instanceof HttpServletRequest) {
HttpServletRequest httpReq = (HttpServletRequest) req;
System.out.println("攔截請求: " + httpReq.getServletPath());
// 可在此做:編碼統一、登入校驗、日誌記錄、XSS 防護等
}
chain.doFilter(req, resp); // 必須調用,否則請求鏈中斷
}
@Override
public void destroy() {
System.out.println("Filter 銷毀");
}
}
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import jakarta.servlet.DispatcherType;
import java.util.EnumSet;
// 在上述 ServletContextHandler 中添加:
context.addFilter(
new FilterHolder(new MyFilter()), // 包裝 Filter 實例
"/*", // URL 模式
EnumSet.allOf(DispatcherType.class) // 派發類型:REQUEST, FORWARD, INCLUDE, ERROR, ASYNC
);
| 類型 | 觸發場景 |
|---|---|
| REQUEST | 客戶端直接請求(預設) |
| FORWARD | `RequestDispatcher.forward()` 轉發 |
| INCLUDE | `RequestDispatcher.include()` 包含 |
| ERROR | 錯誤頁面派發 |
| ASYNC | 異步請求 (`startAsync()`) |
使用 `EnumSet.allOf(DispatcherType.class)` 覆蓋所有場景;若僅需攔截外部請求,用 `EnumSet.of(DispatcherType.REQUEST)`。
Spring Boot 2.x/3.x 內嵌 Jetty 時,推薦使用 `ServletContextInitializer` 或 `FilterRegistrationBean`:
@Configuration
public class JettyConfig {
@Bean
public ServletContextInitializer sessionListenerInitializer() {
return servletContext -> {
servletContext.addListener(MyHttpSessionListener.class);
};
}
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter());
bean.addUrlPatterns("/*");
bean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
bean.setOrder(1);
return bean;
}
}
| 現象 | 原因 | 解決 |
|---|---|---|
| Listener 不觸發 | 請求未調用 getSession() | 確認業務代碼主動獲取 Session |
| Filter 不攔截 | URL 模式不匹配 / 請求 404 | 檢查 addFilter urlPatterns、確認 Servlet 映射 |
| Session 丟失 | SessionHandler 未啟用 / Cookie 未攜帶 JSESSIONID | ServletContextHandler.SESSIONS、檢查瀏覽器 Cookie |
| 重複過濾 | DispatcherType 包含 FORWARD 且轉發鏈長 | 按需精確設定 DispatcherType |
Jetty 9 程式化註冊核心 API:
ServletContextHandler.getSessionHandler().addEventListener(listener) 註冊 Session 監聽器。ServletContextHandler.addFilter(FilterHolder, urlPattern, dispatcherTypes) 註冊過濾器。掌握觸發條件(Session 需主動創建、Filter 需路徑匹配且非 404)與 `DispatcherType` 語義,能避免 90% 的配置坑。Spring Boot 專案優先使用 `ServletContextInitializer` / `FilterRegistrationBean` 享受自動配置優勢。