Spring MVC(2)------SpringBoot启动流程、启动Tomcat、整合MVC

本文深入解析SpringBoot的启动流程,从启动类开始,详细介绍了SpringApplication的创建、应用上下文的创建与刷新,以及如何判断Web环境。接着,文章探讨了SpringBoot如何启动内嵌的Tomcat,包括内嵌Tomcat的创建和启动步骤。最后,简要提及了SpringBoot整合MVC的过程,主要依赖自动配置实现。
摘要由CSDN通过智能技术生成

一、SpringBoot启动流程

1.1 启动类

@SpringBootApplication
public class Start {

    public static void main(String[] args) {
       //从run方法开始
        SpringApplication.run(Start.class, args);
        System.out.println("aaaa");
    }

}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        //执行该方法
        //创建一个SpringApplication实例
        //执行run之后返回ApplicationContext
        return (new SpringApplication(primarySources)).run(args);
}

1.2 创建SpringApplication

创建Spring应用

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //进行一些初始化设置
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //将启动类加入到主资源列表中
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        //推断是否为web环境
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //从spring.factories文件中获取所有的ApplicationContextInitializer类
        //用于创建ApplicationContext执行初始化操作
        //获取的原理是通过类加载器扫描classpath下所有jar包的META-INF
        //扫描所有META-INF文件夹下的spring.facotries文件
        //得到文件的内容,通过反射创建实例对象
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //从spring.factories文件中获取所有的ApplicationListener监听器
        //用于监听ApplicationContext创建、启动时发布的事件
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //推断主启动类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
1.2.1 deduceFromClasspath

推断是否为Web环境

static WebApplicationType deduceFromClasspath() {
        //如果不含有servlet、DispatcherServlet
        //但是含有reactive.DispatcherHandler,则项目类型为Reactive
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            //否则判断是否含有
            //new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
            //数组中的元素,如果含有则为servlet型的
            //都不含有则为普通项目,不是web项目
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                //isPresent方法会使用forName尝试加载这些类
                //如果返回false,则表示没有加载到
                //则表示没有web相关的类
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }
1.2.2 deduceMainApplicationClass

判断主启动类

private Class<?> deduceMainApplicationClass() {
        try {
        //通过创建一个异常,获取当前方法执行的调用栈信息
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;
            //从栈底开始遍历,找到main方法的栈帧,得到该栈帧所属的类名 
            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

1.3 run方法(创建应用上下文)

返回一个ConfigurableApplicationContext供主程序使用。

 public ConfigurableApplicationContext run(String... args) {
        //启动计时器,计算应用启动时间,打印在日志中
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //run方法返回的对象,应用上下文
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        //配置是否是缺少显示屏、鼠标或键盘的环境
        this.configureHeadlessProperty();
        
        //从spring.factories文件中获取所有的
        //SpringApplicationRunListener
        //用于监听SpringApplication运行过程中的各个启动事件
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //1.3.1 触发应用启动中的事件监听器的方法
        listeners.starting();

        Collection exceptionReporters;
        try {
            //得到启动参数,并封装成应用上下文可用的参数形式
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //1.3.2 配置环境,包括系统变量、classpath、jdk路径等等
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            //配置忽略的属性
            this.configureIgnoreBeanInfo(environment);
            //打印banner
            Banner printedBanner = this.printBanner(environment);
            //1.3.3 创建应用上下文
            context = this.createApplicationContext();
            //从spring.factories文件中获取异常处理相关的类
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //1.3.4 将上述资源全部放入应用上下文中
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //1.3.5 刷新上下文
            this.refreshContext(context);
            //1.3.6 刷新上下文之后执行一些方法
            this.afterRefresh(context, applicationArguments);
            //停止计时,打印启动日志,输出应用启动时间
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            //监听器执行应用启动完成的事件回调 
            listeners.started(context);
            //从应用上下文获取ApplicationRunner、CommandLineRunner
            //类型的Bean,并调用他们的run方法
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
           //监听运行时发生的事件,并进行处理
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
1.3.1 listener.starting()

触发监听器的应用启动中事件回调

    void starting() {
        Iterator var1 = this.listeners.iterator();

        while(var1.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();
            //执行所有SpringApplicationRunListener的starting方法
            //即触发所有监听器的应用启动中回调
            listener.starting();
        }

    }
1.3.2 prepareEnvironment

准备环境变量,生成Environment对象,并将一些属性绑定到SpingApplication中

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        //获取属性,包括resources目录下的application.proerties文件
        //和application.yml文件
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach((Environment)environment);
        //触发SpringApplicationRunListener的环境准备就绪回调
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        //将一些属性绑定到SpringApplication中,
        //例如通过注解@ConfigurationProperties设置的
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }
1.3.3 createApplicationContext

创建应用上下文

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                //根据前面判断的应用类型,创建不同的上下文对象
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
        //创建上下文实例对象
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
1.3.4 prepareContext

为应用上下文设置Environment、注册一些必要的组件

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置环境变量
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        //调用初始化组件,即调用ApplicationContextInitlizer类
        //1.2步 通过spring.factories获取
        this.applyInitializers(context);
        //触发监听器的应用上下文准备就绪事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        //获取所有资源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //将资源加载到应用上下文中
        this.load(context, sources.toArray(new Object[0]));
        //触发监听器的资源加载完毕回调
        listeners.contextLoaded(context);
    }
1.3.5 refreshContext

核心是refresh()方法,和spring启动过程类似,只是springboot重写了部分方法,例如onRefresh()用于创建Tomcat容器。

private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
            }
        }

    }
