Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

最近同学在把 springboot 升级到 2.x 版本的过程中碰到了小问题,可能升级变更里能找到信息,不过我们以学习为目的,可以看看代码是怎么样的
报错是在这段代码里的
org.apache.tomcat.util.http.fileupload.util.LimitedInputStream#checkLimit

1
2
3
4
5
private void checkLimit() throws IOException {
if (count > sizeMax) {
raiseError(sizeMax, count);
}
}

其中的 raiseError 是个抽象方法

1
2
protected abstract void raiseError(long pSizeMax, long pCount)
throws IOException;

具体的实现是在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public FileItemStreamImpl(FileItemIteratorImpl pFileItemIterator, String pName, String pFieldName, String pContentType, boolean pFormField, long pContentLength) throws FileUploadException, IOException {
this.fileItemIteratorImpl = pFileItemIterator;
this.name = pName;
this.fieldName = pFieldName;
this.contentType = pContentType;
this.formField = pFormField;
long fileSizeMax = this.fileItemIteratorImpl.getFileSizeMax();
if (fileSizeMax != -1L && pContentLength != -1L && pContentLength > fileSizeMax) {
FileSizeLimitExceededException e = new FileSizeLimitExceededException(String.format("The field %s exceeds its maximum permitted size of %s bytes.", this.fieldName, fileSizeMax), pContentLength, fileSizeMax);
e.setFileName(pName);
e.setFieldName(pFieldName);
throw new FileUploadIOException(e);
} else {
final MultipartStream.ItemInputStream itemStream = this.fileItemIteratorImpl.getMultiPartStream().newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1L) {
istream = new LimitedInputStream(itemStream, fileSizeMax) {
protected void raiseError(long pSizeMax, long pCount) throws IOException {
itemStream.close(true);
FileSizeLimitExceededException e = new FileSizeLimitExceededException(String.format("The field %s exceeds its maximum permitted size of %s bytes.", FileItemStreamImpl.this.fieldName, pSizeMax), pCount, pSizeMax);
e.setFieldName(FileItemStreamImpl.this.fieldName);
e.setFileName(FileItemStreamImpl.this.name);
throw new FileUploadIOException(e);
}
};
}

this.stream = (InputStream)istream;
}
}

后面也会介绍到,这里我们其实主要是要找到这个 pSizeMax 是哪里来的
通过阅读代码会发现跟这个类 MultipartConfigElement 有关系
而在升级后的 springboot 中这个类已经有了自动装配类,也就是
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

有了这个自动装配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(
prefix = "spring.servlet.multipart",
name = {"enabled"},
matchIfMissing = true
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({MultipartProperties.class})
public class MultipartAutoConfiguration {
private final MultipartProperties multipartProperties;

public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}

@Bean
@ConditionalOnMissingBean({MultipartConfigElement.class, CommonsMultipartResolver.class})
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}

而这个 MultipartProperties 类中

1
2
3
4
5
6
7
8
9
10
11
@ConfigurationProperties(
prefix = "spring.servlet.multipart",
ignoreUnknownFields = false
)
public class MultipartProperties {
private boolean enabled = true;
private String location;
private DataSize maxFileSize = DataSize.ofMegabytes(1L);
private DataSize maxRequestSize = DataSize.ofMegabytes(10L);
private DataSize fileSizeThreshold = DataSize.ofBytes(0L);
private boolean resolveLazily = false;

并且在前面 createMultipartConfig 中就使用了这个maxFileSize 的默认值

1
2
3
4
5
6
7
8
9
public MultipartConfigElement createMultipartConfig() {
MultipartConfigFactory factory = new MultipartConfigFactory();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.fileSizeThreshold).to(factory::setFileSizeThreshold);
map.from(this.location).whenHasText().to(factory::setLocation);
map.from(this.maxRequestSize).to(factory::setMaxRequestSize);
map.from(this.maxFileSize).to(factory::setMaxFileSize);
return factory.createMultipartConfig();
}

而在 org.apache.catalina.connector.Request#parseParts 中,会判断 mce 的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void parseParts(boolean explicit) {

// 省略一部分代码

ServletFileUpload upload = new ServletFileUpload();
upload.setFileItemFactory(factory);
upload.setFileSizeMax(mce.getMaxFileSize());
upload.setSizeMax(mce.getMaxRequestSize());

