SpringBoot自动配置原理

   在微服务概念兴起的今天,不少公司转型使用微服务做为架构。在技术选型上Spring Cloud 是很是好的选择,它提供了一站式的分布式系统解决方案,而Spring Cloud中的每一个组件都是基于Spring Boot构建的,Spring Boot提供J2EE一站式解决方案,具备如下优势:java

  • 快速建立独立运行的Spring项目以及与主流框架集成
  • 使用嵌入式的Servlet容器,应用无需打成WAR包
  • starters自动依赖与版本控制
  • 大量的自动配置,简化开发,也可修改默认值
  • 无需配置XML,无代码生成,开箱即用
  • 准生产环境的运行时应用监控
  • 与云计算的自然集成

1、Spring Boot的核心组件模块

模块 java文件数
spring-boot 551
spring-boot-actuator 423
spring-boot-autoconfigure 783
spring-boot-devtools 169
spring-boot-cli 180
spring-boot-tools 355

从上面的java文件数量大体能够看出,SpringBoot技术框架的核心组成部分:
spring-boot-autoconfigure
spring-boot
spring-boot-tools

咱们把SpringBoot源码导入IntelliJ IDEA,查看artifact的所有依赖关系。
IDEA有个Maven Projects窗口,通常在右侧可以找到,若是没有能够从菜单栏打开:View>Tool Windows>Maven Projects;
选择要分析的maven module(idea的module至关于eclipse的project),右击show dependencies,会出来该module的所有依赖关系图,很是清晰细致。
例如,spring-boot-starter-freemarker的依赖图分析以下:react

在spring-boot-build 的pom中,咱们能够看到:git

<modules>
    <module>spring-boot-dependencies</module>
    <module>spring-boot-parent</module>
    <module>spring-boot-tools</module>
    <module>spring-boot</module>
    <module>spring-boot-test</module>
    <module>spring-boot-autoconfigure</module>
    <module>spring-boot-test-autoconfigure</module>
    <module>spring-boot-actuator</module>
    <module>spring-boot-devtools</module>
    <module>spring-boot-docs</module>
    <module>spring-boot-starters</module>
    <module>spring-boot-actuator-docs</module>
    <module>spring-boot-cli</module>
</modules>

其中,在spring-boot-dependencies中,SpringBoot项目维护了一份庞大依赖。这些依赖的版本都是通过实践,测试经过,不会发生依赖冲突的。就这样一个事情,就大大减小了Spring开发过程当中,出现jar包冲突的几率。spring-boot-parent依赖spring-boot-dependencies。github

下面咱们简要介绍一下SpringBoot子modules。web

  • spring-boot:SpringBoot核心工程。
  • spring-boot-starters:是SpringBoot的启动服务工程。
  • spring-boot-autoconfigure:是SpringBoot实现自动配置的核心工程。
  • spring-boot-actuator:提供SpringBoot应用的外围支撑性功能。

  好比:redis

  1. Endpoints,SpringBoot应用状态监控管理
  2. HealthIndicator,SpringBoot应用健康指示表
  3. 提供metrics支持
  4. 提供远程shell支持
  • spring-boot-tools:提供了SpringBoot开发者的经常使用工具集。诸如,spring-boot-gradle-plugin,spring-boot-maven-plugin就是这个工程里面的。
  • spring-boot-cli:是Spring Boot命令行交互工具,可用于使用Spring进行快速原型搭建。你能够用它直接运行Groovy脚本。若是你不喜欢Maven或Gradle,Spring提供了CLI(Command Line Interface)来开发运行Spring应用程序。你可使用它来运行Groovy脚本,甚至编写自定义命令。

2、Spring Boot Starters

  Spring boot中的starter概念是很是重要的机制,可以抛弃之前繁杂的配置,统一集成进starter,应用者只须要引入starter jar包,spring boot就能自动扫描到要加载的信息。
  starter让咱们摆脱了各类依赖库的处理,须要配置各类信息的困扰。Spring Boot会自动经过classpath路径下的类发现须要的Bean,并织入bean。
  例如,若是你想使用Spring和用JPA访问数据库,你只要依赖 spring-boot-starter-data-jpa 便可。
  目前,github上spring-boot项目的最新的starter列表spring-boot/spring-boot-starters以下:spring

