Tomcat是如何加载server.xml的

时间:2022-10-15 19:36:27

前言

最近在弄一个新的项目,涉及到LDAP服务器,并且前端页面也需要我自己一个人全权负责,之前一直在写服务器端,突然要写页面还是有很大压力的,不过既然要做,就好好做吧。刚好也想好好回顾下SpringMVC相关的知识。而在这之前突然想到之前自己一直都没有好好的去了解Servlet容器的具体实现,这一块基本是我的知识盲区。所以在写前端以及View层之前,我还是先看下我们常用的Servlet容器Tomcat是怎么工作的吧,源码版本是tomcat7.0.82,会在一些地方说明该版本和tomcat8版本之间的实现区别。

tomcat和我们之前接触的许多框架其实都一样,都需要解析相应的配置文件(随着Spring Boot的出现,现在已经可以实现零配置文件配置),而在tocmat中,我们需要解析的配置文件有server.xml和我们用户(程序员)自己编写的web.xml。

今天就先来看看server.xml的解析。

从tomcat的默认启动类Bootstrap说起

我们知道tomcat的默认启动类是位于org,apache.catalina.startup包下的Bootstrap,我们来看看他的main方法。

 public static void main(String args[]) {

if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}

try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}

if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
}
}

我们主要需要看这个方法的三个方法

bootstrap.init();

先初始化了真正的启动类Catalina。

demon.load();

然后调用Catalina的load方法

demon.start()

最后调用了Catalina的start方法

所以tomcat启动的过程主要就是调用了Catalina的load方法和start方法

我们先来看看load()方法

load()


/**
* Start a new server instance.
*/

public void load() {

long t1 = System.nanoTime();


// Create and execute our Digester
// 设置了解析server.xml的规则
Digester digester = createStartDigester();

InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//加载server.xml
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}


try {
//解析server.xml
//解析server.xml 使用的是digester,这是apache下一个解析xml的工具,可以了解一下,具体的在这就不过多介绍了
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}

getServer().setCatalina(this);

// Stream redirection
initStreams();

// Start the new server
try {
//调用解析server.xml得到的Server实例的init()方法
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}

}

long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}

}

这里并没有吧load()方法的代码全部搬过来,只是把关键部分的代码拿过来分析。

先说下这个方法都干了些什么:load()方法主要做的就是将server.xml解析到对应的Java类中,最后调用Server实例的init()来进行初始化工作。

我们对这个方法里面的一些之前没接触的知识点讲解一下:

configFile()

   protected String configFile = "conf/server.xml";
//Globals
public static final String CATALINA_BASE_PROP = "catalina.base";

protected File configFile() {

File file = new File(configFile);
//是否是绝对路径名,很明显不是,无论是在linux上还是在windows上都不是
if (!file.isAbsolute()) {
//调用的是File(String parent, String child)构造方法
//也就是System.getProperty是为了得到configFile的父目录
file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile);
}
return (file);

}

file.isAbsolute()判断是否为绝对路径

isAbsolute
public boolean isAbsolute()测试此抽象路径名是否为绝对路径名。绝对路径名的定义与系统有关。在 UNIX 系统上,如果路径名的前缀是 "/",那么该路径名是绝对路径名。在 Microsoft Windows 系统上,如果路径名的前缀是后跟 "\\" 的盘符,或者是 "\\\\",那么该路径名是绝对路径名。

返回:

System.getProperty()

getProperty
public static String getProperty(String key)获取指定键指示的系统属性。
首先,如果有安全管理器,则用该键作为其参数来调用 checkPropertyAccess 方法。结果可能导致 SecurityException。

如果没有当前系统属性的集合,则首先用与 getProperties 方法相同的方式创建并初始化系统属性的集合。


参数:
key - 系统属性的名称。
返回:
系统属性的字符串值,如果没有带有此键的属性,则返回 null。
抛出:
SecurityException - 如果安全管理器存在并且其 checkPropertyAccess 方法不允许访问指定的系统属性。
NullPointerException - 如果 key 为 null。
IllegalArgumentException - 如果 key 为空。

我们可以看到catalina.base并不是JVM中自带的系统属性,如果要想使用catalina.base,且并没有在程序中显式的调用System.setProperties来设置属性的话,我们需要手动来设置我们自定义的JVM系统属性

使用java -D 配置系统属性。
使用格式是:java -Dkey=value

当我们使用TomcatDebug的时候,我们发现我们的VM arguments(Run–>Run Confgurations“,然后在对话框的右边选择”Arguments”,即可看到)中已经配置了catalina.base,eclipse已经帮我们配置好了相关的JVM系统属性。

-Dcatalina.base="D:\hardworking\.metadata\.plugins\org.eclipse.wst.server.core\tmp0" -Dcatalina.home="D:\apache\apache-tomcat-7.0.56" -Dwtp.deploy="D:\hardworking\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps" -Djava.endorsed.dirs="D:\apache\apache-tomcat-7.0.56\endorsed"