parts = new ArrayList<>();
try {
List<FileItem> items =
upload.parseRequest(new ServletRequestContext(this));
int maxPostSize = getConnector().getMaxPostSize();
int postSize = 0;
Charset charset = getCharset();

主要 org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public List<FileItem> parseRequest(final RequestContext ctx)
throws FileUploadException {
final List<FileItem> items = new ArrayList<>();
boolean successful = false;
try {
final FileItemIterator iter = getItemIterator(ctx);
final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
"No FileItemFactory has been set.");
final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
while (iter.hasNext()) {
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = item.getName();
final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
} catch (final FileUploadIOException e) {

其中 org.apache.tomcat.util.http.fileupload.FileUploadBase#getItemIterator

1
2
3
4
5
6
7
8
9
public FileItemIterator getItemIterator(final RequestContext ctx)
throws FileUploadException, IOException {
try {
return new FileItemIteratorImpl(this, ctx);
} catch (final FileUploadIOException e) {
// unwrap encapsulated SizeException
throw (FileUploadException) e.getCause();
}
}

这里就创建了 org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl

1
2
3
4
5
6
7
8
9
10
public FileItemIteratorImpl(final FileUploadBase fileUploadBase, final RequestContext requestContext)
throws FileUploadException, IOException {
this.fileUploadBase = fileUploadBase;
sizeMax = fileUploadBase.getSizeMax();
fileSizeMax = fileUploadBase.getFileSizeMax();
ctx = Objects.requireNonNull(requestContext, "requestContext");
skipPreamble = true;
findNextItem();
}

内部使用了前面给 upload 设置的文件大小上限 upload.setFileSizeMax(mce.getMaxFileSize());

然后在 findNextItem 里执行了初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private boolean findNextItem() throws FileUploadException, IOException {
if (eof) {
return false;
}
if (currentItem != null) {
currentItem.close();
currentItem = null;
}
final MultipartStream multi = getMultiPartStream();
for (;;) {
final boolean nextPart;
if (skipPreamble) {
nextPart = multi.skipPreamble();
} else {
nextPart = multi.readBoundary();
}
if (!nextPart) {
if (currentFieldName == null) {
// Outer multipart terminated -> No more data
eof = true;
return false;
}
// Inner multipart terminated -> Return to parsing the outer
multi.setBoundary(multiPartBoundary);
currentFieldName = null;
continue;
}
final FileItemHeaders headers = fileUploadBase.getParsedHeaders(multi.readHeaders());
if (currentFieldName == null) {
// We're parsing the outer multipart
final String fieldName = fileUploadBase.getFieldName(headers);
if (fieldName != null) {
final String subContentType = headers.getHeader(FileUploadBase.CONTENT_TYPE);
if (subContentType != null
&& subContentType.toLowerCase(Locale.ENGLISH)
.startsWith(FileUploadBase.MULTIPART_MIXED)) {
currentFieldName = fieldName;
// Multiple files associated with this field name
final byte[] subBoundary = fileUploadBase.getBoundary(subContentType);
multi.setBoundary(subBoundary);
skipPreamble = true;
continue;
}
final String fileName = fileUploadBase.getFileName(headers);
currentItem = new FileItemStreamImpl(this, fileName,
fieldName, headers.getHeader(FileUploadBase.CONTENT_TYPE),
fileName == null, getContentLength(headers));
currentItem.setHeaders(headers);
progressNotifier.noteItem();
itemValid = true;
return true;
}
} else {
final String fileName = fileUploadBase.getFileName(headers);
if (fileName != null) {
currentItem = new FileItemStreamImpl(this, fileName,
currentFieldName,
headers.getHeader(FileUploadBase.CONTENT_TYPE),
false, getContentLength(headers));
currentItem.setHeaders(headers);
progressNotifier.noteItem();
itemValid = true;
return true;
}
}
multi.discardBodyData();
}
}

这里面就会 new 这个 FileItemStreamImpl

1
2
3
4
currentItem = new FileItemStreamImpl(this, fileName,
fieldName, headers.getHeader(FileUploadBase.CONTENT_TYPE),
fileName == null, getContentLength(headers));

构造方法比较长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public FileItemStreamImpl(final FileItemIteratorImpl pFileItemIterator, final String pName, final String pFieldName,
final String pContentType, final boolean pFormField,
final long pContentLength) throws FileUploadException, IOException {
fileItemIteratorImpl = pFileItemIterator;
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
final long fileSizeMax = fileItemIteratorImpl.getFileSizeMax();
if (fileSizeMax != -1 && pContentLength != -1
&& pContentLength > fileSizeMax) {
final FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(fileSizeMax)),
pContentLength, fileSizeMax);
e.setFileName(pName);
e.setFieldName(pFieldName);
throw new FileUploadIOException(e);
}
// OK to construct stream now
final ItemInputStream itemStream = fileItemIteratorImpl.getMultiPartStream().newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {
istream = new LimitedInputStream(istream, fileSizeMax) {
@Override
protected void raiseError(final long pSizeMax, final long pCount)
throws IOException {
itemStream.close(true);
final FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(pSizeMax)),
pCount, pSizeMax);
e.setFieldName(fieldName);
e.setFileName(name);
throw new FileUploadIOException(e);
}
};
}
stream = istream;
}

