天天看点

深入Jetty源码之Servlet框架及实现(ServletContext)

servlet是server applet的缩写,即在服务器端运行的小程序,而servlet框架则是对http服务器(servlet container)和用户小程序中间层的标准化和抽象。这一层抽象隔离了http服务器的实现细节,而servlet规范定义了各个类的行为,从而保证了这些“服务器端运行的小程序”对服务器实现的无关性(即提升了其可移植性)。

在servlet规范有以下几个核心类(接口):

servletcontext:定义了一些可以和servlet container交互的方法。

registration:实现filter和servlet的动态注册。

servletrequest(httpservletrequest):对http请求消息的封装。

servletresponse(httpservletresponse):对http响应消息的封装。

requestdispatcher:将当前请求分发给另一个url,甚至servletcontext以实现进一步的处理。

servlet(httpservlet):所有“服务器小程序”要实现了接口,这些“服务器小程序”重写doget、dopost、doput、dohead、dodelete、dooption、dotrace等方法(httpservlet)以实现响应请求的相关逻辑。

filter(filterchain):在进入servlet前以及出servlet以后添加一些用户自定义的逻辑,以实现一些横切面相关的功能,如用户验证、日志打印等功能。

asynccontext:实现异步请求处理。

context在这里是指一个web application的上下文(web application是一个server子url下的servlet和资源的集合),即它包含了这个web application级别的信息,如当前web application对应的根路径、使用的servlet版本、使用的classloader等,在一个jvm中的一个web application只能有一个context(一个jvm可以包含多个web application,它们包含不同的根路径,即不同的context路径,context路径可以是空("/")即这个jvm只能包含一个web application)。servletcontext则是对这个context的抽象,它还定义了一些和servlet container交互的方法,如获取文件的mine type、dispatch请求到另一个url或context、将日志写入文件、根据提供的路径获取resource实例、向这个servletcontext注册并获取servlet或filter、向这个servletcontext注册并获取attribute或初始参数、向这个servletcontext注册或获取相关listener等。对distributed的web application来说,每个jvm下的web application都有一个独立的servletcontext,因而servletcontext不可以作为全局信息存储的地方,因而它并没有分布式信息同步的功能,即它只是本地的servletcontext。在servlet中,使用servletconfig实例可以获取servletcontext实例。

类图如下:

深入Jetty源码之Servlet框架及实现(ServletContext)

servletcontext的接口定义如下:

public interface servletcontext {

    // servlet container为当前web application设置临时目录,并将该临时目录的值存储到当前servletcontext的属性中使用的属性名。

    // jetty使用webinfconfiguration(在preconfig ure()方法中)来设置该值,设置temp目录的规则:

    // 1. 如果存在web-inf/work目录,则temp目录的值为:web-inf/work/jetty_<host>_<port>__<resourcebase>_<context>_<virtualhost+base36_hashcode_of_whole_string>

    // 2. 如果"javax.servlet.context.tempdir"已经在外部被设置,并且该目录存在且可写,则temp目录直接设置为该目录实例。

    // 3. 如果系统变量中"jetty.home"目录下存在"work"目录且可写,则设置temp目录的值为:${jetty.home}/work/jetty_<host>_<port>__<resourcebase>_<context>_<virtualhost+base36_hashcode_of_whole_string>

    // 4. 如果存在"org.eclipse.jetty.webapp.basetempdir"的属性,且该目录存在并可写,设置temp目录为:${org.eclipse.jetty.webapp.basetempdir}/jetty_<host>_<port>__<resourcebase>_<context>_<virtualhost+base36_hashcode_of_whole_string>

    // 5. 如果以上条件都不成立,则设置temp目录为:${java.io.tmpdir}/jetty_<host>_<port>__<resourcebase>_<context>_<virtualhost+base36_hashcode_of_whole_string>,且删除已存在临时目录。

    // 注:对temp目录的父目录不是work,会注册在jvm退出时删除该temp目录,并在temp目录下创建.active目录。

    public static final string tempdir = "javax.servlet.context.tempdir";

    // servlet 3.0中新引入的特性,即可以在web-inf/lib下的jar包中定义/meta-inf/web-fragment配置响应的servlet等。

    // 如果在web.xml文件中定义了absolute-ordering,或者在jar包中存在/meta-inf/web-fragment.xml文件,且定义了ordering,

    // 则该属性的值即为根据规范中定义的规则计算出来的读取jar包中web-fragment.xml文件的jar包名顺序,它时一个list<string>类型,包含jar包名字。