----------refresh方法-----------------------
//由于创建的ApplicationContext并未重写该方法,所以调用的是抽象父类
//AbstractApplicationContext的该方法,子类只需要重写部分需要的方法即可
//模板方法模式,抽象类定义骨架,子类按需实现血肉
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

具体每个过程可参考:

Spring核心流程

1.3.6 afterRefresh

抽象方法,由子类去具体实现,方便我们去扩展Springboot框架

 protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
    }

二、SpringBoot如何启动Tomcat

这里主要是两个地方:

  • 应用上下文的refresh()里的onRefresh()用于创建Tomcat
  • 应用上下文的refresh()里的finishRefresh()用于启动Tomcat

2.1 内嵌Tomcat的创建

这里由子类的onRefresh()覆盖抽象类的该方法,抽象类AbstractApplicationContext中的该方法为空,此处使用的普通Web项目对应的AnnotationConfigServletWebServerApplicationContext的父类,AbstractApplicationContext的子类ServletWebServerApplication中的onRefresh()方法。

protected void onRefresh() {
        super.onRefresh();

        try {
            //创建web容器,这里以默认的Tomcat为例
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

---------createWebServer()-----------------------

  private void createWebServer() {
        WebServer webServer = this.webServer;
        //获取servletContext
        ServletContext servletContext = this.getServletContext();
        //应用刚启动时,webServer为空,web上下文也为空
        //servletContext是给mvc用的
        //所以进入if逻辑,先得到一个webServer工厂
        //然后创建一个webServer实例,
        //并将一些初始化webServer的初始化类当作参数传入(lambda)
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = this.getWebServerFactory();
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }
2.1.1 getWebServerFactory
  protected ServletWebServerFactory getWebServerFactory() {
        //从bean工厂中获取webServer工厂
        //此时beanFactory中的类都已经完成了实例化(还未初始化),
        //且BeanPostProcessor也都已经加载到BeanFactory中了,
        //不会影响AOP等bean增强功能
        String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        //如果没获取到webServerFactory或者数量大于1,则抛出异常
        if (beanNames.length == 0) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
        } else if (beanNames.length > 1) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
        } else {
            return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
        }
    }
  1. webServerFactory是如何加载到BeanFactory中的呢?
    这是通过配置类来完成的
    配置类的名字为ServletWebServerFactoryConfiguration
  2. 该配置类又是如何被引入的呢?
    该配置类是通过SpringBoot的自动配置引入的,SpringBoot的自动配置jar包下的META-INF/spring.factories文件中定义了:
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
    这样在SpringBoot只要引入了web包,就会自动配置ServletWebServerFactoryAutoConfiguration,而ServletWebServerFactoryAutoConfiguration通过@Import注解导入了ServletWebServerFactoryConfiguration,同时还会导入一些用于配置webServer和webServerFactory的类
    详细自动装配流程:
    SpringBoot自动装配原理

在这里插入图片描述

2.2.2 getWebServer

getWebserver由具体的工厂去实现,此处看默认Tomcat工厂。

public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        //创建Tomcat提供的同名类,该类用于内嵌tomcat的启动
        Tomcat tomcat = new Tomcat();
        //Tomcat的基础目录
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        //配置连接器,protocol属性在创建webFactory时就通过前面提到的
        //配置类进行了设置
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        //将连接器和service关联,内嵌tomcat只有一个service,即当前应用
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        //为tomcat设置连接器,用于接收请求
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        //设置engine
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();
        //添加处理其他协议的connector 
        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }
        //准备包含当前web应用的StandadrContext,
        //并设置从应用上下文传递过来的初始化类  
        this.prepareContext(tomcat.getHost(), initializers);
        //将web容器封装为Springboot统一的WebServer类型
        //等待后面启动
        return this.getTomcatWebServer(tomcat);
    }

prepareContext()这一步很重要,Tomcat如何感知到当前web应用,并将请求传递给内嵌该tomcat的应用全都在一步设置。详细过程请参考:
Spring MVC(3)------Tomcat加载DispatcherServlet及MVC启动流程

2.2 Tomcat的启动

Tomcat启动是在Tomcat封装WebServer时进行,即调用getTomcatWebServer()时。在调用getTomcatWebServer()会创建一个WebServer对象,该对象的构造方法中会调用initilizer()方法,该方法调用了this.tomcat.start()启动tomcat。

注意finishRefresh()中也会有一个startWebServer()方法,但是此时Tomcat已经启动了,只会进行一些别的设置。该start()执行的是WebServer的start()方法,该方法会延迟加载一些其他的懒加载的Servlet的加载,即deferredLoadOnStartup(),该方法会启动一个后台线程去加载那些懒加载的Servlet (那些loadOnstartup属性不是正数的,是懒加载的)。

详细的Tomcat启动流程,参考:

  1. Tomcat核心流程

三、SpringBoot如何整合MVC

MVC框架以前是通过配置文件与Spring整合的,在Springboot通过自动配置替代了以前复杂的配置文件。

所以Spring MVC的启动也是通过自动配置来完成的。

在自动配置包下:
在这里插入图片描述

//DispatcherServletAutoConfiguration部分代码
@AutoConfigureOrder(-2147483648)
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration {
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
    //.....
    //注入DispatcherServlet
    @Bean(
            name = {"dispatcherServlet"}
        )
        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

详情参考:
Spring MVC(3)------Tomcat加载DispatcherServlet及MVC启动流程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值