封装 Servlet API

原创
2013/09/20 14:55
阅读数 8K

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

展开阅读全文
加载中
点击加入讨论🔥(48) 发布并加入讨论🔥
打赏
48 评论
93 收藏
4
分享
返回顶部
顶部