fileSizeMax != 0 的时候就会初始化 LimitedInputStream,这就就是会在前面的

org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest

1
Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);

这里的 item

1
2
3
4
5
6
final FileItemIterator iter = getItemIterator(ctx);
final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
"No FileItemFactory has been set.");
final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
while (iter.hasNext()) {
final FileItemStream item = iter.next();

调用了 FileItemIterator 迭代器的 next

1
2
3
4
5
6
7
8
@Override
public FileItemStream next() throws FileUploadException, IOException {
if (eof || (!itemValid && !hasNext())) {
throw new NoSuchElementException();
}
itemValid = false;
return currentItem;
}

这个 currentItem 就是前面 new 的 FileItemStreamImpl

然后在 Streams.copy 的时候调用 openStream 也就是 org.apache.tomcat.util.http.fileupload.impl.FileItemStreamImpl#openStream

1
2
3
4
5
6
7
@Override
public InputStream openStream() throws IOException {
if (((Closeable) stream).isClosed()) {
throw new FileItemStream.ItemSkippedException();
}
return stream;
}

这里的 stream 就是 FileItemStreamImpl 构造方法最后赋值的 stream,会在大小超过限制时抛出错误

而这个可以通过设置 properties 来修改,spring.servlet.multipart.max-file-size 和 spring.servlet.multipart.max-request-size

1
2
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

而老版本的 spring.http.multipart.maxFileSize
其实就是配置名称改了下,但是能看一下代码也是有点收获的。

这部分其实之前在讲线程池的时候也有点带到了, 主要是在这个类里
org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
protected class ContainerBackgroundProcessor implements Runnable {

@Override
public void run() {
processChildren(ContainerBase.this);
}

protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;

try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}

// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
// 调用 Container 的 backgroundProcess
container.backgroundProcess();
// 然后寻找 children
Container[] children = container.findChildren();
for (Container child : children) {
// 如果 backgroundProcessorDelay <= 0 就调用执行
// 否则代表这个 Container 有之前第八篇说的 StartChild 这种
if (child.getBackgroundProcessorDelay() <= 0) {
processChildren(child);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("containerBase.backgroundProcess.error"), t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}

这个触发方式是在 ContainerBase 里的

1
2
3
4
5
6
7
8
protected class ContainerBackgroundProcessorMonitor implements Runnable {
@Override
public void run() {
if (getState().isAvailable()) {
threadStart();
}
}
}

而在这个 threadStart 里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void threadStart() {
if (backgroundProcessorDelay > 0
&& (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState()))
&& (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) {
if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) {
// There was an error executing the scheduled task, get it and log it
try {
backgroundProcessorFuture.get();
} catch (InterruptedException | ExecutionException e) {
log.error(sm.getString("containerBase.backgroundProcess.error"), e);
}
}
backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor()
.scheduleWithFixedDelay(new ContainerBackgroundProcessor(),
backgroundProcessorDelay, backgroundProcessorDelay,
TimeUnit.SECONDS);
}
}

就调用了线程池的 scheduleWithFixedDelay 方法提交了这个 ContainerBackgroundProcessor
仔细看代码会发现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public StandardEngine() {

super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;

}

这个就不用开启后台热加载,而主要的热加载同学应该是
org.apache.catalina.core.StandardContext#backgroundProcess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void backgroundProcess() {

if (!getState().isAvailable()) {
return;
}

Loader loader = getLoader();
if (loader != null) {
try {
// 这里就用了 loader 的 backgroundProcess
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.loader", loader), e);
}
}
Manager manager = getManager();
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.manager", manager),
e);
}
}
WebResourceRoot resources = getResources();
if (resources != null) {
try {
resources.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.resources",
resources), e);
}
}
InstanceManager instanceManager = getInstanceManager();
if (instanceManager != null) {
try {
instanceManager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.instanceManager",
resources), e);
}
}
super.backgroundProcess();
}

loader 的后台处理就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void backgroundProcess() {
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}

