06-tomcat的容器启动过程
在02-tomcat的启动和关闭流程中,在容器初始化之后,Catalina.start()方法会触发容器的启动流程。
public void start() {
try {
getServer().start();
} catch (LifecycleException e) {
}
}
1. StandardServer.start() ==> StandardServer.startInternal()
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
2. StandardService.start() ==> StandardService.startInternal()
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
if (container != null) {
synchronized (container) {
container.start(); //启动container
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start(); //启动executor
}
}
synchronized (connectors) {
for (Connector connector: connectors) {
try {
if (connector.getState() != LifecycleState.FAILED) {
connector.start(); //启动connector
}
} catch (Exception e) {
}
}
}
}
3. StandardEngine.startInternal()。==》 containerBase.startInternal()
protected synchronized void startInternal() throws LifecycleException {
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
logger = null;
getLogger();
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<Future<Void>>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i]))); //启动webapp的地方
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
threadStart(); //开启一个后台线程,扫描过期的session。
}
4. StartChild启动webapp
4.1 要了解这块逻辑,我们需要先回到server.xml的解析Host的地方。
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass")); //创建HostConfig生命周期监听器。
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
}
上面的创建HostConfig生命周期监听器的地方会在解析host节点的时候为host创建一个HostConfig的生命周期监听,记住这里,我们等下就要用到了。
4.2 现在我们回到StartChild这里,他是一个Callable。
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
public Void call() throws LifecycleException {
child.start();
return null;
}
}
他的child就是上面我们的StandardEngine的child,也就是StandardHost。所以接下来我们需要看StandardHost.startInternal(),因为这里面其他的逻辑跟我们这个流程关系不大,所以指定跳到他调用到了父类ContainerBase.startInternal()。
setState(LifecycleState.STARTING);
这里我们暂时只需要注意上面这一句。他最后调用LifecycleBase.setStateInternal()发送了一条Host生命周期切换的消息。
这时候我们上面创建的HostConfig接受到消息:
public void lifecycleEvent(LifecycleEvent event) {
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) {
return;
}
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start(); //LifecycleState.STARTING(true, Lifecycle.START_EVENT)
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
public void 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) {
}
if (host.getCreateDirs()) {
File[] dirs = new File[] {appBase(),configBase()}; //appBase就是<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
for (int i=0; i<dirs.length; i++) {
if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
log.error(sm.getString("hostConfig.createDirs",dirs[i]));
}
}
}
if (host.getDeployOnStartup())
deployApps();
}
protected void deployApps() {
File appBase = appBase();
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list()); //获取appBase里面的项目,有examples,docs和你自己的项目等等。
deployDescriptors(configBase, configBase.list()); //部署configBase
deployWARs(appBase, filteredAppPaths); //部署war包,war包的解压在这里面
deployDirectories(appBase, filteredAppPaths); //部署appBase里面的项目,一般我们的项目在这里
}
protected void deployDirectories(File appBase, String[] files) {
ExecutorService es = host.getStartStopExecutor(); //获取Host里面的线程池
List<Future<?>> results = new ArrayList<Future<?>>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
results.add(es.submit(new DeployDirectory(this, cn, dir))); //提交任务到线程池。
}
}
}
4.3 DeployDirectory.run() ==> HostConfig.deployDirectory()
protected void deployDirectory(ContextName cn, File dir) {
Context context = null;
File xml = new File(dir, Constants.ApplicationContextXml);
File xmlCopy = new File(configBase(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = copyXML;
try {
if (deployXML && xml.exists()) {
} else if (!deployXML && xml.exists()) {
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).newInstance(); //创建一个StandardContext对象。
}
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance(); //创建ContextConfig监听
context.addLifecycleListener(listener); //添加监听器
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context); //把StandardContext加入到StandardHost的子集里面
} catch (Throwable t) {
} finally {
}
deployed.put(cn.getName(), deployedApp);
}
4.4 StandardHost.addChild() ==> ContainerBase.addChild() ==> ContainerBase.addChildInternal() ==> StandardContext.start() ==> StandardContext.startInternal()
protected synchronized void startInternal() throws LifecycleException {
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); //创建WebappLoader类加载器
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
try {
if (ok) {
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); //读取web.xml中的servlet等数据并封装成wrapper后addChild到StandardContext中。添加applicationListener等。
for (Container child : findChildren()) { //遍历wrapper也就是servlet。
if (!child.getState().isAvailable()) {
child.start();
}
}
}
}
unbindThread(oldCCL);
}
try {
if (ok) {
if (!listenerStart()) { //启动web项目
}
}
if (ok) {
if (!filterStart()) { //启动filter
}
}
if (ok) {
loadOnStartup(findChildren()); //启动loadOnStartup大于0的servlet。
}
super.threadStart();
} finally {
}
}
关于WebappLoader的父类加载器,是在ContainerBase获取的,而ContainerBaes的父加载器是在Catalina的
digester.addRule(“Server/Service/Engine”, new SetParentClassLoaderRule(parentClassLoader));
中解析Engine的时候设置的。所以其实也就是catalina的父加载器,也就是在Bootstrap.init()中设置的sharedLoader。所以所有的web应用的父加载器都是sharedLoader。
5. Connector.start() ==> Connector.startInternal() ==> AbstractProtocol.start() ==> AbstractEndpoint.start()
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
在初始化过程中已经bind(),所以这里我们只需要关注startInternal()
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (getExecutor() == null) {
createExecutor(); //创建一个默认最大200线程的线程池。
}
initializeConnectionLatch(); //对于8080默认创建一个200容量的LimitLatch,200是在初始化bind()的时候设置的,可以到上一篇查找
startAcceptorThreads(); //开启接受请求的线程
Thread timeoutThread = new Thread(new AsyncTimeout(), //处理请求超时的线程
getName() + "-AsyncTimeout");
timeoutThread.setPriority(threadPriority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
}
5.1 AbstractEndpoint.startAcceptorThreads()
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount(); //在bind()过程中设置为了1.
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor(); //new Acceptor()
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start(); //启动acceptor。
}
}
在看下Acceptor具体实现。
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
int errorDelay = 0;
while (running) {
state = AcceptorState.RUNNING;
try {
countUpOrAwaitConnection();
Socket socket = null;
try {
socket = serverSocketFactory.acceptSocket(serverSocket); //serverSocket.accept()
} catch (IOException ioe) {
}
errorDelay = 0;
if (running && !paused && setSocketOptions(socket)) {
if (!processSocket(socket)) { //处理请求
countDownConnection();
closeSocket(socket);
}
} else {
countDownConnection();
closeSocket(socket);
}
}
state = AcceptorState.ENDED;
}
}
总结:
容器的启动过程也是重父容器以此向下,这里也很好的展示了tomcat容器的父子关系。
war包的解压和web项目的部署都在容器的启动过程中实现。
创建了一个默认为1个线程的acceptor在后台监听socket连接。
因为endpoint属性基本都是protocolHandler设置的,在上一章我们讲过protocolHandler可以通过在server.xml对应的Connector节点中指明相应的属性。
所以我们可以指明acceptorThreadCount属性增加我们想要的acceptor数量。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" acceptorThreadCount="3"/>
我们可以在Host节点中修改appBase,让tomcat实现部署我们放在tomcat的webapp以外的目录里面。