<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
Host>
Engine>
Service>
Server>
【https://tomcat.apache.org/tomcat-9.0-doc/architecture/overview.html】
术语
Server代表整个tomcat容器,tomcat自身提供了一个实现,很少用户会去自定义自己的实现
Service是位于Server下的一个直接组件,它绑定了一个或者多个Connector到一个Engine。这个接口也很少用户会去自定义
Engine代表为特定Service提供服务的管道。Service可能会有多个Connector,Engine接收和处理从这些Connector来的所有请求,然后返回Response给Connector,继而再返回给Client
通过Host,Tomcat Server可以关联上一个域名,比如www.yourcompany.com
。一个Engine可能会包含多个Host,Host可以支持二级域名,比如yourcompany.com
和abc.yourcompany.com
Connector用于处理与客户端的通讯。Tomcat里面提供了一些Connector。例如Http Connector,当tomcat作为一个标准的服务器时,用于处理http请求。另外,AJP Connector,它实现了AJP协议,用于连接Tomcat到一个web服务器,比如Apache HTTPD server
一个Context代表一个web应用。一个Host里面可能包含多个Context,每个Context有一个唯一的路劲
【https://tomcat.apache.org/tomcat-9.0-doc/architecture/startup/serverStartup.txt】
类:org.apache.catalina.startup.Bootstrap
a)设置classloaders
commonLoader (common)-> System Loader
sharedLoader (shared)-> commonLoader -> System Loader
catalinaLoader(server) -> commonLoader -> System Loader
源码如下Bootstrap类
:
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);
}
}
b)加载启动类(通过反射)
org.apache.catalina.startup.Catalina
setParentClassloader -> sharedLoader
Thread.contextClassloader -> catalinaLoader
源码如下Bootstrap类
:
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);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
c)Bootstrap.daemon.init()完成
类org.apache.catalina.startup.Bootstrap
这里我们假设命令参数是start
a)Catalina.setAwait(true);
b)Catalina.load()
b1)initDirs()
原文说设置一些属性,如catalina.home,但源码没看到
源码如下Catalina类
:
protected void initDirs() {
String temp = System.getProperty("java.io.tmpdir");
if (temp == null || (!(new File(temp)).isDirectory())) {
log.error(sm.getString("embedded.notmp", temp));
}
}
b2)initNaming设置系统属性
b3)createStartDigester()配置digester用于解析server.xml
例如org.apache.catalina.core.StandardServer
代表整个tomcat,org.apache.catalina.deploy.NamingResources
用于处理JNDI相关,org.apache.catalina.LifecycleListener
用于主要组件的start/stop事件监听,org.apache.catalina.core.StandardService
,org.apache.catalina.Connector
用于处理请求等
源码如下(原方法是在太长,我删减了些)Catalina类
:
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
// Ignore className on all elements
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
// Ignore Connector attribute used internally but set on Server
List<String> connectorAttrs = new ArrayList<>();
connectorAttrs.add("portOffset");
fakeAttributes.put(Connector.class, connectorAttrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
...
// Add RuleSets for nested elements
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return digester;
}
b4)加载server.xml,并通过上方创建的digester解析,生成对象,但实际还没有start
例如Http11NioProtocol的生成,debug截图
源码如下Digester类
:
public Object parse(InputSource input) throws IOException, SAXException {
configure();
getXMLReader().parse(input);
return root;
}
b5)设置System.out和System.err到SystemLogHandler类
源码如下Catalina类
:
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
b6)层层调用所有的组件调用初始化方法
例如StandardServer的init
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());
}
}
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// 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();//globalNamingResources的初始化
// 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 e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();//services的初始化
}
}
c)Catalina.start()
c1)启动NamingContext,并绑定所有JNDI
c2)启动services
StandardService -> starts Engine (ContainerBase -> Realm,Cluster etc)
源码如下StandardServer类
:
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();//Services启动
}
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
startPeriodicLifecycleEvent();
}
}, 0, 60, TimeUnit.SECONDS);
}
}
c3)StandardHost的启动
作用:
1.配置一个ErrorReportValve根据HTTP的错误码输出对应HTML
2.开启Valves(例如前面这个ErrorReportValve)
3.配置StandardHostValve,用于绑定WebappClassLoader到线程上下文,为request查找session
4.启动HostConfig组件,这个组件会去部署所有的webapps
源码如下HostConfig类
:
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之后会创建Digester,Digester会调用ContextConfig.start()
用于处理web.xml
c4)在StandardEngine容器的生命周期内,会启动一个后台线程监控context是否有变更。如果context变更了(根据war文件/context.xml文件/web.xml文件的时间戳),热加载将会被触发(stop/remove/deploy/start)
d)Tomcat接收http请求
d1)通过ServerSocket.accept()接收请求,接收是在一个单独的线程中进行的。
其实应该是在Acceptor这个类的run方法中,这个文档可能是很久之前的,还用BIO
源码如下Acceptor类
:
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (endpoint.isRunning()) {
// Loop if endpoint is paused
while (endpoint.isPaused() && endpoint.isRunning()) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!endpoint.isRunning()) {
break;
}
state = AcceptorState.RUNNING;
try {
...
try {
// Accept the next incoming connection from the server
// socket
socket = endpoint.serverSocketAccept();//接收请求
} catch (Exception ioe) {
...
}
...
} catch (Throwable t) {
...
}
}
state = AcceptorState.ENDED;
}
d2)线程池ThreadPoolExecutor指派TaskThread线程处理
源码如下AbstractEndpoint类
:
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = null;
if (processorCache != null) {
sc = processorCache.pop();
}
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);//提交线程池执行处理
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
d3)Http11Processor调用process处理
d4)这个http会使用Http11InputBuffer来解析。解析请求头、请求行等信息,然后把这些信息存储到一个Coyote request(不是HTTP request)中,这个request包含所有HTTP信息,如主机,端口,scheme等
源码如下(原方法太长了,我删了很多代码)Http11InputBuffer类
:
boolean parseRequestLine(boolean keptAlive, int connectionTimeout, int keepAliveTimeout)
throws IOException {
...
if ((end - parsingRequestLineStart) > 0) {
request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart,
end - parsingRequestLineStart);//设置请求的协议,如HTTP/1.1
} else {
request.protocol().setString("");
}
parsingRequestLine = false;
parsingRequestLinePhase = 0;
parsingRequestLineEol = false;
parsingRequestLineStart = 0;
return true;
...
}
d5)Processor有一个Adapter的引用,对于http请求来说,它就是CoyoteAdapter。上方的解析完成之后,Adapter.service()会被调用。在这个service方法中,会创建实现了接口HttpServletRequest的Request对象,和实现了HttpServletResponse接口的Response对象,把cookies和context等关联给到request对象
源码如下(删了部分代码)Mapper类
:
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
...
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
// Wrapper mapping
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
}
d6)CoyoteAdapter调用容器StandardEngine的invoke(request,response)
方法
其实这里一句语句connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);直接调用StandardEngineValve的invoke(Request request, Response response)方法了
源码如下CoyoteAdapter类
:
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = connector.createRequest();//创建request对象
request.setCoyoteRequest(req);
response = connector.createResponse();//创建response对象
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringCharset(connector.getURICharset());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean async = false;
boolean postParseSuccess = false;
req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);//传递cookie,context等相关信息到request中
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);//调用StandardEngine的invoke方法
}
if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener();
if (readListener != null && request.isFinished()) {
// Possible the all data may have been read during service()
// method so this needs to be checked here
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}
Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// If an async request was started, is not going to end once
// this container thread finishes and an error occurred, trigger
// the async error process
if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} finally {
...
}
}
d7)d8)调用StandardEngineValve的invoke(Request request, Response response)方法
d9)StandardHost默认有两个valves,一个StandardHostValve,另一个ErrorReportValve
其实加上server.xml中配置的org.apache.catalina.valves.AccessLogValve,总计有三个,他们都是通过getNext().invoke(request,response);调用下一个处理
源码如下AbstractAccessLogValve类
:
public void invoke(Request request, Response response) throws IOException,
ServletException {
if (tlsAttributeRequired) {
// The log pattern uses TLS attributes. Ensure these are populated
// before the request is processed because with NIO2 it is possible
// for the connection to be closed (and the TLS info lost) before
// the access log requests the TLS info. Requesting it now causes it
// to be cached in the request.
request.getAttribute(Globals.CERTIFICATES_ATTR);
}
for (CachedElement element : cachedElements) {
element.cache(request);
}
getNext().invoke(request, response);//调用下一个valve
}
d10)StandardHostValve关联合适的classloader到当前线程。同时也会检索session关联给到request,如果有session的话,session的acess()方法会被调用来keep alive
d11)StandardHostValve调用pipeline的invoke(request, response)方法
d12)首先是FormAuthenticator的invoke(Request request, Response response)被调用,然后再调用getNext().invoke(request, response);,继而走到StandardContextValve的invoke(Request request, Response response)
源码如下StandardContextValve类
:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
...
// Select the Wrapper to be used for this Request
Wrapper wrapper = request.getWrapper();//wrapper会关联一个servlet
...
wrapper.getPipeline().getFirst().invoke(request, response);
}
d13)StandardWrapperValve的invoke方法中,jsp会被编译成servlet,然后调用真正的servlet
e)servlet被调用
补充:
在d13)调用过程中,其实是构造了一个filterChain,调用filterChain.doFilter(ServletRequest request, ServletResponse response),在这个方法中最终会调用到servlet.service(request, response);,这之后就是根据请求的方法调用doGet/doPost之类的方法了
源码如下ApplicationFilterChain类
:
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
...
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);//调用servlet,如果是jsp,其实是在这个方法才编译的
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
可以直接下载官方的
https://tomcat.apache.org/tomcat-9.0-doc/architecture/startup/serverStartup.pdf
https://tomcat.apache.org/tomcat-9.0-doc/architecture/requestProcess/request-process.png
https://tomcat.apache.org/tomcat-9.0-doc/architecture/requestProcess/authentication-process.png
如果想编译源码,可以查看这篇(https://tomcat.apache.org/tomcat-9.0-doc/building.html)文章,但Tomcat默认是用ant编译的,如果想用maven编译,大家另外再找参考教程吧。我个人是用ant编译的,改了不少地方才编译成功…