接触了一小段时间的servlet,下面就总结一下关于servlet的两个方面的知识,一个是模板方法的应用。另外一个是servlet多线程产生的原因。
1. 模板方法设计模式
定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
2. Servlet中模板方法的应用
模板方法在servlet中的应用的类图
当客户端请求一个servlet的时候首先被调用的是service方法。Service方法就是定义骨架的方法,在service方法中会根据HTTP请求的类型(GET,POST还是其他)来调用具体的doGet和doPost等方法;即实际的处理委派给了doGet和doPost等方法,这些子类的方法来最终处理浏览器的请求。下面是HttpServlet对service方法的具体实现。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
3. servlet并不是严格的按照模板方法来设计的
我们常说具体问题具体分析,在这里也是。servlet 讲模板方法的精髓应用到了,但是却没有完全按照模板方法设计。
3.1 子类可以重写service()方法
模板方法模式是通过把不变的行为搬移到超类,去除了子类中的重复代码。所以超类中的模板方法一般情况下是不会被重写的。模板方法允许子类重新定义算法的某些 步骤,而不改变算法的结构。但是在servlet中的service方法中是可以被子类重写的。当子类重写了service方法之后就破坏掉了原本定义好的模板方法。
3.2 doGet和doPost方法有默认的实现
在HttpServlet类中对于这两个被子类具体实现的方式是有实现的。下面是HttpServlet对于这两个实现的代码
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } }
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } }
这些说明在使用设计模式的时候并不是生搬硬套,而是根据具体的情况灵活变动。但是其本质是不变的。
4. 为什么会出现线程安全问题
在学习servlet过程中一直听到的一个问题就是线程问题,学习的资料中提到servlet是多线程,不安全的。
servlet是由web容器管理的,当请求这个servlet的时候,web容器会根据web.xml中的配置实例化这个servlet。当再次请求这个servlet的时候不会再实例化此servlet而是多线程的使用第一次请求的时候实例化的servlet。也就是一个servlet只实例化一次。
当面对多个请求的时候web容器就会使用多线程来处理这些请求。下面的图形是servlet多线程的结构图形
从图中可以看出,当容器收到一个请求servlet的请求的时候会从线程池中分出一个工作线程来处理这个请求,当有另外一个请求的时候会从线程池中分出另外一个工作线程来处理这个请求;所以如果多个用户同时请求同一个servlet,那么在容器中就会出现一个servlet对象的service方法并发执行。多线程是共享资源的,当他们并发执行的时候就很有可能出现访问资源冲突问题。
这种处理请求的方式就是我们经常说的单实例多线程的处理方式。
下面是自己查找到的一个作者:http://blog.csdn.net/zljjava/article/details/6887266从内存的角度来分析的servlet的多线程问题。如果有兴趣的话可以看看。
从内存中看一下这个问题可以总结下面的两种图形,其中一张是我们都知道JMM模型图
另外一张是多个用户请求同一个servlet的多线程执行的过程是:
主内存中的实例变量是在多个线程之间共享的,每个线程在自己的工作线程中拷贝自己需要的实例变量到自己的缓存中。就如同上面图形中的A,当线程1修改了A,但是线程2中的A还是主存中的样子,并没有马上同步到线程2和线程3中就会出现线程安全问题。
5. 总结
对于servlet的单实例多线程问题知道的原因就可以对症下药的找解决方案了。主要是针对变量要注意。对于模板方法可以看到我们的应用很多但是也要注意活学活用。
Servlet的模板方法和多线程