Tomcat(一):Tomcat启动时加载web.xml
server.xml配置文件样例:
<?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Note: A "Server" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/server.html --> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup.VersionLoggerListener"/> <!-- Security listener. Documentation at /docs/config/listeners.html <Listener className="org.apache.catalina.security.SecurityListener" /> --> <!--APR library loader. Documentation at /docs/apr.html --> <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/> <!-- 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"/> <!-- Global JNDI resources Documentation at /docs/jndi-resources-howto.html --> <GlobalNamingResources> <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users --> <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> <!-- A "Service" is a collection of one or more "Connectors" that share a single "Container" Note: A "Service" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/service.html --> <Service name="Catalina"> <!--The connectors can use a shared executor, you can define one or more named thread pools--> <!-- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> --> <!-- A "Connector" represents an endpoint by which requests are received and responses are returned. Documentation at : Java HTTP Connector: /docs/config/http.html Java AJP Connector: /docs/config/ajp.html APR (HTTP/AJP) Connector: /docs/apr.html Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 --> <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> <!-- A "Connector" using the shared thread pool--> <!-- <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> --> <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 This connector uses the NIO implementation. The default SSLImplementation will depend on the presence of the APR/native library and the useOpenSSL attribute of the AprLifecycleListener. Either JSSE or OpenSSL style configuration may be used regardless of the SSLImplementation selected. JSSE style configuration is used below. --> <!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeystoreFile="conf/localhost-rsa.jks" type="RSA" /> </SSLHostConfig> </Connector> --> <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2 This connector uses the APR/native implementation which always uses OpenSSL for TLS. Either JSSE or OpenSSL style configuration may be used. OpenSSL style configuration is used below. --> <!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" > <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> </Connector> --> <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/> <!-- An Engine represents the entry point (within Catalina) that processes every request. The Engine implementation for Tomcat stand alone analyzes the HTTP headers included with the request, and passes them on to the appropriate Host (virtual host). Documentation at /docs/config/engine.html --> <!-- You should set jvmRoute to support load-balancing via AJP ie : <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1"> --> <Engine defaultHost="localhost" name="Catalina"> <!--For clustering, please take a look at documentation at: /docs/cluster-howto.html (simple how to) /docs/config/cluster.html (reference documentation) --> <!-- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> --> <!-- Use the LockOutRealm to prevent attempts to guess user passwords via a brute-force attack --> <Realm className="org.apache.catalina.realm.LockOutRealm"> <!-- This Realm uses the UserDatabase configured in the global JNDI resources under the key "UserDatabase". Any edits that are performed against this UserDatabase are immediately available for use by the Realm. --> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"> <!-- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html --> <!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" refix="localhost_access_log" suffix=".txt"/> <Context docBase="mybaits-test-dynamic-sql" path="/mybaits-test-dynamic-sql" reloadable="true" source="org.eclipse.jst.j2ee.server:mybaits-test-dynamic-sql"/> </Host> <!--spring_security_01_war--> <Host name="lhd.myweb.com" appBase="/opt/apache-tomcat-8.5.49/webapps" unpackWARs="true" autoDeploy="true"> <Context docBase="/opt/apache-tomcat-8.5.49/webapps/spring_security_01_war" path="" /> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" refix="localhost_access_log" suffix=".txt"/> </Host> </Engine> </Service> <Service name="Catalina2"> <Connector connectionTimeout="20000" port="8200" protocol="HTTP/1.1" redirectPort="8443"/> <Connector port="8010" protocol="AJP/1.3" redirectPort="8443"/> <Engine defaultHost="localhost" name="Catalina"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <!--spring_security_01_war--> <Host name="lhd.myweb.com" appBase="/opt/apache-tomcat-8.5.49/webapps" unpackWARs="true" autoDeploy="true"> <Context docBase="/opt/apache-tomcat-8.5.49/webapps/spring_security_01_war" path="" /> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" refix="localhost_access_log" suffix=".txt"/> </Host> </Engine> </Service> </Server>
在Tomcat启动时,会加载并解析tomcat安装目录/conf/server.xml文件,在server.xml配置文件中通过Host或Content进行web应用加载:
1)Host配置: 在tomcat的server.xml中默认Host配置:
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
如此设置则会自动加载webapps下的文件夹或者war包。
2)Content配置:
<Context docBase="/xxxxx" path="/xxxx" reloadable="true" ></Context>
如此则会加载指定的context。
备注:
样例server.xml访问站点的方式包含:
1)http://localhost:8080/mybaits-test-dynamic-sql
2)http://lhd.myweb.com:8080/
3)http://lhd.myweb.com:8200/
1)配置Host的appBase则可以自动加载appBase文件夹下的应用
1.1)查看StandardHost的startInternal方法:
/** * 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 { // 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) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("standardHost.invalidErrorReportValveClass", errorValve), t); } } super.startInternal(); }
Super.startInternal()方法是ContainerBase.java中的方法:
/** * 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 instanceof Lifecycle) { ((Lifecycle) cluster).start(); } Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // 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(); }
查看ContainerBase#startInternal()中调用了ContainerBase#setState(LifecycleState.STARTING);
/** * Provides a mechanism for sub-classes to update the component state. * Calling this method will automatically fire any associated * {@link Lifecycle} event. It will also check that any attempted state * transition is valid for a sub-class. * * @param state The new state for this component * @throws LifecycleException when attempting to set an invalid state */ protected synchronized void setState(LifecycleState state) throws LifecycleException { setStateInternal(state, null, true); }
查看ContainerBase#setState(LifecycleState state)方法内部调用了ContainerBase#setStateInternal(state,null,true)方法:
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) { // 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); } }
在ContainerBase#setStateInternal()内部调用了ContainerBase#fireLifecycleEvent(lifecycleEvent,data);方法:
/** * The list of registered LifecycleListeners for event notifications. */ private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>(); /** * Allow sub classes to fire {@link Lifecycle} events. * * @param type Event type * @param data Data associated with event. */ protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } }
StandardHost在启动时,会通知所有listener它的启动状态,而StandardHost有一个默认listener是HostConfig,所以会调用到HostConfig的lifecycleEvent方法。
LifecycleListener接口的实现类:
1.2)查看HostConfig的lifecycleEvent方法:
查看HostConfig#lifecycleEvent()方法代码:
/** * 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()方法内部会调用HostConfig#start()方法:
/** * 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.warn(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(); }
在HostConfig#start()内部会调用HostConfig#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 deployDescriptors(configBase, configBase.list()); // Deploy WARs deployWARs(appBase, filteredAppPaths); // Deploy expanded folders deployDirectories(appBase, filteredAppPaths); }
在HostConfig#deployApps()方法内部会一次调用deployWARs(appBase,filteredAppPaths);和deployDirectories(appBase,filteredAppPaths);方法:
/** * Deploy exploded webapps. * @param appBase The base path for applications * @param files The exploded webapps that should be deployed */ protected void deployDirectories(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 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))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString("hostConfig.deployDir.threaded.error"), e); } } }
在HostConfig#deployDirectories()方法内部调用了ExecutorService#submit(new DeployDirectory(this,cn,dir));,对于每一个文件夹,提交了一个部署任务。
1.3)查看DeployDirectory的run方法:
private static class DeployDirectory implements Runnable { private HostConfig config; private ContextName cn; private File dir; public DeployDirectory(HostConfig config, ContextName cn, File dir) { this.config = config; this.cn = cn; this.dir = dir; } @Override public void run() { config.deployDirectory(cn, dir); } }
在DeployDirectory#run()内部调用config.deployDirectory(cn,dir);代码,HostConfig#deployDirectory(cn,dir)方法内部代码:
/** * Deploy exploded webapp. * @param cn The context name * @param dir The path to the root folder of the weapp */ protected void deployDirectory(ContextName cn, File dir) { long startTime = 0; // Deploy the application in this directory if( log.isInfoEnabled() ) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath())); } Context context = null; File xml = new File(dir, Constants.ApplicationContextXml); File xmlCopy = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); DeployedApplication deployedApp; boolean copyThisXml = isCopyXML(); boolean deployThisXML = isDeployThisXML(dir, cn); try { if (deployThisXML && xml.exists()) { synchronized (digesterLock) { try { context = (Context) digester.parse(xml); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", xml), e); context = new FailedContext(); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } if (copyThisXml == false && context instanceof StandardContext) { // Host is using default value. Context may override it. copyThisXml = ((StandardContext) context).getCopyXML(); } if (copyThisXml) { Files.copy(xml.toPath(), xmlCopy.toPath()); context.setConfigFile(xmlCopy.toURI().toURL()); } else { context.setConfigFile(xml.toURI().toURL()); } } else if (!deployThisXML && xml.exists()) { // Block deployment as META-INF/context.xml may contain security // configuration necessary for a secure deployment. log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), xml, xmlCopy)); context = new FailedContext(); } else { context = (Context) Class.forName(contextClass).getConstructor().newInstance(); } Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); context.setDocBase(cn.getBaseName()); host.addChild(context); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployDir.error", dir.getAbsolutePath()), t); } finally { deployedApp = new DeployedApplication(cn.getName(), xml.exists() && deployThisXML && copyThisXml); // Fake re-deploy resource to detect if a WAR is added at a later // point deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war", Long.valueOf(0)); deployedApp.redeployResources.put(dir.getAbsolutePath(), Long.valueOf(dir.lastModified())); if (deployThisXML && xml.exists()) { if (copyThisXml) { deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(xmlCopy.lastModified())); } else { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); } } else { // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); if (!xml.exists()) { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(0)); } } addWatchedResources(deployedApp, dir.getAbsolutePath(), context); // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } deployed.put(cn.getName(), deployedApp); if( log.isInfoEnabled() ) { log.info(sm.getString("hostConfig.deployDir.finished", dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime))); } }
部署任务中会调用HostConfig的方法进行实际的部署操作,再次方法中会构造Context,并使用host.addChild(context)方法加入到Host中。
2)自行配置Context
如果在Host下配置了Context,则在解析server.xml时会创建Context对象,并使用host.addChild(context)方法加入到Host。
3)加载web.xml
一个Context对应一个web应用,而一个web应用应该由一个web.xml。
查看StandardContext#startInternal()方法:
/** * 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 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(); // Add missing components as necessary if (getResources() == null) { // (1) Required by Loader if (log.isDebugEnabled()) log.debug("Configuring default Resources"); try { 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 Rfc6265CookieProcessor(); } // 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 instanceof Lifecycle) { ((Lifecycle) loader).start(); } // since the loader just started, the webapp classloader is now // created. setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets()); setClassLoaderProperty("clearReferencesStopThreads", getClearReferencesStopThreads()); setClassLoaderProperty("clearReferencesStopTimerThreads", getClearReferencesStopTimerThreads()); setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread", getClearReferencesHttpClientKeepAliveThread()); setClassLoaderProperty("clearReferencesObjectStreamClassCaches", getClearReferencesObjectStreamClassCaches()); setClassLoaderProperty("clearReferencesThreadLocals", getClearReferencesThreadLocals()); // 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(null != realm) { 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()); InstanceManagerBindings.bind(getLoader().getClassLoader(), 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) { 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 instanceof Lifecycle) { ((Lifecycle) manager).start(); } } catch(Exception e) { log.error(sm.getString("standardContext.managerFail"), e); ok = false; } // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } // Load and initialize all "load on startup" servlets if (ok) { 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); } }
其内部主要调用流水线:
startInternal()->fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT,NULL) ->child.start() ->mergeParameters() ->listenerStart() ->setApplicationEventListeners() ->setApplicationLifecycleListeners() ->filterStart() ->loadOnStartup(findChildren())
3.1)StandardContext#fireLifecycleEvent()方法
StandardContext默认会添加ContextConfig到listener,当StandardContext启动时会通知所有listener它的启动状态,所以会调用到ContextConfig#lifecycleEvent方法。
查看ContextConfig#lifecycleEvent()方法:
/** * Process events for an associated Context. * * @param event The lifecycle event that has occurred */ @Override public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with try { context = (Context) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase != null) { context.setDocBase(originalDocBase); } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } }
因为传递的event是CONFIGURES_START_EVENT,因此在ContextConfig#lifecycleEvent()内部会调用ContextConfig#configureStart(),查看ContextConfig#configureStart()代码:
/** * Process a "contextConfig" event for this Context. */ protected synchronized void configureStart() { // Called from StandardContext.start() if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.start")); } if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.xmlSettings", context.getName(), Boolean.valueOf(context.getXmlValidation()), Boolean.valueOf(context.getXmlNamespaceAware()))); } webConfig(); // Just for debug context.addServletContainerInitializer(new JasperInitializer(), null); if (!context.getIgnoreAnnotations()) { applicationAnnotationsConfig(); } if (ok) { validateSecurityRoles(); } // Configure an authenticator if we need one if (ok) { authenticatorConfig(); } // Dump the contents of this pipeline if requested if (log.isDebugEnabled()) { log.debug("Pipeline Configuration:"); Pipeline pipeline = context.getPipeline(); Valve valves[] = null; if (pipeline != null) { valves = pipeline.getValves(); } if (valves != null) { for (int i = 0; i < valves.length; i++) { log.debug(" " + valves[i].getClass().getName()); } } log.debug("======================"); } // Make our application available if no problems were encountered if (ok) { context.setConfigured(true); } else { log.error(sm.getString("contextConfig.unavailable")); context.setConfigured(false); } }
在ContextConfig#configureStart()内部会调用ContextConfig#webConfig()方法:
/** * Scan the web.xml files that apply to the web application and merge them * using the rules defined in the spec. For the global web.xml files, * where there is duplicate configuration, the most specific level wins. ie * an application's web.xml takes precedence over the host level or global * web.xml file. */ protected void webConfig() { /* * Anything and everything can override the global and host defaults. * This is implemented in two parts * - Handle as a web fragment that gets added after everything else so * everything else takes priority * - Mark Servlets as overridable so SCI configuration can replace * configuration from the defaults */ /* * The rules for annotation scanning are not as clear-cut as one might * think. Tomcat implements the following process: * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of * which Servlet spec version is declared in web.xml. The EG has * confirmed this is the expected behaviour. * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main * web.xml is marked as metadata-complete, JARs are still processed * for SCIs. * - If metadata-complete=true and an absolute ordering is specified, * JARs excluded from the ordering are also excluded from the SCI * processing. * - If an SCI has a @HandlesType annotation then all classes (except * those in JARs excluded from an absolute ordering) need to be * scanned to check if they match. */ WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(), context.getXmlValidation(), context.getXmlBlockExternal()); Set<WebXml> defaults = new HashSet<>(); defaults.add(getDefaultWebXmlFragment(webXmlParser)); WebXml webXml = createWebXml(); // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource(); if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) { ok = false; } ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application and those // provided by the container. If any of the application JARs have a // web-fragment.xml it will be parsed at this point. web-fragment.xml // files are ignored for container provided JARs. Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser); // Step 2. Order the fragments. Set<WebXml> orderedFragments = null; orderedFragments = WebXml.orderWebFragments(webXml, fragments, sContext); // Step 3. Look for ServletContainerInitializer implementations if (ok) { processServletContainerInitializers(); } if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // Steps 4 & 5. processClasses(webXml, orderedFragments); } if (!webXml.isMetadataComplete()) { // Step 6. Merge web-fragment.xml files into the main web.xml // file. if (ok) { ok = webXml.merge(orderedFragments); } // Step 7. Apply global defaults // Have to merge defaults before JSP conversion since defaults // provide JSP servlet definition. webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { configureContext(webXml); } } else { webXml.merge(defaults); convertJsps(webXml); configureContext(webXml); } if (context.getLogEffectiveWebXml()) { log.info("web.xml:\n" + webXml.toXml()); } // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set<WebXml> resourceJars = new LinkedHashSet<>(); for (WebXml fragment : orderedFragments) { resourceJars.add(fragment); } for (WebXml fragment : fragments.values()) { if (!resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration } // Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } }
在ContextConfig#webConfig内部会创建WebXmlParser对象,并调用该对象的parseWebXml()方法进行web.xml解析(在parseWebXml()方法内部采用Digester读取web.xml);并调用configureContext(webXml)对StandardContext实例属性进行设置:
private void configureContext(WebXml webxml) { // As far as possible, process in alphabetical order so it is easy to // check everything is present // Some validation depends on correct public ID context.setPublicId(webxml.getPublicId()); // Everything else in order context.setEffectiveMajorVersion(webxml.getMajorVersion()); context.setEffectiveMinorVersion(webxml.getMinorVersion()); for (Entry<String, String> entry : webxml.getContextParams().entrySet()) { context.addParameter(entry.getKey(), entry.getValue()); } context.setDenyUncoveredHttpMethods( webxml.getDenyUncoveredHttpMethods()); context.setDisplayName(webxml.getDisplayName()); context.setDistributable(webxml.isDistributable()); for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) { context.getNamingResources().addLocalEjb(ejbLocalRef); } for (ContextEjb ejbRef : webxml.getEjbRefs().values()) { context.getNamingResources().addEjb(ejbRef); } for (ContextEnvironment environment : webxml.getEnvEntries().values()) { context.getNamingResources().addEnvironment(environment); } for (ErrorPage errorPage : webxml.getErrorPages().values()) { context.addErrorPage(errorPage); } for (FilterDef filter : webxml.getFilters().values()) { if (filter.getAsyncSupported() == null) { filter.setAsyncSupported("false"); } context.addFilterDef(filter); } for (FilterMap filterMap : webxml.getFilterMappings()) { context.addFilterMap(filterMap); } context.setJspConfigDescriptor(webxml.getJspConfigDescriptor()); for (String listener : webxml.getListeners()) { context.addApplicationListener(listener); } for (Entry<String, String> entry : webxml.getLocaleEncodingMappings().entrySet()) { context.addLocaleEncodingMappingParameter(entry.getKey(), entry.getValue()); } // Prevents IAE if (webxml.getLoginConfig() != null) { context.setLoginConfig(webxml.getLoginConfig()); } for (MessageDestinationRef mdr : webxml.getMessageDestinationRefs().values()) { context.getNamingResources().addMessageDestinationRef(mdr); } // messageDestinations were ignored in Tomcat 6, so ignore here context.setIgnoreAnnotations(webxml.isMetadataComplete()); for (Entry<String, String> entry : webxml.getMimeMappings().entrySet()) { context.addMimeMapping(entry.getKey(), entry.getValue()); } // Name is just used for ordering for (ContextResourceEnvRef resource : webxml.getResourceEnvRefs().values()) { context.getNamingResources().addResourceEnvRef(resource); } for (ContextResource resource : webxml.getResourceRefs().values()) { context.getNamingResources().addResource(resource); } boolean allAuthenticatedUsersIsAppRole = webxml.getSecurityRoles().contains( SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS); for (SecurityConstraint constraint : webxml.getSecurityConstraints()) { if (allAuthenticatedUsersIsAppRole) { constraint.treatAllAuthenticatedUsersAsApplicationRole(); } context.addConstraint(constraint); } for (String role : webxml.getSecurityRoles()) { context.addSecurityRole(role); } for (ContextService service : webxml.getServiceRefs().values()) { context.getNamingResources().addService(service); } for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); // Description is ignored // Display name is ignored // Icons are ignored // jsp-file gets passed to the JSP Servlet as an init-param if (servlet.getLoadOnStartup() != null) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { if (multipartdef.getMaxFileSize() != null && multipartdef.getMaxRequestSize()!= null && multipartdef.getFileSizeThreshold() != null) { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), Long.parseLong(multipartdef.getMaxFileSize()), Long.parseLong(multipartdef.getMaxRequestSize()), Integer.parseInt( multipartdef.getFileSizeThreshold()))); } else { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation())); } } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); } for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); } SessionConfig sessionConfig = webxml.getSessionConfig(); if (sessionConfig != null) { if (sessionConfig.getSessionTimeout() != null) { context.setSessionTimeout( sessionConfig.getSessionTimeout().intValue()); } SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig(); scc.setName(sessionConfig.getCookieName()); scc.setDomain(sessionConfig.getCookieDomain()); scc.setPath(sessionConfig.getCookiePath()); scc.setComment(sessionConfig.getCookieComment()); if (sessionConfig.getCookieHttpOnly() != null) { scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue()); } if (sessionConfig.getCookieSecure() != null) { scc.setSecure(sessionConfig.getCookieSecure().booleanValue()); } if (sessionConfig.getCookieMaxAge() != null) { scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue()); } if (sessionConfig.getSessionTrackingModes().size() > 0) { context.getServletContext().setSessionTrackingModes( sessionConfig.getSessionTrackingModes()); } } // Context doesn't use version directly for (String welcomeFile : webxml.getWelcomeFiles()) { /* * The following will result in a welcome file of "" so don't add * that to the context * <welcome-file-list> * <welcome-file/> * </welcome-file-list> */ if (welcomeFile != null && welcomeFile.length() > 0) { context.addWelcomeFile(welcomeFile); } } // Do this last as it depends on servlets for (JspPropertyGroup jspPropertyGroup : webxml.getJspPropertyGroups()) { String jspServletName = context.findServletMapping("*.jsp"); if (jspServletName == null) { jspServletName = "jsp"; } if (context.findChild(jspServletName) != null) { for (String urlPattern : jspPropertyGroup.getUrlPatterns()) { context.addServletMappingDecoded(urlPattern, jspServletName, true); } } else { if(log.isDebugEnabled()) { for (String urlPattern : jspPropertyGroup.getUrlPatterns()) { log.debug("Skipping " + urlPattern + " , no servlet " + jspServletName); } } } } for (Entry<String, String> entry : webxml.getPostConstructMethods().entrySet()) { context.addPostConstructMethod(entry.getKey(), entry.getValue()); } for (Entry<String, String> entry : webxml.getPreDestroyMethods().entrySet()) { context.addPreDestroyMethod(entry.getKey(), entry.getValue()); } }
3.2)StandardContext#mergeParameters()方法:
/** * 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()); } }
步骤3.1)中会将web.xml中的context-param元素设置到context的parameters里,此处则是把parameters设置到servletContext里。
3.3)StandardContext#listenerStart(),启动listener:
/** * 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; }
步骤3.1)中会将web.xml中的listener元素设置到context的applicationListeners里,此处则取出listener类名,创建实例,并将listener分为两类:
eventListener:ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener;
lifecycleListener:ServletContextListener、HttpSesssionListener。
对于ServletContextListener,会调用fireContainerEvent("beforeContextInitialized", listener);、listener.contextInitialized(event);、fireContainerEvent("afterContextInitialized", listener);。
3.4)StandardContext#filterStart(),启动filter
/** * 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; }
步骤3.1)中会将web.xml中的filter元素设置到filter的FilterDef里,此处则会实例化filter设置到filterConfigs里。
3.5)StandardContext#loadOnStartup(findChildren()),启动servlet
/** * 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) * @return <code>true</code> if load on startup was considered successful */ 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(); //如果web.xml中servlet配置的<load-on-startup>值小于0,则不会在启动时初始化servlet; //如果设置的<load-on-startup>值大于等于0,则会在启动时初始化servlet。 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; }
步骤3.1)中会将web.xml中的servlet元素封装成wrapper,并调用addChild()方法设置到Context里。此处则会检查是否需要loadOnStartup(),如果需要则对servlet执行servlet.load()。
参考:
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。