    // 在jetty中使用ordering来表示这种顺序,它有两个实现类:absoluteordering和relativeordering用来分别表示在web.xml和web-fragment.xml中定义的absolute-ordering和ordering定义,

    // 并且将最终的解析结果汇总到metadata类中,并根据规范中定义的规则以及metadata-complete的定义来计算实际的解析顺序,

    // 而对这两种配置文件的解析由webdescriptor和fragmentdescriptor来实现,它们都包含了metadata-complete解析,而真正的解析入口在webxmlconfiguration和fragmentconfiguration中。

    // 该规范的定义参考:https://blogs.oracle.com/swchan/entry/servlet_3_0_web_fragment

    public static final string ordered_libs = "javax.servlet.context.orderedlibs";

    // 返回当前web application的context path,即当前web application的根路径,servlet container根据该路径以及一个request url的匹配情况来选择一个request应该交给那个web application处理该请求。

    // context path以"/"开始,但是不能以"/"结尾,对默认的根context,它返回"",而不是"/"。该值在配置jetty的contexthandler中设置。

    // 有些servlet container支持多个context path指向同一个context,此时可以使用httpservletrequest中的getcontextpath()来获取该request实际对应的context path,此时这两个context path的值可能会不同,但是servletcontext中返回的context path值是主要的值。另外jetty也不支持这种特性。

    public string getcontextpath();

    // 通过给定一个context path以在当前servlet container中找到其对应的servletcontext实例。

    // 可以通过该方法获取servlet container中定义的另一个web application的servletcontext实例,并获得其requestdispatcher,并将当前请求dispatch到那个web application中做进一步的处理。这里的uripath必须以"/"开头,且其路径相对于当前server的根路径。出于安全考虑,该方法可能会返回null。

    // 在jetty的实现中,这里uripath可以是一个具体的路径,并且支持查找最准确的匹配。如:对uripath为/foo/goo/abc.html,在server中由以下context path定义:"/", "/foo", "/foo/goo",则最终查找到的servletcontext为"/foo/goo"作为context path对应的servletcontext实例。

    public servletcontext getcontext(string uripath);

    //返回servlet规范的主版本,如3

    public int getmajorversion();

    // 返回servlet规范的次版本,如0

    public int getminorversion();

    // 返回当前web application基于的servlet规范的主版本,如在web.xml文件中定义的version(<web-app version="..." ...>...</web-app>,即jetty中的实现)

    public int geteffectivemajorversion();

    // 返回当前web application基于的servlet规范的次版本,如在web.xml文件中定义的version(<web-app version="..." ...>...</web-app>,即jetty中的实现)

    public int geteffectiveminorversion();

    // 返回给定file的mime type,返回null如果无法计算出其mime type。这个映射关系由servlet container定义或在web.xml文件中定义(mime-mapping, extension, mine-type)。

    // 常见的mime type有:text/html, image/gif等。jetty使用mimetypes类来封装所有和mime type相关的操作,mimetypes类中定义了所有默认支持的mime type以及编码类型,

    // 并且默认从org/eclipse/jetty/http/mime.properties文件中加载默认的映射,如css=text/css, doc=application/msword等,使用addmimemapping()方法向该类注册web.xml中定义的文件名扩展名到mime type的映射。

    // 而从org/eclipse/jetty/http/encoding.properties文件中加载mime type的默认编码类型,如text/xml=utf-8等。

    // 在使用文件名查找mime type时,根据文件名的扩展名查找已注册或默认注册的mime type。用户自定义的映射优先。用户定义的mime type映射支持extension为"*",表示任意扩展名。

    public string getmimetype(string file);

    // 对于给定目录,返回该目录下所有的资源以及目录。path必须以"/"开头,如果它不是指向一个目录,则返回空的set。所有返回的路径都相对当前web application的根目录,

    // 或对于/web-inf/lib中jar包中的/meta-inf/resources/目录,如一个web application包含以下资源:/catalog/offers/music.html, /web-inf/web.xml, 

    // 以及/web-inf/lib/catalog.jar!/meta-inf/resources/catalog/moreoffers/books.html,则getresourcepaths("/catalog")返回{"/catalog/offers/", /catalog/moreoffers/"}

    // jetty的实现中,在metainfconfiguration中,它会扫描web-inf/lib目录下所有的jar包,如果发现在某个jar包中存在meta-inf/resources/目录,

    // 就会将该目录资源作为baseresource在webinfconfiguration中注册到contexthandler(webappcontext)中。从而实现jar包中的meta-inf/resources/目录作为根目录的查找。  

