WebAppContext即Web Application ContextHandler,表示一个Web应用的上下文,是上篇文章介绍的ContextHandler的一个子类,也是实际中应用的ContextHandler。先来看下类图:
可以看到在ContextHandler和WebAppContext中间还有一个ServletContxtHandler,下面就先从这个类开始分析。
ServletContextHandler是ContextHandler的直接子类,具有ContextHandler的大部分特征,不同的地方是ServletContextHandler中管理了三个Handler:ServletHandler、SessionHandler、SecurityHandler。
protected SessionHandler _sessionHandler;
protected SecurityHandler _securityHandler;
protected ServletHandler _servletHandler;
ServletHandler对ContextHandler的扩展也主要集中在对三个Handler特别是ServletHandler的管理上。SessionHandler和SecurityHandler都是可选的,ServletHandler中定义了几个常量用来表示是否需要相应的Handler。
public final static int SESSIONS=1; //需要SessionHandler
public final static int SECURITY=2; //需要SecurityHandler
public final static int NO_SESSIONS=0;//不需要SessionHandler
public final static int NO_SECURITY=0;//不需要SecurityHandler
通过 new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
可以配置SessionHandler和SecurityHandler。对这几个的Handler的组织是放在startContext()方法里进行的:
protected void startContext() throws Exception
{
//防止默写Handler没有正确配置,强行初始化Handler
getSessionHandler();
getSecurityHandler();
getServletHandler();
//下面是Handler链的组织过程
Handler handler = _servletHandler;
if (_securityHandler!=null)
{
_securityHandler.setHandler(handler);
handler=_securityHandler;
}
if (_sessionHandler!=null)
{
_sessionHandler.setHandler(handler);
handler=_sessionHandler;
}
// 跳过所有Wrapper类型的Handler
_wrapper=this;
while (_wrapper!=handler && _wrapper.getHandler() instanceof HandlerWrapper)
_wrapper=(HandlerWrapper)_wrapper.getHandler();
// if we are not already linked
if (_wrapper!=handler)
{
if (_wrapper.getHandler()!=null )
throw new IllegalStateException("!ScopedHandler");
_wrapper.setHandler(handler);
}
super.startContext();
//下面是ServletHandler初始化的过程
if (_servletHandler != null && _servletHandler.isStarted())
{
for (int i=_decorators.size()-1;i>=0; i--)
{
Decorator decorator = _decorators.get(i);
if (_servletHandler.getFilters()!=null)
for (FilterHolder holder:_servletHandler.getFilters())
decorator.decorateFilterHolder(holder);
if(_servletHandler.getServlets()!=null)
for (ServletHolder holder:_servletHandler.getServlets())
decorator.decorateServletHolder(holder);
}
_servletHandler.initialize();
}
}
上面最重要的工作就是形成了ServletContextHandler—>SessionHandler—>SecurityHandler—>ServletHandler的Handler链,通过这个Handler链,ServletContextHandler拿到请求之后就能依次传递给各个Handler处理,最后会通过ServletHandler转交给具体的Servlet处理。最后再这个方法里还对ServeltHandler进行了初始化。ServletHandler的initialize()方法中主要是对Filter和Servlet调用其start()方法进行启动。
ServletContextHandler中还有一些添加Servlet和Filter的方法,但实际上都是调用ServletHandler的相应方法:
/** conveniance method to add a servlet.
*/
public ServletHolder addServlet(Class extends Servlet> servlet,String pathSpec)
{
return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
}
/** conveniance method to add a servlet.
*/
public void addServlet(ServletHolder servlet,String pathSpec)
{
getServletHandler().addServletWithMapping(servlet, pathSpec);
}
/** conveniance method to add a filter
*/
public void addFilter(FilterHolder holder,String pathSpec,EnumSet dispatches)
{
getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
}
/** convenience method to add a filter
*/
public FilterHolder addFilter(Class extends Filter> filterClass,String pathSpec,EnumSet dispatches)
{
return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
}
主要的来说,ServletContextHandler就是重写了父类的startContext()方法,将SessionHandler、SecurityHandler、ServletHandler组成一个Handler链来对请求进行处理,并且在startContext()方法中进行了ServletHandler的初始化。另外ServletContextHandler中还提供了一个内部类Context,它继承了ContextHandler的内部类,也是ServletContext的一个具体实现。
上篇文章分析ContextHandler的时候就提到过,作为Web应用的上下文ContextHandler没有实现对资源文件的处理,而WebAppContext作为ContextHandler的子类正是对这一部分进行了扩展。
主要的启动逻辑和资源加载逻辑都放在doStart()方法里,下面来重点看下这个方法:
@Override
protected void doStart() throws Exception
{
try
{
_metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
preConfigure();
super.doStart();
postConfigure();
if (isLogUrlOnStart())
dumpUrl();
}
catch (Exception e)
{
//start up of the webapp context failed, make sure it is not started
LOG.warn("Failed startup of context "+this, e);
_unavailableException=e;
setAvailable(false);
if (isThrowUnavailableOnStartupException())
throw e;
}
}
super.start()会调用startContext()方法,而WebAppContext也重写了这个方法:
@Override
protected void startContext()
throws Exception
{
configure();
//resolve the metadata
_metadata.resolve(this);
super.startContext();
}
这里面主要是调用configure()方法,结合上面doStart()方法中流程,可以看到大概就是preConfigure()—>configure()—>postConfigure()这么三个步骤。
先来看下preConfigure()方法:
public void preConfigure() throws Exception
{
// 设置配置类集合:configurations
loadConfigurations();
// 设置系统类class:system classes
loadSystemClasses();
// 设置应用服务自己需要的一些class:server classes
loadServerClasses();
// 为应用创建一个ClassLoader,每个web应用都需要一个自己的ClassLoader
_ownClassLoader=false;
if (getClassLoader()==null)
{
WebAppClassLoader classLoader = new WebAppClassLoader(this);
setClassLoader(classLoader);
_ownClassLoader=true;
}
if (LOG.isDebugEnabled())
{
ClassLoader loader = getClassLoader();
LOG.debug("Thread Context classloader {}",loader);
loader=loader.getParent();
while(loader!=null)
{
LOG.debug("Parent class loader: {} ",loader);
loader=loader.getParent();
}
}
// 调用每个配置类的preConfigure()方法
for (int i=0;i<_configurations.length;i++)
{
LOG.debug("preConfigure {} with {}",this,_configurations[i]);
_configurations[i].preConfigure(this);
}
}
一开始就是三个load()方法,先看下loadConfigurations():
protected void loadConfigurations()
throws Exception
{
//如果_configurations属性不为空,说明已经初始化过了
if (_configurations!=null)
return;
//如果没有设置过配置类,则使用默认提供的配置类,否则使用设置的
if (!_configurationClassesSet)
_configurationClasses=__dftConfigurationClasses;
//下面其实就是通过ClassLoader将_configurationClasses中指定的配置类加载到内存中并新建一个实例
_configurations = new Configuration[_configurationClasses.length];
for (int i = 0; i < _configurationClasses.length; i++)
{
_configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
}
}
下面是jetty默认的配置类集合:
//每一个配置类负责一部分配置文件的解析或class文件/jar包的导入
private static String[] __dftConfigurationClasses =
{
"org.eclipse.jetty.webapp.WebInfConfiguration",//对webinfo的处理,主要用于载入class文件以及jar包
"org.eclipse.jetty.webapp.WebXmlConfiguration",//负责web.xml的解析
"org.eclipse.jetty.webapp.MetaInfConfiguration",
"org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
//"org.eclipse.jetty.webapp.TagLibConfiguration"
} ;
上面的_configurations中就是存放了这些配置类的实例。
接下来的loadSystemClasses()、loadServerClasses()分别用来设置系统需要的classes 和应用服务自己需要的classes,这两个类型的类在jetty中都有默认值:
// System classes are classes that cannot be replaced by
// the web application, and they are *always* loaded via
// system classloader.
public final static String[] __dftSystemClasses =
{
"java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"org.xml.", // needed by javax.xml
"org.w3c.", // needed by javax.xml
"org.apache.commons.logging.", // TODO: review if special case still needed
"org.eclipse.jetty.continuation.", // webapp cannot change continuation classes
"org.eclipse.jetty.jndi.", // webapp cannot change naming classes
"org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes
"org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
"org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
"org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
} ;
// Server classes are classes that are hidden from being
// loaded by the web application using system classloader,
// so if web application needs to load any of such classes,
// it has to include them in its distribution.
public final static String[] __dftServerClasses =
{
"-org.eclipse.jetty.continuation.", // don't hide continuation classes
"-org.eclipse.jetty.jndi.", // don't hide naming classes
"-org.eclipse.jetty.plus.jaas.", // don't hide jaas classes
"-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
"-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
"-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
"-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
"org.eclipse.jetty." // hide other jetty classes
} ;
上面的load操作,其实就是从Server里面拿到对应属性的设置,然后为其创建ClasspathPattern对象,如果从Server中没有对其进行设置,则使用上面的默认值创建ClasspathPattern对象,下面是loadSystemClasses()方法:
protected void loadSystemClasses()
{
if (_systemClasses != null)
return;
//look for a Server attribute with the list of System classes
//to apply to every web application. If not present, use our defaults.
Server server = getServer();
if (server != null)
{
Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
if (systemClasses != null && systemClasses instanceof String[])
_systemClasses = new ClasspathPattern((String[])systemClasses);
}
if (_systemClasses == null)
_systemClasses = new ClasspathPattern(__dftSystemClasses);
}
完成load操作后就是给当前应用创建一个ClassLoader并且对上面获得的每个配置对象Configuration都调用一次PreConfigure()方法。每个配置对象在这个阶段做的事情都不太相同,下面是WebInfConfiguration的preConfigure方法,方法中一些代码的具体作用见下面的注释:
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
// 查找工作目录,如果不存在则创建一个
File work = findWorkDirectory(context);
if (work != null)
makeTempDirectory(work, context, false);
//Make a temp directory for the webapp if one is not already set
resolveTempDirectory(context);
//如果有必要解压war包
unpack (context);
//确定jar包的载入顺序
String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
//Apply ordering to container jars - if no pattern is specified, we won't
//match any of the container jars
PatternMatcher containerJarNameMatcher = new PatternMatcher ()
{
public void matched(URI uri) throws Exception
{
context.getMetaData().addContainerJar(Resource.newResource(uri));
}
};
ClassLoader loader = context.getClassLoader();
while (loader != null && (loader instanceof URLClassLoader))
{
URL[] urls = ((URLClassLoader)loader).getURLs();
if (urls != null)
{
URI[] containerUris = new URI[urls.length];
int i=0;
for (URL u : urls)
{
try
{
containerUris[i] = u.toURI();
}
catch (URISyntaxException e)
{
containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
}
i++;
}
containerJarNameMatcher.match(containerPattern, containerUris, false);
}
loader = loader.getParent();
}
//Apply ordering to WEB-INF/lib jars
PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
{
@Override
public void matched(URI uri) throws Exception
{
context.getMetaData().addWebInfJar(Resource.newResource(uri));
}
};
List jars = findJars(context);
//Convert to uris for matching
URI[] uris = null;
if (jars != null)
{
uris = new URI[jars.size()];
int i=0;
for (Resource r: jars)
{
uris[i++] = r.getURI();
}
}
webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match
}
总的来说就是对当前的web程序的war包进行处理,进行解压,放到相应的文件夹下面,并处理WEB-INF下放置的依赖jar包。
preConfigure之后就是configure():
public void configure() throws Exception
{
// Configure webapp
for (int i=0;i<_configurations.length;i++)
{
LOG.debug("configure {} with {}",this,_configurations[i]);
_configurations[i].configure(this);
}
}
可以看到主要是调用各个配置对象的configure()方法。
下面还是来看下WebInfConfiguration的configure()方法:
@Override
public void configure(WebAppContext context) throws Exception
{
//cannot configure if the context is already started
if (context.isStarted())
{
if (LOG.isDebugEnabled())
LOG.debug("Cannot configure webapp "+context+" after it is started");
return;
}
Resource web_inf = context.getWebInf();
// Add WEB-INF classes and lib classpaths
if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
{
// Look for classes directory
Resource classes= web_inf.addPath("classes/");
if (classes.exists())
((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
// Look for jars
Resource lib= web_inf.addPath("lib/");
if (lib.exists() || lib.isDirectory())
((WebAppClassLoader)context.getClassLoader()).addJars(lib);
}
// Look for extra resource
@SuppressWarnings("unchecked")
List resources = (List)context.getAttribute(RESOURCE_URLS);
if (resources!=null)
{
Resource[] collection=new Resource[resources.size()+1];
int i=0;
collection[i++]=context.getBaseResource();
for (Resource resource : resources)
collection[i++]=resource;
context.setBaseResource(new ResourceCollection(collection));
}
}
这里的逻辑是很简单明了的,主要就是将WEB-INF目录下的classe文件和依赖jar包加入到到classpath中去。至于最后的postConfigure,就是做些清除工作,这里就不展开讲了。
至此对于WebAppContext启动时候所做的工作应该有了大致的了解。下面再总结下,其实主要的初始化工作是两个方面:1是war包的解压为其创建工作目录并将其中WEB-INF下的classes文件和依赖jar包加入到classpath中,还需要为应用创建一个ClassLoader。2是将管理的SessionHandler、SecurityHandler、ServletHandler和WebAppContext构建成一个Handler链,用来处理请求,再将请求交给其它链中的其它节点处理之前,还需要对请求进行url和目标主机的校验,如果校验不通过则直接返回。这就是WebAppContext大致的工作。