阅读的目的?我想要从中获得什么?
对tomcat容器的分析 总结 ,实现servlet规范 web容器
版本情况
Tomcat的顶层结构
Catalina 管理整个Tomcat的管理类
Server 最顶层容器,代表整个服务器
Service 提供具体服务 (多个)
Connector 负责网络连接、request/response的创建(可以有多个连接,从servet.xml的配置也可以看出,同时提供http和https,也可以提供相同协议不同端口的连接)
Container 具体处理Servlet
贴个图(源自这里https://my.oschina.net/masterworker/blog/844225)
Tomcat的启动过程
org.apache.catalina.startup.Bootstrap
是Tomcat的入口,作用类似一个CatalinaAdptor
,具体处理还是Catalina来完成,这样做的好处是可以把启动的入口和具体的管理类分开,从而可以很方便地创建出多种启动方式。
BootStrap不在Tomcat依赖包下 ,而是在bin目录 通过反射 完全松耦合
package org.apache.catalina.startup;
import ...;
public final class Bootstrap {
private static final Log log = LogFactory.getLog(Bootstrap.class);
/**
* Daemon object used by main.
*/
private static Bootstrap daemon = null;
/**
* Daemon reference.
*/
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// 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);
}
/**
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
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.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;
}
/**
* 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);
method.invoke(catalinaDaemon, param);
}
// ----------------------------------------------------------- Main Program
/**
* Load the Catalina daemon.
* @param arguments Initialization arguments
* @throws Exception Fatal initialization error
*/
public void init(String[] arguments)
throws Exception {
init();
load(arguments);
}
/**
* 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);
method.invoke(catalinaDaemon, (Object [])null);
}
/**
* Stop the Catalina Daemon.
* @throws Exception Fatal stop error
*/
public void stop()
throws Exception {
//实现略,主要通过反射调用了catalina的stop
}
/**
* Stop the standalone server.
* @throws Exception Fatal stop error
*/
public void stopServer()
throws Exception {
//实现略,主要通过反射调用了catalina的stopServer
}
/**
* Set flag.
* @param await <code>true</code> if the daemon should block
* @throws Exception Reflection error
*/
public void setAwait(boolean await)
throws Exception {
//实现略 ,主要通过反射调用了catalina的setAwait
}
public boolean getAwait()
throws Exception{
//实现略 ,主要通过反射调用了catalina的getAwait
}
/**
* Destroy the Catalina Daemon.
*/
public void destroy() {
// FIXME
}
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
}
Tomcat 启动脚本 startup.bat 是从main方法中开始的。其中主要做了:
准备容器环境,
init()
初始化类加载器,初始化容器,调用
load()
实际是调用catalina里的init()
启动容器,通过引用
catalinaDaemon
反射射调用start()
方法(实际还是通过catalina操作容器)关于类加载,我们都知道 J2EE 默认的类加载机制是双亲委派原则(详细查看如下🔎https://www.cnblogs.com/miduos/p/9250565.html)
通过debug可以发现 commonLoader、catalinaLoader 、sharedLoader 其实三个是同一个(底层都是URLClassLoader),原因是因为
catalina.properties
的配置中默认是空的。另外在
init()
中Thread.currentThread().setContextClassLoader(catalinaLoader);
Catalina的启动过程
Catalina的启动主要是调用setAwait()
、load()
和start()
方法来完成。
setAwait()
方法用于设置Server启动完成后是否进入等待状态的标记load()
方法主要是用来加载配置文件conf/server.xml
创建Server对象 (解析是通过Digester),然后调用Server的init()
start()
主要是调用Server的start()
Server的启动过程
Server的默认实现org.apache.catalina.core.StandardServer
,在其父类中org.apache.catalina.util.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);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
}
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;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
}
其中 startInternal() 和 initInternal()
为模版方法 ,查看其实现类 可以发现是循环调用了每个service
的start()
和init()
Service的启动过程
类似于Server , StandardService
的initInternal()
和 startInternal()
的方法主要调用container
、executors
、mapperListener
、connectors
的init()
和start()
方法。
mapperListener是Mapper的监听器,可以监听container容器的变化
executors是用在connectors中管理线程的线程池,在server.xml配置文件中tomcatThreadPool
总结
下图为整个启动流程
请求处理
Connector用于接收请求并将请求封装成Request
和Response
来处理,最底层是使用Socket来进行连接的,封装完后交给Container进行处理(通过pipeline-Value管道来处理),Container处理完之后返回给Connector,最后Connector使用Socket将处理结果返回给客户端,整个请求就处理完了。
Connector的结构
Connector
中具体是用ProtocolHandler
来处理请求的,有多种不同的连接类型(Ajp、HTTP和Spdy)。
其中有3个非常重要的组件。(Connector
的创建过程主要是初始化ProtocolHandler
,serverx.xml中可配置)
- Endpoint 用于处理底层的Socket的网络连接,用来实现TCP/IP协议
- Processor 用于将Endpoint接收到的Socket封装成Request,用来实现HTTP协议(BIO、NIO、APR)
- Adapter 用于将封装好的Request交给Container进行具体处理,将适配到Servlet容器(转换
org.apache.coyote.Request
为org.apache.catalina.connector.Request
)
Ajp :连接器监听8009端口,负责和其他的HTTP服务器建立连接。在把Tomcat与其他HTTP服务器集成时,就需要用到这个连接器。
Apr : 是从操作系统级别解决异步IO问题,大幅度提高服务器的并发处理性能 http://tomcat.apache.org/tomcat-8.5-doc/apr.html
协议升级:在servlet 3.1之后新增 WebSocket协议,如果Processor处理之后Socket的状态是UPGRADING,则EndPoint中的handler回接着创建并调用upgrade包中的processor进行处理
request转换:Adapter转换后的request,其实就是封装了一层,
public class Request implements org.apache.catalina.servlet4preview.http.HttpServletRequest
Pipeline-Value管道
Piepeline的管道模型和普通的责任链模式稍微有点不同,区别主要如下
- 每个pipeline都有特定的value,而且是在管道的最后一个执行,这个Value叫做BaseValue
- 上层的BaseValue(类似配货车到中转站的感觉)会调用下层容器的管道
每个BaseValue都有一个StandarValue的实现,例如WrapperValue的标准实现是StanderWrpperValue
经过所有管道(EnginePipeline、HostPipeline、ContextPipeline、WrapperPieline)后,在最后的WrapperPieline的BaseValue中会创建FilterChain
并调用其doFilter来处理请求,FilterChain包含着我们配置的与请求相匹配的Filter
和Servlet
。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com