    public set<string> getresourcepaths(string path);

    // 返回给定path的url,path必须以"/"开头,它相对当前web application的根目录或相对/web-inf/lib中jar包中的/meta-inf/resources/目录。

    // 其中查找顺序前者优先于后者,但是在/web-inf/lib/目录下的jar包的查找顺序未定义。该方法不同于class.getresource()在于它不使用classloader,如果没有找到给定资源,返回null。

    // 在webappcontext实现中,它还支持alias查找,并且如果其extractwar的变量为false,给定的资源在war包中,则该url返回war包中的url。    

    public url getresource(string path) throws malformedurlexception;

    // 参考getresource(path)的查找实现,如果其返回的url为null,则该方法返回null,否则返回url对应的inputstream。

    public inputstream getresourceasstream(string path);

    // 创建一个requestdispatcher用于将一个request、response分发到path对应的url中,这里path必须以"/"开头,且它相对于当前context path。如果无法创建requestdispatcher,返回null。

    // path可以包含query数据用于传递参数:uriincontext?param1=abc&param2=123....该方法可以和getcontext(uripath)一起使用,以将当前请求分发到另一个web application中。

    // 该方法的另一种用法是先有一个servlet或filter处理基本的逻辑,然后使用这个requestdispatcher将当前请求forward到另一个url中或include一个jsp文件生成响应页面,如果在处理过程中出错,则将其当前请求分发到错误处理的流程中。

    // requestdispatcher支持两种类型的分发:forward和include,唯一的区别是include只可以改变response的内容,不可以改变其header信息,forward则没有这种限制。

    public requestdispatcher getrequestdispatcher(string path);

    // 创建一个requestdispatcher用于将一个request、response分发到name对应的servlet(jsp)中。如果没能找到响应的servlet,返回null。

    public requestdispatcher getnameddispatcher(string name);    

    // 将msg打印到servlet对应的log文件中,在jetty中,使用info级别打印,logger名称为web.xml定义的display-name,或者context path。

    // jetty默认使用slf4j作为日志打印框架,可以使用"org.eclipse.jetty.util.log.class"系统属性改变其日志打印框架。

    public void log(string msg);   

    // 打印message和throwable到servlet对应的log文件中,在jetty中使用warn级别打印该信息。

    public void log(string message, throwable throwable);

    // 返回给定path在当前机器上操作系统对应的位置。对/meta-inf/resources下的资源,除非他们已经解压到本地目录,否则对那些资源该方法返回null。

    // 在jetty实现中,使用getresource()方法相同的实现获取resource实例,如果其getfile()返回不为null,则返回该file的canonical路径。

    public string getrealpath(string path);

    // 返回servlet container的名称和版本号,其格式为:<servername>/<versionnumber>,如:jetty/8.1.9.v20130131。

    public string getserverinfo();

    // servletcontext级别的初始参数操作,可以在web.xml中使用context-param定义,也可以手动设置。在get中如果找不到对应的项,返回null,在set时,如果已存在name,则返回false,并且不更新相应的值。

    public string getinitparameter(string name);

    public enumeration<string> getinitparameternames();

    public boolean setinitparameter(string name, string value);

    // servletcontext级别的属性操作,其中属性名遵循包命名规则。在set中,如果object为null表示移除该属性,如果name以存在,则会替换原有的值,如果注册了servletcontextattributelistener,则会出发相应的attributeremoved、attributereplaced、attributeadded事件。在remove中,如果name存在且被移除了,则会触发attributeremoved事件。

    // 在jetty中使用contexthandler中的context内部类实现servletcontext,在contexthandler中定义了两个相关字段:_attributes以及_contextattributes,其中_attributes表示在jetty内部通过contexthandler设置的属性,而_contextattributes表示用户设置的属性,但是在获取属性值时,两个字段的属性都会考虑进去,在移除属性时,如果是移除_attributes字段中的值,则不会触发attributeremoved事件。

    public object getattribute(string name);

    public enumeration<string> getattributenames();

    public void setattribute(string name, object object);

    public void removeattribute(string name);

    // 返回当前web application在web.xml中的display-name定义的servletcontext名字,在jetty实现中,如果该值为null,则返回context path。

    public string getservletcontextname();

    // 该部分具体的使用可以参考:http://www.blogjava.net/yongboy/archive/2010/12/30/346209.html 

    // 动态的向servletcontext中注册servlet,注册的servlet还可以通过返回的servletregistration继续配置,如addmapping、setinitparameter等。

