当前位置:   article > 正文

Tomcat8.5.43源码分析-(4)Tomcat启动过程探究 第二部分 Web应用加载_standardservice.connector.startfailed

standardservice.connector.startfailed

我们回到Tomcat启动过程探究 第一部分的结尾,回到StandardService的初始化方法initInternal()。

StandardService的重点方法有四个:

  • engine.init():初始化Servlet引擎,引擎只负责请求的处理不需要考虑请求连接,协议等等。一个Service存在一个对应的Engine。
  • executor.init():初始化线程池,该线程池可以在组件中共享。默认实现为StandardThreadExecutor。
  • mapperListener.init():初始化监听路由。MapperListener未重写生命周期中的init()方法,将在start()方法中详细讨论。
  • connector.init():初始化连接器,connector负责监听、读取请求,对请求进行制定协议的解析,匹配正确的处理容器,反馈响应。Server.xml中的默认的两个Connector如下,关于协议这块此处不再展开,以后若有机会给予补充:
  1. <Connector port="8080" protocol="HTTP/1.1"
  2. connectionTimeout="20000"
  3. redirectPort="8443" /><!HTTP 1.1协议>
  4. <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><!AJP 1.3协议>

在StandardServer.init()方法中,可以看到Server和Service是一对多的关系。为此,干脆先整理各层级容器直接的关系图:

接下来,我们关注Engine(StandardEngine)的初始化时会做些什么。

首先在StandardEngine构造方法里,我发现了有意思的东西:

  1. /**
  2. * Create a new StandardEngine component with the default basic Valve.
  3. */
  4. public StandardEngine() {
  5. super();
  6. pipeline.setBasic(new StandardEngineValve());
  7. /* Set the jmvRoute using the system property jvmRoute */
  8. try {
  9. setJvmRoute(System.getProperty("jvmRoute"));
  10. } catch(Exception ex) {
  11. log.warn(sm.getString("standardEngine.jvmRouteFail"));
  12. }
  13. // By default, the engine will hold the reloading thread
  14. backgroundProcessorDelay = 10;
  15. }

注意这一句:

pipeline.setBasic(new StandardEngineValve());

查询资料后,知道:

Pipeline(管道)采用了责任链的方式处理客户端的请求,每一个Valve(阀)表示责任链上的每一个处理器。

StandardPipeline重写了startInternal()方法,Pieline和Valve也将在start()方法中详细讨论。

至此,从Bootstrap.load()开始的长途跋涉就告一段落了。不过后面是流程更复杂的Bootstrap.start()。

经过了load的查看源码的经历,很容易就找到了start的关键路径为:Bootstrap.start()->Catalina.start()->StandardServer.startInternal()

  1. /**
  2. * Start nested components ({@link Service}s) and implement the requirements
  3. * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
  4. *
  5. * @exception LifecycleException if this component detects a fatal error
  6. * that prevents this component from being used
  7. */
  8. @Override
  9. protected void startInternal() throws LifecycleException {
  10. fireLifecycleEvent(CONFIGURE_START_EVENT, null);//使注册的监听器响应事件
  11. setState(LifecycleState.STARTING);//设置生命周期状态,使注册的监听器响应事件
  12. globalNamingResources.start();
  13. // Start our defined Services
  14. synchronized (servicesLock) {
  15. for (int i = 0; i < services.length; i++) {
  16. services[i].start();
  17. }
  18. }
  19. }

StandardServer此刻注册的监听器列表如下:

遍历启动StandardService.startInternal():

  1. /**
  2. * Start nested components ({@link Executor}s, {@link Connector}s and
  3. * {@link Container}s) and implement the requirements of
  4. * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
  5. *
  6. * @exception LifecycleException if this component detects a fatal error
  7. * that prevents this component from being used
  8. */
  9. @Override
  10. protected void startInternal() throws LifecycleException {
  11. if(log.isInfoEnabled())
  12. log.info(sm.getString("standardService.start.name", this.name));
  13. setState(LifecycleState.STARTING);
  14. // Start our defined Container first
  15. if (engine != null) {
  16. synchronized (engine) {
  17. engine.start();
  18. }
  19. }
  20. synchronized (executors) {
  21. for (Executor executor: executors) {
  22. executor.start();
  23. }
  24. }
  25. mapperListener.start();
  26. // Start our defined Connectors second
  27. synchronized (connectorsLock) {
  28. for (Connector connector: connectors) {
  29. try {
  30. // If it has already failed, don't try and start it
  31. if (connector.getState() != LifecycleState.FAILED) {
  32. connector.start();
  33. }
  34. } catch (Exception e) {
  35. log.error(sm.getString(
  36. "standardService.connector.startFailed",
  37. connector), e);
  38. }
  39. }
  40. }
  41. }

