Servlet API解耦

为什么需要与Servlet API解耦

目前在Controller中是无法调用Servlet API的,因为无法获取Request与Response这类对象,我们必须在Dispatcher中将这些对象传递给Controller的Action方法才能拿到这些对象,这显然会增加Controller对Servlet API的耦合。最好能让Controller完全不使用Servlet API就能操作Request与Response对象。

最容易拿到Request与Response对象的地方就是DispatcherServlet的service方法:

@WebServlet(urlPatterns = "/*",loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet { @Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
}

然而,我们又不想把Request和Response对象传递到Controller的Action方法中,所以我们需要提供一个线程安全的对象,通过它来封装Request和Response对象,并提供一系列常用的Servlet API,这样我们就可以在Controller中随时通过该对象来操作Request与Response对象的方法了。需要强调的是,这个对象一定是线程安全的,也就是说每个请求线程独自拥有一份Request与Response对象,不同请求线程间是隔离的

与Servlet API解耦的实现过程

一个简单的思路是,编写一个ServletHelper类,让它去封装Request与Response对象,提供常用的ServletAPI工具方法,并利用ThreadLocal技术来保证线程安全,代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; public class ServletHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(ServletHelper.class); /**
* 使每个线程独自拥有一份ServletHelper实例
*/
private static final ThreadLocal<ServletHelper> SERVLET_HELPER_HOLDER = new ThreadLocal<ServletHelper>(); private HttpServletRequest request;
private HttpServletResponse response; public ServletHelper(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
} /**
* 初始化
* @param request
* @param response
*/
public static void init(HttpServletRequest request,HttpServletResponse response){
SERVLET_HELPER_HOLDER.set(new ServletHelper(request,response));
} /**
* 销毁
*/
public static void destroy(){
SERVLET_HELPER_HOLDER.remove();
} /**
* 获取Request对象
* @return
*/
private static HttpServletRequest getRequest(){
return SERVLET_HELPER_HOLDER.get().request;
} /**
* 获取Response对象
* @return
*/
private static HttpServletResponse getResponse(){
return SERVLET_HELPER_HOLDER.get().response;
} /**
* 获取Session对象
* @return
*/
private static HttpSession getSession(){
return getRequest().getSession();
} /**
* 获取ServletContext对象
* @return
*/
private static ServletContext getContext(){
return getRequest().getServletContext();
}
}

最重要的就是init和destroy方法,我们需要在恰当的地方调用它们,哪里是最恰当的地方呢?当然是上面提到的DispatcherServlet的service方法。此外还提供了一系列私有的getter和setter方法,因为我们需要封装几个常用的Servlet API工具方法:

    /**
* 将属性放入Request中
* @param key
* @param val
*/
public static void setRequestAttribute(String key,Object val){
getRequest().setAttribute(key,val);
} /**
* 获取Request中的属性
* @param key
* @param <T>
* @return
*/
public static <T> T getRequestAttribute(String key){
return (T) getRequest().getAttribute(key);
} /**
* 从Request中移除属性
* @param key
*/
public static void removeRequestAttribute(String key){
getRequest().removeAttribute(key);
} /**
* 重定向
* @param location
*/
public static void sendRedirect(String location){
try {
getResponse().sendRedirect(location);
} catch (IOException e) {
LOGGER.error("redirect failure",e);
}
} /**
* 将属性放入Session中
* @param key
* @param val
*/
public static void setSessionAttribute(String key,Object val){
getSession().setAttribute(key,val);
} /**
* 获取Session中的属性
* @param key
* @param <T>
* @return
*/
public static <T> T getSessionAttribute(String key){
return (T) getSession().getAttribute(key);
} /**
* 移除Session中的属性
* @param key
*/
public static void removeSessionAttribute(String key){
getSession().removeAttribute(key);
} /**
* 使Session失效
*/
public static void invalidateSession(){
getSession().invalidate();
}

以上这些工具方法都是可拓展的,只要是我们认为比较常用的都可以封装起来。

现在ServletHelper已经开发完毕,是时候将其整合到DispatcherServlet中并初始化Request与Response对象了,实际上就是调用init与destroy方法。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletHelper.init(req,resp); //使每个线程都有独立的request和response
try {
/****/
}finally {
ServletHelper.destroy();
}
}

现在就可以在Controller类中随时调用ServletHelper封装的Servlet API了:而且不仅仅可以在Controller类中调用,实际上在Service类中也是可以调用。因为所有调用都来自同一请求线程。DispatcherServlet是请求线程的入口,随后请求线程会先后来到Controller与Service中,我们只需要使用ThreadLocal来确保ServletHelper对象中的Request与Response对象线程安全即可。

代码

