总结:主要是创建Context对象,并且将默认context配置,host级别配置,context配置的值设置进去,设置docBase,如果是war包就解压到webapp的目录中,重新设置docBase为war包解压后的目录。如果配置文件中没有指定docBase,那么就以webapps为基路径+context的baseName作为docBase

HostConfig.deployApps()
//在监听到start事件类型,也就是StandardHost调用startInternal
protected void deployApps() { File appBase = host.getAppBaseFile();
//这个值是在触发before_start时间时生成的,默认是tomcat安装目录+engine名+host名
File configBase = host.getConfigBaseFile();
//获取host上配置的webapp下的所有文件,默认是webapps目录下的所有文件
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase 发布xml描述文件
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths); } deployDescriptors protected void deployDescriptors(File configBase, String[] files) { if (files == null)
return; ExecutorService es = host.getStartStopExecutor();//获取线程池
List<Future<?>> results = new ArrayList<>(); for (int i = 0; i < files.length; i++) {
File contextXml = new File(configBase, files[i]); if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
//context命名,在构造函数里面进行设置,设置版本,path,命名。
ContextName cn = new ContextName(files[i], true);
//是否已经部署过,如果已经部署过了,就不再进行部署
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//异步发布context描述xml
results.add(
es.submit(new DeployDescriptor(this, cn, contextXml)));
}
} for (Future<?> result : results) {
try {
//等待异步部署context描述文件结束
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.threaded.error"), e);
}
}
} ContextName.ContextName() //name 文件名
public ContextName(String name, boolean stripFileExtension) {
//假设文件名为myContext.xml和my/context.xml
String tmp1 = name; // Convert Context names and display names to base names // Strip off any leading "/" 剥离开头的/
if (tmp1.startsWith("/")) {
tmp1 = tmp1.substring(1);
} // Replace any remaining / 将/替换成#,如果是myContext.xml,那么依然是myContext.xml,如果是my/context.xml,那么就是my#context.xml
tmp1 = tmp1.replaceAll("/", FWD_SLASH_REPLACEMENT); // Insert the ROOT name if required 如果需要,插入ROOT名称
//如果是以##开头或者没有名字的
if (tmp1.startsWith(VERSION_MARKER) || "".equals(tmp1)) {
tmp1 = ROOT_NAME + tmp1;//比如##a,就会变成ROOT##a,为空就是ROOT
} // Remove any file extensions 去除扩展名
if (stripFileExtension &&
(tmp1.toLowerCase(Locale.ENGLISH).endsWith(".war") ||
tmp1.toLowerCase(Locale.ENGLISH).endsWith(".xml"))) {
tmp1 = tmp1.substring(0, tmp1.length() -4);
} baseName = tmp1;//myContext,my#context String tmp2;
// Extract version number 提取版本号
int versionIndex = baseName.indexOf(VERSION_MARKER);
if (versionIndex > -1) {
//如果存在##这种的,提取##后面作为版本号
version = baseName.substring(versionIndex + 2);
//比如myContext##1.2,这里version就是1.2,tmp2为myContext
tmp2 = baseName.substring(0, versionIndex);
} else {
version = "";
tmp2 = baseName;
}
//如果为ROOT,那么path路径为域名根目录
if (ROOT_NAME.equals(tmp2)) {
path = "";
} else {
// /myContext /my/context
path = "/" + tmp2.replaceAll(FWD_SLASH_REPLACEMENT, "/");
}
//如果存在版本号的应用
if (versionIndex > -1) {
// /myContext##1.2 /my/context##1.2
this.name = path + VERSION_MARKER + version;
} else {
this.name = path;
}
} DeployDescriptor.run() public void run() {
config.deployDescriptor(cn, descriptor);
} HostConfig.deployDescriptor(ContextName cn, File contextXml) protected void deployDescriptor(ContextName cn, File contextXml) {
//发布应用,用于记录发布的context名,和是否有描述文件,一些可能被修改的文件的修改时间,用于日后检测是否需要重新加载
DeployedApplication deployedApp =
new DeployedApplication(cn.getName(), true); long startTime = 0;
// Assume this is a configuration descriptor and deploy it
if(log.isInfoEnabled()) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDescriptor",
contextXml.getAbsolutePath()));
} Context context = null;
//是否为扩展war包
boolean isExternalWar = false;
//是否是扩展web 应用
boolean isExternal = false;
//记录扩展web应用的地址
File expandedDocBase = null; try (FileInputStream fis = new FileInputStream(contextXml)) {
synchronized (digesterLock) {
try {
//解析contextxml文件
context = (Context) digester.parse(fis);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), e);
} finally {
//释放内存
digester.reset();
//如果创建失败,就new出一个失败的context
if (context == null) {
context = new FailedContext();
}
}
}
//host.getConfigClass() == org.apache.catalina.startup.ContextConfig
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
//给context设置ContextConfig生命周期监听器
context.addLifecycleListener(listener); context.setConfigFile(contextXml.toURI().toURL());
context.setName(cn.getName());
context.setPath(cn.getPath());//一般我们在config/engine名+host名下的配置文件都是
context.setWebappVersion(cn.getVersion());
// Add the associated docBase to the redeployed list if it's a WAR
if (context.getDocBase() != null) {
File docBase = new File(context.getDocBase());
if (!docBase.isAbsolute()) {
docBase = new File(host.getAppBaseFile(), context.getDocBase());
}
// If external docBase, register .xml as redeploy first,如果是扩展的web应用
//那么首先进行重发布处理
if (!docBase.getCanonicalPath().startsWith(
host.getAppBaseFile().getAbsolutePath() + File.separator)) {
isExternal = true;
//设置应用配置xml为重发布资源,记录最后修改的时间
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
//设置应用目录为重发布资源,记录最后修改的时间
deployedApp.redeployResources.put(docBase.getAbsolutePath(),
Long.valueOf(docBase.lastModified()));
//如果是war包,设置isExternalWar为true
if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
isExternalWar = true;
}
} else {
log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
docBase));
// Ignore specified docBase
context.setDocBase(null);
}
} host.addChild(context);//将context添加到对应host容器中,这里会进行context的初始化,启动生命周期
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDescriptor.error",
contextXml.getAbsolutePath()), t);
} finally {
// Get paths for WAR and expanded WAR in appBase // default to appBase dir + name 默认是AppBase路径加上容器的继承名称
expandedDocBase = new File(host.getAppBaseFile(), cn.getBaseName());
//如果应用的docBase不为空,也就是设置了应用的位置,并且不是war包
if (context.getDocBase() != null
&& !context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
// first assume docBase is absolute 重新设置expandedDocBase
expandedDocBase = new File(context.getDocBase());
if (!expandedDocBase.isAbsolute()) {
// if docBase specified and relative, it must be relative to appBase
//如果是相对路径,都会认为相对appbase
expandedDocBase = new File(host.getAppBaseFile(), context.getDocBase());
}
} boolean unpackWAR = unpackWARs;
if (unpackWAR && context instanceof StandardContext) {
unpackWAR = ((StandardContext) context).getUnpackWAR();
} // Add the eventual unpacked WAR and all the resources which will be
// watched inside it
//如果是war包,并且允许解压,那么把war加入重新部署检测列表
if (isExternalWar) {
if (unpackWAR) {
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
//加入以expandedDocBase为基路径的资源重新加载监控
//在配置文件中有WatchedResource这样的xml元素,可以配置需要重加载检测文件
addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
} else {
//如果不允许解压,那么就直接用他们的相对路径进行资源修改监控
addWatchedResources(deployedApp, null, context);
}
} else {
//如果不是war包,而又不是扩展目录应用,那么自动加上war后缀
// Find an existing matching war and expanded folder
if (!isExternal) {
File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war");
if (warDocBase.exists()) {
deployedApp.redeployResources.put(warDocBase.getAbsolutePath(),
Long.valueOf(warDocBase.lastModified()));
} else {
// Trigger a redeploy if a WAR is added 如果这个war后面被添加进来了,那么就触发重新加载
deployedApp.redeployResources.put(
warDocBase.getAbsolutePath(),
Long.valueOf(0));
}
}
//这段代码和上面的war时的代码是一样的
if (unpackWAR) {
deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
Long.valueOf(expandedDocBase.lastModified()));
addWatchedResources(deployedApp,
expandedDocBase.getAbsolutePath(), context);
} else {
addWatchedResources(deployedApp, null, context);
}
if (!isExternal) {
// For external docBases, the context.xml will have been
// added above.
deployedApp.redeployResources.put(
contextXml.getAbsolutePath(),
Long.valueOf(contextXml.lastModified()));
}
}
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
//添加全局重新部署资源,如conf/engine名称+host名/context.xml.default和conf/context.xml
addGlobalRedeployResources(deployedApp);
}
//如果这个web应用已经成功添加到host中,那么记录这个应用已经被发布
if (host.findChild(context.getName()) != null) {
deployed.put(context.getName(), deployedApp);
} if (log.isInfoEnabled()) {
log.info(sm.getString("hostConfig.deployDescriptor.finished",
contextXml.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
} host.addChild最终调用了StandardHost.addChildInternal方法 private void addChildInternal(Container child) { if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE 给子容器设置父容器,并且触发属性更改事件
children.put(child.getName(), child);将生成的context以context的名字做key,进行保存到map中
} // Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
//启动context容器
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
} child.setParent(this); public void setParent(Container container) { Container oldParent = this.parent;
this.parent = container;
//调用实现了PropertyChangeListener接口的观察者
support.firePropertyChange("parent", oldParent, this.parent); } child.start();
StandardContext.start(); public final synchronized void start() throws LifecycleException {
//如果已经正在启动了,那么无需重复启动,直接返回,由于多线程的原因,这个state是volatile修饰的,保证内存可见性
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;
}
//如果当前状态还是NEW,也就是连init都么有,那么就需要进行初始化
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);
}
} init(); //StandardContext触发after_init事件
public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
} // Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();-----》2
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init(); -----》1
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
} } ContextConfig.init(); protected void init() {
// Called from StandardContext.init()
//创建一个Digester用于解析context.xml
Digester contextDigester = createContextDigester();
contextDigester.getParser(); if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.init"));
}
context.setConfigured(false);//设置配置状态,默认设置为失败,以免被误任务成功
ok = true; contextConfig(contextDigester);
} ContextConfig.contextConfig() protected void contextConfig(Digester digester) { String defaultContextXml = null; // Open the default context.xml file, if it exists 如果配置了默认的配置,使用它
if (context instanceof StandardContext) {
defaultContextXml = ((StandardContext)context).getDefaultContextXml();
}
// set the default if we don't have any overrides 如果没有使用tomcat默认的全局配置
if (defaultContextXml == null) {
defaultContextXml = Constants.DefaultContextXml;//conf/context.xml
}
//如果还没有进行解析,那么就会重新解析,默认的全局配置-》configBase下的context.xml.default-》configBase下的配置
if (!context.getOverride()) {
File defaultContextFile = new File(defaultContextXml);
if (!defaultContextFile.isAbsolute()) {
defaultContextFile =
new File(context.getCatalinaBase(), defaultContextXml);
}
if (defaultContextFile.exists()) {
try {
URL defaultContextUrl = defaultContextFile.toURI().toURL();
//处理应用配置,这个配置是默认配置,如果用户指定了默认配置,那么就解析用户指定的配置,如果没有指定,那么就直接使用tomcat提供的全局配置
processContextConfig(digester, defaultContextUrl);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.badUrl", defaultContextFile), e);
}
} //Constants.HostContextXml == context.xml.default,这个默认配置文件是host范围的context配置
File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml);
if (hostContextFile.exists()) {
try {
URL hostContextUrl = hostContextFile.toURI().toURL();
processContextConfig(digester, hostContextUrl);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.badUrl", hostContextFile), e);
}
}
}
//context.getConfigFile() 获取应用的配置的文件,这个配置文件就是config/engine名+host名下扫描出来的配置文件
if (context.getConfigFile() != null) {
processContextConfig(digester, context.getConfigFile());
} } ContextConfig. processContextConfig() //解析应用配置文件,从目前tomcat调用这个方法的先后顺序可以看出,tomcat给一个web应用设置属性是从全局默认的配置开始-》host级别的配置-》再到context级别的配置,所以context级别的配置最高。
protected void processContextConfig(Digester digester, URL contextXml) { if (log.isDebugEnabled()) {
log.debug("Processing context [" + context.getName()
+ "] configuration file [" + contextXml + "]");
} InputSource source = null;
InputStream stream = null; try {
source = new InputSource(contextXml.toString());
URLConnection xmlConn = contextXml.openConnection();
xmlConn.setUseCaches(false);
stream = xmlConn.getInputStream();
} catch (Exception e) {
log.error(sm.getString("contextConfig.contextMissing",
contextXml) , e);
} if (source == null) {
return;
} try {
source.setByteStream(stream);
digester.setClassLoader(this.getClass().getClassLoader());
//这里这个digester不会再创建StandardContext对象了,因为在前面已经创建了一个
digester.setUseContextClassLoader(false);
digester.push(context.getParent());
digester.push(context);
XmlErrorHandler errorHandler = new XmlErrorHandler();
digester.setErrorHandler(errorHandler);
digester.parse(source);
if (errorHandler.getWarnings().size() > 0 ||
errorHandler.getErrors().size() > 0) {
errorHandler.logFindings(log, contextXml.toString());
ok = false;
}
if (log.isDebugEnabled()) {
log.debug("Successfully processed context [" + context.getName()
+ "] configuration file [" + contextXml + "]");
}
} catch (SAXParseException e) {
log.error(sm.getString("contextConfig.contextParse",
context.getName()), e);
log.error(sm.getString("contextConfig.defaultPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log.error(sm.getString("contextConfig.contextParse",
context.getName()), e);
ok = false;
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
log.error(sm.getString("contextConfig.contextClose"), e);
}
}
} //before_start事件
ContextConfig.beforeStart(); protected synchronized void beforeStart() { try {
fixDocBase();
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.fixDocBase", context.getName()), e);
} antiLocking();
} ContextConfig.fixDocBase()
//对docBase做调整
protected void fixDocBase() throws IOException { Host host = (Host) context.getParent();
File appBase = host.getAppBaseFile(); String docBase = context.getDocBase();
//如果没有设置docBase,那么就根据应用路径重新设置路径
if (docBase == null) {
// Trying to guess the docBase according to the path
String path = context.getPath();
if (path == null) {
return;
}
//通过路径和版本去获取docBase
ContextName cn = new ContextName(path, context.getWebappVersion());
docBase = cn.getBaseName();
} File file = new File(docBase);
if (!file.isAbsolute()) {
docBase = (new File(appBase, docBase)).getPath();
} else {
docBase = file.getCanonicalPath();
}
file = new File(docBase);
String origDocBase = docBase; ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
String pathName = cn.getBaseName(); boolean unpackWARs = true;
if (host instanceof StandardHost) {
unpackWARs = ((StandardHost) host).isUnpackWARs();
if (unpackWARs && context instanceof StandardContext) {
unpackWARs = ((StandardContext) context).getUnpackWAR();
}
}
//判断这个docBase路径是否是webapps下的
boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
//如果这个docBase指定的是一个war包,那么就将其解压
if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
URL war = UriUtil.buildJarUrl(new File(docBase));
if (unpackWARs) {
//解压war包,返回解压后的目录路径,war解析源码请看war包解析笔记
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
if (context instanceof StandardContext) {
//设置未解压时指定的位置,因为解压时会将解压后的内容放到host指定的appBase目录下
((StandardContext) context).setOriginalDocBase(origDocBase);
}
} else {
//如果不需要解压,那么就校验对应的docbase是由已经存在了,如果不存在,直接报错
ExpandWar.validate(host, war, pathName);
}
} else {
File docDir = new File(docBase);
File warFile = new File(docBase + ".war");
URL war = null;
//如果war包存在,并且是在APPBase中的,那么直接获取其war的url
if (warFile.exists() && docBaseInAppBase) {
war = UriUtil.buildJarUrl(warFile);
}
//如果目录存在,并且是war包,允许解压
if (docDir.exists()) {
if (war != null && unpackWARs) {
// Check if WAR needs to be re-expanded (e.g. if it has
// changed). Note: HostConfig.deployWar() takes care of
// ensuring that the correct XML file is used.
// This will be a NO-OP if the WAR is unchanged.
ExpandWar.expand(host, war, pathName);
}
} else {
if (war != null) {
if (unpackWARs) {
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
} else {
docBase = warFile.getCanonicalPath();
ExpandWar.validate(host, war, pathName);
}
}
if (context instanceof StandardContext) {
((StandardContext) context).setOriginalDocBase(origDocBase);
}
}
} // Re-calculate now docBase is a canonical path
docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
//如果目录为appBase下的,那么直接截取到appBase后面一截
if (docBaseInAppBase) {
docBase = docBase.substring(appBase.getPath().length());
docBase = docBase.replace(File.separatorChar, '/');
if (docBase.startsWith("/")) {
docBase = docBase.substring(1);
}
} else {
docBase = docBase.replace(File.separatorChar, '/');
} context.setDocBase(docBase);
} protected synchronized void startInternal() throws LifecycleException {
setConfigured(false);
boolean ok = true; // Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
} // Post work directory 生成工作目录,这个目录用于存放编译后的jsp文件,一般生成的目录格式为tomcat安装目录work+engine名+host名+context的baseName
postWorkDirectory(); // Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources"); try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
//设置web加载器
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
} 。。。省略代码 // Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // Start our child containers, if not already started,开始子容器context的子容器是wrapper
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
} // Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//省略了集群管理代码 // Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
// Set up the context init params 设置初始化参数
mergeParameters(); // Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
} // Configure and call application event listeners 实例化监听器,并且调用实现了ServletContextListener监听器的初始化方法
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
} // Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
} try {
// Start manager session管理器
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
} // Configure and call application filters 实例化filter,并且调用其初始化方法
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
} // Load and initialize all "load on startup" servlets 实例化设置了load on startup的Servlet并调用初始化方法
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
} // Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
} // Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
} startTime=System.currentTimeMillis(); // Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
} // The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc(); // Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
} configure_start事件
ContextConfig.configureStart() protected synchronized void configureStart() {
// Called from StandardContext.start()
//开始web.xml的配置
webConfig(); //如果未配置忽略应用注解配置,那么对filter,servlet,listener进行Resource注解的搜索
Resource配置在类,字段,方法上,会根据资源的类型进行划分资源类型,添加到不同资源集合中,比如环境变量,JNDI等等资源
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
//将context约束的角色和wrapper中注解RunAs或配置中设置的角色添加到context容器中,重复的不会被再次添加
validateSecurityRoles();
} // Configure an authenticator if we need one
//配置验证器,如果没有Ralm或者实现了 Authenticator的管道阀,那么就会添加一个默认的NonLoginAuthenticator验证器
if (ok) {
authenticatorConfig();
}
// Make our application available if no problems were encountered
//如果配置context时没有遇到任何问题,那么就表示配置成功
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
} } ContextConfig.webConfig(); protected void webConfig() {
//new出一个webxml的解析器
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal()); Set<WebXml> defaults = new HashSet<>();
//获取webxml片段
defaults.add(getDefaultWebXmlFragment(webXmlParser)); WebXml webXml = createWebXml(); // Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
} ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
//解析应用程序jar包中META-INF/web-fragment.xml
//key为jar包的全路径名,value是从META-INF/web-fragment.xml解析后的WebXml对象
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser); // Step 2. Order the fragments. 对片段进行排序,因为在片段中可以设置依赖,在什么什么之前启动,什么什么之后启动。
Set<WebXml> orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext); // Step 3. Look for ServletContainerInitializer implementations
if (ok) {
//查找META-INF/services/javax.servlet.ServletContainerInitializer配置文件,获取ServletContainerInitializer,这里的ServletContainerInitializer可以使用HandlesTypes注解指定需要处理的类型
processServletContainerInitializers();
} if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>(); if (ok) {
//加载/WEB-INF/classes下的资源
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes"); for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
//使用自定义的字节码解析器去解析class文件,寻找有@WebServlet,@WebFilter,@WebListener,然后将解析好的ServletDef,FilterDef,Listener注入到WebXml对象当中
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
} // Step 5. Process JARs for annotations and
// @HandlesTypes matches - only need to process those fragments we
// are going to use (remember orderedFragments includes any
// container fragments)
//解析jar包中的注解Servlet,Listener,FIlter
if (ok) {
processAnnotations(
orderedFragments, webXml.isMetadataComplete(), javaClassCache);
} // Cache, if used, is no longer required so clear it
javaClassCache.clear();
} if (!webXml.isMetadataComplete()) {
// Step 6. Merge web-fragment.xml files into the main web.xml
//合并片段配置文件的内容到主web.xml中
// file.
if (ok) {
ok = webXml.merge(orderedFragments);
} // Step 7. Apply global defaults
// Have to merge defaults before JSP conversion since defaults
// provide JSP servlet definition. //应用全局默认配置
webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets
//准备数据转换被显示配置的jsp为servlet,指定初始化参数,指定ServletClass为org.apache.jasper.servlet.JspServlet
if (ok) {
convertJsps(webXml);
} // Step 9. Apply merged web.xml to Context 将web.xml配置的数据设置到context中
if (ok) {
configureContext(webXml);
}
} else {
webXml.merge(defaults);
convertJsps(webXml);
configureContext(webXml);
} if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + webXml.toXml());
} // Always need to look for static resources
// Step 10. Look for static resources packaged in JARs 从jar中中查找静态资源
if (ok) {
// Spec does not define an order.
// Use ordered JARs followed by remaining JARs
Set<WebXml> resourceJars = new LinkedHashSet<>();
for (WebXml fragment : orderedFragments) {
resourceJars.add(fragment);
}
for (WebXml fragment : fragments.values()) {
if (!resourceJars.contains(fragment)) {
resourceJars.add(fragment);
}
}
processResourceJARs(resourceJars);
// See also StandardContext.resourcesStart() for
// WEB-INF/classes/META-INF/resources configuration
} // Step 11. Apply the ServletContainerInitializer config to the
// context 应用ServletContainerInitializer Initializer --》要处理的类型(可能是注解标识的,也可能就是指定的类型)
if (ok) {
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry :
initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(
entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
} ContextConfig.getDefaultWebXmlFragment private WebXml getDefaultWebXmlFragment(WebXmlParser webXmlParser) { // Host should never be null
Host host = (Host) context.getParent(); DefaultWebXmlCacheEntry entry = hostWebXmlCache.get(host);
//获取全局webxml配置文件,config/web.xml
InputSource globalWebXml = getGlobalWebXmlSource();
//获取host级别的xml配置文件,文件名为web.xml.default
InputSource hostWebXml = getHostWebXmlSource(); long globalTimeStamp = 0;
long hostTimeStamp = 0; if (globalWebXml != null) {
URLConnection uc = null;
try {
//获取systemid对应数据的最后修改时间
URL url = new URL(globalWebXml.getSystemId());
uc = url.openConnection();
globalTimeStamp = uc.getLastModified();
} catch (IOException e) {
globalTimeStamp = -1;
} finally {
if (uc != null) {
try {
uc.getInputStream().close();
} catch (IOException e) {
ExceptionUtils.handleThrowable(e);
globalTimeStamp = -1;
}
}
}
} if (hostWebXml != null) {
URLConnection uc = null;
try {
//获取systemid对应数据的最后修改时间
URL url = new URL(hostWebXml.getSystemId());
uc = url.openConnection();
hostTimeStamp = uc.getLastModified();
} catch (IOException e) {
hostTimeStamp = -1;
} finally {
if (uc != null) {
try {
uc.getInputStream().close();
} catch (IOException e) {
ExceptionUtils.handleThrowable(e);
hostTimeStamp = -1;
}
}
}
}
//如果已经解析过了,那么关闭资源,直接返回已经解析好的WebXml
if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
entry.getHostTimeStamp() == hostTimeStamp) {
InputSourceUtil.close(globalWebXml);
InputSourceUtil.close(hostWebXml);
return entry.getWebXml();
} // Parsing global web.xml is relatively expensive. Use a sync block to
// make sure it only happens once. Use the pipeline since a lock will
// already be held on the host by another thread
synchronized (host.getPipeline()) {
entry = hostWebXmlCache.get(host);
if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
entry.getHostTimeStamp() == hostTimeStamp) {
return entry.getWebXml();
}
//直接new出一个WebXml对象
WebXml webXmlDefaultFragment = createWebXml();
webXmlDefaultFragment.setOverridable(true);
// Set to distributable else every app will be prevented from being
// distributable when the default fragment is merged with the main
// web.xml
webXmlDefaultFragment.setDistributable(true);
// When merging, the default welcome files are only used if the app has
// not defined any welcomes files.
webXmlDefaultFragment.setAlwaysAddWelcomeFiles(false); // Parse global web.xml if present
if (globalWebXml == null) {
// This is unusual enough to log
log.info(sm.getString("contextConfig.defaultMissing"));
} else {
//解析全局web配置
if (!webXmlParser.parseWebXml(
globalWebXml, webXmlDefaultFragment, false)) {
ok = false;
}
} // Parse host level web.xml if present
// Additive apart from welcome pages
webXmlDefaultFragment.setReplaceWelcomeFiles(true);
//解析host级别的webxml,会覆盖掉全局的相同属性值的内容
if (!webXmlParser.parseWebXml(
hostWebXml, webXmlDefaultFragment, false)) {
ok = false;
} // Don't update the cache if an error occurs
if (globalTimeStamp != -1 && hostTimeStamp != -1) {
entry = new DefaultWebXmlCacheEntry(webXmlDefaultFragment,
globalTimeStamp, hostTimeStamp);
hostWebXmlCache.put(host, entry);
} return webXmlDefaultFragment;
}
} WebXmlParser.parseWebXml(InputSource source, WebXml dest,
boolean fragment) public boolean parseWebXml(InputSource source, WebXml dest,
boolean fragment) { boolean ok = true; if (source == null) {
return ok;
} XmlErrorHandler handler = new XmlErrorHandler(); Digester digester;
WebRuleSet ruleSet;
if (fragment) {//如果是web.xml配置片段,那么就使用片段Digester
digester = webFragmentDigester;
ruleSet = webFragmentRuleSet;
} else {
digester = webDigester;
ruleSet = webRuleSet;
} digester.push(dest);
digester.setErrorHandler(handler);
try {
digester.parse(source);
//如果解析出了错误,那么设置ok为false,意味着context启动失败
if (handler.getWarnings().size() > 0 ||
handler.getErrors().size() > 0) {
ok = false;
handler.logFindings(log, source.getSystemId());
}
} catch (SAXParseException e) {
log.error(sm.getString("webXmlParser.applicationParse",
source.getSystemId()), e);
log.error(sm.getString("webXmlParser.applicationPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log.error(sm.getString("webXmlParser.applicationParse",
source.getSystemId()), e);
ok = false;
} finally {
InputSourceUtil.close(source);
digester.reset();
ruleSet.recycle();
} return ok;
} Digester public WebXmlParser(boolean namespaceAware, boolean validation,
boolean blockExternal) {
webRuleSet = new WebRuleSet(false);
webDigester = DigesterFactory.newDigester(validation,
namespaceAware, webRuleSet, blockExternal);
webDigester.getParser(); webFragmentRuleSet = new WebRuleSet(true);
webFragmentDigester = DigesterFactory.newDigester(validation,
namespaceAware, webFragmentRuleSet, blockExternal);
webFragmentDigester.getParser();
} public WebRuleSet(String prefix, boolean fragment) {
super();
this.prefix = prefix;
this.fragment = fragment; if(fragment) {
fullPrefix = prefix + "web-fragment";
} else {
fullPrefix = prefix + "web-app";
} absoluteOrdering = new AbsoluteOrderingRule(fragment);
relativeOrdering = new RelativeOrderingRule(fragment);
} private void configureContext(WebXml webxml) {
// As far as possible, process in alphabetical order so it is easy to
// check everything is present
// Some validation depends on correct public ID
context.setPublicId(webxml.getPublicId()); // Everything else in order
context.setEffectiveMajorVersion(webxml.getMajorVersion());
context.setEffectiveMinorVersion(webxml.getMinorVersion());
//将上下文参数添加到context中
for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
context.addParameter(entry.getKey(), entry.getValue());
}
context.setDenyUncoveredHttpMethods(
webxml.getDenyUncoveredHttpMethods());
context.setDisplayName(webxml.getDisplayName());
context.setDistributable(webxml.isDistributable());
//EJB相关的引用资源,反正我不懂
for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
context.getNamingResources().addLocalEjb(ejbLocalRef);
}
for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
context.getNamingResources().addEjb(ejbRef);
}
//
for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
context.getNamingResources().addEnvironment(environment);
}
//添加错误页面
for (ErrorPage errorPage : webxml.getErrorPages().values()) {
context.addErrorPage(errorPage);
}
//添加过滤器,filterName->filterDef
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
//FilterMap urlPattern与Servlet的关联
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
//添加监听器到context的数组中
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
//添加locale-》字符集
for (Entry<String, String> entry :
webxml.getLocaleEncodingMappings().entrySet()) {
context.addLocaleEncodingMappingParameter(entry.getKey(),
entry.getValue());
}
// Prevents IAE 设置登录配置
if (webxml.getLoginConfig() != null) {
context.setLoginConfig(webxml.getLoginConfig());
}
//消息目标引用
for (MessageDestinationRef mdr :
webxml.getMessageDestinationRefs().values()) {
context.getNamingResources().addMessageDestinationRef(mdr);
} // messageDestinations were ignored in Tomcat 6, so ignore here context.setIgnoreAnnotations(webxml.isMetadataComplete());
//设置媒体类型
for (Entry<String, String> entry :
webxml.getMimeMappings().entrySet()) {
context.addMimeMapping(entry.getKey(), entry.getValue());
}
// Name is just used for ordering 添加JNDI引用
for (ContextResourceEnvRef resource :
webxml.getResourceEnvRefs().values()) {
context.getNamingResources().addResourceEnvRef(resource);
}
//添加JNDI
for (ContextResource resource : webxml.getResourceRefs().values()) {
context.getNamingResources().addResource(resource);
}
//设置权限
boolean allAuthenticatedUsersIsAppRole =
webxml.getSecurityRoles().contains(
SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
if (allAuthenticatedUsersIsAppRole) {
constraint.treatAllAuthenticatedUsersAsApplicationRole();
}
context.addConstraint(constraint);
}
//添加角色
for (String role : webxml.getSecurityRoles()) {
context.addSecurityRole(role);
}
//添加服务引用
for (ContextService service : webxml.getServiceRefs().values()) {
context.getNamingResources().addService(service);
}
//添加ServletDef,包装成StandardWrapper容器
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// Description is ignored
// Display name is ignored
// Icons are ignored // jsp-file gets passed to the JSP Servlet as an init-param
//设置启动顺序
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
//设置上传文件的大小
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
//是否异步支持
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(servlet.isOverridable());
//添加子容器,初始化和启动子容器
context.addChild(wrapper);
}
//添加ServletMapping urlPattern->servletName
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
//获取session配置
SessionConfig sessionConfig = webxml.getSessionConfig();
if (sessionConfig != null) {
if (sessionConfig.getSessionTimeout() != null) {
context.setSessionTimeout(
sessionConfig.getSessionTimeout().intValue());
}
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
scc.setName(sessionConfig.getCookieName());
scc.setDomain(sessionConfig.getCookieDomain());
scc.setPath(sessionConfig.getCookiePath());
scc.setComment(sessionConfig.getCookieComment());
if (sessionConfig.getCookieHttpOnly() != null) {
scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
}
if (sessionConfig.getCookieSecure() != null) {
scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
}
if (sessionConfig.getCookieMaxAge() != null) {
scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
}
if (sessionConfig.getSessionTrackingModes().size() > 0) {
context.getServletContext().setSessionTrackingModes(
sessionConfig.getSessionTrackingModes());
}
} // Context doesn't use version directly
//添加欢迎配置
for (String welcomeFile : webxml.getWelcomeFiles()) {
/*
* The following will result in a welcome file of "" so don't add
* that to the context
* <welcome-file-list>
* <welcome-file/>
* </welcome-file-list>
*/
if (welcomeFile != null && welcomeFile.length() > 0) {
context.addWelcomeFile(welcomeFile);
}
} // Do this last as it depends on servlets
for (JspPropertyGroup jspPropertyGroup :
webxml.getJspPropertyGroups()) {
String jspServletName = context.findServletMapping("*.jsp");
if (jspServletName == null) {
jspServletName = "jsp";
}
if (context.findChild(jspServletName) != null) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
context.addServletMappingDecoded(urlPattern, jspServletName, true);
}
} else {
if(log.isDebugEnabled()) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
log.debug("Skipping " + urlPattern + " , no servlet " +
jspServletName);
}
}
}
}
//添加初始方法
for (Entry<String, String> entry :
webxml.getPostConstructMethods().entrySet()) {
context.addPostConstructMethod(entry.getKey(), entry.getValue());
}
//添加销毁方法
for (Entry<String, String> entry :
webxml.getPreDestroyMethods().entrySet()) {
context.addPreDestroyMethod(entry.getKey(), entry.getValue());
}
}

