本文是《轻量级 Java Web 框架架构设计》的系列博文。
Session 对象对于 Web 应用而言是至关重要的,当我们需要实现跨请求传递数据时,就需要使用它,因为它能保证多个会话之间是隔离的。
比如,在 LoginAction 中,当用户认证通过后,往往需要将 User 对象放入 Session 中:
User user = ... HttpSession session = request.getSession(); session.setAttribute("user", user);
先获取 User 对象,然后从 HttpServletRequest 对象中获取 HttpSession 对象,这样就可以调用 session.setAttribute() 方法,将数据放入 Session 中了。
从 Session 中获取数据,也于此类似,只不过需要调用 session.getAttribute() 方法而已。
总之,我们的 Action 必须与 Servlet API 绑定起来。这样依赖性就加强了,而且也不利于做单元测试。
有什么方法可以在 Action 中无需接触任何的 Servlet API 来操作 Session 对象呢?
以下是我的解决方案。
只需在 DispatcherServlet 中定义一个 ThreadLocal<HttpSession> 变量,在每次请求到来的时候,都从 Request 中获取 Session,并放入 ThreadLocal 中。此外,还需要提供一个 getSession() 的 static 方法,以供外界随时获取 Session。下面是代码片段:
@WebServlet(urlPatterns = "/*", loadOnStartup = 0) public class DispatcherServlet extends HttpServlet { private static final ThreadLocal<HttpSession> sessionContainer = new ThreadLocal<HttpSession>(); ... @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); sessionContainer.set(session); ... } public static HttpSession getSession() { return sessionContainer.get(); } ... }
既然 Session 已经可以随时拿到了,那么我们就写一个 DataContext 类吧,让它去封装 Session 的相关操作,代码如下:
public class DataContext { public static void put(String key, Object value) { getSession().setAttribute(key, value); } @SuppressWarnings("unchecked") public static <T> T get(String key) { return (T) getSession().getAttribute(key); } public static void remove(String key) { getSession().removeAttribute(key); } public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); Enumeration<String> names = getSession().getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); map.put(name, getSession().getAttribute(name)); } return map; } public static void removeAll() { getSession().invalidate(); } private static HttpSession getSession() { return DispatcherServlet.getSession(); } }
以上定义了一个私有的静态方法 getSession(), 用于从 DispatcherServlet 中获取 Session。其他的共有方法都是供程序员使用的。
我们现在看看如何来使用吧!
在 LoginAction 中放入登录的用户:
@Bean public class UserAction extends BaseAction { @Inject private UserService userService; @Request("post:/login") public Result login(Map<String, Object> fieldMap) { User user = userService.login(fieldMap); if (user != null) { DataContext.put("user", user); // 将 user 对象放入 Session 中 return new Result(true).data(user); } else { return new Result(false).error(ERROR_DATA); } } }
在 ProductAction 中获取登录的用户:
@Bean public class ProductAction extends BaseAction { @Inject private ProductService productService; @Request("get:/products") public Result getProducts() { User user = DataContext.get("user"); // 从 Session 中获取 User 对象 System.out.println(user); ... } ... }
从此以后,程序员就可以使用 DataContext 来操作 Session 了,而无需再依赖于 Servlet API,编写编码简单了,单元测试也容易了,此外还可以在 Service 层中获取 Session 中的数据。
期待您的评价!
补充(2013-09-25)
之前在 DispatcherServlet 中搞了一个 ThreadLocal<HttpSession>,虽然基本上是可以解决问题的,但总感觉有些怪怪的。DispatcherServlet 的职责就是为了处理 URL 请求,再让它去管 Session 的封装问题,是不是违背了“单一职责原则(SRP)”呢?封装 Session 的活应该让 DataContext 来干才对!
于是我做了一些重构,现在的 DataContext 不仅仅可以封装 Session,还可以封装 Request、Response 以及 ServletContext,以后凡是涉及到 Servlet 的数据调用问题,都可以找 DataContext 帮忙了。
DataContext 代码如下:
public class DataContext { private static final ThreadLocal<DataContext> dataContextContainer = new ThreadLocal<DataContext>(); private HttpServletRequest request; private HttpServletResponse response; // 初始化 public static void init(HttpServletRequest request, HttpServletResponse response) { DataContext dataContext = new DataContext(); dataContext.request = request; dataContext.response = response; dataContextContainer.set(dataContext); } // 销毁 public static void destroy() { dataContextContainer.remove(); } // 获取 Request private static HttpServletRequest getRequest() { return dataContextContainer.get().request; } // 获取 Response private static HttpServletResponse getResponse() { return dataContextContainer.get().response; } // 获取 Session private static HttpSession getSession() { return getRequest().getSession(); } // 获取 Servlet Context private static ServletContext getServletContext() { return getRequest().getServletContext(); } // 封装 Request 相关操作 public static class Request { // 将数据放入 Request 中 public static void put(String key, Object value) { getRequest().setAttribute(key, value); } // 从 Request 中获取数据 @SuppressWarnings("unchecked") public static <T> T get(String key) { return (T) getRequest().getAttribute(key); } // 移除 Request 中的数据 public static void remove(String key) { getRequest().removeAttribute(key); } // 从 Request 中获取所有数据 public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); Enumeration<String> names = getRequest().getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); map.put(name, getRequest().getAttribute(name)); } return map; } } // 封装 Response 相关操作 public static class Response { // 将数据放入 Response 中 public static void put(String key, Object value) { getResponse().setHeader(key, CastUtil.castString(value)); } // 从 Response 中获取数据 @SuppressWarnings("unchecked") public static <T> T get(String key) { return (T) getResponse().getHeader(key); } // 从 Response 中获取所有数据 public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); for (String name : getResponse().getHeaderNames()) { map.put(name, getResponse().getHeader(name)); } return map; } } // 封装 Session 相关操作 public static class Session { // 将数据放入 Session 中 public static void put(String key, Object value) { getSession().setAttribute(key, value); } // 从 Session 中获取数据 @SuppressWarnings("unchecked") public static <T> T get(String key) { return (T) getSession().getAttribute(key); } // 移除 Session 中的数据 public static void remove(String key) { getSession().removeAttribute(key); } // 从 Session 中获取所有数据 public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); Enumeration<String> names = getSession().getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); map.put(name, getSession().getAttribute(name)); } return map; } // 移除 Session Attribute 中所有的数据 public static void removeAll() { getSession().invalidate(); } } // 封装 ServletContext 相关操作 public static class Context { // 将数据放入 ServletContext 中 public static void put(String key, Object value) { getServletContext().setAttribute(key, value); } // 从 ServletContext 中获取数据 @SuppressWarnings("unchecked") public static <T> T get(String key) { return (T) getServletContext().getAttribute(key); } // 移除 ServletContext 中的数据 public static void remove(String key) { getServletContext().removeAttribute(key); } // 从 ServletContext 中获取所有数据 public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); Enumeration<String> names = getServletContext().getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); map.put(name, getServletContext().getAttribute(name)); } return map; } } }
只需在 DispatcherServlet 的 service() 方法的第一步调用 DataContext.init() 方法,最后一步调用 DataContext.destroy() 方法即可。之前忽略了 destory 行为,当并发量大的时候会对性能有一定开销。
DispatcherServlet 代码如下:
@WebServlet(urlPatterns = "/*", loadOnStartup = 0) public class DispatcherServlet extends HttpServlet { ... @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 初始化 DataContext DataContext.init(request, response); ... } catch (Exception e) { logger.error(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e); } finally { // 销毁 DataContext DataContext.destroy(); } } ... }
如果需要往 Session 中放数据,可以这样写:
User user = ... DataContext.Session.put("user", user); // 将 user 对象放入 Session 中
如果想从 Session 中取数据,可以这样写:
User user = DataContext.Session.get("user");
当然也可以通过 DataContext 操作 Request、Response 与 ServletContext 中的数据,只需要通以上过这种静态内部类的访问方式即可。
补充(2013-09-26)
在 DataContext 中增加了对 Cookie 的封装:
public class DataContext { ... // 封装 Cookie 相关操作 public static class Cookie { // 将数据放入 Cookie 中 public static void put(String key, Object value) { String strValue = CodecUtil.encodeForUTF8(CastUtil.castString(value)); javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(key, strValue); getResponse().addCookie(cookie); } // 从 Cookie 中获取数据 @SuppressWarnings("unchecked") public static <T> T get(String key) { T value = null; javax.servlet.http.Cookie[] cookieArray = getRequest().getCookies(); if (ArrayUtil.isNotEmpty(cookieArray)) { for (javax.servlet.http.Cookie cookie : cookieArray) { if (key.equals(cookie.getName())) { value = (T) CodecUtil.decodeForUTF8(cookie.getValue()); break; } } } return value; } // 从 Cookie 中获取所有数据 public static Map<String, Object> getAll() { Map<String, Object> map = new HashMap<String, Object>(); javax.servlet.http.Cookie[] cookieArray = getRequest().getCookies(); if (ArrayUtil.isNotEmpty(cookieArray)) { for (javax.servlet.http.Cookie cookie : cookieArray) { map.put(cookie.getName(), cookie.getValue()); } } return map; } } ... }