Springboot 随笔(1) -- 自动引入配置与启动机制
Springboot 随笔(1) -- 自动引入配置与启动机制
alexqdjay 发表于1年前
Springboot 随笔(1) -- 自动引入配置与启动机制
  • 发表于 1年前
  • 阅读 427
  • 收藏 4
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

摘要: 依赖引入和配置的现成化使得Springboot在开发简单小项目时异常方便,一个项目从0到1可能只需要5分钟,而以前对于一个熟练的Spring使用者可能都需要1个小时以上,不熟练的还会遇到各种跑不起来

为什么用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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

DelegatingWebMvcConfiguration 就是配置的Configuration类

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    //...
}

2) 引入 ImportSelector 接口的实现

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

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

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  接口的实现类

public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}

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

2. 以 DataSource 自动配置为例子

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

@Configuration
@Import({DataSourceAutoConfiguration.class})
public class Application {
    // ...
}

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

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class})
public class DataSourceAutoConfiguration {
}

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

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脚步)。

重点是下面

@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为例:

@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

@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 是自动化配置的关键

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

EnableAutoConfigurationImportSelector implements DeferredImportSelector

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

spring.factories 中都是些什么:

# 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 的启动

    public static void main(String[] args) throws LifecycleException, InterruptedException {
        SpringApplication.run(Application.class, args);
    }

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

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  return (new SpringApplication(sources)).run(args);
}

实例化,然后run。

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

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的源码:

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)的源码

    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)的源码

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext)applicationContext).refresh();
    }

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

    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  中:

@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的,逻辑也类似就是判断是否存在一些关键类:

@ConditionalOnClass({Servlet.class, Tomcat.class})

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

总结

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

 

标签: java spring springboot
共有 人打赏支持
粉丝 32
博文 23
码字总数 24798
×
alexqdjay
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: