@w1992wishes
2018-02-23T05:54:54.000000Z
字数 17600
阅读 1111
源码分析 tomcat
本篇结构:
上一篇中介绍了startup.bat和catalina.bat脚本。了解到日常双击startup.bat启动tomcat,其实是来到catalina.bat脚本中,由catalina.bat脚本去执行org.apache.catalina.startup.Bootstrap这个类中的main方法。
这里就来看看Bootstrap类的main方法做了些什么。
public static void main(String args[]) {//上部分if (daemon == null) {// Don't set daemon until init() has completedBootstrap 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();} else if (command.equals("stopd")) {args[args.length - 1] = "stop";daemon.stop();} else if (command.equals("start")) {daemon.setAwait(true);daemon.load(args);daemon.start();} else if (command.equals("stop")) {daemon.stopServer(args);} else if (command.equals("configtest")) {daemon.load(args);if (null==daemon.getServer()) {System.exit(1);}System.exit(0);} else {log.warn("Bootstrap: command \"" + command + "\" does not exist.");}} catch (Throwable t) {// Unwrap the Exception for clearer error reportingif (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}
可以看出main方法大致可以分为两部分。
上部分是实例化一个Bootstrap对象,并调用init方法,然后赋值给daemon变量,当然如果daemon已经不是空了,说明已经初始化过了,就将daemon.catalinaLoader直接设置到当前线程(daemon.catalinaLoader是用来加载tomcat内部服务器所需类的类加载器)。
下部分是根据传递进来的参数决定走哪一步,当双击startup.bat时,传进来的是start,所以会来到这段:
else if (command.equals("start")) {daemon.setAwait(true);daemon.load(args);daemon.start();}
这里主要是调用三个方法,setAwait,load和start。
所以对于Bootstrap重要关注的就是init,setAwait,load和start这四个方法。
public void init() throws Exception {initClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif (log.isDebugEnabled())log.debug("Loading startup class");Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.getConstructor().newInstance();// Set the shared extensions class loaderif (log.isDebugEnabled())log.debug("Setting startup class properties");String methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);catalinaDaemon = startupInstance;}
init方法调用initClassLoaders初始化类加载器,然后将初始化好的catalinaLoader设置到当前线程,接着通过反射调用org.apache.catalina.startup.Catalina类的setParentClassLoader,将sharedLoader传入。
private void initClassLoaders() {try {commonLoader = createClassLoader("common", null);if( commonLoader == null ) {// no config file, default to this loader - we might be in a 'single' env.commonLoader=this.getClass().getClassLoader();}catalinaLoader = createClassLoader("server", commonLoader);sharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}
在类加载器篇中有提到,这里初始化三个类加载器,分别是commonLoader,catalinaLoader,sharedLoader并建立他们之间的关系,catalinaLoader和sharedLoader的parent是commonLoader。
这三个类加载器都是通过createClassLoader建立的:
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {String value = CatalinaProperties.getProperty(name + ".loader");if ((value == null) || (value.equals("")))return parent;value = replace(value);List<Repository> repositories = new ArrayList<>();String[] repositoryPaths = getPaths(value);for (String repository : repositoryPaths) {// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}// Local repositoryif (repository.endsWith("*.jar")) {repository = repository.substring(0, repository.length() - "*.jar".length());repositories.add(new Repository(repository, RepositoryType.GLOB));} else if (repository.endsWith(".jar")) {repositories.add(new Repository(repository, RepositoryType.JAR));} else {repositories.add(new Repository(repository, RepositoryType.DIR));}}return ClassLoaderFactory.createClassLoader(repositories, parent);}
createClassLoader首先回去读取CatalinaProperties中的common.loader,server.loader,shared.loader三个属性,进入CatalinaProperties类中会发现这三个属性来自conf/catalina.properties文件。
接着往下createClassLoader会将common.loader,server.loader,shared.loader三个属性中的值获取然后解析成Repository,然后交给ClassLoaderFactory.createClassLoader方法去创建类加载器,最后可以实现三个不同的类加载器分别加载不同目录下的类。
当然要说清楚的是,默认情况下,catalina.properties中server.loader,shared.loader并没有配置值,三个类加载是同一个,默认加载{catalina.home}/lib目录下的类和jar包。
如果想配置对所有web应用都可见但对tomcat内部服务器不可见的类,此时应该在catalina.properties文件中的shared.loader下进行配置。
Thread.currentThread().setContextClassLoader(catalinaLoader)将catalinaLoader设置为Tomcat主线程的线程上下文类加载器。
SecurityClassLoad.securityClassLoad用于线程安全的加载tomcat容器所需的class。
当然,要使这个方法真正起作用,需要启动tomcat安全管理器,由代码可知:
public static void securityClassLoad(ClassLoader loader) throws Exception {securityClassLoad(loader, true);}static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)throws Exception {if (requireSecurityManager && System.getSecurityManager() == null) {return;}loadCorePackage(loader);loadCoyotePackage(loader);loadLoaderPackage(loader);loadRealmPackage(loader);loadServletsPackage(loader);loadSessionPackage(loader);loadUtilPackage(loader);loadJavaxPackage(loader);loadConnectorPackage(loader);loadTomcatPackage(loader);}
如果没启用安全管理器,System.getSecurityManager()=null,直接return。
可以通过命令行的方式启功安全管理器:
catalina.bat run -security或者startup.bat -security
一旦启动了安全管理器,就会根据conf/catalina.policy文件定义的提供默认的安全策略,securityClassLoad方法中System.getSecurityManager()不再等于null,于是就会去执行一系列加载方法,将tomcat的class加载进来。
想了解to吗tomcat的安全策略,可以参考下这篇博文:
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.getConstructor().newInstance();// Set the shared extensions class loaderString methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);catalinaDaemon = startupInstance;
可以看到这部分代码是通过反射来实例化catalina,然后反射调用setParentClassLoader将sharedLoader传入到catalina实例中。
为什么不直接通过Bootstrap类直接启动tomcat,而是通过反射生成Catalina实例启动?
再查看tomcat目录结构时应该发现,Bootstrap并不在CATALINA_HOME/bin目录中,Bootstrap和Catalina松耦合(通过反射调用Catalina),它直接依赖JRE运行并为Tomcat应用服务器创建共享类加载器,用于构造Catalina和整个tomcat服务器。实现了启动入口和核心环境的解耦,简化了启动。
来看setAwait:
public void setAwait(boolean await)throws Exception {Class<?> paramTypes[] = new Class[1];paramTypes[0] = Boolean.TYPE;Object paramValues[] = new Object[1];paramValues[0] = Boolean.valueOf(await);Method method =catalinaDaemon.getClass().getMethod("setAwait", paramTypes);method.invoke(catalinaDaemon, paramValues);}
通过反射调用catalina的setAwait方法,其设置的值是留给后面用的,当Catalina将Tomcat的所有组件启动之后,会检查await属性,如果为true,会调用Catalina.await(),而Catalina.await()又会调用其内部的Server的await()。
public void start() {...if (await) {await();stop();}}
Server.await()包含一个while循环,此循环用于监听指定socket端口(默认为8005)的连接,当某个连接传入的参数为”SHUTDOWN”(默认为”SHUTDOWN”)时,终止此while循环(端口号和终止while循环的参数,在server.xml的Server标签设置)。
Server.await()用来维持Bootstrap的main方法(main thread)处于运行状态,而线程池中监听http请求的线程是守护线程(daemon thread)。
当Tomcat的指定端口接收到关闭命令时,Server.await()内的while循环终止,然后Catalina会调用stop()方法,关闭Tomcat的所有组件,最终Bootstrap的main thread终止,Tomcat关闭。
private void load(String[] arguments)throws Exception {// Call the load() methodString methodName = "load";Object param[];Class<?> paramTypes[];if (arguments==null || arguments.length==0) {paramTypes = null;param = null;} else {paramTypes = new Class[1];paramTypes[0] = arguments.getClass();param = new Object[1];param[0] = arguments;}Method method =catalinaDaemon.getClass().getMethod(methodName, paramTypes);if (log.isDebugEnabled())log.debug("Calling startup class " + method);method.invoke(catalinaDaemon, param);}
该方法通过反射调用catalina的load方法。
catalina的load方法首先初始化目录(initDirs)和初始化命名服务(initNaming),然后是createStartDigester(要了解这个方法,应该先了解一下Digester,它是apache的一个开源组件,通过它可以很方便的从xml文件生成java对象),该方法初始化Digester,为Xml的标签即解析模式增加处理规则rule。
关于Digester,可以参考这篇博文:
来看createStartDigester方法:
protected Digester createStartDigester() {long t1=System.currentTimeMillis();// Initialize the digesterDigester digester = new Digester();digester.setValidating(false);digester.setRulesValidation(true);Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();List<String> attrs = new ArrayList<>();attrs.add("className");fakeAttributes.put(Object.class, attrs);digester.setFakeAttributes(fakeAttributes);digester.setUseContextClassLoader(true);// Configure the actions we will be usingdigester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");digester.addSetProperties("Server");digester.addSetNext("Server","setServer","org.apache.catalina.Server");digester.addObjectCreate("Server/GlobalNamingResources","org.apache.catalina.deploy.NamingResourcesImpl");digester.addSetProperties("Server/GlobalNamingResources");digester.addSetNext("Server/GlobalNamingResources","setGlobalNamingResources","org.apache.catalina.deploy.NamingResourcesImpl");digester.addObjectCreate("Server/Listener",null, // MUST be specified in the element"className");digester.addSetProperties("Server/Listener");digester.addSetNext("Server/Listener","addLifecycleListener","org.apache.catalina.LifecycleListener");digester.addObjectCreate("Server/Service","org.apache.catalina.core.StandardService","className");digester.addSetProperties("Server/Service");digester.addSetNext("Server/Service","addService","org.apache.catalina.Service");digester.addObjectCreate("Server/Service/Listener",null, // MUST be specified in the element"className");digester.addSetProperties("Server/Service/Listener");digester.addSetNext("Server/Service/Listener","addLifecycleListener","org.apache.catalina.LifecycleListener");//Executordigester.addObjectCreate("Server/Service/Executor","org.apache.catalina.core.StandardThreadExecutor","className");digester.addSetProperties("Server/Service/Executor");digester.addSetNext("Server/Service/Executor","addExecutor","org.apache.catalina.Executor");digester.addRule("Server/Service/Connector",new ConnectorCreateRule());digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName", "protocol"}));digester.addSetNext("Server/Service/Connector","addConnector","org.apache.catalina.connector.Connector");digester.addObjectCreate("Server/Service/Connector/SSLHostConfig","org.apache.tomcat.util.net.SSLHostConfig");digester.addSetProperties("Server/Service/Connector/SSLHostConfig");digester.addSetNext("Server/Service/Connector/SSLHostConfig","addSslHostConfig","org.apache.tomcat.util.net.SSLHostConfig");digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",new CertificateCreateRule());digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",new SetAllPropertiesRule(new String[]{"type"}));digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate","addCertificate","org.apache.tomcat.util.net.SSLHostConfigCertificate");digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf","org.apache.tomcat.util.net.openssl.OpenSSLConf");digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf","setOpenSslConf","org.apache.tomcat.util.net.openssl.OpenSSLConf");digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd","org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd","addCmd","org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");digester.addObjectCreate("Server/Service/Connector/Listener",null, // MUST be specified in the element"className");digester.addSetProperties("Server/Service/Connector/Listener");digester.addSetNext("Server/Service/Connector/Listener","addLifecycleListener","org.apache.catalina.LifecycleListener");digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",null, // MUST be specified in the element"className");digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");digester.addSetNext("Server/Service/Connector/UpgradeProtocol","addUpgradeProtocol","org.apache.coyote.UpgradeProtocol");// Add RuleSets for nested elementsdigester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));digester.addRuleSet(new EngineRuleSet("Server/Service/"));digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));// When the 'engine' is found, set the parentClassLoader.digester.addRule("Server/Service/Engine",new SetParentClassLoaderRule(parentClassLoader));addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");long t2=System.currentTimeMillis();if (log.isDebugEnabled()) {log.debug("Digester for server.xml created " + ( t2-t1 ));}return digester;}
该方法初始化digester,创建一系列解析规则,然后在load方法中会调用:
digester.parse(inputSource);
可见digester解析的源是inputSource,而inputSource是来自于conf/server.xml:
file = configFile();inputStream = new FileInputStream(file);inputSource = new InputSource(file.toURI().toURL().toString());
configFile:
protected File configFile() {//protected String configFile = "conf/server.xml";File file = new File(configFile);if (!file.isAbsolute()) {file = new File(Bootstrap.getCatalinaBase(), configFile);}return file;}
这样便创建出了StandardServer对象,接着便调用getServer().init();
init方法来自StandardServer的父类LifecycleBase:
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) {handleSubClassException(t, "lifecycleBase.initFail", toString());}}
具体实现是在子类的initInternal方法中,在调用initInternal方法前后都会设置状态,LifecycleState.INITIALIZING代表正在初始化,LifecycleState.INITIALIZED表示初始化完成,相应会触发生命周期事件。
在StandardServer的initInternal方法中会调用子组件Services的init方法,并依次传递下去,完成所有组件的init。
可见catalina的load方法主要是根据conf/server.xml配置文件利用Digester创建服务器组件,然后调用Server的init方法,逐层次的实现所有组件的初始化。

最后看下start方法:
public void start()throws Exception {if( catalinaDaemon==null ) init();Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);method.invoke(catalinaDaemon, (Object [])null);}
同样也是通过反射调用catalina的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 servertry {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 hookif (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();}}
该方法主要触发StandardServer的start方法,StandardServer的start方法同init方法一样来自LifecycleBase,主要是改变生命周期的状态,同时触发相应的生命周期时间,具体的执行逻辑交由具体的子类startInternal方法实现:
public final synchronized void start() throws LifecycleException {if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||LifecycleState.STARTED.equals(state)) {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)) {// 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.handleSubClassException(t, "lifecycleBase.startFail", toString());}}
在StandardServer的startInternal方法中会调用子组件service的start方法,并依次调用其他组件的start方法。
protected void startInternal() throws LifecycleException {fireLifecycleEvent(CONFIGURE_START_EVENT, null);setState(LifecycleState.STARTING);globalNamingResources.start();// Start our defined Servicessynchronized (servicesLock) {for (int i = 0; i < services.length; i++) {services[i].start();}}}
所以同load方法很相似,start方法主要是实现各组件的start方法依次调用,可以用一张图来理解:

还应该看到Catalina的start方法会使用前面的setAwait方法传递的值,为true时,会在8005端口监听,保证主线程一直在运行,直到收到SHUTDOWN命令。