回到StandardService的start方法,我们已经分析了engine及其子容器的启动,现在我们继续往下看看其他组件的启动
protected void org.apache.catalina.core.StandardService.startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
//线程池的启动
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
//映射监听的启动
mapperListener.start();
// Start our defined Connectors second
//连接的启动
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
在研究其他组件的启动时,我们别漏了Engine还有一个后台的线程操作(也是每个容器都有的操作),这个操作主要是为了进行资源的监控,如果发生变化就进行
reload,也就是context的重启
protected void org.apache.catalina.core.ContainerBase.threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
//容器后台操作进程
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
在ContainerBase的backgroundProcess方法中会触发一个periodic事件,这个事件被HostConfig捕获
protected void check() {
//判断是否是自动部署
if (host.getAutoDeploy()) {
// Check for resources modification to trigger redeployment
//获取所有部署的应用
DeployedApplication[] apps =
deployed.values().toArray(new DeployedApplication[0]);
//检查资源是否被更改
for (int i = 0; i < apps.length; i++) {
if (!isServiced(apps[i].name))
checkResources(apps[i], false);
}
// Check for old versions of applications that can now be undeployed
//检查不被重新部署的老版本
if (host.getUndeployOldVersions()) {
checkUndeploy();
}
// Hotdeploy applications
//热部署
deployApps();
}
}
检查资源是否发生变更
//app:维护这某个context需要进行监控的资源,skipFileModificationResolutionCheck:是否跳过资源修改时间分辨率的检查
protected synchronized void org.apache.catalina.startup.HostConfig.checkResources(DeployedApplication app,
boolean skipFileModificationResolutionCheck) {
//获取需要需要重新部署的资源
String[] resources =
app.redeployResources.keySet().toArray(new String[0]);
// Offset the current time by the resolution of File.lastModified()
//计算当前时间与FILE_MODIFICATION_RESOLUTION_MS(1000ms)的差值,也就是提前1000ms
//这主要是因为File的lastModified()方法的分辨率是1s,所以在1s内发生的更改,其时间会直接加一秒
//所以这里会进行偏移
long currentTimeWithResolutionOffset =
System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS;
for (int i = 0; i < resources.length; i++) {
File resource = new File(resources[i]);
if (log.isDebugEnabled())
log.debug("Checking context[" + app.name +
"] redeploy resource " + resource);
//获取发布应用时文件的修改时间
long lastModified =
app.redeployResources.get(resources[i]).longValue();
//如果资源存在或者这个资源不存在,但是修改时间是零(比如那些可能一开始不存在,但是后面可能会动态加进来的资源)
if (resource.exists() || lastModified == 0) {
// 如果上次修改时间不相等,假设某个资源在部署的时间为12:00:00,然后在12:00:01之内做了更改
//那么这个资源的修改时间变更为12:00:01,但是此时真实时间还未到12:00:01,那么这里不就丢失了更新吗???
//直到当前时间超过12:00:01时会发生更新。那么存储到重部署资源map的时间是12:00:01,表示一个过去时间(要不然直接部署就成了未来时间了)
//其实这里没有看懂???
if (resource.lastModified() != lastModified && (!host.getAutoDeploy() ||
resource.lastModified() < currentTimeWithResolutionOffset ||
skipFileModificationResolutionCheck)) {
//如果资源是一个目录
if (resource.isDirectory()) {
// No action required for modified directory
//重新设置修改时间
app.redeployResources.put(resources[i],
Long.valueOf(resource.lastModified()));
//当前资源是否有描述文件,比如context.xml,并且是否是war包
} else if (app.hasDescriptor &&
resource.getName().toLowerCase(
Locale.ENGLISH).endsWith(".war")) {
// 找到对应的context对象
Context context = (Context) host.findChild(app.name);
String docBase = context.getDocBase();
if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
// This is an expanded directory
File docBaseFile = new File(docBase);
if (!docBaseFile.isAbsolute()) {
docBaseFile = new File(host.getAppBaseFile(),
docBase);
}
//重新启动
reload(app, docBaseFile, resource.getAbsolutePath());
} else {
//重新启动
reload(app, null, null);
}
// Update times
//更新资源修改时间
app.redeployResources.put(resources[i],
Long.valueOf(resource.lastModified()));
app.timestamp = System.currentTimeMillis();
boolean unpackWAR = unpackWARs;
if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
}
//如果需要解压,那么当然需要监控解压后的资源,以docBase作为非绝对路径的前缀
if (unpackWAR) {
addWatchedResources(app, context.getDocBase(), context);
} else {
//如果不需要解压,那么没有docBase,直接监控context.xml配置的资源,因为没有docBase
//只能都当前绝对路径使用,可能找不到再下次检查部署时找不到文件(会被undeploy)
addWatchedResources(app, null, context);
}
return;
} else {
// Everything else triggers a redeploy
// (just need to undeploy here, deploy will follow)
//取消部署,将从host中移除context,并且移除部署的App,资源监控对象
undeploy(app);
//删除资源
deleteRedeployResources(app, resources, i, false);
return;
}
}
} else {
// There is a chance the the resource was only missing
// temporarily eg renamed during a text editor save
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
// Ignore
}
// Recheck the resource to see if it was really deleted
if (resource.exists()) {
continue;
}
// Undeploy application
//取消部署,并且删除对应context的资源
undeploy(app);
deleteRedeployResources(app, resources, i, true);
return;
}
}
resources = app.reloadResources.keySet().toArray(new String[0]);
boolean update = false;
for (int i = 0; i < resources.length; i++) {
File resource = new File(resources[i]);
if (log.isDebugEnabled()) {
log.debug("Checking context[" + app.name + "] reload resource " + resource);
}
long lastModified = app.reloadResources.get(resources[i]).longValue();
// File.lastModified() has a resolution of 1s (1000ms). The last
// modified time has to be more than 1000ms ago to ensure that
// modifications that take place in the same second are not
// missed. See Bug 57765.
if ((resource.lastModified() != lastModified &&
(!host.getAutoDeploy() ||
resource.lastModified() < currentTimeWithResolutionOffset ||
skipFileModificationResolutionCheck)) ||
update) {
if (!update) {
// Reload application
//重新部署
reload(app, null, null);
update = true;
}
// Update times. More than one file may have been updated. We
// don't want to trigger a series of reloads.
//更新监控
app.reloadResources.put(resources[i],
Long.valueOf(resource.lastModified()));
}
app.timestamp = System.currentTimeMillis();
}
}
从上面我们看到,tomcat使用一个单独的后台线程去监控资源,如果发生变更,那么就进行重新部署,资源不完整的context会被下掉。
//app:发布的应用,用于维护会导致重新部署的资源修改时间,fileToRemove:docBase,用于删除,newDocBase就是那个被修改了的资源绝对路径
private void org.apache.catalina.startup.HostConfig.reload(DeployedApplication app, File fileToRemove, String newDocBase) {
if(log.isInfoEnabled())
log.info(sm.getString("hostConfig.reload", app.name));
//查找对应的context
Context context = (Context) host.findChild(app.name);
if (context.getState().isAvailable()) {
if (fileToRemove != null && newDocBase != null) {
//设置监听器,用于监听after_stop事件,然后删除资源
context.addLifecycleListener(
new ExpandedDirectoryRemovalListener(fileToRemove, newDocBase));
}
// Reload catches and logs exceptions
context.reload();
} else {
// If the context was not started (for example an error
// in web.xml) we'll still get to try to start
if (fileToRemove != null && newDocBase != null) {
ExpandWar.delete(fileToRemove);
context.setDocBase(newDocBase);
}
try {
//启动
context.start();
} catch (Exception e) {
log.warn(sm.getString
("hostConfig.context.restart", app.name), e);
}
}
}
context的reload,reload首先需要将自己标记为暂停中,然后停止,然后删除资源,启动,标记为可用。
public synchronized void org.apache.catalina.core.StandardContext.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 {
//停止,修改状态,触发before_stop,stoping等事件,对应ContextConfig生命周期监听器来说,主要是将context中维护的filter,servlet
//listener,监控资源,JNDI,session等等进行关闭,session的话还会进行钝化。
stop();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.stoppingContext", getName()), e);
}
try {
//启动,这部分逻辑就是StandardContext的启动,不再赘述
start();
} catch (LifecycleException e) {
log.error(
sm.getString("standardContext.startingContext", getName()), e);
}
//修改暂停标记为false,表示已经启动
setPaused(false);
if(log.isInfoEnabled())
log.info(sm.getString("standardContext.reloadingCompleted",
getName()));
}
线程池的启动,tomcat的线程池实现是StandardThreadExecutor,我们直接看到它的startInternal方法,其父类的start方法就不再看了,和其他组件基本一样的操作,因为它们继承的基类都是一样
protected void org.apache.catalina.core.StandardThreadExecutor.startInternal() throws LifecycleException {
//创建队列,这个队列继承自JDK的LinkedBlockingQueue并发队列
taskqueue = new TaskQueue(maxQueueSize);
//线程工厂,namePrefix固定前缀tomcat-exec-,daemon默认为true,getThreadPriority()默认为5
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
//创建线程池,getMinSpareThreads()默认为25,getMaxThreads()默认200,maxIdleTime默认60000毫秒,
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
//threadRenewalDelay默认1000毫秒,表示两个线程之间隔着1秒的时间进行启动(为了避免所有线程同时启动)
executor.setThreadRenewalDelay(threadRenewalDelay);
//是否重新启动所有的核心线程
if (prestartminSpareThreads) {
executor.prestartAllCoreThreads();
}
//给任务队列设置线程池
taskqueue.setParent(executor);
//给这个线程池设置状态并触发生命周期事件
setState(LifecycleState.STARTING);
}
mapperListener.start()
MapperListener属于ContainerListener和LifecycleListener,关联Mapper
public void org.apache.catalina.mapper.MapperListener.startInternal() throws LifecycleException {
//设置状态starting
setState(LifecycleState.STARTING);
//获取Engine容器
Engine engine = service.getContainer();
if (engine == null) {
return;
}
//查找默认的Host,一般就是localhost,如果找到就设置到Mapper中
findDefaultHost();
//给子容器添加MapperListener
//(*1*)
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
//注册host,host又注册context,context又注册wrapper
registerHost(host);
}
}
}
//(*1*)
private void addListeners(Container container) {
container.addContainerListener(this);
container.addLifecycleListener(this);
for (Container child : container.findChildren()) {
addListeners(child);
}
}
注册Host
private void org.apache.catalina.mapper.MapperListener.registerHost(Host host) {
//获取host的别名
String[] aliases = host.findAliases();
//注册host
mapper.addHost(host.getName(), aliases, host);
//注册context
for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",
host.getName(), domain, service));
}
}
mapper.addHost
public synchronized void org.apache.catalina.mapper.Mapper.addHost(String name, String[] aliases,
Host host) {
//重命名通配符的host名字,如果是*.开头,那么会从1开始截取
name = renameWildcardHost(name);
//MappedHost对象数组,用于包装host,别名Host,context集合
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
//创建一个MappedHost
MappedHost newHost = new MappedHost(name, host);
//插入host到数组中
//(*1*)
if (insertMap(hosts, newHosts, newHost)) {
//插入成功,赋值新数组
hosts = newHosts;
//如果是默认的host,那么额外进行赋值
if (newHost.name.equals(defaultHostName)) {
defaultHost = newHost;
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.success", name));
}
} else {
//如果插入失败,那就说明存在冲突,找出重复的MappedHost
MappedHost duplicate = hosts[find(hosts, name)];
//如果连host对象都相同,那么直接打印debug信息
if (duplicate.object == host) {
// The host is already registered in the mapper.
// E.g. it might have been added by addContextVersion()
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.sameHost", name));
}
//用旧的MappedHost覆盖newHost引用,以便后面直接设置别名MappedHost集合
newHost = duplicate;
} else {
如果是不同的Host对象,那么出现了冲突,打印错误日志,并直接返回
log.error(sm.getString("mapper.duplicateHost", name,
duplicate.getRealHostName()));
// Do not add aliases, as removeHost(hostName) won't be able to
// remove them
return;
}
}
//别名MappedHost集合
List<MappedHost> newAliases = new ArrayList<>(aliases.length);
for (String alias : aliases) {
alias = renameWildcardHost(alias);
//创建别名MappedHost,alias别名,newHost是前面插入的MappedHost对象
MappedHost newAlias = new MappedHost(alias, newHost);
//添加别名MappedHost
//(*3*)
if (addHostAliasImpl(newAlias)) {
//如果成功,添加到别名集合中
newAliases.add(newAlias);
}
}
//添加与原始MappedHost的关联集合中
newHost.addAliases(newAliases);
}
//(*1*)
private static final <T> boolean insertMap
(MapElement<T>[] oldMap, MapElement<T>[] newMap, MapElement<T> newElement) {
//二分法查找
int pos = find(oldMap, newElement.name);
//如果找到了相同的MapHost,那么直接返回,不允许插入相同的MapHost,否则就出现了歧义
if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
return false;
}
//拷贝0到pos位置的MapHost
System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
//将新的MapHost插入到pos的后面一个,这就保持了从小到大的顺序
newMap[pos + 1] = newElement;
//拷贝剩下的MapHost
System.arraycopy
(oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
//表示插入成功
return true;
}
//(*2*)
private static final <T> int find(MapElement<T>[] map, String name) {
int a = 0;
int b = map.length - 1;
// Special cases: -1 and 0
//如果旧的mapHost数组就没有值,那么直接返回-1
if (b == -1) {
return -1;
}
//如果这个名字比第一个的host都还小,那么肯定不存在旧的maphost
if (name.compareTo(map[0].name) < 0) {
return -1;
}
//如果b为零,说明只存在一个MapHost,直接返回下标零。因为只有一个MapHost的时候
//将要插入的MapHost肯定是大于或者等于旧的这个MapHost,肯定是要插在零下标的后面
if (b == 0) {
return 0;
}
int i = 0;
//二分法查找
while (true) {
i = (b + a) / 2;
int result = name.compareTo(map[i].name);
if (result > 0) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
//找到最后一个比将要插入MapHost要小的MapHost,返回其下标
if ((b - a) == 1) {
int result2 = name.compareTo(map[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
//(*3*)
private synchronized boolean org.apache.catalina.mapper.Mapper.addHostAliasImpl(MappedHost newAlias) {
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
//插入别名MappedHost对象
if (insertMap(hosts, newHosts, newAlias)) {
hosts = newHosts;
//是否为默认MappedHost
if (newAlias.name.equals(defaultHostName)) {
defaultHost = newAlias;
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHostAlias.success",
newAlias.name, newAlias.getRealHostName()));
}
return true;
} else {
//查找重复名称的MappedHost
MappedHost duplicate = hosts[find(hosts, newAlias.name)];
//获取原始的MappedHost对象,如果他们原始MappedHost对象相同,那么表示别名冲突
if (duplicate.getRealHost() == newAlias.getRealHost()) {
// A duplicate Alias for the same Host.
// A harmless redundancy. E.g.
// <Host name="localhost"><Alias>localhost</Alias></Host>
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHostAlias.sameHost",
newAlias.name, newAlias.getRealHostName()));
}
return false;
}
//打印错误,发生别名与其他Host的名字冲突
log.error(sm.getString("mapper.duplicateHostAlias", newAlias.name,
newAlias.getRealHostName(), duplicate.getRealHostName()));
return false;
}
}
registerContext
注册Host的子容器Context
private void org.apache.catalina.mapper.MapperListener.registerContext(Context context) {
//获取context容器的请求路径
String contextPath = context.getPath();
//如果是根路径,contextPath设置为空字符串
if ("/".equals(contextPath)) {
contextPath = "";
}
//获取父容器Host
Host host = (Host)context.getParent();
//获取资源根节点
WebResourceRoot resources = context.getResources();
//获取欢迎页面名
String[] welcomeFiles = context.findWelcomeFiles();
//WrapperMappingInfo集合,用于维护Mapping,Wrapper等一些信息
List<WrapperMappingInfo> wrappers = new ArrayList<>();
//获取Wrapper
for (Container container : context.findChildren()) {
//创建WrapperMappingInfo集合
//(*1*)
prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerWrapper",
container.getName(), contextPath, service));
}
}
//注册context
mapper.addContextVersion(host.getName(), host, contextPath,
context.getWebappVersion(), context, welcomeFiles, resources,
wrappers);
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerContext",
contextPath, service));
}
}
//(*1*)
private void org.apache.catalina.mapper.MapperListener.prepareWrapperMappingInfo(Context context, Wrapper wrapper,
List<WrapperMappingInfo> wrappers) {
//获取servletName
String wrapperName = wrapper.getName();
boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
//获取该Servlet的映射数组
String[] mappings = wrapper.findMappings();
//每个mapping创建一个WrapperMappingInfo对象
for (String mapping : mappings) {
//判断是否为jsp通配符模式
boolean jspWildCard = (wrapperName.equals("jsp")
&& mapping.endsWith("/*"));
wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard,
resourceOnly));
}
}
注册context
//hostName, host, contextPath路径,version版本,welcomeResources欢迎页数组,context的根resources, wrappers刚才的Servlet容器集合
public void org.apache.catalina.mapper.Mapper.addContextVersion(String hostName, Host host, String path,
String version, Context context, String[] welcomeResources,
WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
//和前面设置Host是一样的
hostName = renameWildcardHost(hostName);
//精确查找MappedHost
MappedHost mappedHost = exactFind(hosts, hostName);
//按道理应该已经存在的,如果真的没有,那么重新注册
if (mappedHost == null) {
addHost(hostName, new String[0], host);
//精确查找
mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
log.error("No host found: " + hostName);
return;
}
}
//如果找出来的是一个别名Host,那么表示没有找到原始的Host
if (mappedHost.isAlias()) {
log.error("No host found: " + hostName);
return;
}
//"/"的个数
int slashCount = slashCount(path);
synchronized (mappedHost) {
//维护context,wrapper,版本的关系
ContextVersion newContextVersion = new ContextVersion(version,
path, slashCount, context, resources, welcomeResources);
if (wrappers != null) {
//注册wrapper,分为三种类型的wrapper,通配符wrapper,扩展名wrapper,精确匹配的wrapper
addWrappers(newContextVersion, wrappers);
}
//获取维护host与context关系的集合对象
ContextList contextList = mappedHost.contextList;
//精确匹配对应的MappedContext,与查找host是一样的,不过多的说明了
MappedContext mappedContext = exactFind(contextList.contexts, path);
if (mappedContext == null) {
//构建MappedContext
mappedContext = new MappedContext(path, newContextVersion);
//注册MappedContext,与注册Host一样的套路,从小到大的顺序
ContextList newContextList = contextList.addContext(
mappedContext, slashCount);
if (newContextList != null) {
//给mappedHost与其别名MappedHost设置ContextList
updateContextList(mappedHost, newContextList);
//Mapper的一个成员变量context-》ContextVersion
contextObjectToContextVersionMap.put(context, newContextVersion);
}
} else {
//如果已经存在旧得MappedContext了,那么获取它所维护的不同版本的contextversion
ContextVersion[] contextVersions = mappedContext.versions;
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
//像host那样从小到大插入
if (insertMap(contextVersions, newContextVersions,
newContextVersion)) {
mappedContext.versions = newContextVersions;
//Mapper的一个成员变量context-》ContextVersion
contextObjectToContextVersionMap.put(context, newContextVersion);
} else {
// Re-registration after Context.reload()
// Replace ContextVersion with the new one
int pos = find(contextVersions, version);
//如果已经存在相同版本的contextVersion,那么覆盖
if (pos >= 0 && contextVersions[pos].name.equals(version)) {
contextVersions[pos] = newContextVersion;
contextObjectToContextVersionMap.put(context, newContextVersion);
}
}
}
}
}
注册Wrapper
private void org.apache.catalina.mapper.Mapper.addWrappers(ContextVersion contextVersion,
Collection<WrapperMappingInfo> wrappers) {
for (WrapperMappingInfo wrapper : wrappers) {
addWrapper(contextVersion, wrapper.getMapping(),
wrapper.getWrapper(), wrapper.isJspWildCard(),
wrapper.isResourceOnly());
}
}
protected void addWrapper(ContextVersion context, String path,
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) {
//如果是以/*结尾
if (path.endsWith("/*")) {
// Wildcard wrapper
//如/user/*,截取之后就是/user
String name = path.substring(0, path.length() - 2);
//包装成MappedWrapper
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.wildcardWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
//像注册host一样从小到大注册(以name进行排序)
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.wildcardWrappers = newWrappers;
//计算wrapper的/个数
int slashCount = slashCount(newWrapper.name);
//给context保存最长路径的/个数(嵌套层数最多的那个)
if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
//后缀
} else if (path.startsWith("*.")) {
// Extension wrapper
//获取后缀
String name = path.substring(2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.extensionWrappers;
MappedWrapper[] newWrappers =
new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.extensionWrappers = newWrappers;
}
//默认wrapper
} else if (path.equals("/")) {
// Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper;
} else {
// Exact wrapper
//精确匹配wrapper
final String name;
if (path.length() == 0) {
// Special case for the Context Root mapping which is
// treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.exactWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
//操作方式与host,context一样,以name进行从小到大排序
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.exactWrappers = newWrappers;
}
}
}
}
以下为Mapper的类图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?