context创建过程解析(一)之deployDescriptors的更多相关文章

  1. context创建过程解析(三)之deployDirectories

    HostConfig.deployApps() //在监听到start事件类型,也就是StandardHost调用startInternal protected void deployApps() { ...

  2. context创建过程解析(二)之deployWARs

    HostConfig.deployApps() //在监听到start事件类型,也就是StandardHost调用startInternal protected void deployApps() { ...

  3. Android深入理解Context(一)Context关联类和Application Context创建过程

    前言 Context也就是上下文对象,是Android较为常用的类,但是对于Context,很多人都停留在会用的阶段,这个系列会带大家从源码角度来分析Context,从而更加深入的理解它. 1.Con ...

  4. Android深入理解Context(二)Activity和Service的Context创建过程

    前言 上一篇文章我们学习了Context关联类和Application Context的创建过程,这一篇我们接着来学习Activity和Service的Context创建过程.需要注意的是,本篇的知识 ...

  5. Android Context创建过程

        特定的资源或者类构成了Android应用程序的运行上下文环境 PackageManager, ClassLoader, Assert等等 Android应用程序窗口的运行上下文环境是通过Con ...

  6. InputSplit—>RecordReder—>map(key,value,context)的过程解析

    上图首先描述了在TaskTracker端Task(MapTask.ReduceTask)的执行过程,MapTask(org.apache.hadoop.mapred)首先被TaskRunner调用,然 ...

  7. ZooKeeper(三):请求处理链路的创建过程解析

    我们知道,zk就是一个个处理链组成的. 但是,这些处理链是在什么创建的呢? ZooKeeper 中有三种角色的服务节点存在: Leader, Follower, Observer . 而每个服务节点的 ...

  8. JVM系列(三):JVM创建过程解析

    上两篇中梳理了整个java启动过程中,jvm大致是如何运行的.即厘清了我们认为的jvm的启动过程.但那里面仅为一些大致的东西,比如参数解析,验证,dll加载等等.把最核心的loadJavaVM()交给 ...

  9. [原创]Andorid DexClassLoader的创建过程解析(基于5.0)

    做Android插件框架时,经常会用到dex的动态加载,就要直接或间接的使用DexClassLoader,在new DexClassLoader的时候Android系统做了很多工作,下面我们详细分析一 ...

