tomcat 整体架构
Server:代表整个服务,也能够了解为 tomcat。
service: 代表一个服务,它将若干个 connector 和 contain 连接起来。
connector:连接器,解决用户的 web 申请,反对三种协定:
AJP
HTTP
HTTPS
Container:容器,能够看做 servelet 容器。
engine:servelet 引擎,一个 container 只能有一个 engine,engine 也能够看做是 container 自身。
host:虚拟主机,比方 www.baidu.com,localhost,一个 engine 能够有多个 host。
context:代表一个利用,比方订单服务 order,url 就为 localhost/order, 用户服务 user,url 就为 localhost/user。一个 host 能够有多个 context
Wrapper:servelet,比方订单信息,url 就为 localhost/order/detail/1,一个 context 能够有多个 wrapper。
从配置文件 server.xml 更能感触分层构造:
tomcat 初始化流程
启动流程:
BootStrap.main->
BootStrap.load->Catalina.load->Server.init->Service.init->engine.init->connector.init->
BootStrap.start->Server.start->service-start->engine.start->host.start(被 engine 异步启动)->context.start(被 host 异步启动)->connector.start
能够看到,次要分两步 BootStrap 的 load 和 start 办法,这两个办法里都是逐级调用下一级的 init 或 start 办法。
从咱们启动 tomcat 的命令:./start.sh 动手:
#!/bin/sh
# 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.
# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# -----------------------------------------------------------------------------
# Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [-h "$PRG"] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
#通过 catalina.sh 来执行
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [! -x "$PRGDIR"/"$EXECUTABLE"]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" start "[email protected]"
看看 catalina.sh 文件,代码太长,咱们间接看启动局部:
if ["$1" = "-security"] ; then
if [$have_tty -eq 1]; then
echo "Using Security Manager"
fi
shift
eval $_NOHUP "\"$_RUNJAVA\"""\"$CATALINA_LOGGING_CONFIG\""$LOGGING_MANAGER"$JAVA_OPTS""$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
# 启动 Bootstrap 类,并将 start 作为参数传进去
org.apache.catalina.startup.Bootstrap "[email protected]" start \
>> "$CATALINA_OUT" 2>&1 "&"
当初进入 java 代码,BootStrap 类:
public static void main(String args[]) {synchronized (daemonLock) {if (daemon == null) {// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 初始化 Bootstrap
bootstrap.init();} catch (Throwable t) {handleThrowable(t);
t.printStackTrace();
return;
}
// 初始化实现后再赋值给 daemon
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);
// load 并且 start
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {System.exit(1);
}
} 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 reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
首先初始化 Bootstrap 类,而后依据传入的参数执行不同操作,启动就是执行 load 和 start 办法,先看 init 办法做了什么:
public void init() throws Exception {
// 初始化类加载器
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (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 loader
if (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);
// Catalina 的父类加载器设为 sharedLoader
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance; //Catalina 实例
}
initClassLoaders():
private void initClassLoaders() {
try {
// 初始化 common 类加载器
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();
}
// 父类加载器为 commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// catalinaLoader 和 sharedLoader 的加门路为空,所以应用父类加载器
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 repository
try {@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {// Ignore}
// 遍历指定目录下的 jar 包
// Local repository
if (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);
}
initClassLoaders 办法初始化了三个类加载器:commonLoader,catalinaLoader,sharedLoader,并将 catalinaLoader,sharedLoader 的父类加载器指定为 commonLoader,初始化类加载器后又实例化了 catalina 实例,init 办法完结,再看 load 办法:
/**
* Load daemon.
*/
private void load(String[] arguments) throws Exception {// Call the load() method
String 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);
}
// 调用了 catalina 的 load 办法
method.invoke(catalinaDaemon, param);
}
其实就是调用了 Catalina 的 load 办法:
public void load() {if (loaded) {return;}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
//xml 文件解析器,比方解析 server.xml
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {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);
}
}
if (inputStream == null) {
try {inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {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);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
// 重点看 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");
}
}
getServer().init() 会调用 LifeCycleBase 的 init 办法,LifeCycle 是一个治理 tomcat 生命周期的接口,LifeCycleBase 是它的实现类:
@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) {handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
initInternal() 是个形象办法,由 StandServer 实现:
@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
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {if (cl instanceof URLClassLoader) {URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {if (url.getProtocol().equals("file")) {
try {File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException | IOException e) {// Ignore}
}
}
}
cl = cl.getParent();}
}
// Initialize our defined Services
for (Service service : services) {
// 进入这里
service.init();}
}
service 是 StandService,但它的 init 是父类 LifeCycleBase 的办法,又一次运行了这个办法:
@Override
public final synchronized void init() throws LifecycleException {if (!state.equals(LifecycleState.NEW)) {invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {setStateInternal(LifecycleState.INITIALIZING, null, false);
// 进入这里,这里由 StandService 实现
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
@Override
protected void initInternal() throws LifecycleException {super.initInternal();
if (engine != null) {
// 初始化 engine
engine.init();}
// Initialize any Executors
for (Executor executor : findExecutors()) {if (executor instanceof JmxEnabled) {((JmxEnabled) executor).setDomain(getDomain());
}
// 初始化 executor
executor.init();}
// Initialize mapper listener
// 初始化 mapperListener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {for (Connector connector : connectors) {
try {
// 初始化 connector
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);
}
}
}
}
}
又一次调用了 LifeCycleBase 的 init 办法:
@Override
public final synchronized void init() throws LifecycleException {if (!state.equals(LifecycleState.NEW)) {invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {setStateInternal(LifecycleState.INITIALIZING, null, false);
// 进入这里,这次由 Connect 实现
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
@Override
protected void initInternal() throws LifecycleException {super.initInternal();
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
getProtocolHandlerClassName()));
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
// 初始化 ProtocolHandler
protocolHandler.init();} catch (Exception e) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
protocolHandler.init() 由 AbstractProtocol 实现:
@Override
public void init() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
logPortOffset();}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
this.rgOname = rgOname;
Registry.getRegistry(null, null).registerComponent(getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// 初始化 endPoint
endpoint.init();}
endpoint.init() 由 AbstractEndPoint 实现:
public void init() throws Exception {if (bindOnInit) {
// 重点在这里
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=SocketProperties,name=\"" + getName() + "\"");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {registerJmx(sslHostConfig);
}
}
}
private void bindWithCleanup() throws Exception {
try {bind();
} catch (Throwable t) {
// Ensure open sockets etc. are cleaned up if something goes
// wrong during bind
ExceptionUtils.handleThrowable(t);
unbind();
throw t;
}
}
进入 NioEndPoint:
/**
* Initialize the endpoint.
*/
@Override
public void bind() throws Exception {
// 初始化 ServerSocket
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();}
// Separated out to make it easier for folks that extend NioEndpoint to
// implement custom [server]sockets
protected void initServerSocket() throws Exception {if (getUseInheritedChannel()) {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
} else {serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
}
serverSock.configureBlocking(true); //mimic APR behavior
}
BootStrap 的 load 办法实现,就是初始化了一些组件。
再来看 start 办法:
/**
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start() throws Exception {if (catalinaDaemon == null) {init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
// 调用 Catalina 的 start 办法
method.invoke(catalinaDaemon, (Object [])null);
}
/**
* Start a new server instance.
*/
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 {
// start
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() 调用 LifecycleBase 的 start 办法:
/**
* {@inheritDoc}
*/
@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;
}
// start 办法时,state 为 INITIALIZED
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);
// 进入这里,调用的是 StandServer 的 startInternal()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());
}
}
@Override
protected void startInternal() throws LifecycleException {fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {for (Service service : services) {
// standService
service.start();}
}
}
前面和 load 办法一样,都是调用了父类 LifecycleBase 的模板办法实现各个组件的 start。