servlet生命周期和持续性

本文深入探讨了Servlet的生命周期,包括Servlet实例的持续性、单线程模式、动态加载机制及Servlet的初始化与销毁过程。通过具体代码示例展示了多线程环境下的Servlet行为,并解释了如何利用SingleThreadModel确保线程安全。
摘要由CSDN通过智能技术生成

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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值