SpringBoot启动流程源码解析

共 22651字,需浏览 46分钟

 ·

2021-05-14 12:27

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

  作者 |  Lucky帅小武

来源 |  urlify.cn/A7rErq

前言

SpringBoot本质上没有技术革新,而是在Spring框架的基础之上简化了系统的配置,核心功能是自动装配和starter组件

一、SpringBoot核心功能

1、独立运行程序(SpringBoot程序可以不依赖其他容器独立运行)

2、内嵌Servlet容器(内嵌Servlet容器,不依赖其他Web容器)

3、提供starter组件简化maven配置(只需要依赖一个starter组件就可以自动依赖相关jar包,简化maven配置)

4、自动装配配置(为大多数应用场景提供了默认配置,大幅度减少了应用配置)

二、SpringBoot常用注解

注解修饰用途案例
@ComponentScan自动扫描,修饰某个类则Spring会自动扫描当前类所在包路径下所以被@Component修饰的类,也可以通过basePackages自定义扫描包的根目录@ComponentScan(basePackages="com.test.lucky")
@Component声明当前类表示当前类为一个组件,Spring容器会很自动扫描被@Component修饰的类加载bean到Spring容器中@Component(value="beanName")
@Controller本质是一个Component,声明当前类表示当前类是一个控制器类,分发处理器会扫描被@Controller注解修饰的类的方法@Controller(value="testController")
@RequestMapping方法声明方法表示请求URL映射的方法@RequestMapping(value="/test")
@ResponseBody方法/类将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据@ResponseBody
@RestController等同于@Controller + @ResponseBody组合,表示当前类的所有方法都自动被@ResponseBody注解修饰@RestController(value="testController")
@Service本质是一个Component, 声明当前类是Service层的bean@Service(value="testService")
@Repository本质是一个Component, 声明当前类是Dao层的bean@Repository(value="testDao")
@Configuration本质是一个Component, 表面当前类是一个配置类,会生成一个代理对象,表示当前类是@Bean定义的源类@Configuration
@Bean方法相当于<bean>标签,被@Bean修饰的方法返回对象会被加载到Spring容器中@Bean(name="beanName")
@Import用于导入第三方包中的bean,导入的bean自动加载到Spring容器中@Import(value=Test.class)
@Value方法/属性将常量、配置中的值、其他bean注入到变量中@Value("${properteis.test}")

三、SpringBoot启动流程解析

SpringBoot的功能是基于Spring框架,所以启动时的核心步骤自然少不了Spring容器的初始化以及Spring容器的启动过程,另外SpringBoot还提供了自动装配配置功能。

1、SpringBootApplication.run()方法

SpringBoot程序启动类案例如下:

@SpringBootApplication
public class BootStrap {

    public static void main(String[] args){
        ApplicationContext applicationContext = SpringApplication.run(BootStrap.class);
    }
}

SpringBoot程序启动入口,代码比较简洁,除了被@SpringBootApplication注解修饰之外,main丰富直接调用SpringBootApplication类的静态方法run方法即可,最终会是调用了SpringBootApplication的实例方法run方法,核心逻辑如下:

public ConfigurableApplicationContext run(String... args) {
        ConfigurableApplicationContext context = null;
        /** 1.通过反射创建ApplicationContext对象 */
        context = createApplicationContext();
        /** 2.准备ApplicationContext上下文环境 */
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        /** 3.刷新ApplicationContext */
        refreshContext(context);
        /** 4.刷新ApplicationContext后置处理*/
        afterRefresh(context, applicationArguments);
        return context;
    }

可以看出SpringBoot程序启动的核心逻辑实际就两步,1、创建IOC容器ApplicationContext实例;2、启动IOC容器。

而SpringBoot的其他核心功能很显然就是通过@SpringBootApplication注解来实现,这个注解才是SpringBoot的核心实现

2、@SpringBootApplication注解

@SpringBootApplication注解定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

从定义可以看出@SpringBootApplication是一个组合注解,内部包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解

2.1、@SpringBootConfiguration

@SpringBootConfiguration注解定义如下:

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Configuration
5 public @interface SpringBootConfiguration

可以看出@SpringBootConfiguration本质上就是一个@Configuration注解,标注当前类是一个配置类