spring-boot-starter
spring-boot-starter-activemq
spring-boot-starter-actuator
spring-boot-starter-amqp
spring-boot-starter-aop
spring-boot-starter-artemis
spring-boot-starter-batch
spring-boot-starter-cache
spring-boot-starter-cloud-connectors
spring-boot-starter-data-cassandra
spring-boot-starter-data-couchbase
spring-boot-starter-data-elasticsearch
spring-boot-starter-data-jpa
spring-boot-starter-data-ldap
spring-boot-starter-data-mongodb
spring-boot-starter-data-mongodb-reactive
spring-boot-starter-data-neo4j
spring-boot-starter-data-redis
spring-boot-starter-data-rest
spring-boot-starter-data-solr
spring-boot-starter-freemarker
spring-boot-starter-groovy-templates
spring-boot-starter-hateoas
spring-boot-starter-integration
spring-boot-starter-jdbc
spring-boot-starter-jersey
spring-boot-starter-jetty
spring-boot-starter-jooq
spring-boot-starter-jta-atomikos
spring-boot-starter-jta-bitronix
spring-boot-starter-jta-narayana
spring-boot-starter-log4j2
spring-boot-starter-logging
spring-boot-starter-mail
spring-boot-starter-mobile
spring-boot-starter-mustache
spring-boot-starter-parent
spring-boot-starter-reactor-netty
spring-boot-starter-security
spring-boot-starter-social-facebook
spring-boot-starter-social-linkedin
spring-boot-starter-social-twitter
spring-boot-starter-test
spring-boot-starter-thymeleaf
spring-boot-starter-tomcat
spring-boot-starter-undertow
spring-boot-starter-validation
spring-boot-starter-web
spring-boot-starter-web-services
spring-boot-starter-webflux
spring-boot-starter-websocket

这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。它的项目依赖图以下:mongodb

 

  能够看出,这些starter只是配置,真正作自动化配置的代码的是在spring-boot-autoconfigure里面。同时spring-boot-autoconfigure依赖spring-boot工程,这个spring-boot工程是SpringBoot的核心。
  SpringBoot会基于你的classpath中的jar包,试图猜想和配置您可能须要的bean。
  例如,若是你的classpath中有tomcat-embedded.jar,你可能会想要一个TomcatEmbeddedServletContainerFactory Bean (SpringBoot经过获取EmbeddedServletContainerFactory来启动对应的web服务器。经常使用的两个实现类是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。
  其余的全部基于Spring Boot的starter都依赖这个spring-boot-starter。好比说spring-boot-starter-actuator的依赖树,以下图:shell

 

3、SpringBoot 自动配置原理

SpringBoot 自动配置主要经过 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @ConfigurationProperties 等几个注解来进行自动配置完成的。
@EnableAutoConfiguration 开启自动配置,主要做用就是调用 Spring-Core 包里的 loadFactoryNames(),将 autoconfig 包里的已经写好的自动配置加载进来。
@Conditional 条件注解,经过判断类路径下有没有相应配置的 jar 包来肯定是否加载和自动配置这个类。
@EnableConfigurationProperties的做用就是,给自动配置提供具体的配置参数,只须要写在application.properties 中,就能够经过映射写入配置类的 POJO 属性中。数据库

一、SpringBoot启动主程序类

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

每次咱们直接直接启动这个启动类,SpringBoot就启动成功了,而且帮咱们配置了好多自动配置类。其中最重要是 @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 : SpringBoot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类
  • @EnableAutoConfiguration: 开启自动配置类,SpringBoot的精华所在。
  • @ComponentScan包扫描

之前咱们须要配置的东西,Spring Boot帮咱们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;

  • @EnableAutoConfiguration注解

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

@EnableAutoConfiguration声明时,引用了两个重要的注解:

  • @AutoConfigurationPackage:自动配置包
  • @Import: 导入自动配置的组件
  • @AutoConfigurationPackage注解
     static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }

它实际上是注册了一个Bean的定义。
new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的同级以及子级的包组件。

以上图为例,DemoApplication是和demo包同级,可是demo2这个类是DemoApplication的父级,和example包同级
也就是说,DemoApplication启动加载的Bean中,并不会加载demo2,这也就是为何,咱们要把DemoApplication放在项目的最高级中。

  • @Import(AutoConfigurationImportSelector.class)注解

能够从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector继承了ImportSelector,ImportSelector有一个方法为:selectImports。

  @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); 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); return StringUtils.toStringArray(configurations); }

能够看到第九行,它实际上是去加载public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有不少自动配置的

类。以下:

三、如何自定义本身的Bean

咱们以RedisTemplate为例:

@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }

咱们每次在Spring中使用Redis,都会使用到RedisTemplate这个工具类,可是他默认给咱们返回的这个工具类,可能不是很符合咱们的要求。好比:咱们想要开启事务,或者想要改变它默认的序列化。

  • 这时候该如何去作呢?

根据前面的分析,只要咱们在容器中放入一个RedisTemplate Bean便可。

 1     @Bean("redisTemplate")  2     public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {  3         RedisTemplate<Object, Object> template = new RedisTemplate<>();  4  template.setConnectionFactory(redisConnectionFactory);  5         // 修改序列化为Jackson
 6         template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());  7         // 开启事务
 8         template.setEnableTransactionSupport(true);  9         return template; 10     }

咱们本身定义咱们的RedisTemplate模板,修改序列化,开启事务等操做。
咱们将咱们本身的Bean加入到IoC容器中之后,他就会默认的覆盖掉原来的RedisTemplate,达到定制的效果。
咱们在以Kafka为例:

假设咱们想要消费的对象不是字符串,而是一个对象呢?好比Person对象,或者其余Object类呢?

  1. 咱们首先去查找KafkaAutoConfiguration(xxxAutoConfiguration),看看是否有关于Serializer属性的配置
  2. 假设没有咱们就去KafkaProperties文件查找是否有Serializer的配置

