在上节中,大致过了一遍Engine到Host的启动过程,重点我认为有以下几点
1、Engine容器启动了一个线程间隔10秒执行各子容器的backgroundProcess()方法,并发布periodic周期事件。默认配置下只有Engine启动后台任务线程,子容器Host/Context不启动这个线程,可以修改server.xml配置文件为容器增加backgroundProcessorDelay=20 的参数配置,则该容器会创建一个新的周期任务线程,之后该容器及子容器的后台backgroundProcess()任务则由该线程执行,20是周期(秒)
2、Host启动,将server.xml中配置的Context信息,还有webapp/文件夹下的war包,应用文件夹都解析为一个StandardContext,进行部署和Context的启动。同时也监听context的配置文件,监听到文件被修改,则在backgroundProcess()周期任务中重新部署
一个Context代表一个应用,下边逐行代码分析启动一个web应用时tomcat都做了什么工作
目录
ContextConfig
init
beforeStart
StandardContext
startInternal
ContextConfig
configureStart
webConfig
ContextConfig
StandardContext是Context容器的默认实现类,启动时会先发布init_before /init_after/start_before/start_after事件,而ContextConfig就是StandardContext的生命周期监听器,先来看监听器在StandardContext.startInternal()之前进行的处理。
@Override 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();// 3333 startinternal 执行期间发布config_start事件触发 } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart();// 2222 初始化后,startinternal 之前触发 } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docbase for management tools if (originalDocbase != null) { context.setDocbase(originalDocbase); }// 4444 } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init();// 1111 初始化结束事件 } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } }init
从各个地方加载Context的配置,设置Context对象的属性
1、tomcat8confcontext.xml 默认情况下如下配置,设置了该Context对象监听的资源路径,如果下边两个路径的xml被修改,则reload该应用Context对象
2、tomcat8confCatalinalocalhostcontext.xml.default 这默认没有,如果需要,则配置的context.xml是当前Host容器(localhost)下应用的通用属性
3、应用路径meta-INFcontext.xml 各应用如有需要自行配置
protected synchronized void init() { // xml文件解析器 Digester contextDigester = createContextDigester(); contextDigester.getParser(); context.setConfigured(false); ok = true; contextConfig(contextDigester); } protected void contextConfig(Digester digester) { String defaultContextXml = null; // ========================1======================== // 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 if (defaultContextXml == null) { defaultContextXml = Constants.DefaultContextXml; } 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(); processContextConfig(digester, defaultContextUrl); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.badUrl", defaultContextFile), e); } } // ===========================2======================== 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); } } } // =============================3======================= if (context.getConfigFile() != null) { processContextConfig(digester, context.getConfigFile()); } }beforeStart
执行StandardContext.startInternal之前进行处理
1、调整docbase,根据配置重新设置docBse属性,在webapp下的应用设置为相对路径,不在的设置为绝对路径
2、Context参数antiResourceLocking,默认false,如果配置为true,则会建立临时文件夹(路径由系统参数java.io.tmpdir设置),复制该应用的所有文件到临时文件夹,启动时加载临时文件夹内容。
因为tomcat支持热部署,例如war包重新部署时需删除旧的文件夹,但可能某旧文件被锁定无法删除,所以此参数通过建立临时文件防止资源锁导致部署失败
protected synchronized void beforeStart() { // 重新设置docbase try { fixDocbase(); } catch (IOException e) { log.error(sm.getString( "contextConfig.fixDocbase", context.getName()), e); } // 防止资源锁 antiLocking(); } protected void antiLocking() { if ((context instanceof StandardContext) && ((StandardContext) context).getAntiResourceLocking()) { // 如果设置参数为true ,获取临时文件目录 if (originalDocbase.toLowerCase(Locale.ENGLISH).endsWith(".war")) { antiLockingDocbase = new File( System.getProperty("java.io.tmpdir"), deploymentCount++ + "-" + docbase + ".war"); } else { antiLockingDocbase = new File( System.getProperty("java.io.tmpdir"), deploymentCount++ + "-" + docbase); } antiLockingDocbase = antiLockingDocbase.getAbsoluteFile(); // 删除原临时文件 ExpandWar.delete(antiLockingDocbase); // 复制所有文件到临时目录 if (ExpandWar.copy(docbaseFile, antiLockingDocbase)) { context.setDocbase(antiLockingDocbase.getPath()); } } }StandardContext startInternal
启动大致的 *** 作如下
1、设置/创建jsp临时work目录
2、封装和加载当前应用的各种资源路径,包括docbase、WEB-INF/lib下jar包路径等
3、创建当前应用的WebAppClassLoader并绑定到当前线程,用于加载/WEB-INF/classes和/WEB-INF/lib目录下的class
4、发布configure_start事件(这里由ContextConfig接入进行加载web.xml等配置文件获取Servlet/Filter等定义)
5、第四步加载完所有类型的配置,这一步是启动所有配置,调用SCI初始化、实例化Listener、Filter、Servlet等。普通web.xml配置Spring容器的启动就在Listener的启动这步,没什么太需要说的,底下没有详细列代码
6、容器通用 *** 作,启动后台任务线程、发布已启动事件start
@Override protected synchronized void startInternal() throws LifecycleException { // 。。。。 setConfigured(false); boolean ok = true; // 创建work目录,示例:apache-tomcat-6.0.53workCatalinalocalhost // jsp转为class的临时目录 postWorkDirectory(); // Add missing components as necessary if (getResources() == null) { // (1) Required by Loader try { setResources(new StandardRoot(this)); } catch (IllegalArgumentException e) { ok = false; } } if (ok) { // 启动StandardRoot对象 // StandardRoot对象中封装当前web应用资源 // 包括当前应用的项目class类路径、WEB-INF/lib下的jar包路径等 resourcesStart(); } // 创建Context的加载器,其中包含web应用的WebappClassLoader if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } // 。。。。。 // 将当前的webappClassLoader设置到当前线程,返回原线程中的classLoader // 我debug执行,这里会返回null,因为此时webappClassLoader还未实例化 ClassLoader oldCCL = bindThread(); try { if (ok) { // 启动当前webAppLoader,主要是实例化当前应用的类加载器WebappClassLoader/ParallelWebappClassLoader Loader loader = getLoader(); if (loader instanceof Lifecycle) { ((Lifecycle) loader).start(); } // 将原线程绑定的classLoader再恢复回来 // 如果oldCCL为null,空执行 unbindThread(oldCCL); // 启动了webAppLoader之后,将实例化后的WebappClassLoader绑定到当前线程,返回原线程的classLoader oldCCL = bindThread(); // 。。。。 } // 发布configure_start事件,触发ContextConfig中的方法执行加载web.xml等配置 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // configure_start事件执行完后,此时已经加载了子容器Warpper,启动之 for (Container child : findChildren()) { if (!child.getState().isAvailable()) { child.start(); } } // 。。。。 } // 。。。 // 调用ServletContainerInitializers,例如SpringBoot war包定义的启动类 for (Map.EntryContextConfig configureStart>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } // 开始实例化Listener/Filter/Servlet // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } // Load and initialize all "load on startup" servlets if (ok) { if (!loadonStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } } // Start ContainerBackgroundProcessor thread super.threadStart(); } finally { // Unbinding thread unbindThread(oldCCL); } // Reinitializing if something went wrong if (!ok) { setState(LifecycleState.FAILED); } else { setState(LifecycleState.STARTING); } }
在StandardContext启动中途会发布configure_start事件触发ContextConfig监听器的该方法执行,来加载web应用具体配置,加载web.xml、初始化容器等
protected synchronized void configureStart() { // 解析/WEB-INF/web.xml webConfig(); if (!context.getIgnoreAnnotations()) { applicationAnnotationsConfig(); } if (ok) { validateSecurityRoles(); } // Configure an authenticator if we need one // Tomcat对Servlet的访问提供的安全机制,和Realm配合使用 // 简单的看就是web.xml中给Servlet配置能访问的角色role,在Realm组件中配置角色的用户名密码等信息(支持xml配置、jdbc等) // 在请求访问Servlet时会被tomcat拦截要求输入用户名密码,调用Realm组件核验无误后才允许访问 // 没用过,好像不重要?? if (ok) { authenticatorConfig(); } // Make our application available if no problems were encountered if (ok) { context.setConfigured(true); } else { context.setConfigured(false); } }webConfig
web应用配置的解析过程如下:
1、获取并加载路径 Tomcat目录/conf/web.xml 中通用的web.xml配置。其中配置了两个默认的Servlet,DefaultServlet和JspServlet,前者处理静态资源,后者处理后缀为 .jsp的请求(获取jsp文件,将其编译为Servlet,并执行其service方法返回静态页面)
2、获取应用目录/WEB-INF/web.xml 中的配置,解析为一个WebXml对象
3、解析web-fragment.xml,扫描ClassPath下(WebAppClassLoader及其父类能加载到的)所有的jar包的meta-INF/web-fragment.xml文件。是Servlet3.0模块化新特性,xml配置与web.xml相同,即可以将通用配置配置在web-fragment.xml封入jar包,避免重复配置,具体可以百度用法
4、多个web-fragment.xml间的执行顺序在xml中可配置,这里按照顺序将多个xml对象排序。具体如何配置可以参考解析规则配置类WebRuleSet#addRuleInstances,博客参考Servlet3.0新特性之web-fragment.xml模块化配置文件 - 图图小淘气_real - 博客园
5、扫描ServletContainerInitializer实现类,这一步很重要,是不通过web.xml配置方式的入口,SpringBoot启动的时机就在这一步。ServletContainerInitializer 用于取代web.xml配置,使用编程风格注册Filter/Servlet等组件,使得编程框架(eg.spring)高度内聚,和容器Tomcat解耦。
原理:ServletContainerInitializer和HandlesTypes组合使用,ServletContainerInitializer是接口,定义如下,HandlesTypes是注解,表示ServletContainerInitializer实现类的处理对象类型,在Tomcat启动时由tomcat扫描所有class,将符合HandlesTypes配置的对象class作为参数c,然后调用该实现类进行初始化动作
public interface ServletContainerInitializer { void onStartup(Set> c, ServletContext ctx) throws ServletException; }
接口实现类通过java SPI机制将实现类声明,例如spring-web.jar声明如下
SpringServletContainerInitializer 声明如下,即表示SpringServletContainerInitializer类中只处理实现了WebApplicationInitializer接口的类。在Tomcat启动该Context时会查找应用下所有实现了WebApplicationInitializer的类,将类列表作为参数传入调用SpringServletContainerInitializer.onStartup方法配置容器。
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { // 实例化所有WebApplicationInitializer的实现类,挨个调用onStartup方法 } } public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
我们熟悉的SpringBoot项目war包部署初始化类如下,父类SpringBootServletInitializer 就是实现了WebApplicationInitializer
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class); } }
6、扫描当前应用和WEB-INF/lib下所有类,查找符合ServletContainerInitializer实现类配置的HandlesTypes的所有实现类。此外还有查找所有被WebServlet/WebFilter/WebListener注解的类,顾名思义也是个组件配置的路子,将这些类也作为配置添加到WebXml对象配置中
7、合并当前应用的WebXml对象和WEB-INF/lib下jar包的WebXml对象,把合并后的总配置信息挨个设置到StandardContext对象上
8、扫描WEB-INF/lib下jar包的meta-INF/resources/路径,将静态资源也添加到当前Context
9、将扫描到的ServletContainerInitializer和HandlesTypes实现类配置到Context中
protected void webConfig() { // web.xml的解析器,里边配置xml中各种元素的父子关系、解析规则等,和解析server.xml模式一致 WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(), context.getXmlValidation(), context.getXmlBlockExternal()); // ============================1================================ Setdefaults = new HashSet<>(); defaults.add(getDefaultWebXmlFragment(webXmlParser)); // ===========================2================================= WebXml webXml = createWebXml(); // 开始解析,把WEB-INF/web.xml中的信息解析到WebXml对象中 InputSource contextWebXml = getContextWebXmlSource(); if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) { ok = false; } ServletContext sContext = context.getServletContext(); // =============================3=================================== Map fragments = processJarsForWebFragments(webXml, webXmlParser); // =============================4==================================== Set orderedFragments = null; orderedFragments = WebXml.orderWebFragments(webXml, fragments, sContext); // =============================5==================================== if (ok) { // 查当前应用和WEB-INF/lib下 SPI配置的SCI接口实现类 processServletContainerInitializers(); } // =============================6================================ if (!webXml.ismetadataComplete() || typeInitializerMap.size() > 0) { // 1、查当前应用和WEB-INF/lib下 SCI实现类配置的处理类型实现类 // 2、查当前应用和WEB-INF/lib下 被WebServlet/WebFilter/WebListener注解的类,将其也当作配置添加到当前Context processClasses(webXml, orderedFragments); } // ================================7============================== if (!webXml.ismetadataComplete()) { // Step 6. Merge web-fragment.xml files into the main 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 if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { configureContext(webXml); } } else { webXml.merge(defaults); convertJsps(webXml); configureContext(webXml); } // ===============================8========================== // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set 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 } // ===============================9======================== if (ok) { for (Map.Entry >> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)