在 SpringMVC 開發中,Controller 方法若需存取 Session 資料,最直觀的方式是在方法簽名中直接宣告 HttpSession 參數,框架會自動注入。例如:
@RequestMapping(value = "/logout")
public String logout(HttpSession session) {
session.removeAttribute("user");
return "/login";
}
雖然這樣寫法簡單直接,但會讓 Controller 強依賴 Servlet API,不利於單元測試與程式碼的純粹性(POJO 化)。為了解耦,我們可以自訂註解搭配 HandlerMethodArgumentResolver 實現參數自動解析,從 Session 中取值並注入 Controller 方法參數。
@SessionScope,可標註在方法或參數上,屬性 value 指定 Session 中的鍵名。SessionScopeMethodArgumentResolver 實作 HandlerMethodArgumentResolver 介面,在 supportsParameter 判斷參數或方法是否帶有該註解,在 resolveArgument 中從 NativeWebRequest 的 Session 作用域取值並回傳。RequestMappingHandlerAdapter 的 customArgumentResolvers 列表。@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionScope {
String value();
}
public class SessionScopeMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final Logger logger = LoggerFactory.getLogger(SessionScopeMethodArgumentResolver.class);
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(SessionScope.class)
|| parameter.getMethodAnnotation(SessionScope.class) != null;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String attrName = null;
SessionScope ann = parameter.getParameterAnnotation(SessionScope.class);
if (ann != null) {
attrName = ann.value();
} else {
ann = parameter.getMethodAnnotation(SessionScope.class);
if (ann != null) attrName = StringUtils.defaultString(ann.value());
}
if (attrName != null) {
return webRequest.getAttribute(attrName, RequestAttributes.SCOPE_SESSION);
}
return null;
}
}
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.example.resolver.SessionScopeMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
注意: mvc:annotation-driven 必須在 mvc:default-servlet-handler 之前宣告,否則可能導致靜態資源處理異常。
在方法層級標註,參數名自動對應 Session 鍵值:
@RequestMapping(value = "/index")
@SessionScope("currentUser")
public ModelAndView index(User currentUser) {
ModelAndView mav;
if (currentUser == null || currentUser.getId() == null) {
mav = new ModelAndView("/login");
} else {
mav = new ModelAndView("/index");
}
return mav;
}
或在參數層級標註:
@RequestMapping(value = "/welcome")
public String welcome(@SessionScope("currentUser") User currentUser) {
return "/main";
}
Session 資料的寫入(如登入後放入使用者物件)仍建議搭配 @SessionAttributes 與 ModelMap 方式,或整合 Apache Shiro 等安全框架由其統一管理 Subject 與 Session,架構更清晰。