侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

封装 Servlet API

2022-06-25 星期六 / 0 评论 / 0 点赞 / 76 阅读 / 13729 字

本文是《轻量级 Java Web 框架架构设计》的系列博文。 Session 对象对于 Web 应用而言是至关重要的,当我们需要实现跨请求传递数据时,就需要使用它,因为它能保证多个会话之间是隔离的

本文是《轻量级 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 中放入登录的用户:

@Beanpublic 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 中获取登录的用户:

@Beanpublic 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;        }    }...}

广告 广告

评论区