此刻StandardService的监听器列表为空。

在Catalina.createStartDigester()中,给StandardEngine通过addChild添加了一个child,即StandardHost。

在容器抽象类ContainerBase中startInternal()方法有这样一段:

  1. // Start our child containers, if any
  2. Container children[] = findChildren();
  3. List<Future<Void>> results = new ArrayList<>();
  4. for (int i = 0; i < children.length; i++) {
  5. results.add(startStopExecutor.submit(new StartChild(children[i])));
  6. }

它代表了当一个父容器start的时候,子容器会开启新线程。并执行子容器的start()方法。

接下来我们关注StandardHost的startInternal()方法:

  1. /**
  2. * Start this component and implement the requirements
  3. * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
  4. *
  5. * @exception LifecycleException if this component detects a fatal error
  6. * that prevents this component from being used
  7. */
  8. @Override
  9. protected synchronized void startInternal() throws LifecycleException {
  10. // Set error report valve
  11. String errorValve = getErrorReportValveClass();
  12. if ((errorValve != null) && (!errorValve.equals(""))) {
  13. try {
  14. boolean found = false;
  15. Valve[] valves = getPipeline().getValves();
  16. for (Valve valve : valves) {
  17. if (errorValve.equals(valve.getClass().getName())) {
  18. found = true;
  19. break;
  20. }
  21. }
  22. if(!found) {
  23. Valve valve =
  24. (Valve) Class.forName(errorValve).getConstructor().newInstance();
  25. getPipeline().addValve(valve);
  26. }
  27. } catch (Throwable t) {
  28. ExceptionUtils.handleThrowable(t);
  29. log.error(sm.getString(
  30. "standardHost.invalidErrorReportValveClass",
  31. errorValve), t);
  32. }
  33. }
  34. super.startInternal();
  35. }

这个方法只设置了一个关闭阀门,并没有做太多的事情。

但此刻StandardHost的监听器列表:

我们知道接下来HostConfig会接收到Lifecycle.START_EVENT的消息。

我们看下HostConfig在接收到消息后会做什么:

  1. /**
  2. * Process the START event for an associated Host.
  3. *
  4. * @param event The lifecycle event that has occurred
  5. */
  6. @Override
  7. public void lifecycleEvent(LifecycleEvent event) {
  8. // Identify the host we are associated with
  9. try {
  10. host = (Host) event.getLifecycle();
  11. if (host instanceof StandardHost) {
  12. setCopyXML(((StandardHost) host).isCopyXML());
  13. setDeployXML(((StandardHost) host).isDeployXML());
  14. setUnpackWARs(((StandardHost) host).isUnpackWARs());
  15. setContextClass(((StandardHost) host).getContextClass());
  16. }
  17. } catch (ClassCastException e) {
  18. log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
  19. return;
  20. }
  21. // Process the event that has occurred
  22. if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
  23. check();
  24. } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
  25. beforeStart();
  26. } else if (event.getType().equals(Lifecycle.START_EVENT)) {
  27. start();
  28. } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
  29. stop();
  30. }
  31. }

首先是标识符的设置,这边只说比较重要的标识符:

  • CopyXML:若为true,复制context.xml到$CATALINA_BASE/conf/<Engine名称>/<Host名称>目录下
  • DeployXML:若为true,Digester解析context.xml并创建Context对象。