    // 在使用classname实例话servlet时,使用当前servletcontext相关联的classloader。在创建servlet实例时,会根据该类中定义的以下annotation做相应的配置:

    // javax.servlet.annotation.servletsecurity、javax.servlet.annotation.multipartconfig、javax.annotation.security.runas、javax.annotation.security.declareroles

    public servletregistration.dynamic addservlet(string servletname, string classname);

    public servletregistration.dynamic addservlet(string servletname, servlet servlet);

    public servletregistration.dynamic addservlet(string servletname, class <? extends servlet> servletclass);

    // 创建给定servlet类的servlet实例,并且会根据该类中定义的以下annotation做相应的配置:

    // 在创建servlet实例后,一般还要调用addservlet()方法将其注册到servletcontext中。

    public <t extends servlet> t createservlet(class<t> clazz) throws servletexception;

    // 根据servletname获取servletregistration实例

    public servletregistration getservletregistration(string servletname);

    // 获取所有在servletcontext中注册的servletname到servletregistration映射的map。所有动态注册和使用配置注册的映射。

    public map<string, ? extends servletregistration> getservletregistrations();

    // 动态的向servletcontext中注册filter,注册的filter可以通过返回的filterregistration进一步配置,如addmappingforurlpatterns、setinitparameter等

    public filterregistration.dynamic addfilter(string filtername, string classname);

    public filterregistration.dynamic addfilter(string filtername, filter filter);

    public filterregistration.dynamic addfilter(string filtername, class <? extends filter> filterclass);

    // 创建给定filter类实例的filter实例,一般都会后继调用addfilter将该实例注册到servletcontext中。

    public <t extends filter> t createfilter(class<t> clazz) throws servletexception;

    // 根据filtername获取filterregistration实例。

    public filterregistration getfilterregistration(string filtername);

    // 返回所有filtername到filterregistration映射的map,包括所有动态注册和使用配置注册的映射。

    public map<string, ? extends filterregistration> getfilterregistrations();

    // 返回sessioncookieconfig实例,用于session tracking的cookie属性,多次调用该方法返回相同的实例。

    public sessioncookieconfig getsessioncookieconfig();

    // 设置session tracking模式,可以是url、cookie、ssl。jetty8只支持url和cookie。

    public void setsessiontrackingmodes(set<sessiontrackingmode> sessiontrackingmodes);

    // 返回当前servletcontext默认支持的session tracking模式。

    public set<sessiontrackingmode> getdefaultsessiontrackingmodes();

    // 返回当前servletcontext目前使用的session tracking模式。

    public set<sessiontrackingmode> geteffectivesessiontrackingmodes();

    // 向servletcontext动态的注册listener,该listener类或实例必须实现以下的一个或多个接口:

    // servletcontextattributelistener、servletrequestlistener、servletrequestattributelistener、httpsessionlistener、httpsessionattributelistener}</tt>

    // 如果这个servletcontext传入servletcontainerinitializer的onstartup方法,那么这个listener类或实例也可以实现servletcontextlistener接口。

    // 注:动态注册的servletcontextlistener中的contextinitialized方法中不可以调用servlet 3.0中定义的这些动态注册servlet、filter、listener等方法,不然会抛出unsupportedoperationexception,看起来像是出于一致性、安全性或是兼容性的考虑,但是具体是什么原因一直想不出来。而且在jetty实现中,它在注册eventlistener实例是确取消了这种限制,而对注册eventlistener类实例和类名确有这种限制,不知道这是jetty的bug还是其他什么原因。。。。。

    // 对于调用顺序按定义顺序来的eventlistener(如servletrequestlistener、servletcontextlistener、httpsessionlistener),那这个新的eventlistener会添加到相应列表末尾。

    public void addlistener(string classname);

    public <t extends eventlistener> void addlistener(t t);

    public void addlistener(class <? extends eventlistener> listenerclass);

    // 创建clazz对应的eventlistener实例,一般这个新创建的eventlistener会之后注册到servletcontext中。

    public <t extends eventlistener> t createlistener(class<t> clazz) throws servletexception; 

    // 返回web.xml和web-fragment.xml配置文件中定义<jsp-config>的汇总,或返回null如果没有相关配置。看起来像jetty并没有实现该方法。

    public jspconfigdescriptor getjspconfigdescriptor();

    // 返回当前web application对应的classloader实例。

    public classloader getclassloader();

