目录

Spring Boot 自动配置

Spring Boot Auto-configuration

spring boot相比spring之所以简化很多就是因为spring boot搭建一个项目的时候可以引入多个starter,这样就可以直接使用不需要过多的各种配置。spring官方提供了不少starter,而我们自己也可以自定义starter,为了能够区分,从命名上进行了规范。官方的starter名称为:spring-boot-starter-xxx,而自定义的为xxx-spring-boot-starter。

什么是 Spring Boot 自动装配

提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

在我看来,自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

SpringBoot 是如何实现自动装配

@SpringBootApplication

等同于下面三个注解:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter

其中@EnableAutoConfiguration是关键(启用自动配置),内部实际上就去加载META-INF/spring.factories文件的信息,然后筛选出以EnableAutoConfiguration为key的数据,加载到IOC容器中,实现自动配置功能!

@EnableAutoConfiguration注释,此注释自动载入应用程序所需的所有Bean,这依赖于Spring Boot在类路径中的查找。

Auto-configuration Classes

Test Auto-configuration Annotations

@EnableAutoConfiguration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { 
   	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};    
    String[] excludeName() default {};
}

显然通过@Import 注解导入 spring-boot-autoconfigure-*.jar 中的 AutoConfigurationImportSelector 类。

1
2
3
4
5
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

使用了Spring Core包的 SpringFactoriesLoader 类的 loadFactoryNames() 方法。 查询spring-boot-autoconfigure-*.jar META-INF/spring.factories文件中 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.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.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
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.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
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.ldap.LdapDataAutoConfiguration,\

以上为Spring Boot中所有的自动配置相关类;在启动过程中会解析对应类配置信息

解析 MongoAutoConfiguration

以Mongo为例,则会去解析MongoAutoConfiguration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@ConditionalOnClass(Mongo.class)
@EnableConfigurationProperties(MongoProperties.class)
public class MongoAutoConfiguration {
 
    @Autowired
    private MongoProperties properties;
 
    private Mongo mongo;
 
    @PreDestroy
    public void close() throws UnknownHostException {
        if (this.mongo != null) {
            this.mongo.close();
        }
    }
 
    @Bean
    @ConditionalOnMissingBean
    public Mongo mongo() throws UnknownHostException {
        this.mongo = this.properties.createMongoClient();
        return this.mongo;
    }
}

@ConditionOnClass激活一个配置,在类路径中只能存在一到几个这样的类。 2)@EnableConfigurationProperties自动映射一个POJO到Spring Boot配置文件(默认是application.properties文件)的属性集。 3)@ConditionalOnMissingBean启用一个Bean定义,但必须是这个Bean之前未定义过才有效。 还可以使用@ AutoConfigureBefore注释、@AutoConfigureAfter注释来定义这些配置类的载入顺序。

属性映射

下面看MongoProperties类,它是一个Spring Boot属性映射的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {
 
    private String host;
    private int port = DBPort.PORT;
    private String uri = "mongodb://localhost/test";
    private String database;
 
    // ... getters/ setters omitted
}

@ConfigurationProperties注释将POJO关联到指定前缀的每一个属性。例如,spring.data.mongodb.port属性将映射到这个类的端口属性。

@Conditional 注释

Spring Boot的强大之处在于使用了Spring 4框架的新特性:@Conditional注释,此注释使得只有在特定条件满足时才启用一些配置。 在Spring Boot的org.springframework.boot.autoconfigure.condition包中说明了使用@Conditional注释能给我们带来什么,下面对这些注释做一个概述:

Spring Boot 提供的条件注解

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

以@ConditionalOnExpression注释为例,它允许在Spring的EL表达式中写一个条件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Conditional(OnExpressionCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnExpression {
 
    /**
     * The SpEL expression to evaluate. Expression should return {@code true} if the
     * condition passes or {@code false} if it fails.
     */
    String value() default "true";
}

在这个类中,我们想利用@Conditional注释,条件在OnExpressionCondition类中定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

public class OnExpressionCondition extends SpringBootCondition {
 
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // ...
        // we first get a handle on the EL context via the ConditionContext
 
        boolean result = (Boolean) resolver.evaluate(expression, expressionContext);
 
        // ...
        // here we create a message the user will see when debugging
 
        return new ConditionOutcome(result, message.toString());
    }
}

在最后,@Conditional通过简单的布尔表达式(即ConditionOutcome方法)来决定。

应用程序上下文初始化器