2.2、@ComponentScan

@ComponentScan注解定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan  

@ComponentScan注解表示自动扫描,会扫描当前类包路径下所有被@Component注解修饰的bean

2.3、@EnableAutoConfiguration

@EnableAutoConfiguration注解定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

可以看出@EnableAutoConfiguration注解也是一个组合注解,包含了@AutoConfigurationPackage和@Import注解,再看@AutoConfigurationPackage注解的定义:

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @Import(AutoConfigurationPackages.Registrar.class)
6 public @interface AutoConfigurationPackage

该注解内部包含了一个@Import注解,所以可以得出结论@EnableAutoConfiguration实际上就是由两个@Import注解组成,分别导入了AutoConfiguartionImportSelector和AutoConfigurationPackages.Registrar类的实例。

所以总结可以得出这两个类是实现SpringBoot自动装载配置的核心实现。

关于@Import注解的详细用法这里不多介绍,先简单介绍下,@Import注解有三种用法,分别如下:

1.显示引入需要加载的bean,如:@Import(value=TestService.class)

2.引入实现ImportSelector接口的类,如自定义MyImportSelector类实现ImportSelector并重写selectImports方法,返回需要导入的bean的数组,案例如下:

1 public class MyImportSelector implements ImportSelector {
2     @Override
3     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4         return new String[]{UserService.class.getName(), GoodsService.class.getName()};
5     }
6 }

3.引入实现ImportBeanDefinitionRegistrar接口的类,如自定义MyImportBeanDefinitionRegistrar并重写registBeanDefinitions方法,手动注册需要加载的bean,案例如下:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
                                         BeanNameGenerator importBeanNameGenerator) {
        /** 手动注册需要加载的bean*/
        registerBeanDefinitions(importingClassMetadata, registry);
    }
}

回过头再看@EnableAutoConfiguration注解,分别导入了ImportSelector接口的实现类AutoConfigurationImportSelector和ImportBeanDefinitionRegistrar的实现类AutoConfigurationPackages.Registrar,接下来依次分析。

2.4、AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了ImportSelector接口,所以会加载selectImports方法返回的String数组中的beanName,所以重点是selectImports方法,源码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        /** 加载元数据*/
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

核心步骤就两步,第一步是执行loadMetadata方法,源码如下:

protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        /** 加载指定路径下的元数据*/
        return loadMetadata(classLoader, PATH);
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
                    : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();
            /** 遍历Spring-autoconfigure-metadata.properties配置文件,读取数据封装为AutoConfigurationMetadata对象*/
            while (urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
            }
            return loadMetadata(properties);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
        }
    }

逻辑是从加载路径下找到spring-autoconfigure-metadata.properties配置文件,然后遍历读取配置文件内容加入到Properties对象中,并封装成AutoConfigurationMetadata对象

spring-autoconfigure-metadata.properties配置文件位于spring-boot-autoconfigure包中的META-INF目录下,暂时可以不关心作用是什么,目前可以得出的结论是会先把这个配置中的内容全部加载出来。

再看第二步getAutoConfigurationEntry方法的实现,源码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                               AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        /** 封装加载的配置元数据为AnnotationAttributes对象*/
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        /** 获取所有候选的配置信息集合 (IOC容器需要加载的bean)
         *  实际是加载META-INF/spring.factories配置文件中的bean
         * */
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        /** 去除重复的*/
        configurations = removeDuplicates(configurations);
        /** 去除排除的*/
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        /** 去除过滤的*/
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        /** 封装成AutoConfigurationEntry对象*/
        return new AutoConfigurationEntry(configurations, exclusions);
    }

目的是加载META-INF/spring.factories配置文件,得到所有需要加载的类,然后通过去除、排除的方式筛选需要加载的类

而spring.factories中有一个key为EnableAutoConfiguration,value是一个集合,包含了几乎常用的所有各种中间件的自动配置类,这些类都在spring-boot-autoconfigure包中,包括常用的有:

MongoDataAutoConfiguration、RedisAutoConfiguration、DataSourceAutoConfiguration、RabbitAutoConfiguration、ElasticsearchAutoConfiguration等等,每个类都封装了各自的自动配置类

