本文是《轻量级 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;
}
}
...
}