前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springboot 随笔(1) -- 自动引入配置与启动机制

Springboot 随笔(1) -- 自动引入配置与启动机制

作者头像
alexqdjay
发布2018-05-11 13:58:52
9690
发布2018-05-11 13:58:52
举报
文章被收录于专栏:alexqdjayalexqdjay

为什么用SpringBoot?

同上题记。总结:快速开始,方便搭建,开发web时并不需要Tomcat或者Jetty,甚至连插件都不用(因为自带Tomcat或自配置成Jetty)。

肯定有缺点吧?

一个框架除了知道他的优点,肯定要知道他的缺点。

SpringBoot 缺点如下(暂时发现):

  1. 配置逻辑隐藏太深,所以如果有很多自定义的需要翻源码看,如配置多个Servlet
  2. 配置Bean化,替代XML。Bean和XML谁更优?一半一半,所以建议Bean和XML混用(SpringBoot提供这种方式),有时XML定义更加清晰。
  3. 文档略少,有时需要翻源码才知道用法。
  4. 默认加载的AutoConfig有点多,所以影响启动速度。网上有优化方式,基本思想就是去除@SpringBootApplication,使用自己编写@Import,但是这样SpringBoot的便捷性就没有了。

SpringBoot 的运行机制

1. 引入配置

使用 @Import  引入配置Bean,有三种方式:

1) 直接引入 configuration.class

代码语言:javascript
复制
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

DelegatingWebMvcConfiguration 就是配置的Configuration类

代码语言:javascript
复制
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    //...
}

2) 引入 ImportSelector 接口的实现

代码语言:javascript
复制
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

只要实现该接口,返回需要加载的 Configuration 类名字符串即可,见例子:

代码语言:javascript
复制
static class CacheConfigurationImportSelector implements ImportSelector {
    CacheConfigurationImportSelector() {
    }

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        CacheType[] types = CacheType.values();
        String[] imports = new String[types.length];

        for(int i = 0; i < types.length; ++i) {
            imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
        }

        return imports;
    }
}

3) 引入 ImportBeanDefinitionRegistrar  接口的实现类

代码语言:javascript
复制
public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}

很好理解,手动将需要注入的bean的definition放入BeanDefinitionRegistry 

2. 以 DataSource 自动配置为例子

所有的开始都是源于 DataSourceAutoConfiguration 这个类,就是说如果你想自动化生成 DataSrouce  你只要在你的配置类引入该类:

代码语言:javascript
复制
@Configuration
@Import({DataSourceAutoConfiguration.class})
public class Application {
    // ...
}

DataSourceAutoConfiguration 中引入了其他配置类,并且使用 @Bean 来生成需要的组件:

代码语言:javascript
复制
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class})
public class DataSourceAutoConfiguration {
}

引入了 Registrar 和 DataSourcePoolMetadataProvidersCofniguration , 这里以 Registrar 为例介绍:

代码语言:javascript
复制
static class Registrar implements ImportBeanDefinitionRegistrar {
    private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";

    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if(!registry.containsBeanDefinition("dataSourceInitializerPostProcessor")) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
            beanDefinition.setRole(2);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition("dataSourceInitializerPostProcessor", beanDefinition);
        }

    }
}

这是符合上面的第3种方式,目的就是注册一个PostProcessor 来处理注册进来的 DataSource, 凡是有DataSource实例,就实例化 DataSourceInitializer  (用于预跑一些初始化的SQL脚步)。

重点是下面

代码语言:javascript
复制
@Configuration
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Tomcat.class, Hikari.class, Dbcp.class, Dbcp2.class})
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

引入三个配置类,Tomcat、Hikari、Dbcp、Dbcp2,处理逻辑基本一致,都是判断是否有对于的类和配置,以Dbcp为例:

代码语言:javascript
复制
@ConditionalOnClass({org.apache.commons.dbcp.BasicDataSource.class})
@ConditionalOnProperty(
    name = {"spring.datasource.type"},
    havingValue = "org.apache.commons.dbcp.BasicDataSource",
    matchIfMissing = true
)
static class Dbcp extends DataSourceConfiguration {
    Dbcp() {
    }

    @Bean
    @ConfigurationProperties("spring.datasource.dbcp")
    public org.apache.commons.dbcp.BasicDataSource dataSource(DataSourceProperties properties) {
        org.apache.commons.dbcp.BasicDataSource dataSource = (org.apache.commons.dbcp.BasicDataSource)this.createDataSource(properties, org.apache.commons.dbcp.BasicDataSource.class);
        DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
        String validationQuery = databaseDriver.getValidationQuery();
        if(validationQuery != null) {
            dataSource.setTestOnBorrow(true);
            dataSource.setValidationQuery(validationQuery);
        }

        return dataSource;
    }
}

最终 @Bean 生成 dataSource 实例。

