ServletContext用法
一、基本概念
说起ServletContext,一些人会产生误解,以为一个servlet对应一个ServletContext。其实不是这样的,事实是一个web应用对应一个ServletContext,所以ServletContext的作用范围是整个应用,明确这点很重要,这是基础中的基础。
我曾经想,为什么不起名叫WebContext或者ApplicationContext或者WebApplicationContext?这样见名知意多好。后来我想这也可能是有历史原因的:最初的客户端-服务端的架构模型非常简单,服务端运行着一些servlet用来处理客户端的请求。那个时候服务器很轻量级,运行一个应用,应用就由一堆servlet组成。所以这样简单的服务器也被称作servlet容器,主要作用就是运行servlet的。那么提供给应用的上下文就叫做ServletContext。
一个web应用对应一个ServletContext实例,这个实例是应用部署启动后,servlet容器为应用创建的。ServletContext实例包含了所有servlet共享的资源信息。通过提供一组方法给servlet使用,用来和servlet容器通讯,比如获取文件的MIME类型、分发请求、记录日志等。
这里需要注意一点,如果你的应用是分布式部署的,那么每台服务器实例上部署的应用实例都各自拥有一个ServletContext实例。
总结
由servlet容器微应用创建的数据共享池,同一个应用内的所有servlet都可以存取ServletContext的数据资源
二、源码分析
1 ServletContext(上)
下面我们先逐个分析ServletContext中servlet3.0之前的规范定义的方法(其中有三个方法是servlet3.0规范定义的,放在一起讲是出于方便的考虑,讲到的时候会特别说明)。
public interface ServletContext {
public String getContextPath();
public ServletContext getContext(String uripath);
public int getMajorVersion();
public int getMinorVersion();
public int getEffectiveMajorVersion();
public int getEffectiveMinorVersion();
public String getServerInfo();
public String getServletContextName();
public String getMimeType(String file);
public void log(String msg);
public void log(String message, Throwable throwable);
public Set getResourcePaths(String path);
public URL getResource(String path) throws MalformedURLException;
public InputStream getResourceAsStream(String path);
public RequestDispatcher getRequestDispatcher(String path);
public RequestDispatcher getNamedDispatcher(String name);
public String getRealPath(String path);
public String getInitParameter(String name);
public Enumeration getInitParameterNames();
public boolean setInitParameter(String name, String value);
public Object getAttribute(String name);
public Enumeration getAttributeNames();
public void setAttribute(String name, Object object);
public void removeAttribute(String name);
// 省略servlet3.0及3.1规范定义的方法
}
1.1 getContextPath
方法返回web应用的上下文路径。就是我们部署的应用的根目录名称。拿Tomcat举例,我们在webapps部署了应用demo。那么方法返 回"/demo"。如果是部署在ROOT下,那么方法返回空字符串""。这里的路径可以再server.xml里面修改,比如我们的demo应用路径修改 为"/test":
那么方法将会返回"/test"。
1.2 getContext
方法入参为uriPath(String),是一个资源定位符的路径。返回一个ServletContext实例。我们说过一个web应用对应一个ServletContext实例,那么这个方法根据资源的路径返回其servlet上下文。
比如说我们当前应用是demo,这个时候我们要访问servlet容器中的另外一个应用test中的资源index.jsp,假使资源的路径为/test/index.jsp。那么我们就可用通过调用getContext("/test/index.jsp")去获取test应用的上下文。如果在servlet容器中找不到该资源或者该资源限制了外部的访问,那么方法返回null。(这个方法一般配合RequestDispatcher使用,实现请求转发)。
1.3 getMajorVersion、getMinorVersion、getEffectiveMajorVersion、getEffectiveMinorVersion
getMajorVersion和getMinorVersion分别返回当前servlet容器支持的Servlet规范最高版本和最低版本。
getEffectiveMajorVersion和getEffectiveMinorVersion分别返回当前应用基于的Servlet规范最高版本和最低版本,是servlet3.0规范增加的新特性。
所以一般情况下:
getMajorVersion>=getEffectiveMajorVersion>getEffectiveMinorVersion>=getMinorVersion。
1.4 getServerInfo、getServletContextName
getServerInfo返回servlet容器的名称和版本,格式为servername/versionnumber。比如我在Tomcat下测试,输出的信息是:Apache Tomcat/9.0.0.M10。当然容器也可以多返回些额外的信息,这个就看各个servlet容器的实现了。
getServletContextName返回应用的名称,这里的名称是web.xml里面配置的display-name,如果没配置则返回null。
Archetype Created Web Application
1.5 getMimeType
方法返回文件的MIME类型,MIME类型是容器配置的。可用通过web.xml进行配置,比如:
doc
application/vnd.ms-word
那么我们用浏览器打开文件的时候发现如果是doc文件,则会调用相应的word程序去打开。
1.6 log
两个重载的log方法都是记录日志到servlet日志文件,这个对于有编程经验的来说没什么好解释的。需要注意的是servlet日志文件的路径由具体的 servlet容器自己去决定。如果你是在MyEclipse、STS这类的IDE中跑应用的话,那么日志信息将在控制台(Console)输出。如果是 发布到Tomcat下的话,日志文件是Tomcat目录下的/logs/localhost.yyyy-MM-dd.log。
1.7 getResourcePaths
根据传入的路径,列出该路径下的所有资源路径。返回的路径是相对于web应用的上下文根或者相对于/WEB-INF/lib目录下的各个JAR包里面的/META-INF/resources目录。
比如我们的web应用下有这些资源:/welcome.html,/catalog/index.html,/catalog/products.html, /catalog/offers/books.html,/catalog/offers/music.html,/customer /login.jsp,/WEB-INF/web.xml,/WEB-INF/classes /com.acme.OrderServlet.class,/WEB-INF/lib/catalog.jar!/META-INF/resources/catalog/moreOffers/books.html。
如果调用方法getResourcePaths("/"),那么返回的是{"/welcome.html", "/catalog/", "/customer/", "/WEB-INF/"}。
如果调用方法getResourcePaths("/catalog/"),那么返回的是{"/catalog/index.html", "/catalog/products.html", "/catalog/offers/"}。
这里需要注意的是:1.路径一定要以"/"开头,结尾的"/"可要可不要。2.路径要从应用根目录下开始,如果调用方法getResourcePaths("/offers/"),此时返回的是null。
1.8 getResource和getResourceAsStream
getResource将指定路径的资源封装成URL实例并返回,getResourceAsStream获取指定路径资源的输入流InputStream并返回。关于URL和InputStream的解释和使用,不在本篇博文的关注点。
这里和上面一样,资源的路径以"/"开头,这个路径是相对于应用上下文根目录或者相对于/WEB-INF/lib目录下的各个JAR包里面的/META-INF/resources目录。在搜索资源的时候,servlet容器先是从应用上下文根目录下搜索再从JAR文件搜索,对于JAR文件的搜索顺序(哪个JAR先,哪个JAR后),servlet规范没有规定。
1.9 getRequestDispatcher和getNamedDispatcher
将指定的资源包装成RequestDispatcher实例并返回。区别是前者根据资源路径(该路径相对于当前应用上下文根),后者根据资源的名称(通过服务器控制台或者web.xml里面配置的,比如web.xml里面配置servlet的名称)。
RequestDispatcher这个接口,看名字就知道主要用来进行分发的。所有的资源都可以包装成RequestDispatcher实例(主要是用于包装servlet),然后调用它的方法进行转发和包含。这里比较简单,不过过多赘述,直接上源码。(这里只列关键的两个方法,简单介绍下forward。如有兴趣深入,可自行去研究RequestDispatcher相关知识)
public interface RequestDispatcher {
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException;
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException;
}
还拿我们上面的demo应用例子来说:客户端访问http://xxx.xxx.xxx.xxx:xxxx/demo/test,这个时候访问我们的 TestServlet。这个时候如果要把请求转发到另外一个servlet,假使这个servlet的资源路径是/demo/test2。那么我们可以 调用getRequestDispatcher("/demo/test2")把资源包装成RequestDispatcher实例,再调用forward的方法。就实现了请求的转发。
请求的转发使用起来很简单,因为servlet容器提供了ServletContext实例,我们在应用中只需要调用它的API就行。这个时候容器为我们做了很多事情,容器会根据资源的路径去获取ServletContext实例,正如上面的getContext方法。这里不一定就是当前 ServletContext,可以使其他应用的。如果找到了ServletContext,容器再将资源包装成RequestDispatcher实例进行转发。
1.10 getRealPath
根据资源虚拟路径,返回实际路径。
比如说应用中有个JSP页面index.jsp,调用getRealPath("index.jsp"),则返回index.jsp文件在文件系统中的绝对路径。在windows下或许是这样:D:\xxx\xxx\index.jsp,在linux下或许是这样:/root/xxx/index.jsp。
这里可能存在应用中有多个index.jsp,它们在不同的路径下。这时候servlet容器是先从应用根目录下向下查找,再从/WEB-INF/lib目录下的各个JAR包里面的/META-INF/resources目录查找(前提是servlet容器解压了这些JAR),把找到的第一个资源绝对路径返回。
1.11 getInitParameter、getInitParameterNames、setInitParameter
getInitParameter和getInitParameterNames是用来获取应用的初始化参数相关数据的,参数的作用域是整个应用。这个参数是在web.xml里面配置的(如下所示)或者使用setInitParameter方法设置。getInitParameter是根据参数名获取参数值,getInitParameterNames获取参数名集合。对于setInitParameter需要注意的是,如果设置的参数名已经存在会设置失败,这是servlet3.0规范增加的新特性。
这里需要注意的是在web.xml配置多个初始化参数时,应该写多个 对,而不是在一个 对里面写多个 和 对。
param1
1
param2
2
1.12 getAttribute、getAttributeNames、setAttribute、removeAttribute
应用的属性相关操作,建议属性名遵循java包名的风格。这里需要讲的是setAttribute,当设置的属性名已经存在,则会替换掉就的属性值。如果设置属性值为null,那么效果和removeAttribute是一样的。
这里需要注意的是,这些属性都是应用级的,在一个地方设置的属性可以被应用中其他地方使用。
2 ServletContext(下)
下面我们逐个分析ServletContext中servlet3.0及3.1的规范中定义的方法。
public interface ServletContext {
// 省略servlet3.0之前的规范定义的方法
public static final String TEMPDIR = "javax.servlet.context.tempdir";
public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
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);
public T createServlet(Class clazz) throws ServletException;
public ServletRegistration getServletRegistration(String servletName);
public Map getServletRegistrations();
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);
public T createFilter(Class clazz) throws ServletException;
public FilterRegistration getFilterRegistration(String filterName);
public Map getFilterRegistrations();
public void addListener(String className);
public void addListener(T t);
public void addListener(Class <? extends EventListener> listenerClass);
public T createListener(Class clazz) throws ServletException;
public SessionCookieConfig getSessionCookieConfig();
public void setSessionTrackingModes(Set sessionTrackingModes);
public Set getDefaultSessionTrackingModes();
public Set getEffectiveSessionTrackingModes();
public JspConfigDescriptor getJspConfigDescriptor();
public ClassLoader getClassLoader();
public void declareRoles(String... roleNames);
public String getVirtualServerName();
}
2.1 TEMPDIR和ORDERED_LIBS
这是两个ServletContext的属性名,Servlet规范建议属性名遵循java包名的风格。
TEMPDIR 对应的属性值是个java.io.File对象,表示该ServletContext对应的临时目录。拿tomcat来说,比如有个应用叫demo,那么 demo对应的ServletContext临时目录就是{Tomcat目录}\work\Catalina\localhost\demo。
ORDERED_LIBS 对应的属性值是个java.util.List