然后又会回到 context 的 reload,也就是 StandardContext 的 reload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public synchronized void reload() {

// Validate our current component state
if (!getState().isAvailable()) {
throw new IllegalStateException
(sm.getString("standardContext.notStarted", getName()));
}

if(log.isInfoEnabled()) {
log.info(sm.getString("standardContext.reloadingStarted",
getName()));
}

// Stop accepting requests temporarily.
setPaused(true);

try {
stop();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.stoppingContext", getName()), e);
}

try {
start();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.startingContext", getName()), e);
}

setPaused(false);

if(log.isInfoEnabled()) {
log.info(sm.getString("standardContext.reloadingCompleted",
getName()));
}

}

这样就是线程池结合后台处理,还是有些复杂的。

之前在 Windows 里用 vmware workstation 搭了个黑裙,然后硬盘直通,硬盘跑着倒还好,但是宿主机 Windows 隔一段时间就会重启,就去搜索了下,发现其实 Windows 里的事件查看器就有点像是 Linux 系统里的 dmesg 或者 journal 日志,然后根据重启的时间排查事件查看器里,Windows 日志 –> 系统,

然后可以在右侧 “筛选当前日志”里选择 eventlog 类型的,发现有这样一条事件
进程 C:\Windows\system32\svchost.exe (APP) 为用户 NT AUTHORITY\SYSTEM 开始计算机 APP 的 重新启动,原因如下: 操作系统: 恢复(计划内)
然后在网上搜索的时候发现有一些相关解答
可能的一种原因是系统在应用一些更新失败时,默认设置的策略是失败后重启,我们可以自己选择不重启,因为老是重启在我虚拟机里的黑裙没关机的情况下直接就断电重启了,还是可能会造成一些影响,
可以在资源管理器里右键计算机,选左侧的”高级系统设置” –> 然后在”高级” tab 下面有个”启动和故障恢复” –> “设置”
在弹出弹窗里将 “自动重新启动” 取消勾选

这样到目前还没继续发生过重启

Mapper 顾名思义是作一个映射作用,在 Tomcat 中会根据域名找到 host 组件,再根据 uri 可以找到对应的 context 和 wrapper 组件,但是对于当前这个环境 (Springboot) 会有一点小区别
之前说到
请求会经过 coyote 适配器进行进一步处理,
org.apache.coyote.http11.Http11Processor#service

1
2
3
4
5
// Process the request in the adapter
if (getErrorState().isIoAllowed()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
getAdapter().service(request, response);

然后到 coyoteAdapter 的 service
org.apache.catalina.connector.CoyoteAdapter#service

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
// ----------> 到这就是调用 pipeline 去处理了,我们要关注上面的 postParseRequest
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}

主要先看到 postParseRequest
在 postParseRequest 的代码里会调用 Mapper 的 map 方法

1
2
3
4
5
6
7
8
9
10
while (mapRequired) {
// This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());

// If there is no context at this point, either this is a 404
// because no ROOT context has been deployed or the URI was invalid
// so no context could be mapped.
if (request.getContext() == null) {
// Allow processing to continue.

而后面的 context 就是在 map 方法里处理塞进去的
往里看就是 org.apache.catalina.mapper.Mapper#internalMap
第一步找的是 host

1
2
3
// Virtual host mapping
MappedHost[] hosts = this.hosts;
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);

而在后续代码里继续设置 context

1
2
3
4
5
6
if (contextVersion == null) {
// Return the latest version
// The versions array is known to contain at least one element
contextVersion = contextVersions[versionCount - 1];
}
mappingData.context = contextVersion.object;

然后是 wrapper

1
2
3
4
// Wrapper mapping
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}

在这个方法 org.apache.catalina.mapper.Mapper#internalMapWrapper

1
2
3
4
5
6
7
8
9
// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;

这里就设置了 wrapper ,因为我们是在 Springboot 中,所以只有默认的 dispatchServlet
上面主要是在请求处理过程中的查找映射过程,一开始的注册是从 MapperListener 开始的
MapperListener 继承了 LifecycleMbeanBase,也就是有了 Lifecycle 状态变化那一套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void startInternal() throws LifecycleException {

setState(LifecycleState.STARTING);

Engine engine = service.getContainer();
if (engine == null) {
return;
}

findDefaultHost();

addListeners(engine);

Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}

在启动过程中就会去把 engine 的子容器 host 找出来进行注册,就是调用 registerHost 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void registerHost(Host host) {

String[] aliases = host.findAliases();
mapper.addHost(host.getName(), aliases, host);

for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}

// Default host may have changed
findDefaultHost();

if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",
host.getName(), domain, service));
}
}