spring.factories 还提供了第二种可能性,即定义应用程序的初始化。这使得我们可以在应用程序载入前操纵Spring的应用程序上下文ApplicationContext。 特别是,可以在上下文创建监听器,使用ConfigurableApplicationContext类的addApplicationListener()方法。 AutoConfigurationReportLoggingInitializer监听到系统事件时,比如上下文刷新或应用程序启动故障之类的事件,Spring Boot可以执行一些工作。这有助于我们以调试模式启动应用程序时创建自动配置的报告。 要以调试模式启动应用程序,可以使用-Ddebug标识,或者在application.properties文件这添加属性debug= true。

调试Spring Boot自动配置

Spring Boot的 官方文档 有助于理解自动配置期间发生了什么。当以调试模式运行时,Spring Boot会产生一个报告,如下:

Positive matches:
-----------------
 
   MessageSourceAutoConfiguration
      - @ConditionalOnMissingBean (types: org.springframework.context.MessageSource; SearchStrategy: all) found no beans (OnBeanCondition)
 
   JmxAutoConfiguration
      - @ConditionalOnClass classes found: org.springframework.jmx.export.MBeanExporter (OnClassCondition)
      - SpEL expression on org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration: ${spring.jmx.enabled:true} (OnExpressionCondition)
      - @ConditionalOnMissingBean (types: org.springframework.jmx.export.MBeanExporter; SearchStrategy: all) found no beans (OnBeanCondition)
 
   DispatcherServletAutoConfiguration
      - found web application StandardServletEnvironment (OnWebApplicationCondition)
      - @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)
 
 
Negative matches:
-----------------
 
   DataSourceAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType (OnClassCondition)
 
   DataSourceTransactionManagerAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.jdbc.core.JdbcTemplate,org.springframework.transaction.PlatformTransactionManager (OnClassCondition)
 
   MongoAutoConfiguration
      - required @ConditionalOnClass classes not found: com.mongodb.Mongo (OnClassCondition)
 
   FallbackWebSecurityAutoConfiguration
      - SpEL expression on org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration: !${security.basic.enabled:true} (OnExpressionCondition)
 
   SecurityAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.security.authentication.AuthenticationManager (OnClassCondition)
 
   EmbeddedServletContainerAutoConfiguration.EmbeddedJetty
      - required @ConditionalOnClass classes not found: org.eclipse.jetty.server.Server,org.eclipse.jetty.util.Loader (OnClassCondition)
 
   WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#localeResolver
      - @ConditionalOnMissingBean (types: org.springframework.web.servlet.LocaleResolver; SearchStrategy: all) found no beans (OnBeanCondition)
      - SpEL expression: '${spring.mvc.locale:}' != '' (OnExpressionCondition)
 
   WebSocketAutoConfiguration
      - required @ConditionalOnClass classes not found: org.springframework.web.socket.WebSocketHandler,org.apache.tomcat.websocket.server.WsSci (OnClassCondition)

对于每个自动配置,可以看到它启动或失败的原因。

自定义 EnableAutoConfiguration

1
2
3
4
5
6
7
8
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(MyEnableAutoConfigurationImport.class)
    public @interface MyEnableAutoConfiguration {
    }
  1. 自定义EnableAutoConfigurationImport,注入了ClassLoader,并调用SpringFactoriesLoader.loadFactoryNames()方法,导出Configuration的类。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyEnableAutoConfigurationImport implements DeferredImportSelector, BeanClassLoaderAware {
        private ClassLoader classLoader;
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            List<String> beanNames = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
            return beanNames.toArray(new String[beanNames.size()]);
        }
    }

入口类,这里使用了MyEnableAutoConfiguration注解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    @Configuration
    @MyEnableAutoConfiguration
    public class CustomizeEnableAutoConfigure {
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(CustomizeEnableAutoConfigure.class);
            application.run(args);
        }
        @Controller
        public static class MyController {
            @RequestMapping
            @ResponseBody
            public Map index() {
                Map<String, String> map = new HashMap<String, String>();
                map.put("hello", "world");
                return map;
            }
        }
    }

如何实现一个 Starter

总结

@EnableAutoConfiguration 作用 从classpath中搜索所有META-INF/spring.factories配置文件然后,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key对应的配置项加载到spring容器 只有spring.boot.enableautoconfiguration为true(默认为true)的时候,才启用自动配置

@EnableAutoConfiguration还可以进行排除,排除方式有2中,一是根据class来排除(exclude),二是根据class name(excludeName)来排除 其内部实现的关键点有 1)ImportSelector 该接口的方法的返回值都会被纳入到spring容器管理中

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

2)SpringFactoriesLoader 该类可以从classpath中搜索所有META-INF/spring.factories配置文件,并读取配置