3. 自动化引入配置类

这个秘密就隐藏在 @SpringBootApplication

代码语言:javascript
复制
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
)}
)
public @interface SpringBootApplication

从源码中可见,@SpringBootApplication = @EnableAutoConfiguration + @SpringBootConfiguration + @ComponentScan

很明显,EnableAutoConfiguration 是自动化配置的关键

代码语言:javascript
复制
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration
代码语言:javascript
复制
EnableAutoConfigurationImportSelector implements DeferredImportSelector

符合上面第3中引入方式,EnableAutoConfigurationImportSelector 的主要功能就是将spring.factories 配置的config获取,返回出来

spring.factories 中都是些什么:

代码语言:javascript
复制
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

前面分析的 DataSourceAutoConfiguration  就在上面,由此可见,这些AutoConfig都被自动引入。

这么多配置可能由于的就一半都不到,所以如果优化启动速度,那么就 手动@Import 即可,不过有点麻烦。

SpringBoot 的启动

代码语言:javascript
复制
    public static void main(String[] args) throws LifecycleException, InterruptedException {
        SpringApplication.run(Application.class, args);
    }

一切的开始都是从这段代码,所以 SpringApplication.run 是分析入口,最终追踪到源码:

代码语言:javascript
复制
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  return (new SpringApplication(sources)).run(args);
}

实例化,然后run。

实例化中,还调用判断了是否Web环境,原理是判断是否存在两个class:

代码语言:javascript
复制
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};    
    
    private boolean deduceWebEnvironment() {
        String[] var1 = WEB_ENVIRONMENT_CLASSES;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String className = var1[var3];
            if(!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return false;
            }
        }

        return true;
    }

run的源码:

代码语言:javascript
复制
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.started();

    try {
        DefaultApplicationArguments ex = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, ex); // env配置处理, application.properties等
        Banner printedBanner = this.printBanner(environment); // 打印 Banner
        context = this.createApplicationContext(); // 关键(1)
        this.prepareContext(context, environment, listeners, ex, printedBanner);// 一些赋值,及调用initer
        this.refreshContext(context); // refresh ctx, 视为启动ctx 关键(2)
        this.afterRefresh(context, ex);
        listeners.finished(context, (Throwable)null);
        stopWatch.stop();
        if(this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        return context;
    } catch (Throwable var8) {
        this.handleRunFailure(context, listeners, var8);
        throw new IllegalStateException(var8);
    }
}

分析关键(1)的源码

代码语言:javascript
复制
    protected ConfigurableApplicationContext createApplicationContext() {
        Class contextClass = this.applicationContextClass;
        if(contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment?"org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext":"org.springframework.context.annotation.AnnotationConfigApplicationContext");
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
    }

很简单,判断是否 web环境,如果是就是要使用 AnnotationConfigEmbeddedWebApplicationContext  这个Ctx类实例化Context。

关键(2)的源码

代码语言:javascript
复制
    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext)applicationContext).refresh();
    }

那么 AnnotationConfigEmbeddedWebApplicationContext  的 onRefresh 和普通的 context的区别在于:

代码语言:javascript
复制
    protected void onRefresh() {
        super.onRefresh();

        try {
            this.createEmbeddedServletContainer();  // 创建自带的web容器
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start embedded container", var2);
        }
    }

    private void createEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        ServletContext localServletContext = this.getServletContext();
        if(localContainer == null && localServletContext == null) {
            EmbeddedServletContainerFactory ex = this.getEmbeddedServletContainerFactory(); // 获取ContainerFactory
            this.embeddedServletContainer = ex.getEmbeddedServletContainer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if(localServletContext != null) {
            //...
        }

        this.initPropertySources();
    }

那么 ContainerFactory 肯定是自动引入的配置咯!

EmbeddedServletContainerAutoConfiguration  中:

代码语言:javascript
复制
@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class})
@ConditionalOnMissingBean(
    value = {EmbeddedServletContainerFactory.class},
    search = SearchStrategy.CURRENT
)
public static class EmbeddedTomcat {
    public EmbeddedTomcat() {
    }

    @Bean
    public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory();
    }
}

当然,该类中还有其他web容器的引入配置,形同上面 Tomcat的,逻辑也类似就是判断是否存在一些关键类:

代码语言:javascript
复制
@ConditionalOnClass({Servlet.class, Tomcat.class})

其他的,refresh 过程跟一般的spring context一致,不作分析。

总结

SpringBoot 远不止如此, 且学且记录吧!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么用SpringBoot?
  • 肯定有缺点吧?
  • SpringBoot 的运行机制
    • 1. 引入配置
      • 2. 以 DataSource 自动配置为例子
        • 3. 自动化引入配置类
        • SpringBoot 的启动
        • 总结
        相关产品与服务
        Elasticsearch Service
        腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档