架构探险笔记11-与Servlet API解耦的更多相关文章

  1. Struts 2读书笔记-----Action访问Servlet API

    Action访问Servlet API Struts2中的Action并没有和任何Servlet API耦合,这样框架更具灵活性,更易测试. 对于Web应用的控制器而言,不访问ServletAPI是几 ...

  2. Struts2笔记--Action访问Servlet API

    Web应用中通常需要访问的Servlet API就是HttpServletRequest.HttpSession和ServletContext,这三个接口分别代表JSP内置对象中的request.se ...

  3. 架构探险笔记3-搭建轻量级Java web框架

    MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦. 上一章我们使用Servlet来充当MVC模式中的Controller ...

  4. 【Java EE 学习 35 上】【strus2】【类型转换器】【struts2和Servlet API解耦】【国际化问题】【资源文件乱码问题已经解决】

    一.类型转换器 1.在动作类action中,声明和表单中name属性的值同名的属性,提供get和set方法,struts2就可以通过反射机制,从页面中获取对应的内容 package com.kdyzm ...

  5. 架构探险笔记12-安全控制框架Shiro

    什么是Shiro Shiro是Apache组织下的一款轻量级Java安全框架.Spring Security相对来说比较臃肿. 官网 Shiro提供的服务 1.Authentication(认证) 2 ...

  6. 架构探险笔记6-ThreadLocal简介

    什么是ThreadLocal? ThreadLocal直译为“线程本地”或“本地线程”,如果真的这么认为,那就错了!其实它就是一个容器,用于存放线程的局部变量,应该叫ThreadLocalVariab ...

  7. 架构探险笔记4-使框架具备AOP特性(上)

    对方法进行性能监控,在方法调用时统计出方法执行时间. 原始做法:在内个方法的开头获取系统时间,然后在方法的结尾获取时间,最后把前后台两次分别获取的系统时间做一个减法,即可获取方法执行所消耗的总时间. ...

  8. 架构探险笔记5-使框架具备AOP特性(下)

    开发AOP框架 借鉴SpringAOP的风格,写一个基于切面注解的AOP框架.在进行下面的步骤之前,确保已经掌了动态代理技术. 定义切面注解 /** * 切面注解 */ @Target(Element ...

  9. 读《架构探险——从零开始写Java Web框架》

    内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...

随机推荐

  1. cmd copy命令 文件复制【转】

    本文转载自:https://www.jb51.net/article/18981.htm copy,中文含义为“复制”,一个很容易见名知意的命令,它的作用是复制文件,用法十分简单:copy 源文件 目 ...

  2. tp框架中的一些疑点知识-6

    vim自带一个目录浏览器,使用命令:E就可以调出来,实际上就是浏览器的名字就是"网络读写"netrw vim也自带了 补全功能, 启动键是 "ctrl_N" 或 ...

  3. 【做题】agc006C - Rabbit Exercise——模型转换

    原文链接https://www.cnblogs.com/cly-none/p/9745177.html 题意:数轴上有\(n\)个点,从\(1\)到\(n\)编号.有\(m\)个操作,每次操作给出一个 ...

  4. 【做题】agc002D - Stamp Rally——整体二分的技巧

    题意:给出一个无向连通图,有\(n\)个顶点,\(m\)条边.有\(q\)次询问,每次给出\(x,y,z\),最小化从\(x\)和\(y\)开始,总计访问\(z\)个顶点(一个顶点只计算一次),经过的 ...

  5. 【做题】51NOD1518 稳定多米诺覆盖——容斥&dp

    题意:求有多少种方案,用多米诺骨牌覆盖一个\(n\times m\)的棋盘,满足任意一对相邻行和列都至少有一个骨牌横跨.对\(10^9+7\)取模. \(n,m \leq 16\) 首先,这个问题的约 ...

  6. (转)开源项目miaosha(上)

    石墨文档:https://shimo.im/docs/iTDoZs4CVfICgSfV/ (二期)19.开源秒杀项目miaosha解读(上) [课程19]几张图.xmind0.6MB [课程19]开源 ...

  7. linux下gzip的压缩详解

    Linux压缩保留源文件的方法: gzip -c filename > filename.gz Linux解压缩保留源文件的方法: gunzip -c filename.gz > file ...

  8. 什么是SpringCloud

    SpringCloud是在SpringBoot的基础上构建的,用于简化分布式系统构建的工具集. 该工具集为微服务架构中所涉及的配置管理,服务发现,智能路由,断路器,微代理和控制总线等操作 提供了一种简 ...

  9. (转载)MySQL用命令行复制表的方法

    mysql中用命令行复制表结构的方法主要有一下几种: 1.只复制表结构到新表 ; 或 CREATE TABLE 新表 LIKE 旧表 ; 注意上面两种方式,前一种方式是不会复制时的主键类型和自增方式是 ...

  10. (转载)C#工具箱Menustrip控件中分割线的设置方法

    最近编C#程序,因为初学,不是太清楚,碰到了toolstripMenu中分割线设置的问题.遍寻中文网页,都是语言不详的,甚是呕人. 上网找了个外文网站,给的答案甚是详细,先贴在下面. http://w ...