1、大多数servlet容器都是将所有servlet放在一个JVM中运行的,当然也有些高端容器,支持分布式servlet。
2、servlet实例的持续性 ,也就是说:一个servlet实例在多个http请求之间保持持续性。通俗地讲就是,当servlet被载入时,服务器生成一个实例对象,这个实例处理所有对该servlet的请求。注意:我们的业务框架和前置框架的交易处理类都不是持续性的,而是来个请求生成一个处理类。
(如果以后某个同事开发一个什么容器,首先就可以问问生命周期和对象的持续性方面的问题。比如,我们现在的业务框架中的service容器中service对象是非持续的。)
持续性有几个好处:(1)内存消耗小,速度快,不用临时去生成一个对象;(2)保证持续性,一般建议在servlet.init中把该servlet处理业务逻辑时所需要的资源全部一次性载入,而不要在servlet.service方法中不断载入,并在servlet.destory中释放这些资源。那么,一个servlet初始化的时候,可能是需要用到一些配置信息的,这些在servlet.init中都有个ServletConfig输入参数,把初始化需要的参数信息从web.xml中携带进来。
这样做,也有麻烦的地方,因为所有对该servlet资源的请求,都是同一个servlet实例来处理,那么也就是说:客户A对servlet状态的修改,其实是会影响到客户B所看到的servlet的状态的。比如,我们给servlet做个统计,用来统计被访问了多少次,实际上统计的是该servlet一共被访问了多少次,而不是说某个用户对它访问了多少次(这属于会话跟踪的话题)。
还有麻烦的问题是,服务端都是多线程环境,做为servlet的开发人员来看,模型应该是这样的: 该servlet极其可能在同一个时刻被两个客户端(在服务端表现为两个并发的线程)访问。所以,我们说servlet实例的持续性包括两方面:(1)在多个请求之间保持持续性;(2)在多线程中保持持续性。
package com.eyesmore.lifecycle;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleCounter extends HttpServlet {
private static final long serialVersionUID = -8007930646306363635L;
private int counter = 0;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
String clientInfo = " Remote="+request.getRemotePort()+" ";
String threadInfo = " in Thread = "+Thread.currentThread().toString() +" ID="+Thread.currentThread().getId();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace(writer);
}
++ counter;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace(writer);
}
writer.println("Counter = "+ counter +clientInfo + " "+threadInfo);
}
/*
* 通过多个浏览器访问,有一下结果:
* 【遨游】
* Counter = 1 Remote=2135 in Thread = Thread[http-8080-1,5,main] ID=18
* Counter = 4 Remote=2169 in Thread = Thread[http-8080-1,5,main] ID=18
* Counter = 5 Remote=2169 in Thread = Thread[http-8080-1,5,main] ID=18
* 【firefox】
* Counter = 2 Remote=2137 in Thread = Thread[http-8080-2,5,main] ID=19
* Counter = 6 Remote=2172 in Thread = Thread[http-8080-3,5,main] ID=20
* 【Opera】(由于没有同步counter,两个请求获得了一样的结果)
* Counter = 3 Remote=2164 in Thread = Thread[http-8080-2,5,main] ID=19
* Counter = 3 Remote=2164 in Thread = Thread[http-8080-2,5,main] ID=19
*
* 结论是:由于http1.1会重用原有的连接,所以而且服务端作业处理时间短的话,可能在同一个线程中处理多个作业。
* 但是,我们从结果中还是可以看出servlet实例会在多个线程中持续的事实的。
* */
}
当然,servlet容器还有可以支持运行一组实例的配置——单线程模式。所谓单线程模式就是保证某个servlet实例不可能同时被两个线程操作,如果某时候有两个请求同时到达某个servlet,那么这个时会构建另外一个servlet实例,并运行在另一个线程里面。很显然,如果某个servlet具有单线程模式,那么它一定是线程安全的。SingleThreadModel的出现就是设计者为了让servlet开发人员尽可能少得关注多线程的问题,但是作为高级开发人员必须对服务端的多线程特性有深刻了解 。SingleThreadModel是个标签接口,仅仅是用来告诉servlet容器需要把它作为SingleThreadModel。
package com.eyesmore.lifecycle;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("deprecation")
public class SingleThreadCounter extends HttpServlet implements SingleThreadModel {
private static final long serialVersionUID = 6688319173103711221L;
private int instanceNo;
private volatile static int instanceCount = 0;
public SingleThreadCounter() {
++ instanceCount;
instanceNo = instanceCount;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter writer = response.getWriter();
writer.println("instanceNo = "+ instanceNo);
String clientInfo = " Remote="+request.getRemotePort()+" ";
String threadInfo = " in Thread = "+Thread.currentThread().toString() +" ID="+Thread.currentThread().getId();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace(writer);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace(writer);
}
writer.println("instanceNo = "+ instanceNo +clientInfo + " "+threadInfo);
}
/*
* 【遨游】
* instanceNo = 2
* instanceNo = 2 Remote=2271 in Thread = Thread[http-8080-1,5,main] ID=18
* instanceNo = 3
* instanceNo = 3 Remote=2272 in Thread = Thread[http-8080-2,5,main] ID=19
* 【firefox】
* instanceNo = 2
* instanceNo = 2 Remote=2274 in Thread = Thread[http-8080-3,5,main] ID=20
* instanceNo = 2
* instanceNo = 2 Remote=2274 in Thread = Thread[http-8080-3,5,main] ID=20
* */
}
3、对象的init和destory,并且理解动态加载(servlet的动态加载 ,这也是服务器设计的一个基本而且重要的指标)
【“servlet重新载入” ====== 非常高级的一个话题,就是servlet的动态加载】
【我们在实践总发现,“新增”,“删除”一个servlet可以被动态反映出来;但是,如果“修改一个类”,却动态加载不了。有兴趣可以深入了解下servlet的动态加载的话题。】
当服务器将一个请求分配给servlet处理时,它首先检查磁盘上的servlet类文件是否有改动。如果有改动,则放弃旧版本,并用自定义的ClassLoader来加载新的实例。(也是使用ClassLoader来实现动态加载的)。环境中的所有的servlet类和支持类都是用一个ClassLoader来加载的,所以在运行时不会出现不可预料的ClassCastException的问题 (以前是每不同的servlet由不同的classloader来加载,这样容易出现classCastException的错误)。注意: 当只有一个支持类文件改变时,类不会被重新载入。
servlet接口包括:servlet本身的描述信息,配置描述信息,初始化,服务,结束。
package javax.servlet;
import java.io.IOException;
// Referenced classes of package javax.servlet:
// ServletException, ServletConfig, ServletRequest, ServletResponse
public interface Servlet
{
public abstract void init(ServletConfig servletconfig)
throws ServletException;
public abstract ServletConfig getServletConfig();
public abstract void service(ServletRequest servletrequest, ServletResponse servletresponse)
throws ServletException, IOException;
public abstract String getServletInfo();
public abstract void destroy();
}
ServletConfig包括:servlet的名字,servlet的初始化用的初始化参数。更重要的是,还可以取得ServletContext的整个servlet的大家庭,就好比spring 的Bean容器,能够依据Bean的名字,在一个Bean中通过BeanContext获取其他Bean的引用。这一点对于Servlet的协作是很有价值的。
package javax.servlet;
import java.util.Enumeration;
// Referenced classes of package javax.servlet:
// ServletContext
public interface ServletConfig
{
public abstract String getServletName();
public abstract ServletContext getServletContext();
public abstract String getInitParameter(String s);
public abstract Enumeration getInitParameterNames();
}
【GenericServlet 一般的servlet模板 典型的Template-callback】
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;
public abstract class GenericServlet
implements Servlet , ServletConfig , Serializable
//实现ServletConfig接口,实际上有点“零适配的”味道, ServletConfig的实例对象来自于init中的servletConfig参数。所以,如果我们需要对自己写的Servlet做初始化工作,请尽可能在init方法中进行,而不要在构造函数中进行,因为那时超类的init方法还没来得即进行。
{
//【第一部分】
public GenericServlet()
{
}
public void destroy() //注意:如果服务器宕机,在destroy中是做不到existHooking的。
{
}
public String getInitParameter(String name) //所有的这些都来元于init中的servletConfig参数
{
ServletConfig sc = getServletConfig();
if(sc == null)
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
else
return sc.getInitParameter(name);
}
public Enumeration getInitParameterNames() //所有的这些都来元于init中的servletConfig参数
{
ServletConfig sc = getServletConfig();
if(sc == null)
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
else
return sc.getInitParameterNames();
}
public ServletConfig getServletConfig () //所有的这些都来元于init中的servletConfig参数
{
return config;
}
public ServletContext getServletContext () //所有的这些都来元于init中的servletConfig参数
{
ServletConfig sc = getServletConfig();
if(sc == null)
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); //对于未能恰当的初始化的东西,提前执行某个方法,一般抛IllegalStateException异常。
else
return sc.getServletContext();
}
public String getServletInfo() //描述信息,umpay的框架也喜欢留个这个东西。
{
return "";
}
public void init(ServletConfig config) //两个初始化接口,一个是可以从ServletConfig中提取参数的,另一个是没有的。
throws ServletException
{
this.config = config; //这个config信息被GenericServlet保存下来了,所以可以在后面任何地方重新获取。getServletConfig 。
init();
}
public void init() //如果我们自己要重写init方法,建议直接重写不带参数的,如果要获取参数信息,可以直接在超类的方法中进行。当然,如果重写带参的,那么,我们还是先调用超类的super.init(config)下。
//这样,我们以后使用ServletConfig接口下的一些方法,就不至于抛出IllegalStateException异常了。
//servlet还可以配置成是服务器一起来时就开始初始化,还在第一次访问时开始初始化。 可以在web.xml中配置。
throws ServletException
{
}
//【第二部分】
// Servlet容器真是为开发者提供了一体化的服务,把日志的接口都流出来了,而我们服务器框架这方面做得还不好。
//所以servlet的开发这应该尽可能不用System.out这个输出日志,尽管tomcat也会把它重定向到日志文件。
public void log(String msg)
{
getServletContext().log((new StringBuilder()).append(getServletName()).append(": ").append(msg).toString());
}
public void log(String message, Throwable t)
{
getServletContext().log((new StringBuilder()).append(getServletName()).append(": ").append(message).toString(), t);
}
【第三部分】
public abstract void service(ServletRequest servletrequest, ServletResponse servletresponse)
throws ServletException, IOException;
// 此时的servlet还仅仅跟通信协议有关系,比如是短连接什么的。tomcat是如何把ServletRequest解析成一个HttpServletRequest的呢,这个是很有意思的一个话题。
//我们又能否在servlet容器上跑我们自定义的报文结构呢?而不仅仅是http。
public String getServletName()
{
ServletConfig sc = getServletConfig();
if(sc == null)
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
else
return sc.getServletName();
}
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
}