Spring2.5访问Session属性的四种策略WEB 应用通常会引入 Session,用来在服务端和客户端之间保存一系列动作
Spring2.5访问Session属性的四种策略
WEB 应用通常会引入 Session,用来在服务端和客户端之间保存一系列动作/消息的状态,比如网上购物维护 user 登录信息直到 user 退出。在 user 登录后,Session 周期里有很多 action 都需要从 Session 中得到 user,再验证身份权限,或者进行其他的操作。这其中就会涉及到程序去访问 Session属性的问题。在java中,Servlet 规范提供了 HttpSession对象来满足这种需求。开发人员可以从 HttpServletRquest对象得到 HttpSession,再从HttpSession中得到状态信息。
还是回到购物车的例子,假设在 controller 某个方法(本文简称为action)中我们要从HttpSession中取到user对象。如果基于Servlet,标准的代码会是这样的:
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {User user = (User)req.getSession().getAttribute("currentUser");//...}
这样的代码在传统的Servlet程序中是很常见的:因为使用了 Servlet API,从而对 Servlet API产生依赖。这样如果我们要测试 action,我们就必须针对 HttpServletRequest、HttpServletResponse 和 HttpSession类提供 mock 或者 stub 实现。当然现在已经有很多开源的 Servlet 测试框架帮助我们减轻这个痛苦,包括 Spring 就自带了对了这些类的 stub 实现,但那还是太冗繁琐碎了。那有没有比较好的办法来让我们的 controller 更 POJO,让我们的 action 脱离 Servlet API 依赖,更有益于测试和复用呢?我们来看看在 Spring2.5 中访问 Session 属性的几种解决方案,并将在本博的后续文章继续探究解决方案选择背后的深层含义。
(一)通过方法参数传入HttpServletRequest对象或者HttpSession对象
笔者的前一篇文章已经简单介绍了Spring2.5的annotation使得 controller 摆脱了 Servlet API 对方法参数的限制,这里就不赘述了。有兴趣的同学可以参考<a href="">这里</a>。Spring对annotationed的 action 的参数提供自动绑定支持的参数类型包括 Servlet API 里面的 Request/Response/HttpSession(包含Request、Response在Servlet API 中声明的具体子类)。于是开发人员可以通过在 action 参数中声明 Request 对象或者 HttpSession 对象,来让容器注入相应的对象。
action 的代码如下:
@RequestMappingpublic void hello(HttpSession session){User user = (User)session.getAttribute("currentUser");//...}
优点:
1. 程序中直接得到底层的 Request/HttpSession 对象,直接使用 Servlet API 规范中定义的方法操作这些对象中的属性,直接而简单。
2. action 需要访问哪些具体的 Session 属性,是由自己控制的,真正精确到 Session 中的每个特定属性。
不足:
1. 程序对 Servlet API 产生依赖。虽然 controller 类已经不需要从 HttpServlet 继承,但仍需要 Servlet API 才能完成编译运行,乃至测试。
2. 暴露了底层 Servlet API,暴露了很多并不需要的底层方法和类,开发人员容易滥用这些 API。
(二)通过定制拦截器(Interceptor)在controller类级别注入需要的User对象
Interceptor 是 Spring 提供的扩展点之一,SpringMVC 会在 handle 某个 request 前后调用在配置中定义的 Interceptor 完成一些切面的工作,比如验证用户权限、处理分发等,类似于 AOP。那么,我们可以提取这样一个“横切点”,在 SpringMVC 调用 action 前,在 Interceptor 的 preHandle 方法中给 controller 注入 User 成员变量,使之具有当前登录的 User 对象。
此外还需要给这些特定 controller 声明一类 interface,比如 IUserAware。这样开发人员就可以只针对这些需要注入 User 对象的 controller 进行注入增强。
IUserAware 的代码:
public interface IUserAware {public void setUser();}
controller 的代码:
@Controllerpublic GreetingController implements IUserAware {private User user;public void setUser(User user){this.user = user;}@RequestMappingpublic void hello(){//user.sayHello();}//...}
Interceptor 的代码:
public class UserInjectInterceptor extends HandlerInterceptorAdapter {@Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { if (handler.isAssignableFrom(IUserAware)){ User user = (User)httpServletRequest.getSession().getAttribute("currentUser"); IUserAware userAware = (IUserAware) handler; userAware.setUser(user); } return super.preHandle(httpServletRequest, httpServletResponse, handler); } //...}
为了让 SpringMVC 能调用开发人员定义的 Interceptor,我们还需要在 SpringMVC 配置文件中声明该 Interceptor,比如:
<bean name="code">@RequestMappingpublic void hello(User user){//user.sayHello()}
Resolver 的代码如下:
public class UserArgumentResolver implements WebArgumentResolver { public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception { if (methodParameter.getParameterType().equals(User.class)) { return webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_SESSION); } return UNRESOLVED; }}
配置文件的相关配置如下:
<bean name="code">@Controller@SessionAttributes("currentUser")public class GreetingController{@RequestMappingpublic void hello(@ModelAttribute("currentUser") User user){//user.sayHello()}//...}
使用这种方案,还需要在 SpringMVC 配置文件中,在定义 ViewResolver 时,加上 p:allowSessionOverride="true",这样如果你对 User 对象做了修改,就会在渲染 View 的同时覆写 Session 中的相关属性。
优点:
1. 具备第二种方案的所有优点
2. 使用 Annotation 声明对 Session 特定属性的存取,每个 action 只需要声明自己想要的 Session 属性。
3. 其他人能很容易地从 action 的参数列表得知 action 所需要的依赖,API 更清晰易懂。
不足:
1. 对于相同属性的 Session 对象,需要在每个 action 上定义。
2. 这种方案并不是 SpringMVC 的初衷,因此有可能会引起一些争议。
纵观这四类方法,我们可以看出我们对 Session 属性的访问控制设置,是从所有 Servlet,到某一类型的 controller 的成员变量,到所有 action 的某一类型参数,再到具体 action 的具体对象。每种方案都有各自的优点和不足:第一种方案虽然精确,但可惜引入了对 Servlet API 的依赖,不利于 controller 的测试和逻辑复用。第二、三种方案虽然解决了对 Servlet API 的依赖,也分别在 controller 和 action 级别上提供了对 Session 属性的访问,但注入粒度在一定程度上还是不够细,要想对具体属性进行访问可能会比较繁琐。不过,这在另一方面也提供了简便而统一的方法来对一系列相同类型的参数进行注入。第四种方案通过使用 Annotation,不仅摆脱了 Servlet API 的依赖,而且在 action 级别上提供了对 Session 具体属性的访问控制。但是这种访问有可能会粒度过细,需要在很多不同 action 上声明相同的 annotation。而且,毕竟这种用法并不是 SpringMVC 的初衷和推荐的,可能会带来一些争议。
本文演示了 Spring2.5 访问 Session 属性的几种不同解决方案,并分析了各自的优点和不足。本文并不打算对这些解决方案评出对错,只是试图列出在选择方案时的思维过程以及选择标准。每种方案都能满足某一类上下文的需求,在特定的开发环境和团队中都可能会是最优的选择。但是笔者还是发现,整个过程中,一些平常容易忽视的 OOP 的准则或者原则在发挥着效应,鉴于本文篇幅已经较长,就留到后续文章中继续探讨解决方案选择背后的深层含义。