随机推荐

  1. 玩转java多线程(wait和notifyAll的正确使用姿势)

    转载请标明博客的地址 本人博客和github账号,如果对你有帮助请在本人github项目AioSocket上点个star,激励作者对社区贡献 个人博客:https://www.cnblogs.com/ ...

  2. SpringBoot整合日志框架LogBack

    日志可以记录我们应用程序的运行情况,我们可以通过日志信息去获取应用程序更多的信息.常用处理java日志的组件有:slf4j.log4j.logback.common-logging等.其中log4j是 ...

  3. Windows下OSGEarth的编译过程

    目录 1. 依赖 1) OpenSceneGraph 2) GDAL 3) CURL 4) GEOS 5) 其他 2. 编译 1) 设置参数 2) 配置路径 3) 生成编译 3. 参考文献 1. 依赖 ...

  4. mysql的配置和启动命令

    一.mysql配置文件在linux系统下的位置 使用命令查询位置: 1.找到安装位置 which mysql  -> /usr/bin/mysql 2.接下来就可以针对这个目录通过一些命令查看配 ...

  5. MySQL下的DB Link

    前言: 在实际工作中,我们可能会遇到需要操作其他数据库实例的部分表,但又不想系统连接多库.此时我们就需要用到数据表映射.如同Oracle中的DBlink一般,使用过Oracle DBlink数据库链接 ...

  6. SQLPLUS执行PL/SQL语句块

    1.首先登录Oracle HR schema: 2.对于PL/SQL程序,分号表示语句的结束:而使用 "."  号表示整个语句块的结束,也可以省略.按回车键后,该语句块不会执行,即 ...

  7. C语言版数据结构笔记

    现在把以前学的数据结构知识再理一遍,上机测试.首先最重要的是链表.在我看来,链表其实就是由一个个结构体连接而成的,创建一个链表有多种方式,头插法,尾插法等,这里采用的是尾插法.表述有不对的地方,欢迎更 ...

  8. vue-cli初始化项目

    vue init webpack cnpm install npm run dev   初始化项目 我们用vue init命令来初始化项目,具体看一下这条命令的使用方法. vue init <t ...

  9. 生产追溯系统-Raspberry Pi帮助我们节省大量硬件成本,助力信息化建设

    初识 Raspberry Pi 竟然有这么小的电脑主机?只有手掌这么大?电源线竟然跟手机数据线一样?当我第一次看到Raspberry Pi的时候,在脑海中产生了一连串的疑问,带着这些疑问逐渐开始研究这 ...

  10. HDU 3068:最长回文(Manacher算法)

    http://acm.hdu.edu.cn/showproblem.php?pid=3068 最长回文 Problem Description   给出一个只由小写英文字符a,b,c...y,z组成的 ...