Springboot 隨筆(1) -- 自動引入配置與啓動機制

爲何用SpringBoot?

同上題記。總結:快速開始,方便搭建,開發web時並不須要Tomcat或者Jetty,甚至連插件都不用(由於自帶Tomcat或自配置成Jetty)。java

確定有缺點吧?

一個框架除了知道他的優勢,確定要知道他的缺點。react

SpringBoot 缺點以下(暫時發現):web

  1. 配置邏輯隱藏太深,因此若是有不少自定義的須要翻源碼看,如配置多個Servlet
  2. 配置Bean化,替代XML。Bean和XML誰更優?一半一半,因此建議Bean和XML混用(SpringBoot提供這種方式),有時XML定義更加清晰。
  3. 文檔略少,有時須要翻源碼才知道用法。
  4. 默認加載的AutoConfig有點多,因此影響啓動速度。網上有優化方式,基本思想就是去除@SpringBootApplication,使用本身編寫@Import,可是這樣SpringBoot的便捷性就沒有了。

SpringBoot 的運行機制

1. 引入配置

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

1) 直接引入 configuration.classspring

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

DelegatingWebMvcConfiguration 就是配置的Configuration類apache

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

2) 引入 ImportSelector 接口的實現tomcat

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

只要實現該接口,返回須要加載的 Configuration 類名字符串便可,見例子:websocket

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  接口的實現類session

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

很好理解,手動將須要注入的bean的definition放入BeanDefinitionRegistry app

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 遠不止如此, 且學且記錄吧!

相關文章
相關標籤/搜索