之后就是HostConfig.start()方法了:

  1. /**
  2. * Process a "start" event for this Host.
  3. */
  4. public void start() {
  5. if (log.isDebugEnabled())
  6. log.debug(sm.getString("hostConfig.start"));
  7. try {
  8. ObjectName hostON = host.getObjectName();
  9. oname = new ObjectName
  10. (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
  11. Registry.getRegistry(null, null).registerComponent
  12. (this, oname, this.getClass().getName());
  13. } catch (Exception e) {
  14. log.warn(sm.getString("hostConfig.jmx.register", oname), e);
  15. }
  16. if (!host.getAppBaseFile().isDirectory()) {
  17. log.error(sm.getString("hostConfig.appBase", host.getName(),
  18. host.getAppBaseFile().getPath()));
  19. host.setDeployOnStartup(false);
  20. host.setAutoDeploy(false);
  21. }
  22. if (host.getDeployOnStartup())
  23. deployApps();
  24. }

这里比较有意思的一段是:

  1. Registry.getRegistry(null, null).registerComponent
  2. (this, oname, this.getClass().getName());

将当前监听器注册到MBean,由其管理。关于MBean,后面会另做一章介绍,这里只要知道是注册上去就可以了。

HostConfig.delpoyApps()方法:

  1. /**
  2. * Deploy applications for any directories or WAR files that are found
  3. * in our "application root" directory.
  4. */
  5. protected void deployApps() {
  6. File appBase = host.getAppBaseFile();
  7. File configBase = host.getConfigBaseFile();
  8. String[] filteredAppPaths = filterAppPaths(appBase.list());
  9. // Deploy XML descriptors from configBase
  10. deployDescriptors(configBase, configBase.list());
  11. // Deploy WARs
  12. deployWARs(appBase, filteredAppPaths);
  13. // Deploy expanded folders
  14. deployDirectories(appBase, filteredAppPaths);
  15. }
  • deployDescriptors:从Context描述文件部署context。例如:$CATALINA_BASE/conf/Catalina/localhost下创建app.xml文件。
  • deployWARs:从War包部署context。例如:把Web工程的打包的War包复制到Host指定的appBase下。
  • deployDirectories:从Web路径部署context。例如:把Web工程的所有资源文件复制到Host指定的appBase下。

这三个方法,其实是在不同的地方做同样一件事情,以deployDirectories为例:

  1. 创建新的线程,通过方法对应的方式,解析生成Context实例StandardContext。
  2. 给StandardContext设置监听器ContextConfig。deployDirectory(ContextName cn, File dir):
    1. Class<?> clazz = Class.forName(host.getConfigClass());
    2. LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();

    这里的关系实际上已在Catalina.createStartDigester()中配置。

  3. 将StandardContext设置为StandardHost的child,若Host已经启动,则会直接启动context:
host.addChild(context);

 接下来执行的是StandardContext.startInternal(),这个方法非常的冗长,这里只选取重要的部分:

  1. if (getLoader() == null) {
  2. WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
  3. webappLoader.setDelegate(getDelegate());
  4. setLoader(webappLoader);
  5. }
  1. Loader loader = getLoader();
  2. if (loader instanceof Lifecycle) {
  3. ((Lifecycle) loader).start();
  4. }

上两段为创建和启动Web应用类加载器WebappClassLoader。

  1. // Notify our interested LifecycleListeners
  2. fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

 CONFIGURE_START_EVENT事件的,通知。顺便整理发送该通知时,StandardContext的监听列表:

 ContextConfig接收到CONFIGURE_START_EVENT后,会执行configureStart()->webConfig()。

webConfig()是真正解析web.xml的方法,是和Web开发关系最紧密,也是我们最常见的配置xml。我们重点看一下这个方法,已经在重要的步骤上添加了注释:

  1. /**
  2. * Scan the web.xml files that apply to the web application and merge them
  3. * using the rules defined in the spec. For the global web.xml files,
  4. * where there is duplicate configuration, the most specific level wins. ie
  5. * an application's web.xml takes precedence over the host level or global
  6. * web.xml file.
  7. */
  8. protected void webConfig() {
  9. /*
  10. * Anything and everything can override the global and host defaults.
  11. * This is implemented in two parts
  12. * - Handle as a web fragment that gets added after everything else so
  13. * everything else takes priority
  14. * - Mark Servlets as overridable so SCI configuration can replace
  15. * configuration from the defaults
  16. */
  17. /*
  18. * The rules for annotation scanning are not as clear-cut as one might
  19. * think. Tomcat implements the following process:
  20. * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
  21. * which Servlet spec version is declared in web.xml. The EG has
  22. * confirmed this is the expected behaviour.
  23. * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
  24. * web.xml is marked as metadata-complete, JARs are still processed
  25. * for SCIs.
  26. * - If metadata-complete=true and an absolute ordering is specified,
  27. * JARs excluded from the ordering are also excluded from the SCI
  28. * processing.
  29. * - If an SCI has a @HandlesType annotation then all classes (except
  30. * those in JARs excluded from an absolute ordering) need to be
  31. * scanned to check if they match.
  32. */
  33. WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
  34. context.getXmlValidation(), context.getXmlBlockExternal());
  35. Set<WebXml> defaults = new HashSet<>();
  36. defaults.add(getDefaultWebXmlFragment(webXmlParser));
  37. WebXml webXml = createWebXml();
  38. // Parse context level web.xml
  39. InputSource contextWebXml = getContextWebXmlSource();//解析容器基本的web.xml
  40. if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
  41. ok = false;
  42. }
  43. ServletContext sContext = context.getServletContext();
  44. // Ordering is important here
  45. // Step 1. Identify all the JARs packaged with the application and those
  46. // provided by the container. If any of the application JARs have a
  47. // web-fragment.xml it will be parsed at this point. web-fragment.xml
  48. // files are ignored for container provided JARs.
  49. //扫描/WEB-INF/lib下的所有JAR包,如果包含/META-INF/web-fragment.xml这类片段webXml
  50. //则创建片段webXml
  51. Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);
  52. // Step 2. Order the fragments.
  53. //对片段webXml进行排序,排序结果影响了Filter的执行顺序
  54. Set<WebXml> orderedFragments = null;
  55. orderedFragments =
  56. WebXml.orderWebFragments(webXml, fragments, sContext);
  57. // Step 3. Look for ServletContainerInitializer implementations
  58. //查找并创建ServletContainerInitializer的实现类
  59. if (ok) {
  60. processServletContainerInitializers();
  61. }
  62. if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
  63. // Steps 4 & 5.
  64. //注解配置解析,将解析结果合并到web.xml
  65. processClasses(webXml, orderedFragments);
  66. }
  67. if (!webXml.isMetadataComplete()) {
  68. // Step 6. Merge web-fragment.xml files into the main web.xml
  69. // file.
  70. //合并片段webXml按照顺序到web.xml
  71. if (ok) {
  72. ok = webXml.merge(orderedFragments);
  73. }
  74. // Step 7. Apply global defaults
  75. // Have to merge defaults before JSP conversion since defaults
  76. // provide JSP servlet definition.
  77. //配置JSP Servlet
  78. webXml.merge(defaults);
  79. // Step 8. Convert explicitly mentioned jsps to servlets
  80. if (ok) {
  81. convertJsps(webXml);
  82. }
  83. // Step 9. Apply merged web.xml to Context
  84. //使用合并后的web.xml配置当前StandardContext,包括Servlet、Filter、Listener
  85. //并针对Servlet创建StandardWrapper,添加到StandardContext
  86. if (ok) {
  87. configureContext(webXml);
  88. }
  89. } else {
  90. webXml.merge(defaults);
  91. convertJsps(webXml);
  92. configureContext(webXml);
  93. }
  94. if (context.getLogEffectiveWebXml()) {
  95. log.info("web.xml:\n" + webXml.toXml());
  96. }
  97. // Always need to look for static resources
  98. // Step 10. Look for static resources packaged in JARs
  99. //添加META-INF/resources/下的静态资源到standardContext
  100. if (ok) {
  101. // Spec does not define an order.
  102. // Use ordered JARs followed by remaining JARs
  103. Set<WebXml> resourceJars = new LinkedHashSet<>();
  104. for (WebXml fragment : orderedFragments) {
  105. resourceJars.add(fragment);
  106. }
  107. for (WebXml fragment : fragments.values()) {
  108. if (!resourceJars.contains(fragment)) {
  109. resourceJars.add(fragment);
  110. }
  111. }
  112. processResourceJARs(resourceJars);
  113. // See also StandardContext.resourcesStart() for
  114. // WEB-INF/classes/META-INF/resources configuration
  115. }
  116. // Step 11. Apply the ServletContainerInitializer config to the
  117. // context
  118. if (ok) {
  119. for (Map.Entry<ServletContainerInitializer,
  120. Set<Class<?>>> entry :
  121. initializerClassMap.entrySet()) {
  122. if (entry.getValue().isEmpty()) {
  123. context.addServletContainerInitializer(
  124. entry.getKey(), null);
  125. } else {
  126. context.addServletContainerInitializer(
  127. entry.getKey(), entry.getValue());
  128. }
  129. }
  130. }
  131. }

至此,终于真正的完成了web.xml的合并、解析,并应用于StandardContext。

我们回到StandardContext.startInternal(),看看接下来还做了什么:

  1. // Start our child containers, if not already started
  2. for (Container child : findChildren()) {
  3. if (!child.getState().isAvailable()) {
  4. child.start();
  5. }
  6. }

启动StandardWrapper后,StandarWrapper.startInternal()只有一个作用,即由MBean发送Notification广播。

随后沿着addChild这条线,会继续调用StandarWrapper.load()->loadServlet()方法,开始初始化和创建Servlet实例。

至此, Web应用的加载过程就完毕了。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/741608
推荐阅读
相关标签
  

闽ICP备14008679号