AOP 切面類是普通 Spring Bean,無法直接注入 HttpServletRequest/HttpSession。權限校驗卻必須依賴 Session 中的用戶資訊。如何在切面中優雅地獲取 Web 容器上下文對象?
利用 ThreadLocal 實現線程隔離的上下文儲存,配合 Filter 在請求進入時將 request/response/session 綁定到當前線程,切面即可隨時取用。
public class SysContext {
private static final ThreadLocal<HttpServletRequest> requestLocal = new ThreadLocal<>();
private static final ThreadLocal<HttpServletResponse> responseLocal = new ThreadLocal<>();
public static HttpServletRequest getRequest() { return requestLocal.get(); }
public static void setRequest(HttpServletRequest request) { requestLocal.set(request); }
public static HttpServletResponse getResponse() { return responseLocal.get(); }
public static void setResponse(HttpServletResponse response) { responseLocal.set(response); }
public static HttpSession getSession() {
HttpServletRequest request = getRequest();
return request != null ? request.getSession() : null;
}
// 清理防止內存洩漏(建議在 Filter 最後調用)
public static void clear() {
requestLocal.remove();
responseLocal.remove();
}
}
@WebFilter("/*") // 或在 web.xml 配置
public class GetContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
SysContext.setRequest((HttpServletRequest) request);
SysContext.setResponse((HttpServletResponse) response);
chain.doFilter(request, response);
} finally {
SysContext.clear(); // 關鍵:請求結束清理 ThreadLocal
}
}
// init/destroy 省略
}
<filter>
<filter-name>contextFilter</filter-name>
<filter-class>com.example.filter.GetContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>contextFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 必須放在最後,確保攔截所有請求 -->
</filter-mapping>
@Aspect
@Component
public class AdminAspect {
@Before("execution(* com.example.action.AdminAction.getPrivileges())")
public void adminPrivilegeCheck() throws Throwable {
HttpSession session = SysContext.getSession();
HttpServletRequest request = SysContext.getRequest();
HttpServletResponse response = SysContext.getResponse();
if (session == null) {
throw new IllegalStateException("Session 不存在,請檢查 Filter 配置順序");
}
String userName = (String) session.getAttribute("userName");
if (userName == null || userName.isEmpty()) {
// 無權限:設置錯誤訊息並轉發到錯誤頁
request.setAttribute("msg", "{\"res\":\"無權限\"}");
request.getRequestDispatcher("/jsp/json.jsp").forward(request, response);
return; // 終止後續執行
}
// 有權限:繼續執行目標方法
}
}
<aop:aspectj-autoproxy />
<bean id="adminAspect" class="com.example.aop.AdminAspect" />
ThreadLocal 提供線程獨享的變量副本,天然隔離併發請求finally 區塊調用 SysContext.clear(),否則線程池復用導致內存洩漏、上下文串聯getRequest() 返回 null,切面需做非空防禦Spring 3.2+ 提供 RequestContextHolder 內建實現:
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
原理相同(同樣基於 ThreadLocal),但框架已幫你封裝好,建議優先使用。
「ThreadLocal + Filter」是 Spring AOP 訪問 Web 上下文的經典模式,理解其背後的「線程邊界」本質,能舉一反三解決類似問題(如審計日誌、多租戶上下文傳遞等)。