Tomcat是如何加载server.xml的


前言

最近在弄一个新的项目,涉及到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);
                }
            }
        }
    }

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值