tips:那么问题来了,SpringBoot默认提供了各种各有的配置类,如果我们的程序中并没有用到相关的组件,会不会讲这些无用的配置类也加载到容器中呢?

答案很显然是否定的,spring-autoconfigure-metadata.properties中定义了各种组件的ConditionalOnClass的key,值为对应的Class全路径,比如:

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate

org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate

ConditionalOnClass代表如果要加载对应的属性,必要要加载对应的类,比如程序中用到了RabbitMQ,那么肯定依赖了RabbitMQ对应的jar包,所以肯定会加载RabbitTemplate类,那么此时才会加载RabbitAutoConfiguration类,同理适用RedisAutoConfiguration类的前提是需要先加载RedisOperations类,只有先加载了对应的类才会加载对应的AutoConfiiguration类

总结

1、SpringBoot启动时会通过@EnableAutoConfiguration注解导入AutoConfigurationImportSelector对象,会从扫描包路径下META-INF/spring.factories中的配置,该配置文件中包含了各种组件的自动配置类对应的AutoConfiguration类,然后通过加载各种组件的AutoConfiguration类从而可以加载对应的自动配置类,并且通过ConditionalOnClass来过滤需要的自动配置类,去除掉程序中没有使用的自动配置类

2、@SpringBootApplication是个组合注解,分别是@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration三个组件,其中@SpringBootConfiguration和@ComponentScan两个组件保证可以扫描用户自定义的bean,@EnableAutoConfiguration用于扫描加载默认的第三方的bean

3、SpringBoot程序启动的流程实际就是IOC容器创建和启动的过程,然后通过扫描用户自定义的bean和第三方的bean完成bean的加载。

四、SpringBoot内置Tomcat源码解析

SpringBoot既然内置了web容器,那么就不会只内置Tomcat一个容器,还有Jetty、Undertow,所以从设计者的角度就会在这三个具体容器之上进行抽象,定义了一个WebServer接口,WebServer接口定义如下:

public interface WebServer {

    /** 启动web容器*/
    void start() throws WebServerException;

    /** 关闭web容器*/
    void stop() throws WebServerException;

    /** 获取web容器监听的端口 */
    int getPort();

}

 

而Tomcat、Jetty、Undertow等容器就会有各自的实现类实现了WebServer接口,分别是TomcatWebServer、JettyWebServer、UndertowWebServer,而SpringBoot采用了工厂模式来决定使用哪一个容器模式,所以定义了工厂类ServertWebServerFactory。在spring.factories配置文件中就定义了ServletWebServerFactoryAutoConfiguration,该类的定义如下:

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration

可以看出该类通过@Import注解分别导入了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow的bean,以EmbeddedTomcat为例,定义如下:

@Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory(
                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers()
                    .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers()
                    .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers()
                    .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }

该类内部通过@Bean注解定义了一个TomcatServletWebServerFactory对象,该对象就是TomcatWebServer的工厂类,TomcatServletWebServerFactory有一个getWebServer方法,定义如下:

@Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
1 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
2         return new TomcatWebServer(tomcat, getPort() >= 0);
3     }

源码不复杂,创建了一个Tomcat对象,然后初始化参数,再传给TomcatWebServer的构造函数创建TomcatWebServer对象,TomcatWebServer构造函数中会调用Tomcat的start方法启动Tomcat服务器

tips:那么问题来了,getWebServer方法是何时调用的呢?

在Spring容器创建之后会执行刷新操作,ApplicationContext的子类ServletWebServerApplicationContext实现了ApplicationContext的onRefresh方法,源码如下:

protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

首先是调用父类的onRefresh方法,然后执行createWebServer方法创建WebServer对象,逻辑就是从IOC容器中找到ServletWebServerFactory对象,执行getWebServer方法获取WebServer对象。

apache的Tomcat实现原理解析可参考:

五、如何实现自定义starter

1、新建项目spring-boot-mytest-starter(官方提供的starter命名为spring-boot-starter-xxx,自定义的通常命名为spring-boot-xxx-starter,用于和官方提供的区分开)






粉丝福利:Java从入门到入土学习路线图

👇👇👇

👆长按上方微信二维码 2 秒


感谢点赞支持下哈 

浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报