而后直接在application.properties修改默认序列化就好,连Bean都不须要本身重写。
相似这种,可使用Spring提供的Json序列化,也能够自动使用第三方框架提供的序列化,好比Avro, Protobuff等

spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.consumer.key-deserializer=com.example.common.MyJson spring.kafka.consumer.value-deserializer=com.example.common.MyJson

 

  • HttpEncodingAutoConfiguration

在这么多xxxxAutoConfiguration中,咱们以HttpEncodingAutoConfiguration(Http自动编码)为例

 

 

 1 @Configuration    
 2 //表示这是一个配置类,之前编写的配置文件同样,也能够给容器中添加组件
 3 
 4 @EnableConfigurationProperties(HttpEncodingProperties.class)
 5 //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中
 6 
 7 @ConditionalOnWebApplication
 8 //Spring底层@Conditional注解(Spring注解版),根据不一样的条件,若是知足指定的条件,整个配置类里面的配置就会生效;这里是判断当前应用是不是web应用,若是是,当前配置类生效
 9 
10 @ConditionalOnClass(CharacterEncodingFilter.class)
11 //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
12 
13 @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
14 //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;若是不存在,判断也是成立的
15 //即便咱们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
16 
17 public class HttpEncodingAutoConfiguration {
18 
19     //他已经和SpringBoot的配置文件映射了
20     private final HttpEncodingProperties properties;
21 
22     //只有一个有参构造器的状况下,参数的值就会从容器中拿
23     public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
24         this.properties = properties;
25     }
26 
27     @Bean
28     //给容器中添加一个组件,这个组件的某些值须要从properties中获取
29     @ConditionalOnMissingBean(CharacterEncodingFilter.class)
30     //判断容器没有这个组件
31     public CharacterEncodingFilter characterEncodingFilter() {
32         CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
33         filter.setEncoding(this.properties.getCharset().name());
34         filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
35         filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
36         return filter;
37     }
38     }
39     .......
40 }

 

  经过上面的类的注解能够看到,经过使用@EnableConfigurationProperties,能够把配置文件中的属性与HttpEncodingProperties类绑定起来而且加入到IOC容器中,进入HttpEncodingProperties类,能够看到他是经过@ConfigurationProperties 注解把配置文件中的spring.http.encoding值与该类的属性绑定起来的。

@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties 

同时咱们能够注意到上面的类中使用了@ConditionalOnClass@ConditionalOnWebApplication注解,这两个都是@Conditional的派生注解,做用是必须是@Conditional指定的条件成立,才给容器中添加组件,配置里的内容才会生效

  • Conditional注解

下面咱们以@ConditionalOnClass为例,来分析一下他的源代码。

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

进入OnClassCondition类,查看他的类继承信息,能够看到他继承SpringBootCondition类,SpringBootCondition又实现了Condition接口

OnClassCondition又override了SpringBootCondition的getMatchOutcome方法,该方法会返回条件匹配结果。
getMatchOutcome方法源代码以下:

public ConditionOutcome getMatchOutcome(ConditionContext context,
         .....
List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
       ......
List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader);
    }

能够看到getMatchOutcome中主要调用了getMatches方法
进入getMatches方法

private List<String> getMatches(Collection<String> candidates, MatchType matchType,
            ClassLoader classLoader) {
        List<String> matches = new ArrayList<String>(candidates.size());
        for (String candidate : candidates) {
            if (matchType.matches(candidate, classLoader)) {
                matches.add(candidate);
            }
        }
        return matches;
    }

getMatches又调用了MatchType的matches方法。

 private enum MatchType {

        PRESENT {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }

        },

        MISSING {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }

        };

        private static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }
            try {
                forName(className, classLoader);
                return true;
            }
            catch (Throwable ex) {
                return false;
            }
        }

        private static Class<?> forName(String className, ClassLoader classLoader)
                throws ClassNotFoundException {
            if (classLoader != null) {
                return classLoader.loadClass(className);
            }
            return Class.forName(className);
        }

        public abstract boolean matches(String className, ClassLoader classLoader);

    }

进入MatchType类中,能够看到他有两个枚举类,进一步看枚举类中的matches的源代码能够发现最终是利用loadClass以及forName 方法,判断类路径下有没有这个指定的类。
下面列举出的是Spring Boot对@Conditional的扩展注解。

用好SpringBoot只要把握这几点:

  • SpringBoot启动会加载大量的自动配置类
  • 所要作的就是咱们须要的功能SpringBoot有没有帮咱们写好的自动配置类:
  • 若是有就再来看这个自动配置类中到底配置了哪些组件,Springboot自动配置类里边只要咱们要用的组件有,咱们就不须要再来配置了,可是若是说没有咱们所须要的组件,那么咱们就须要本身来写一个配置类来把咱们相应的组件配置起来。
  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,而这些属性咱们就能够在配置文件指定这些属性
相关文章
相关标签/搜索