在这里面会添加 host 组件,注册 context 等,注册context 里还会处理 wrapper 的添加记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void registerContext(Context context) {

String contextPath = context.getPath();
if ("/".equals(contextPath)) {
contextPath = "";
}
Host host = (Host)context.getParent();

WebResourceRoot resources = context.getResources();
String[] welcomeFiles = context.findWelcomeFiles();
List<WrapperMappingInfo> wrappers = new ArrayList<>();

for (Container container : context.findChildren()) {
prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerWrapper",
container.getName(), contextPath, service));
}
}

mapper.addContextVersion(host.getName(), host, contextPath,
context.getWebappVersion(), context, welcomeFiles, resources,
wrappers);

if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerContext",
contextPath, service));
}
}

就是在 org.apache.catalina.mapper.Mapper#addContextVersion 方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void addContextVersion(String hostName, Host host, String path,
String version, Context context, String[] welcomeResources,
WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {

hostName = renameWildcardHost(hostName);

MappedHost mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
addHost(hostName, new String[0], host);
mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
log.error(sm.getString("mapper.addContext.noHost", hostName));
return;
}
}
if (mappedHost.isAlias()) {
log.error(sm.getString("mapper.addContext.hostIsAlias", hostName));
return;
}
int slashCount = slashCount(path);
synchronized (mappedHost) {
ContextVersion newContextVersion = new ContextVersion(version,
path, slashCount, context, resources, welcomeResources);
if (wrappers != null) {
addWrappers(newContextVersion, wrappers);
}

大致介绍了下 Mapper 的逻辑

前面介绍过 Tomcat 的层次结构,

1
2
3
4
5
6
7
8
9
10
11
<Server>
<Service>
<Connector />
<Connector />
<Engine>
<Host>
<Context />
</Host>
</Engine>
</Service>
</Server>

参考这个 xml,而对于这些组件中,有一类有相同的基类,也就是这次要介绍的 ContainerBase,

包括 engine,host,context 还有 wrapper ,都是同样的容器组件,
而他们共同实现的接口就是 Container ,
主要包含了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static final String ADD_CHILD_EVENT = "addChild";


/**
* The ContainerEvent event type sent when a valve is added
* by <code>addValve()</code>, if this Container supports pipelines.
*/
public static final String ADD_VALVE_EVENT = "addValve";


/**
* The ContainerEvent event type sent when a child container is removed
* by <code>removeChild()</code>.
*/
public static final String REMOVE_CHILD_EVENT = "removeChild";


/**
* The ContainerEvent event type sent when a valve is removed
* by <code>removeValve()</code>, if this Container supports pipelines.
*/
public static final String REMOVE_VALVE_EVENT = "removeValve";

这几个事件类型,

1
2
3
4
5
6
7
8
9
10
11
12
13
public Pipeline getPipeline();

public Container getParent();

public void addChild(Container child);

public Container[] findChildren();

public int getStartStopThreads();

public void setStartStopThreads(int startStopThreads);


包括 pipeline 和获取 parent,添加 child 跟查找 child,然后设置线程池的线程数等
而对于 ContainerBase
也包含了 Lifecycle 的常规方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void initInternal() throws LifecycleException {
reconfigureStartStopExecutor(getStartStopThreads());
super.initInternal();
}


private void reconfigureStartStopExecutor(int threads) {
if (threads == 1) {
// Use a fake executor
if (!(startStopExecutor instanceof InlineExecutorService)) {
startStopExecutor = new InlineExecutorService();
}
} else {
// Delegate utility execution to the Service
Server server = Container.getService(this).getServer();
server.setUtilityThreads(threads);
startStopExecutor = server.getUtilityExecutor();
}
}

初始化方法主要就是之前讲过的线程池
后面就开始 startInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@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 (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}

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
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}

这里包括了 cluster 的启动和 realm 的启动,然后将 child 子组件添加到线程池里启动,然后循环 get 结果,接下去就是 pipeline 的启动,设置开始中状态,后面是 ContainerBackgroundProcessor 的启动
然后是停止方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Override
protected synchronized void stopInternal() throws LifecycleException {

// Stop our thread
if (monitorFuture != null) {
monitorFuture.cancel(true);
monitorFuture = null;
}
threadStop();

setState(LifecycleState.STOPPING);

// Stop the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle &&
((Lifecycle) pipeline).getState().isAvailable()) {
((Lifecycle) pipeline).stop();
}

// Stop our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StopChild(child)));
}

boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStopFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStopFailed"));
}

// Stop our subordinate components, if any
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).stop();
}
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).stop();
}
}

也是类似的,只不过停止顺序和刚才开始的顺序反了一下

0%