tomcat作为一个web容器,实现了servlet规范,是对http请求进行处理的关键部分。下面是Tomcat整体架构:
catalina类是tomcat处理请求的起点。下面的server是tomcat组件的容器,可以在server.xml中配置多个。service包含了connector和container,connector即连接器,支持http/https/ajp协议,后来又增加了websocket的支持。下面的介绍引用了其他博客对这几个组件的大致描述:
1、Catalina:与开始/关闭shell脚本交互的主类,因此如果要研究启动和关闭的过程,就从这个类开始看起。
2、Server:是整个Tomcat组件的容器,包含一个或多个Service。
3、Service:Service是包含Connector和Container的集合,Service用适当的Connector接收用户的请求,再发给相应的Container来处理。
4、Connector:实现某一协议的连接器,如默认的有实现HTTP、HTTPS、AJP协议的。
5、Container:可以理解为处理某类型请求的容器,处理的方式一般为把处理请求的处理器包装为Valve对象,并按一定顺序放入类型为Pipeline的管道里。Container有多种子类型:Engine、Host、Context和Wrapper,这几种子类型Container依次包含,处理不同粒度的请求。另外Container里包含一些基础服务,如Loader、Manager和Realm。
6、Engine:Engine包含Host和Context,接到请求后仍给相应的Host在相应的Context里处理。
7、Host:就是我们所理解的虚拟主机。
8、Context:就是我们所部属的具体Web应用的上下文,每个请求都在是相应的上下文里处理的
9、Wrapper:Wrapper是针对每个Servlet的Container,每个Servlet都有相应的Wrapper来管理。
可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper这些核心组件的作用范围是逐层递减,并逐层包含。
启动tomcat首先调用startup.sh脚本,可以看到追加了一堆jvm参数,组后调用的是bootstrap类的main方法。tomcat就是从这里开始初始化的。
/usr/bin/java -Djava.util.logging.config.file=/usr/local/apache-tomcat-8.5.31/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/apache-tomcat-8.5.31/bin/bootstrap.jar:/usr/local/apache-tomcat-8.5.31/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/apache-tomcat-8.5.31 -Dcatalina.home=/usr/local/apache-tomcat-8.5.31 -Djava.io.tmpdir=/usr/local/apache-tomcat-8.5.31/temp org.apache.catalina.startup.Bootstrap start
bootstrap.main
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
//配置tomcat_home/base环境变量
//初始化tomcat的三个类加载器
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 {
//从main方法的args接收的tomcat参数处理
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")) {
//反射setAwait,使tomcat保持进程
daemon.setAwait(true);
//主要通过反射调用catalina类的load方法
daemon.load(args);
//调用catalina的start
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
//异常关闭jvm
System.exit(1);
}
//正常关闭jvm
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
System.out.println("bootstrap main has been execute finish");
} 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.init
public void init()
throws Exception
{
//设置了tomcat的环境变量,默认为当前目录,就算不配置环境变量依然能运行
setCatalinaHome();
setCatalinaBase();
//初始化类加载器
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");
//反射加载catalina类,构造catalina对象
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.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);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
catalina.load,第一次被调用是在bootstrap的load方法中。主要作用是通过读取xml实例化server。主要看一下最后的getServer(),init()方法。
public void load() {
long t1 = System.nanoTime();
//初始化cataline_home和base,指定为当前项目路径
initDirs();
// Before digester - it may be needed
//重新设置JMX的naming,加入apache的前缀,初始化命名服务
initNaming();
// Create and execute our Digester
//Digester是apache commons中的一种xml解析器,处理conf/server.xml
//在这个方法里,读取serverl.xml时通过反射实例化了standardServer对象
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//获取配置文件
file = configFile();
inputStream = new FileInputStream(file);
//SAX对象解析xml
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 {
//digester解析xml拼成对象
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": ", e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
// Stream redirection
//自定义了System.out.PrintStream,写了一个子类继承PrintStream,便于多线程下的日志处理
initStreams();
// Start the new server
try {
//初始化new server对象
getServer().init();
} catch (LifecycleException e) {
//boolea.getBoolean 获取系统环境变量,判断是否与true相等
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");
}
}
LifecycleBase.init。tomcat中每个组件都继承了这个lifecycleBase,用于统一管理组件的生命周期,启动tomcat最主要的是两个方法,init和start。tomcat启动时序图如下:
public final synchronized void init() throws LifecycleException {
//这个是lifecycleBase的init方法,即server、service、connector以及各种监听器的初始化方法,这里每个组件都有一个生命周期状态
if (!state.equals(LifecycleState.NEW)) {
//状态校验,不对直接抛异常
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
//设置状态为加载中
setStateInternal(LifecycleState.INITIALIZING, null, false);
try {
//初始化方法
initInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
//设置内部状态为加载完成
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
可以看到,lifecycle中有status,用于判断当前组件的状态如初始化new、加载中initalizing、加载完成initalized,如果状态不对会打印log不做处理抑或直接抛异常。下面来看initInternal这个初始化的核心方法,它是个抽象方法,交给子类,让子类自己完成初始化,第一次调用时是初始化server,之前我们看到server在xml解析的过程中已经被实例化过了,这里主要是做一些注册JMX组件的操作。默认是standardServer的实现,来看一看。
JMX即Java Management Extensions,用于管理java中组件资源,类似于一个系统注册表,或是一个服务注册中心。将散落的配置文件整合到一个MBean对象中,它包括资源管理、组建管理(MBean)和组件管理服务器。JMX可以配合jconsole.exe(位于jdk/bin/下)做监控,监控系统运行状态与异常。详情:https://www.cnblogs.com/dongguacai/p/5900507.html
LifecycleBase.initInternal
//这个方法用于将conponent注册到LifecycleMBeanBase
protected void initInternal() throws LifecycleException {
//Registry.getRegistry方法获取注册表,获取一个MBeanServer实例
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
//一个工具方法——不用使用JMX的MBeanRegistration接口注册tomcat component
//arg1为被注册的对象,arg2为注册对象名
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")) {
//遍历url类加载器的资源,从jar包中装载manifest文件
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
System.out.println("standardServer will init services");
// Initialize our defined Services
//service在catalina的load方法中通过Digester读取server.conf初始化过了
for (int i = 0; i < services.length; i++) {
//默认这里会是一个standardService
services[i].init();
}
}
最后可以看到在standardServer中的initInternal方法里又对service进行了初始化。这个service也是digester解析server.xml时实例化出来的,在server.xml里你可以配置多个service,实现多实例的web容器,部署多个项目。由于service也继承了LifecycleBase这个生命周期抽象类,它调用的init方法在LifecycleBase中被分发到standardService的initInternal方法里。我们继续往下看:
standardService.initInternal
protected void initInternal() throws LifecycleException {
//service组件注册
super.initInternal();
if (container != null) {
container.init();
}
// Initialize any Executors
//可以在server.xml中为一个Service和所有的connector配置一个共享线程池
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize our defined Connectors
//初始化定义的connector
//这个connector是实现协议的连接器,如http、https、ajp、websocket等
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
最上面的super.initInternal()回调LifecycleMBeanBase的initInternal方法,用于注册JMX的MBean。接下来在service中分别初始化了container、executor、connector。这三个类同样继承了LifecycleBase,由LifecycleBase交给他们自己分别初始化。需要注册MBean组件的地方则继承LifecycleMBeanBase。
LifecycleMBeanBase.initInternal
protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
//JMX,这个方法将Component实例注册到LifecycleMBeanBase,比如standardService调用这个,注册type=service
if (oname == null) {
mserver = Registry.getRegistry(null, null).getMBeanServer();
oname = register(this, getObjectNameKeyProperties());
}
}
各个组件的init方法代码逐一贴上去,先来看看这个connector的init方法。
Connector.initInternal
protected void initInternal() throws LifecycleException {
//connector组件注册
super.initInternal();
// Initialize adapter
//请求处理器
adapter = new CoyoteAdapter(this);
//prototolHandler协议处理器
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
// http请求方法列表,默认为POST
if( null == parseBodyMethodsSet ) {
setParseBodyMethods(getParseBodyMethods());
}
//tomcat的connector支持两种会话层协议:http(HTTPS)/ajp,每种协议提供三种实现方式,分别是Apr(apache可移植runtime库),Nio(java NIO) 和 JIo(java io)
/*AJP是定向包协议。因为性能原因,使用二进制格式来传输可读性文本。WEB服务器通过TCP连接和SERVLET容器连接。
为了减少进程生成socket的花费,WEB服务器和SERVLET容器之间尝试保持持久性的TCP连接,对多个请求/回复循环重用一个连接。
一旦连接分配给一个特定的请求,在请求处理循环结束之前不会再分配。
换句话说,在连接上,请求不是多元的。这个使连接两端的编码变得容易,虽然这导致在一时刻会产生许多连接。
*/
//需注意的是,当前版本tomcat不支持websocket协议,7.0.47版本以上才支持jsr-365
if (protocolHandler.isAprRequired() &&
!AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException
(sm.getString
("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
// Initialize mapper listener
//这个mapper监听器继承Mbean类,直接用的MBean类中的init方法注册组件,没有其他初始化操作
mapperListener.init();
}
这里最主要的是初始化了协议处理器,默认使用基于JIO的http协议。
AbstractProtocal.init
public void init() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.init",
getName()));
//在jmx中注册协议处理器
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) {
try {
tpOname = new ObjectName(domain + ":" +
"type=ThreadPool,name=" + getName());
Registry.getRegistry(null, null).registerComponent(endpoint,
tpOname, null);
} catch (Exception e) {
getLog().error(sm.getString(
"abstractProtocolHandler.mbeanRegistrationFailed",
tpOname, getName()), e);
}
rgOname=new ObjectName(domain +
":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null );
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
try {
//初始化endPoint
//endPoint提供基础的网络IO服务,创建ServerSocket,默认socket队列为100
endpoint.init();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.initError",
getName()), ex);
throw ex;
}
}
初始化了一个endPoint,负责socket监听,endPoint方法里调用了bind方法,根据实现方法不同JIO/NIO/APR调用不同的绑定操作。下面来看看默认的JIO。在默认的server.xml配置文件中,tomcat默认提供了基于http(8080端口)和ajp的connector(8009端口),http的用于处理http request,而ajp协议的connector用于和其他http服务器集成。
JioEdepoint.init
这里testServerCipherSuitesOrderSupport有一个的检查当前jre版本的校验,我这里看的是tomcat7源码。如果当前jre版本低于6会抛异常。
public final void init() throws Exception {
testServerCipherSuitesOrderSupport();
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
private void testServerCipherSuitesOrderSupport() {
// Only test this feature if the user explicitly requested its use.
if(!"".equals(getUseServerCipherSuitesOrder().trim())) {
if (!JreCompat.isJre8Available()) {
throw new UnsupportedOperationException(
sm.getString("endpoint.jsse.cannotHonorServerCipherOrder"));
}
}
}
bind方法
public void bind() throws Exception {
// Initialize thread count defaults for acceptor
if (acceptorThreadCount == 0) {
acceptorThreadCount = 1;
}
// Initialize maxConnections
if (getMaxConnections() == 0) {
// User hasn't set a value - use the default
//用最大容量初始化一个线程池
setMaxConnections(getMaxThreadsExecutor(true));
}
//serverSocketFactory
//初始化serverSocket(服务端socket),配置socket消息队列长度
if (serverSocketFactory == null) {
if (isSSLEnabled()) {
serverSocketFactory =
handler.getSslImplementation().getServerSocketFactory(this);
} else {
serverSocketFactory = new DefaultServerSocketFactory(this);
}
}
if (serverSocket == null) {
try {
//以指定端口、backlog(socket连接缓冲队列)、ip创建serverSocket
if (getAddress() == null) {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog());
} else {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog(), getAddress());
}
} catch (BindException orig) {
String msg;
if (getAddress() == null)
msg = orig.getMessage() + " <null>:" + getPort();
else
msg = orig.getMessage() + " " +
getAddress().toString() + ":" + getPort();
BindException be = new BindException(msg);
be.initCause(orig);
throw be;
}
}
}
至此,connection的初始化工作已经完成了,继续service中container和executor的初始化过程。可以去server.xml中看到,这个container默认使用的是engine,调用的是StandardEngine类的init方法。
总结一下,tomcat的组件全部受LifestyleBase调控,通过Digester解析server.xml为树节点对象后,逐步初始化各个子组件,将需要的属性或引用实例化好,同时将其在JMX中注册以便监控,每个组件的生命周期状态统一交给LifestyleBase管理,一旦出现异常根据异常情况抛出异常或者取消初始化过程。全部初始化完成后,回到Catalina中继续调用start方法,和上面的初始化过程一样,逐个start。