Tomcat源码分析(三)-Container和Lifecycle
一、Tomcat生命周期的管理
Tomcat通过package org.apache.catalina.Lifecycle;接口管理Tomcat的生命周期。
package org.apache.catalina;
public interface Lifecycle {
//定义的String常量,用于LifecycleEvent事件的type属性中,作用是区分组件发出的LifecycleEvent事件时的状态(如初始化前,启动前,启动中等)
public static final String BEFORE_INIT_EVENT = "before_init";
public static final String AFTER_INIT_EVENT = "after_init";
public static final String START_EVENT = "start";
public static final String BEFORE_START_EVENT = "before_start";
public static final String AFTER_START_EVENT = "after_start";
public static final String STOP_EVENT = "stop";
public static final String BEFORE_STOP_EVENT = "before_stop";
public static final String AFTER_STOP_EVENT = "after_stop";
public static final String AFTER_DESTROY_EVENT = "after_destroy";
public static final String BEFORE_DESTROY_EVENT = "before_destroy";
public static final String PERIODIC_EVENT = "periodic";
public static final String CONFIGURE_START_EVENT = "configure_start";
public static final String CONFIGURE_STOP_EVENT = "configure_stop";
public void addLifecycleListener(LifecycleListener listener); //添加LifecycleListener
//监听器
public LifecycleListener[] findLifecycleListeners();
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
public interface SingleUse {
}
}
所有生命周期的组件都要实现Lifecycle接口。
LifecycleBase是Lifecycle的默认实现。Server,Service等生命周期组件直接或间接继承了LifecycleBase;
LifecycleBase里面定义了一个 LifecycleSupport类来管理监听器。
/**
* Used to handle firing lifecycle events.
* TODO: Consider merging LifecycleSupport into this class.
*/
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
LifecycleSupport代码如下:
/**
* Support class to assist in firing LifecycleEvent notifications to
* registered LifecycleListeners.
*
* @author Craig R. McClanahan
*/
public final class LifecycleSupport {
// ----------------------------------------------------------- Constructors
/**
* Construct a new LifecycleSupport object associated with the specified
* Lifecycle component.
*
* @param lifecycle The Lifecycle component that will be the source
* of events that we fire
*/
public LifecycleSupport(Lifecycle lifecycle) {
super();
this.lifecycle = lifecycle;
}
// ----------------------------------------------------- Instance Variables
/**
* The source component for lifecycle events that we will fire.
*/
private final Lifecycle lifecycle;
/**
* The list of registered LifecycleListeners for event notifications.
*/
private final List<LifecycleListener> listeners = new CopyOnWriteArrayList<>();
// --------------------------------------------------------- Public Methods
/**
* Add a lifecycle event listener to this component.
*
* @param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
listeners.add(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return listeners.toArray(new LifecycleListener[0]);
}
/**
* Notify all lifecycle event listeners that a particular event has
* occurred for this Container. The default implementation performs
* this notification synchronously using the calling thread.
*
* @param type Event type
* @param data Event data
*/
public void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
for (LifecycleListener listener : listeners) {
listener.lifecycleEvent(event);
}
}
/**
* Remove a lifecycle event listener from this component.
*
* @param listener The listener to remove
*/
public void removeLifecycleListener(LifecycleListener listener) {
listeners.remove(listener);
}
}
而在LifecycleSupport类里面定义了一个LifecycleListener集合类型来保存LifecycleListener监听器。
代码如下:
/**
* Support class to assist in firing LifecycleEvent notifications to
* registered LifecycleListeners.
*
* @author Craig R. McClanahan
*/
public final class LifecycleSupport {
// ----------------------------------------------------------- Constructors
/**
* Construct a new LifecycleSupport object associated with the specified
* Lifecycle component.
*
* @param lifecycle The Lifecycle component that will be the source
* of events that we fire
*/
public LifecycleSupport(Lifecycle lifecycle) {
super();
this.lifecycle = lifecycle;
}
// ----------------------------------------------------- Instance Variables
/**
* The source component for lifecycle events that we will fire.
*/
private final Lifecycle lifecycle;
/**
* The list of registered LifecycleListeners for event notifications.
*/
private final List<LifecycleListener> listeners = new CopyOnWriteArrayList<>();
// --------------------------------------------------------- Public Methods
/**
* Add a lifecycle event listener to this component.
*
* @param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
listeners.add(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return listeners.toArray(new LifecycleListener[0]);
}
/**
* Notify all lifecycle event listeners that a particular event has
* occurred for this Container. The default implementation performs
* this notification synchronously using the calling thread.
*
* @param type Event type
* @param data Event data
*/
public void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
for (LifecycleListener listener : listeners) {
listener.lifecycleEvent(event);
}
}
/**
* Remove a lifecycle event listener from this component.
*
* @param listener The listener to remove
*/
public void removeLifecycleListener(LifecycleListener listener) {
listeners.remove(listener);
}
}
LifecycleListener类代码如下:
/**
* Interface defining a listener for significant events (including "component
* start" and "component stop" generated by a component that implements the
* Lifecycle interface. The listener will be fired after the associated state
* change has taken place.
*
* @author Craig R. McClanahan
*/
public interface LifecycleListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event LifecycleEvent that has occurred
*/
public void lifecycleEvent(LifecycleEvent event);
}
二、 LifecycleBase类的生命周期方法分析:
四个生命周期方法分别是 init(),start(),stop(),destroy()方法。都会线判断状态和当前方法是否匹配。
如在init方法之前调用了start方法。这时会先执行init方法。
LifecycleBase的状态是通过LifecycleBase类型的state属性来保存的,最开始初始化值为LifecycleState.NEW.
initInternal()和startInternal()模板方法会调用具体的子类具体来启动。
LifecycleBase类的init方法代码如下:
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {//如果状态不是NEW,就
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try { //初始化前将状态设置为LifecycleState.INITIALIZING
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal(); //具体执行初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
}
protected abstract void initInternal() throws LifecycleException;
//invalidTransition 抛出LifecycleException异常
//处理不符合要求的状态
private void invalidTransition(String type) throws LifecycleException {
String msg = sm.getString("lifecycleBase.invalidTransition", type,
toString(), state);
throw new LifecycleException(msg);
}
private synchronized void setStateInternal(LifecycleState state,
Object data, boolean check) throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("lifecycleBase.setState", this, state));
}
if (check) { //check 该段主要是校验state状态,state为null抛出异常,
//state不是FAILED 并且state不是STARTING或者this.state不是STARTING_PREP 或state不是STARTING,this.state也不是STARTING_PREP (省略。。)才会抛出异常,无法识别得到state.
//state可以是FAILED
//state可以是STARTING,this.state可以是STARTING_PREP
//state可以是STOPPING,this.state可以是STOPPING_PREP
//state可以是STOPPING,this.state可以是FAILED 都不会进入异常
// Must have been triggered by one of the abstract methods (assume
// code in this class is correct)
// null is never a valid state
if (state == null) {
invalidTransition("null");
// Unreachable code - here to stop eclipse complaining about
// a possible NPE further down the method
return;
}
// Any method can transition to failed
// startInternal() permits STARTING_PREP to STARTING
// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
// STOPPING
if (!(
state == LifecycleState.FAILED ||
(this.state == LifecycleState.STARTING_PREP &&
state == LifecycleState.STARTING) ||
(this.state == LifecycleState.STOPPING_PREP &&
state == LifecycleState.STOPPING) ||
(this.state == LifecycleState.FAILED &&
state == LifecycleState.STOPPING)
)) {
// No other transition permitted
invalidTransition(state.name());
}
}
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent, data); //处理事件
}
}
setStateInternal()方法中通过check参数来判断是否需要检查传入的状态state.如果需要检查则会检查传入的状态是否为空和是否符合正确的状态,最后设置state.
LifecycleBase类的start方法代码如下:
/**
* {@inheritDoc}
*/
@Override
public final synchronized void start() throws LifecycleException {
//检查状态是否已经启动,如果已经启动打印log
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) { //状态失败停止
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) { //如果状态无法识别,抛出异常
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {//如果启动后状态不是
//STARTING则抛出异常
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
//启动成功后将状态设为STARTED
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
}
三、Container源码分析
Container的作用是具体负责处理Servlet,处理Http请求。
public interface Container extends Lifecycle {
Container 四个子容器 Engine,Host,Context,Wrapper逐层包含的关系。
Engine 引擎,用来管理多个站点。
Host 站点(虚拟主机),整个webapps是一个站点,如:www.test.com域名对应webapps目录所代表的站点,其中ROOT目录 是主应用,直接使用域名就可以访问主应用。而webapps/test是test子应用,访问www.test.com/test
Context 应用程序,默认配置下webapps下的每个目录都市一个应用。其中ROOT目录存放着主应用,其他目录是子应用。
Wrapper 每个Wrapper封装一个Servlet.
配置:
Server.xml文件里面放了Tomcat大部分功能的配置。
Server.xml文件默认配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
<Listener className="org.apache.catalina.core.JasperListener"/>
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<GlobalNamingResources>
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
</GlobalNamingResources>
<Service name="Catalina">
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
<Engine defaultHost="localhost" name="Catalina">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log." suffix=".txt"/>
<Context docBase="D:\apache-tomcat-7.0.69\wtpwebapps\tp-user" path="/tp-user" reloadable="true" source="org.eclipse.jst.jee.server:tp-user"/>
</Host>
</Engine>
</Service>
</Server>
以上代码定义一个Server,端口是8005,监听命令是"SHUTDOWN",
Server下定义了listener,如:org.apache.catalina.startup.VersionLoggerListener ,
Server下定义了一个 GlobalNamingResources 配置了服务器的全局JNDI资源,配置Realm,需要配置GlobalNamingResources 。
1、可以在GlobalNamingResources中嵌入 <Resource>元素,定义资源参数 。
GlobalNamingResources官网介绍:http://tomcat.apache.org/tomcat-7.0-doc/config/globalresources.html
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
description-->资源描述,
pathname-->加载资源的路径,
type->获取该资源的时候返回的类型,
auth -->用Application登录到资源管理器,或者用Container代表应用程序登录到资源管理器。 此属性的值必须是Application或Container。 如果Web应用程序将在Web应用程序部署描述符中使用<resource-ref>元素,则此属性是必需的,但如果应用程序使用<resource-env-ref>,则该属性是可选的。
2、 可以在GlobalNamingResources中嵌套<Environment>元素,配置命名的值,这些值作为环境条目资源(Environment Entry Resource),对整个web应用可见 如在GlobalNamingResources下添加:
<Environment name="maxExemptions" value="10" type="java.lang.Integer" override="false"/>
value:通过JNDI context请求时,返回给应用的参数值。这个值必须转换成type属性定义的Java类型.
override: 如果/WEB-INF/web.xml中具有相同名称的<env-entry>覆盖这里指定的值,设为false。缺省值为true.
name :Environment的名称
等价于在/WEB-INF/web.xml中的配置:
<env-entry>
<env-entry-name>maxExemptions</param-name>
<env-entry-value>10</env-entry-value>
<env-entry-type>java.lang.Integer</env-entry-type>
</env-entry>
Server下定义了一个名为"Catalina"的Service ,Service里面定义了两个Connector ,一个是HTTP协议,一个是AJP协议(主要用于集成,与apache集成等),Service下面定义了名为 "Catalina"的Engine,默认站点是localhost。
Engine下定义了一个Host ,Host的appBase的每个文件夹里都代表一个应用,上面定义的站点就是默认是webapps目录,unpackWARs表示十分自动解压war文件,autoDeploy表示是否自动部署,为true,webapps下加入的应用会自动部署并启动。
Engin下定义了一个Realm,用于身份验证等安全机制.如果不指定,则使用Engine的Realm。
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm> 对一个UserDatabase Realm的配置,它让默认的web应用程序(manager)加载tomcat-user.xml来进行用户验证.
Realm可以从这些数据源获取用户验证数据:
- memory:使用在内存中存放的一个表格进行验证。这个表格时在tomcat启动时从一个xml文件加载到内存中的,在这个表格中的信息格式一般为:用户名/密码/角色。这种方式一般只用于测试和开发阶段. 配置方式如下<Realm className="org.apache.catalina.realm.MemoryRealm"/> 默认是读取 tomcat-users.xml
- UserDatabase:实现了一个可以修改的、持久的memory Realm,可以向后兼容memory Realm。
- JDBC:使用一个关系数据库存放用户验证数据
- DataSource:类似于JDBC Realm,使用JNDI的方式来从关系数据库中拿用户验证数据,内容最终还是在一个关系数据库里。
- JNDI:使用JNDI来获取Realm数据,这些数据一般存放在LDAP目录下。
- JAAS: 使用JAAS来获取用户验证信息
具体的数据源配置可参考博客:https://blog.csdn.net/conquer0715/article/details/78206874?locationNum=6&fps=1
Host下有一个Alias标签,<Host name="www.test.com"><Alias> test.com</Alias></Host> 表示alias别名,www.test.com和test.com访问同一个站点。
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log." suffix=".txt"/>访问日志的配置,directory 指的是日志文件放置的目录,在tomcat下面有个logs文件夹,prefix日志文件的名称前缀,suffix后缀,AccessLogValve访问日志类,pattern的匹配如下:
%a |
这是记录访问者的IP,在日志里是127.0.0.1 |
%A |
这是记录本地服务器的IP,在日志里是192.168.254.108 |
%b |
这是发送信息的字节数,不涵括http头,如果字节数为0的话,显示为- |
%B |
看tomcat的解释,没看出来与b%的区别,但我这里显示为-1,没想明白,望知道者告知,我把官方解释贴出来吧 Bytes sent, excluding HTTP headers |
%h |
这个就是服务器名称了,如果resolveHosts为false的话,这里就是IP地址了,我的日志里是127.0.0.1 |
%H |
访问者使用的协议,这里是HTTP/1.1 |
%l |
这个也不太清楚,官方也说这个always return '-' 官方解释:Remote logical username from identd (可能这样翻译:记录浏览者进行身份验证时提供的名字)(always returns '-') |
%m |
访问的方式,是GET还是POST,我这是GET |
%p |
本地接收访问的端口,呵呵,我这里是80啦 |
%q |
比如你访问的是aaa.jsp?bbb=ccc,那么这里就显示?bbb=ccc,明白了吧,这个q是querystring的意思 |
%r |
官方解释:First line of the request (method and request URI),不是很明白 |
%s |
这个是http的状态,我这里返回的是304,咱们经常看见访问某个网页报错误500什么的,那也会返回500 |
%S |
用户的session ID,这个session ID大家可以另外查一下详细的解释,反正每次都会生成不同的session ID |
%t |
这就是时间啦,好像有一个Common Log Format可以改,不过我没找到 |
%u |
得到了验证的访问者,否则就是"-" |
%U |
访问的URL地址,我这里是/rightmainima/leftbott4.swf |
%v |
服务器名称,可能就是你url里面写的那个吧,我这里是localhost |
%D |
官方解释:Time taken to process the request, in millis,应该是访问发生的时间,以毫秒记 |
%T |
官方解释:Time taken to process the request, in seconds,应该是访问发生的时间,以秒记 |
Engin的defaultHost属性,请求的域名在所有的Host的name和Alias都找不到时才使用defaultHost(默认的Host),如果使用IP直接访问会用到defaultHost,删除Engin的defaultHost属性,无法用127.0.0.1访问本机的tomcat.
Host下的定义的Context(应用)配置--》通过文件配置,--》将WAR文件放在Host目录下 ,--》文件夹放在Host目录下。
文件配置方式:conf/server.xml 中context标签--》配置单独的应用
conf/(enginname)/(hostname)/目录下以应用命名的xml文件--》配置单独的应用
/META-INF/context.xml文件 --》配置单独的应用
conf/context.xml文件 --》Context共享(整个Tomcat共享)
conf/(enginname)/(hostname)/context.xml.default文件 --》Context共享(Host共享)
ContainerBase类:
ContainerBase类:
/**
* The number of threads available to process start and stop events for any
* children associated with this container.
*/
private int startStopThreads = 1; //启动和停止的线程数
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
//ThreadPoolExecutor继承自Executor,管理线程
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
/**
* Handles the special values.
*/
private int getStartStopThreadsInternal() {
int result = getStartStopThreads();
// Positive values are unchanged
if (result > 0) { //线程数大于0,直接返回
return result;
}
// Zero == Runtime.getRuntime().availableProcessors()
// -ve == Runtime.getRuntime().availableProcessors() + value
// These two are the same
result = Runtime.getRuntime().availableProcessors() + result;
if (result < 1) { //线程数小于1,也处理为1
result = 1;
}
return result;
}
ContainerBase类:
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start(); //启动cluster
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start(); //启动realm
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
//线程调用子容器的start方法,(children[i].start())
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get(); //获取Future返回值
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start(); //启动管道
setState(LifecycleState.STARTING);
// Start our thread
threadStart(); //后台线程启动
}
1、new StartChild(children[i]))分析,实现Callable接口, 调用子容器start()方法。
// ----------------------------- Inner classes used with start/stop Executor
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
2、 threadStart(); 方法分析
ContainerBase类:
/**
* The processor delay for this component.
*/
protected int backgroundProcessorDelay = -1; //后台线程间隔,小于0就不启动
/**
* Start the background thread that will periodically check for
* session timeouts.
*/
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
/**
* Private thread class to invoke the backgroundProcess method
* of this container and its children after a fixed delay.
*/
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString(
"containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) { //while循环间隔backgroundProcessorDelay * 1000L
//调用backgroundProcess方法
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this);
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
container.backgroundProcess(); //这里调用了container的backgroundProcess
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
在processChildren()方法里面调用了 container.backgroundProcess();
具体实现类有:StandardContext,StandardWrapper类
StandardContext类:
@Override
public void backgroundProcess() {
if (!getState().isAvailable())
return;
Loader loader = getLoader();
if (loader != null) {
try {
loader.backgroundProcess(); //实现类WebappLoader类调用backgroundProcess()
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.loader", loader), e);
}
}
Manager manager = getManager();
if (manager != null) {
try {
manager.backgroundProcess(); //ManageBase类实现,处理Session过期
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.manager", manager),
e);
}
}
WebResourceRoot resources = getResources();
if (resources != null) {
try {
resources.backgroundProcess(); //cache和gc()
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.resources",
resources), e);
}
}
InstanceManager instanceManager = getInstanceManager();
if (instanceManager instanceof DefaultInstanceManager) {
try { //清除map已经过期死亡的key
((DefaultInstanceManager) instanceManager).backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.instanceManager",
resources), e);
}
}
super.backgroundProcess();
}
Engin:
StandardEngin类:
@Override
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
Host:
org.apache.catalina.core.StandardHost类:
private String errorReportValveClass =
"org.apache.catalina.valves.ErrorReportValve";
@Override
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) { //检查管道有没有指定的Value,没有则添加
Valve valve =
(Valve) Class.forName(errorValve).getDeclaredConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
HostConfig类:
package org.apache.catalina.startup;
public class HostConfig implements LifecycleListener
public interface LifecycleListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event LifecycleEvent that has occurred
*/
public void lifecycleEvent(LifecycleEvent event);
}
HostConfig的start方法在 ContainerBase类的backgroundProcess()方法有一个
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);方法调用
org.apache.catalina.startup.HostConfig
/**
* Process the START event for an associated Host.
*
* @param event The lifecycle event that has occurred
*/
@Override
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
HostConfig类的lifecycleEvent方法在接收到Lifecycle.START_EVENT时会调用start方法:
HostConfig的start()方法:
org.apache.catalina.HostConfig类:
/**
* Process a "start" event for this Host.
*/
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.error(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!host.getAppBaseFile().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
if (host.getDeployOnStartup())
deployApps();
}
/**
* Deploy applications for any directories or WAR files that are found
* in our "application root" directory.
*/
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase //部署xml描述文件
//xml文件指的是conf/(enginename)/(hostname)/*.xml
deployDescriptors(configBase, configBase.list());
// Deploy WARs //部署war文件,站点目录下
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders //部署文件夹
deployDirectories(appBase, filteredAppPaths);
}
/**
* Deploy WAR files.
*/
protected void deployWARs(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File war = new File(appBase, files[i]);
if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
war.isFile() && !invalidWars.contains(files[i]) ) {
ContextName cn = new ContextName(files[i], true);
if (isServiced(cn.getName())) {
continue;
}
if (deploymentExists(cn.getName())) {
DeployedApplication app = deployed.get(cn.getName());
boolean unpackWAR = unpackWARs;
if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) {
unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR();
}
if (!unpackWAR && app != null) {
// Need to check for a directory that should not be
// there
File dir = new File(appBase, cn.getBaseName());
if (dir.exists()) {
if (!app.loggedDirWarning) {
log.warn(sm.getString(
"hostConfig.deployWar.hiddenDir",
dir.getAbsoluteFile(),
war.getAbsoluteFile()));
app.loggedDirWarning = true;
}
} else {
app.loggedDirWarning = false;
}
}
continue;
}
// Check for WARs with /../ /./ or similar sequences in the name
if (!validateContextPath(appBase, cn.getBaseName())) {
log.error(sm.getString(
"hostConfig.illegalWarName", files[i]));
invalidWars.add(files[i]);
continue;
}
results.add(es.submit(new DeployWar(this, cn, war)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployWar.threaded.error"), e);
}
}
}
以上代码,HostConfig类start会检查Host站点配置的目录位置是否存在,调用deployApps部署。
Context:
org.apache.catalina.core.standardContext
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
if(log.isDebugEnabled())
log.debug("Starting " + getBaseName());
// Send j2ee.state.starting notification //发送j2ee.state.starting的通知
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
setConfigured(false);
boolean ok = true;
// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
}
// Post work directory
postWorkDirectory(); //设置工作目录等信息到Context中
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {// //这里的setResources指的是WebResourceRoot resources
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// An explicit cookie processor hasn't been specified; use the default
if (cookieProcessor == null) {
cookieProcessor = new LegacyCookieProcessor();
}
// Initialize character set mapper
getCharsetMapper();
// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
}
if (!dependencyCheck) {
// do not make application available if dependency check fails
ok = false;
}
// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}
if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}
// Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup");
// Binding thread
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
// since the loader just started, the webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStatic",
getClearReferencesStatic());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
Realm realm = getRealmInternal();
if (realm != null) {
if (realm instanceof Lifecycle)
((Lifecycle) realm).start();
// Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
}
@Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
}
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
// We put the resources into the servlet context
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
}
}
// Create context attributes that will be required
if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}
// Set up the context init params
mergeParameters();
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) { //开始启动listener
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) { //启动filter
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) { //初始化Servlets
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
}
1、namingResources.start();
NamingResourcesImpl继承自NameingResources,NamingResourcesImpl主要是管理j2EE命名的上下文和其关联的JNDI的上下文。
NamingResourcesImpl类:
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
}
2、 resourcesStart();
resources.start();具体实现是在WebappClassLoaderBase类的start()方法里面。
WebappClassLoaderBase类的start() 主要是加载 WEB-INF/classes下的class和WEB-INF/lib下的class.
StandardContext类:
private WebResourceRoot resources;
/**
* Allocate resources, including proxy.
* Return <code>true</code> if initialization was successfull,
* or <code>false</code> otherwise.
*/
public void resourcesStart() throws LifecycleException {
// Check current status in case resources were added that had already
// been started
if (!resources.getState().isAvailable()) {
resources.start();
}
if (effectiveMajorVersion >=3 && addWebinfClassesResources) {
WebResource webinfClassesResource = resources.getResource(
"/WEB-INF/classes/META-INF/resources");
if (webinfClassesResource.isDirectory()) {
getResources().createWebResourceSet(
WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/",
webinfClassesResource.getURL(), "/");
}
}
}
org.apache.catalina.loader.WebappClassLoaderBase类
/**
* Repositories managed by this class rather than the super class.
*/
private List<URL> localRepositories = new ArrayList<>();
private volatile LifecycleState state = LifecycleState.NEW;
private final HashMap<String,Long> jarModificationTimes = new HashMap<>();
/**
* Start the class loader.
*
* @exception LifecycleException if a lifecycle error occurs
*/
@Override
public void start() throws LifecycleException {
state = LifecycleState.STARTING_PREP;
WebResource classes = resources.getResource("/WEB-INF/classes");
if (classes.isDirectory() && classes.canRead()) {
localRepositories.add(classes.getURL());
}
WebResource[] jars = resources.listResources("/WEB-INF/lib");
for (WebResource jar : jars) {
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
localRepositories.add(jar.getURL());
jarModificationTimes.put(
jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
state = LifecycleState.STARTING;
String encoding = null;
try {
encoding = System.getProperty("file.encoding");
} catch (SecurityException e) {
return;
}
if (encoding.indexOf("EBCDIC")!=-1) {
needConvert = true;
}
state = LifecycleState.STARTED;
}
3、getCharsetMapper方法:
StandardContext类:
/**
* Return the Locale to character set mapper for this Context.
*/
public CharsetMapper getCharsetMapper() {
// Create a mapper the first time it is requested
if (this.charsetMapper == null) {
try {
Class<?> clazz = Class.forName(charsetMapperClass);
this.charsetMapper = (CharsetMapper) clazz.getDeclaredConstructor().newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
this.charsetMapper = new CharsetMapper();
}
}
return (this.charsetMapper);
}
package org.apache.catalina.util;
import java.io.InputStream;
import java.util.Locale;
import java.util.Properties;
import org.apache.tomcat.util.ExceptionUtils;
/**
* Utility class that attempts to map from a Locale to the corresponding
* character set to be used for interpreting input text (or generating
* output text) when the Content-Type header does not include one. You
* can customize the behavior of this class by modifying the mapping data
* it loads, or by subclassing it (to change the algorithm) and then using
* your own version for a particular web application.
*
* @author Craig R. McClanahan
*/
public class CharsetMapper {
// ---------------------------------------------------- Manifest Constants
/**
* Default properties resource name.
*/
public static final String DEFAULT_RESOURCE =
"/org/apache/catalina/util/CharsetMapperDefault.properties";
// ---------------------------------------------------------- Constructors
/**
* Construct a new CharsetMapper using the default properties resource.
*/
public CharsetMapper() {
this(DEFAULT_RESOURCE);
}
public CharsetMapper(String name) {
try (InputStream stream = this.getClass().getResourceAsStream(name)) {
map.load(stream);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
throw new IllegalArgumentException(t.toString());
}
}
// ---------------------------------------------------- Instance Variables
private Properties map = new Properties();
// ------------------------------------------------------- Public Methods
public String getCharset(Locale locale) {
// Match full language_country_variant first, then language_country,
// then language only
String charset = map.getProperty(locale.toString());
if (charset == null) {
charset = map.getProperty(locale.getLanguage() + "_"
+ locale.getCountry());
if (charset == null) {
charset = map.getProperty(locale.getLanguage());
}
}
return (charset);
}
public void addCharsetMappingFromDeploymentDescriptor(String locale, String charset) {
map.put(locale, charset);
}
}
4、ExtensionValidator.validateApplication(getResources(), this);方法
/**
* Runtime validation of a Web Application.
*
* This method uses JNDI to look up the resources located under a
* <code>DirContext</code>. It locates Web Application MANIFEST.MF
* file in the /META-INF/ directory of the application and all
* MANIFEST.MF files in each JAR file located in the WEB-INF/lib
* directory and creates an <code>ArrayList</code> of
* <code>ManifestResource</code> objects. These objects are then passed
* to the validateManifestResources method for validation.
*
* @param resources The resources configured for this Web Application
* @param context The context from which the Logger and path to the
* application
*
* @return true if all required extensions satisfied
*/
public static synchronized boolean validateApplication(
WebResourceRoot resources,
Context context)
throws IOException {
String appName = context.getName();
ArrayList<ManifestResource> appManifestResources = new ArrayList<>();
// Web application manifest
WebResource resource = resources.getResource("/META-INF/MANIFEST.MF");
if (resource.isFile()) {
try (InputStream inputStream = resource.getInputStream()) {
Manifest manifest = new Manifest(inputStream);
ManifestResource mre = new ManifestResource
(sm.getString("extensionValidator.web-application-manifest"),
manifest, ManifestResource.WAR);
appManifestResources.add(mre);
}
}
// Web application library manifests
WebResource[] manifestResources =
resources.getClassLoaderResources("/META-INF/MANIFEST.MF");
for (WebResource manifestResource : manifestResources) {
if (manifestResource.isFile()) {
// Primarily used for error reporting
String jarName = manifestResource.getURL().toExternalForm();
Manifest jmanifest = manifestResource.getManifest();
if (jmanifest != null) {
ManifestResource mre = new ManifestResource(jarName,
jmanifest, ManifestResource.APPLICATION);
appManifestResources.add(mre);
}
}
}
return validateManifestResources(appName, appManifestResources);
}
主要是校验/META-INF/MANIFEST.MF文件里面的信息。
/META-INF/MANIFEST.MF文件内容示例如下:
5、Realm--> CredentialHandler safeHandler = new CredentialHandler()
安全域,主要验证平局是否可以授权进入 这个ServletContext,是否是安全的。(Credential:凭据)
Realm realm = getRealmInternal();
if (realm != null) {
if (realm instanceof Lifecycle)
((Lifecycle) realm).start();
// Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
}
@Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
}
6、mergeParameters()方法合并context的初始化参数
/**
* Merge the context initialization parameters specified in the application
* deployment descriptor with the application parameters described in the
* server configuration, respecting the <code>override</code> property of
* the application parameters appropriately.
*/
private void mergeParameters() {
Map<String,String> mergedParams = new HashMap<>();
String names[] = findParameters();
for (int i = 0; i < names.length; i++) {
mergedParams.put(names[i], findParameter(names[i]));
}
ApplicationParameter params[] = findApplicationParameters();
for (int i = 0; i < params.length; i++) {
if (params[i].getOverride()) {
if (mergedParams.get(params[i].getName()) == null) {
mergedParams.put(params[i].getName(),
params[i].getValue());
}
} else {
mergedParams.put(params[i].getName(), params[i].getValue());
}
}
ServletContext sc = getServletContext();
for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
sc.setInitParameter(entry.getKey(), entry.getValue());
}
}
7、listenerStart()方法
StandardContext类:
/**
* Configure the set of instantiated application event listeners
* for this Context. Return <code>true</code> if all listeners wre
* initialized successfully, or <code>false</code> otherwise.
*/
public boolean listenerStart() {
if (log.isDebugEnabled())
log.debug("Configuring application event listeners");
// Instantiate the required listeners
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
if (getLogger().isDebugEnabled())
getLogger().debug(" Configuring event listener class '" +
listeners[i] + "'");
try {
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
if (!ok) {
getLogger().error(sm.getString("standardContext.applicationSkipped"));
return (false);
}
// Sort listeners in two arrays
ArrayList<Object> eventListeners = new ArrayList<>();
ArrayList<Object> lifecycleListeners = new ArrayList<>();
for (int i = 0; i < results.length; i++) {
if ((results[i] instanceof ServletContextAttributeListener)
|| (results[i] instanceof ServletRequestAttributeListener)
|| (results[i] instanceof ServletRequestListener)
|| (results[i] instanceof HttpSessionIdListener)
|| (results[i] instanceof HttpSessionAttributeListener)) {
eventListeners.add(results[i]);
}
if ((results[i] instanceof ServletContextListener)
|| (results[i] instanceof HttpSessionListener)) {
lifecycleListeners.add(results[i]);
}
}
// Listener instances may have been added directly to this Context by
// ServletContextInitializers and other code via the pluggability APIs.
// Put them these listeners after the ones defined in web.xml and/or
// annotations then overwrite the list of instances with the new, full
// list.
for (Object eventListener: getApplicationEventListeners()) {
eventListeners.add(eventListener);
}
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
// Send application start events
if (getLogger().isDebugEnabled())
getLogger().debug("Sending application start events");
// Ensure context is not null
getServletContext();
context.setNewServletContextListenerAllowed(false);
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (int i = 0; i < instances.length; i++) {
if (!(instances[i] instanceof ServletContextListener))
continue;
ServletContextListener listener =
(ServletContextListener) instances[i];
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error
(sm.getString("standardContext.listenerStart",
instances[i].getClass().getName()), t);
ok = false;
}
}
return (ok);
}
8、checkConstraintsForUncoveredMethods(findConstraints());
StandardContext类:
private void checkConstraintsForUncoveredMethods(
SecurityConstraint[] constraints) {
SecurityConstraint[] newConstraints =
SecurityConstraint.findUncoveredHttpMethods(constraints,
getDenyUncoveredHttpMethods(), getLogger());
for (SecurityConstraint constraint : newConstraints) {
addConstraint(constraint);
}
}
而SecurityConstraint表示WEB应用程序的安全性约束,
就是<security-constraint> 描述文件 所配置的那样。<http-method>是<security-constraint> 子元素.
如果没有 <http-method> 元素,这表示将禁止所有 HTTP 方法访问相应的资源。
<security-constraint>
<display-name>porject</display-name>
<web-resource-collection>
<web-resource-name>baseproject</web-resource-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.do</url-pattern>
<http-method>GET</http-method>
<http-method>HEAD</http-method>
<http-method>TRACE</http-method>
<http-method>POST</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<description>porject</description>
<role-name>All Role</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
一个是对该url-pattern进行了role的限制,即配置auth-constraint了
<security-constraint>
<web-resource-collection>
<web-resource-name>tests</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint> //增加了这个就表示对访问/*有角色访问限制
<role-name>role</role-name>
</auth-constraint>
</security-constraint>
9、filterStart()方法:
StandardContext类:
/**
* Configure and initialize the set of filters for this Context.
* Return <code>true</code> if all filter initialization completed
* successfully, or <code>false</code> otherwise.
*/
public boolean filterStart() {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.filterStart", name), t);
ok = false;
}
}
}
return ok;
}
10、loadOnStartup()方法:
/**
* Load and initialize all servlets marked "load on startup" in the
* web application deployment descriptor.
*
* @param children Array of wrappers for all currently defined
* servlets (including those not declared load on startup)
*/
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0)
continue;
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
Wrapper:
StandardWrapper类:
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification); //发送通知用于JMX
}
// Start up this component
super.startInternal();
setAvailable(0L);
// Send j2ee.state.running notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification); //发送通知用于JMX
}
}
/**
* Set the available date/time for this servlet, in milliseconds since the
* epoch. If this date/time is Long.MAX_VALUE, it is considered to mean
* that unavailability is permanent and any request for this servlet will return
* an SC_NOT_FOUND error. If this date/time is in the future, any request for
* this servlet will return an SC_SERVICE_UNAVAILABLE error.
*
* @param available The new available date/time
*/
@Override
public void setAvailable(long available) { //为servlet设置可用的时间
long oldAvailable = this.available;
if (available > System.currentTimeMillis())
this.available = available;
else
this.available = 0L;
support.firePropertyChange("available", Long.valueOf(oldAvailable),
Long.valueOf(this.available));
}
上一篇: 不允许记录中出现重复索引值的索引是什么?
下一篇: mysql文档存放在哪
推荐阅读
-
Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
-
Vue-Router 源码分析(三) VueRouter实例的属性和方法
-
tomcat源码分析-Container初始化与加载
-
Tomcat分析三—— Container分析
-
Tomcat-Container源码分析
-
Tomcat8源码分析系列-启动分析(一) Lifecycle
-
Tomcat源码分析 (三)----- 生命周期机制 Lifecycle
-
Tomcat 源码分析(三)-(二)-WEB应用中的Listener、Filter、Servlet 的加载和调用
-
Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
-
Vue-Router 源码分析(三) VueRouter实例的属性和方法