鱼跃家庭制氧机要介绍Servlet必须要先把Servlet容器说清楚,Servlet与Servlet容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。从技术角度来说是为了解耦,通过标准化接口来相互协作。既然接口是连接Servlet与Servlet容器的关键,那我们就从它们的接口说起。前面说了Servlet容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。例如现在比较流行的Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉Tomcat为例来介绍Servlet容器如何管理Servlet。Tomcat本身也很复杂,我们只从Servlet与Servlet容器的接口部分开始介绍,关于Tomcat的详细介绍可以参考我的另外一篇文章《Tomcat系统架构与模式设计分析》。Tomcat的容器等级中,Context容器是直接管理Servlet在容器中的包装类Wrapper,所以Context容器如何运行将直接影响Servlet的工作方式。图1.Tomcat容器模型从上图可以看出Tomcat的容器分为四个等级,真正管理Servlet的容器是Context容器,一个Context对应一个Web工程,在Tomcat的配置文件中可以很容易发现这一点,如下:清单1Context配置参数Contextpath=/projectOnedocBase=D:\projects\projectOnereloadable=true/下面详细介绍一下Tomcat解析Context容器的过程,包括如何构建Servlet的过程。Servlet容器的启动过程Tomcat7也开始支持嵌入式功能,增加了一个启动类org.apache.catalina.startup.Tomcat。创建一个实例对象并调用start方法就可以很容易启动Tomcat,我们还可以通过这个对象来增加和修改Tomcat的配鱼跃家庭制氧机置参数,如可以动态增加Context、Servlet等。下面我们就利用这个Tomcat类来管理新增的一个Context容器,我们就选择Tomcat7自带的examplesWeb工程,并看看它是如何加到这个Context容器中的。清单2.给Tomcat增加一个Web工程Tomcattomcat=getTomcatInstance();FileappDir=newFile(getBuildDirectory(),webapps/examples);tomcat.addWebapp(null,/examples,appDir.getAbsolutePath());tomcat.start();ByteChunkres=getUrl(()+/examples/servlets/servlet/HelloWorldExample);assertTrue(res.toString().indexOf(h1HelloWorld!/h1)0);清单1的代码是创建一个Tomcat实例并新增一个Web应用,然后启动Tomcat并调用其中的一个HelloWorldExampleServlet,看有没有正确返回预期的数据。Tomcat的addWebapp方法的代码如下:清单3.Tomcat.addWebapppublicContextaddWebapp(Hosthost,Stringurl,Stringpath){silence(url);Contextctx=newStandardContext();ctx.setPath(url);ctx.setDocBase(path);if(defaultRealm==null){initSimpleAuth();}ctx.setRealm(defaultRealm);ctx.addLifecycleListener(newDefaultWebXmlListener());ContextConfigctxCfg=newContextConfig();ctx.addLifecycleListener(ctxCfg);ctxCfg.setDefaultWebXml(org/apache/catalin/startup/NO_DEFAULT_XML);if(host==null){getHost().addChild(ctx);}else{host.addChild(ctx);}returnctx;鱼跃家庭制氧机}前面已经介绍了一个Web应用对应一个Context容器,也就是Servlet运行时的Servlet容器,添加一个Web应用时将会创建一个StandardContext容器,并且给这个Context容器设置必要的参数,url和path分别代表这个应用在Tomcat中的访问路径和这个应用实际的物理路径,这个两个参数与清单1中的两个参数是一致的。其中最重要的一个配置是ContextConfig,这个类将会负责整个Web应用配置的解析工作,后面将会详细介绍。最后将这个Context容器加到父容器Host中。接下去将会调用Tomcat的start方法启动Tomcat,如果你清楚Tomcat的系统架构,你会容易理解Tomcat的启动逻辑,Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《Tomcat的系统架构与设计模式,第二部分:设计模式》。Tomcat启动的时序图可以用图2表示。图2.Tomcat主要类的启动时序图鱼跃家庭制氧机上图描述了Tomcat启动过程中,主要类之间的时序关系,下面我们将会重点关注添加examples应用所对应的StandardContext容器的启动过程。当Context容器初始化状态设为init时,添加在Contex容器的Listener将会被调用。ContextConfig继承了LifecycleListener接口,它是在调用清单3时被加入到StandardContext容器中。ContextConfig类会负责整个Web应用的配置文件的解析工作。ContextConfig的init方法将会主要完成以下工作:1.创建用于解析xml配置文件的contextDigester对象2.读取默认context.xml配置文件,如果存在解析它3.读取默认Host配置文件,如果存在解析它4.读取默认Context自身的配置文件,如果存在解析它5.设置Context的DocBaseContextConfig的init方法完成后,Context容器的会执行startInternal方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:1.创建读取资源文件的对象2.创建ClassLoader对象3.设置应用的工作目录4.启动相关的辅助类如:logger、realm、resources等5.修改启动状态,通知感兴趣的观察者(Web应用的配置)6.子容器的初始化7.获取ServletContext并设置必要的参数8.初始化“loadonstartup”的ServletWeb应用的初始化工作Web应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化主要是要解析web.xml文件,这个文件描述了一个Web应用的关键信息,也是一个Web应用的入口。Tomcat首先会找globalWebXml这个文件的搜索路径是在engine的工作目录下寻找以下两个文件中的任一个org/apache/catalin/startup/NO_DEFAULT_XML或conf/web.xml。接着会找hostWebXml这个文件可能会在System.getProperty(catalina.base)/conf/${EngineName}/${HostName}/web.xml.default,接着寻找应用的配置文件examples/WEB-INF/web.xml。web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。如果当前应用支持Servlet3.0,解析还将完成额外9项工作,这个额外的9项工作主要是为Servlet3.0新增的特性,包括jar包中的META-INF/web-fragment.xml的解析以及对annotations的支持。接下去将会将WebXml对象中的属性设置到Context容器中,这里包括创建Servlet对象、filter、listener等等。这段代码在WebXml的configureContext方法中。下面是解析Servlet的代码片段:清单4.创建Wrapper实例for(ServletDefservlet:servlets.values()){Wrapperwrapper=context.createWrapper();StringjspFile=servlet.getJspFile();if(jspFile!=null){wrapper.setJspFile(jspFile);}鱼跃家庭制氧机if(servlet.getLoadOnStartup()!=null){wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());}if(servlet.getEnabled()!=null){wrapper.setEnabled(servlet.getEnabled().booleanValue());}wrapper.setName(servlet.getServletName());MapString,Stringparams=servlet.getParameterMap();for(EntryString,Stringentry:params.entrySet()){wrapper.addInitParameter(entry.getKey(),entry.getValue());}wrapper.setRunAs(servlet.getRunAs());SetSecurityRoleRefroleRefs=servlet.getSecurityRoleRefs();for(SecurityRoleRefroleRef:roleRefs){wrapper.addSecurityReference(roleRef.getName(),roleRef.getLink());}wrapper.setServletClass(servlet.getServletClass());MultipartDefmultipartdef=servlet.getMultipartDef();if(multipartdef!=null){if(multipartdef.getMaxFileSize()!=null&&multipartdef.getMaxRequestSize()!=null&&multipartdef.getFileSizeThreshold()!=null){wrapper.setMultipartConfigElement(newMultipartConfigElement(multipartdef.getLocation(),Long.parseLong(multipartdef.getMaxFileSize()),Long.parseLong(multipartdef.getMaxRequestSize()),Integer.parseInt(multipartdef.getFileSizeThreshold())));}else{wrapper.setMultip