我们通过configFile()成功找到了我们要加载的server.xml文件,接着就是解析这个配置文件了,解析这个配置文件用的是apache的开源组件Digester,关于这个组件的讲解会有专门的博客,这里先不详细说了。我们接着看getServer.init()这是关键。

getServer().init()

当解析完xml后,就要轮到getServer().init()发挥作用了。

首先,我们要找到Server对应的实例。

我们回头看看createStartDigester方法

    protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class<?>, List<String>> fakeAttributes =
new HashMap<Class<?>, List<String>>();
ArrayList<String> attrs = new ArrayList<String>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);

// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");

我们看到这里给Server指定的是org.apache.catalina.core.StandardServer,那我们现在把目光集中到这个类的init()方法上。

StandardServer.init()

我们打开StandardServer的源码,你会发现,你并找不到init()方法的足迹,很明显,这个时候如果你不是一个初学者你就应该知道,肯定是StandardServer的父类实现了这个方法,那我们找到StandardServer的定义处,找到他的父类以及一整套继承链,看看到底在哪个祖先类实现了init()方法

public final class StandardServer extends LifecycleMBeanBase implements Server {

public abstract class LifecycleMBeanBase extends LifecycleBase
implements MBeanRegistration {

public abstract class LifecycleBase implements Lifecycle{

最后我们发现在LifecycleBase中实现了init()方法,而且你会发现init()方法在Server接口中并没有定义,而是在他的父接口Lifecycle接口中定义的

 @Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}

try {
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;

主要就是调用了initInternal()方法,我们发现initInternal在LifecycleBase中是个抽象方法,很明显这里使用了框架设计中最喜欢使用的模板方法模式。那么我们就继续来看initInternal在StandardServer的具体实现。

StandardServer.initInternal()

  @Override
protected void initInternal() throws LifecycleException {
//先调用了父类LifecycleMBeanBase的initInternal()
super.initInternal();

//...中间省略了一大坨代码,因为都不是重点呀,重点在下面
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}

initInternal方法主要的工作就是调用Service对应的实例的init()方法

Service.init()

首先我们还是看createStartDigester方法,看看Service的实现类是什么

  digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");

给出了实现类是org.apache.catalina.core.StandardService,我们观看源码发现他的继承链和StandardServer是一样的,init()方法也是在LifecycleBase中实现的,所以我们直接看StandardService的initInternal()方法

StandardServer.initInternal()

    /**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/

@Override
protected void initInternal() throws LifecycleException {

super.initInternal();

if (container != null) {
container.init();
}

// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}

// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);

if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}

我们可以看到该方法对其三个子元素调用了init()

分别是对容器初始化和对线程池初始化以及对链接的初始化。这里先不详细说。

我们直接看start()方法

start()

  public void start() {

if (getServer() == null) {
load();
}

if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}

long t1 = System.nanoTime();

// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}

long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}

// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);

// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}

if (await) {
await();
stop();
}
}

主要操作是:

            getServer().start();
public Server getServer() {
return server;
}

我们发现Server接口没有直接定义start()方法,而是他的父接口Lifecycle定义了这一方法。

注意到我们操作的实现类org.apache.catalina.core.StandardServer(Digester解析XML时指定)也没有显示的实现这个方法,我们往他的祖先类中去寻找,终于,在LifecycleBase这个抽象类中找到了start()方法的实现。

public final class StandardServer extends LifecycleMBeanBase implements Server {

public abstract class LifecycleMBeanBase extends LifecycleBase
implements MBeanRegistration {

public abstract class LifecycleBase implements Lifecycle{
 @Override
public final synchronized void start() throws LifecycleException {

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);
//调用statInternal() start()
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)) {
// 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 {
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);
}
}

主要就是两个方法:

init();

startInternal();

daga

    @Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}

try {
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);
}
}
@Override
protected void initInternal() throws LifecycleException {

super.initInternal();

// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
//中间有一大段代码,但是不是我们要关注的重点,感兴趣的同学可以都读一遍源码
...
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}

services

digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");

所以调用的是org.apache.catalina.core.StandardService的init()方法

同样init()方法的实现是在

    protected void initInternal() throws LifecycleException {

super.initInternal();

if (container != null) {
container.init();
}

// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}

// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);

if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}

从代码可以看出该方法创建了初始化了线程池和Connector 。

startInternal()

在LifeCycleBase中,这是个抽象方法,具体的实现在StandardServer中

  @Override
protected void startInternal() throws LifecycleException {

fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);

globalNamingResources.start();

// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}

start()

这个方法的实现依旧是在LifeCycleBase中,其中还是调用了模板方法startInternal(),具体实现在StandardService中

  @Override
protected void startInternal() throws LifecycleException {

if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);

// Start our defined Container first
if (container != null) {
synchronized (container) {
container.start();
}
}

synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}

// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}

该方法主要是启动线程池和连接器。