    // 定义角色名。角色名在servletregistration.dynamic.setservletsecurity()和servletregistration.dynamic.setrunasrole()中默认定义,因而不需要重新使用这个方法定义。

    public void declareroles(string

深入Jetty源码之Servlet框架及实现(ServletContext)

 rolenames);

}

servletcontext的初始化从contexthandler的dostart()方法开始,在其startcontext()方法快结束时,会调用注册的servletcontextlistener中的contextinitialized()方法,因而这里是用户对servletcontext初始化时做一些自定义逻辑的扩展点。

在servlet 3.0中还引入了servletcontainerinitializer接口,它定义了onstartup()方法,该方法会在webappcontext中的startcontext方法中的configure方法中通过containerinitializerconfiguration.configure()中被调用,该方法的调用要早于所有servletcontextlistener .contextinitialized()事件的触发。

public interface servletcontainerinitializer {

    // 当servletcontext对应的web application初始化时,该方法会被调用。其中c参数是所有继承、实现在servletcontainerinitializer实现类定义的handlestypes注解中定义的类,

    // 如果该注解定义的类数组中有注解,那么c参数还包含所有存在这个注解的类。如果实现servletcontainerinitializer接口的类在web-inf/lib的jar包中绑定,

    // 那么该方法只会在对应的web application初始化被调用一次,如果实现servletcontainerinitializer接口的类在web-inf/lib以外定义,但是还可以被servlet container找到,

    // 那么意味这这个jar包是多个web application共享的,因而该方法会在每个web application初始化时被调用。如果servletcontainerinitializer实现类没有定义handlestypes注解,

    // 那么c参数为null。servletcontainerinitializer实现类的查找使用运行时service provider机制,然而对于定义在fragment jar包中的servletcontainerinitializer实现类,

    // 但是该jar在absolute ordering中被exclude了,那么该jar包会被忽略,即在web.xml中的absolute-ordering中没有包含相应的fragment名。

    // 在查找满足handlestypes注解中定义的类数组的类实例集合时,由于有些jar包是可选的,因而在加载class时有时会遇到问题,此时servlet container可以选择忽略该错误,

    // 但是需要提供配置已让servlet container决定是否要将这些错误打印到日志文件中。 

    public void onstartup(set<class<?>> c, servletcontext ctx) throws servletexception; 

要自定义servletcontainerinitializer逻辑,首先需要meta-inf/services/目录下建立一个javax.servlet.servletcontainerinitializer文件,在该文件内部写入用户在定义的servletcontainerinitializer类,如:x.y.z.myservletcontainerinitializer,该myservletcontainerinitializer类必须实现servletcontainerinitializer接口,如果它有一些特定感兴趣的类,可以向其定义handlestypes注解,该注解的值可以是类实例、接口实例、注解实例,它表示所有继承自注解中定义的类、实现注解中定义的接口、有注解中定义的注解注解的类(注解在类、字段、方法中)都会收集成一个set<class<?>>类型,并传入onstartup()方法中,如果没有handlestypes注解,其onstartup()中set<class<?>>参数为null。

在jetty实现中,annotationconfiguration会查找到所有jar包中在meta-inf/services/javax.servlet.servletcontainerinitializer中定义的servletcontainerinitializer(排除那些被absolute-ordering排除在外的fragment jar包中的定义),使用这些查找到的servletcontainerinitializer创建containerinitializer实例,使用handlestypes注解中定义的class数组初始化interestedtypes字段,如果这个class数组中有注解类型,则将所有存在这个注解类型的类(这个注解可以注解在该类的类、字段、方法中)添加到containerinitializer实例中的_annotatedtypenames集合中(使用containerinitializerannotationhandler实现);最后将他们注册到context的一个属性中。同时在annotationconfiguration中还会向context中注册一个属性,其值是map,它包含了所有类对应的其子类或实现类。然后在containerinitializerconfiguration中,对每个在context中注册的containerinitializer实例,对所有注册的_annotatedtypenames,将该类以及该类的子类、实现类注册到_applicabletypenames集合中;对所有注册的非注解类型的_interestedtypes,将其所有的子类、实现类注册到_applicabletypenames集合中(在jetty当前版本的实现中没有包含_interestedtypes中的类实例本身,在servletcontainerinitializer的注释中确实也没有说明要包含这些类本身,感觉这个有点不合理。。。);最后调用containerinitializer中的callstartup()方法,它加载_applicabletypenames集合中的所有类,并将其传入servletcontainerinitializer的onstartup()方法中(这里没有根据规范忽略不能加载成功的类实例)。