交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
springmvc第1 章:启动
分享
未结
0
911
李延
LV6
2021-05-23
悬赏:20积分
# 环境 所有的代码都是基于springboot的2.4.0版本来做的解析,其中以springboot来启动项目 # 初始化过程 在解析springboot的时候我们看的spring会根据不同的环境创建不同上下文。当我们导入 spring-boot-web 的包后导入的就是web相关的上下文  我们看到在web项目中初始化的是AnnotationConfigServletWebServerApplicationContext对象。 我们查看它的onRefresh方法 ```java @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } ``` 我们看到在普通的上下文onRefresh基础上添加了一个createWebServer方法,而createWebServer方法就是在启动Tomcat。 # createWebServer ```java private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); //当没有WebServer时创建 if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); //获取ServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); //通过ServletWebServerFactory创建webServer this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); } ``` 我们看到其中主要有2步 - 调用getWebServerFactory获取ServletWebServerFactory - 通过ServletWebServerFactory获取WebServer,而web服务就是在这一步启动的 我们跟进这两部详细看一下 ## getWebServerFactory ```java protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } ``` 我们看到ServletWebServerFactory对象是从BeanFactory中获取的。我们在ServletWebServerFactory这个类中找到了对ServletWebServerFactory的@Bean方法:  同时在ServletWebServerFactoryAutoConfiguration类的@Import 注解看到其引入:  而ServletWebServerFactoryAutoConfiguration的引入是通过spring.factories的EnableAutoConfiguration导入的,其过程我们在springboot中已经说明  ## getWebServer 通过上面的分析,我们获取到了ServletWebServerFactory对象。下面我们解析getWebServer 首先看一下 参数 ```java private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } ``` 我们看到返回的ServletContextInitializer对象其实就是这个类的一个方法。ServletContextInitializer对象会在Tomcat启动的时候被调用,具体我们在使用的地方说明。继续返回getWebServer方法。 ```java @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //创建tomcat对象 Tomcat tomcat = new Tomcat(); //设置tomcat根路径 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); //创建connector对象 Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); //将connector添加到service中 tomcat.getService().addConnector(connector); //配置connector对象 customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); //配置Engine对象 configureEngine(tomcat.getEngine()); //添加自定义的Connector for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } //准备context prepareContext(tomcat.getHost(), initializers); //创建并返回WebServer return getTomcatWebServer(tomcat); } ``` 我们看到在这个方法中创建出了Tomcat对象,并对其做了一定的配置,我们下面将详细说明其中一些过程: - prepareContext 准备context - getTomcatWebServer 创建并返回WebServer ### prepareContext ```java protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldPatterns(context); WebappLoader loader = new WebappLoader(); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); } ``` 内容比较多,在不清楚Tomcat的情况下看比较费劲,所以我们只关注重点代码。 我们看到首先创建了TomcatEmbeddedContext对象,而这个对象是StandardContext子类。在之又将其添加到host里:host.addChild(context)。 最后再调用configureContext配置context。 ```java configureContext(context, initializersToUse); ``` 其中initializersToUse为我们最开始传经来那个方法封装的对象。  在这里我们看到initializers被分装为TomcatStarter对象,并添加到context中,而这个对象是ServletContainerInitializer子类。ServletContainerInitializer是javax.servlet接口。它的方法将在tomcat被启动时执行。我们再看一下TomcatStarter类。  TomcatStarter 的onStartup方法其实就是在循环调用我们刚才传进来的对象。 也就是说,我们最开始传进来的那个方法,在tomcat启动时将被调用。 目前我们分析完tomcat配置相关内容。但tomcat目前为止还没有系统,我们返回继续分析getTomcatWebServer方法 ### getTomcatWebServer ```java protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } ``` 我们看到new了TomcatWebServer对象,跟进构造方法 ```java public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; initialize(); } ``` 主要为initialize方法,继续查看  我们看到在这一步启动了tomcat。 ### 总结 到目前为止我们看到spring对于整改tomcat的启动过程,但我们发现,没有一个步骤是添加Servlet的,我们都知道tomcat接收到的请求肯定需要一个Servlet来处理的。 通过之前分析,我们知道Tomcat在启动的时候会调用selfInitialize方法。所有Servlet的添加一定和它有关。我们详细看一下这个方法 ## selfInitialize ```java private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } ``` 前面几步主要是对环境的一些设置,其中重点在getServletContextInitializerBeans方法中。我们看到首先获取到ServletContextInitializer对象。并且执行onStartup方法。 ```java protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory()); } ``` Collection<ServletContextInitializer>对象就是直接通过new出来的。 ### 构造函数 这个方法继承自AbstractCollection<ServletContextInitializer> 我们先看一下它的iterator方法 ```java @Override public Iterator<ServletContextInitializer> iterator() { return this.sortedList.iterator(); } ``` 也就是我在遍历的时候也就是在变量它的成员变量sortedList。所以我们这里的关注点将在sortedList这个变量上。 下面看一构造方法 ```java public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) { this.initializers = new LinkedMultiValueMap<>(); this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class); addServletContextInitializerBeans(beanFactory); addAdaptableBeans(beanFactory); List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream() .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE)) .collect(Collectors.toList()); this.sortedList = Collections.unmodifiableList(sortedInitializers); logMappings(this.initializers); } ``` 我们具体看一下addServletContextInitializerBeans方法 ```java private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) { for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, initializerType)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); } } } ``` 其中initializerType也就是ServletContextInitializer.class。也就是说这一步获取BeanFactory内所有的ServletContextInitializer类型对象。并调用addServletContextInitializerBean 而addServletContextInitializerBean方法就是将其添加到sortedList成员变量内。 我们debug看一下:  我们看到这里加载了一个DispatcherServletRegistrationBean对象。 而在DispatcherServletRegistrationBean的onStartup方法最好会执行到下面内容:  所以我们看到是在这一步添加了Servlet。 # 总结 我们看到首先spring通过自动装配的方式获取到ServletWebServerFactory。 对于ServletWebServerFactory,会根据当前不同的jar包导入不同的对象。 对于各种不同容器的初始化过程,都是交给ServletWebServerFactory自己去完成。而对于统一的内容如添加Servlet,又是交给selfInitialize统一执行。
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号