SpringBoot之@EnableAutoConfiguration原理及自定义扩展

时间:2024-10-30 19:53:20

spring Boot是一个偏执的开源框架,它可用于创建可执行的Spring应用程序,采用了习惯优于配置的方法。 
此框架的神奇之处在于@EnableAutoConfiguration注释,此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。

一、@Enable*注释

@Enable*注释并不是新发明的注释,早在Spring 3框架就引入了这些注释,用这些注释替代XML配置文件。 
很多Spring开发者都知道@EnableTransactionManagement注释,它能够声明事务管理;@EnableWebMvc注释,它能启用Spring MVC;以及@EnableScheduling注释,它可以初始化一个调度器。 
这些注释事实上都是简单的配置,通过@Import注释导入。

@Target()
@Retention()
@Documented
@Import({ ,
         })
public @interface EnableAutoConfiguration {

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     */
    Class<?>[] exclude() default {};

}

EnableAutoConfigurationImportSelector类使用了Spring Core包的SpringFactoriesLoader类的loadFactoryNamesof()方法。 
SpringFactoriesLoader会查询META-INF/文件中包含的JAR文件。 
当找到文件后,SpringFactoriesLoader将查询配置文件命名的属性。在例子中,是。 
让我们来看看spring-boot-autoconfigure JAR文件,它真的包含了一个文件,内容如下:

# Initializers
=\


# Auto Configure
=\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\
,\

在这个文件中,可以看到一系列Spring Boot自动配置的列表。下面我们来看这些配置的细节,以MongoAutoConfiguration为例:

@Configuration
@ConditionalOnClass()
@EnableConfigurationProperties()
public class MongoAutoConfiguration {

    @Autowired
    private MongoProperties properties;

    private Mongo mongo;

    @PreDestroy
    public void close() throws UnknownHostException {
        if ( != null) {
            ();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Mongo mongo() throws UnknownHostException {
         = ();
        return ;
    }

}

这个类进行了简单的Spring配置,声明了MongoDB所需典型Bean。 
这个类跟其它很多类一样,重度依赖于Spring Boot注释: 
1)@ConditionOnClass激活一个配置,在类路径中只能存在一到几个这样的类。 
2)@EnableConfigurationProperties自动映射一个POJO到Spring Boot配置文件(默认是文件)的属性集。 
3)@ConditionalOnMissingBean启用一个Bean定义,但必须是这个Bean之前未定义过才有效。 
还可以使用@ AutoConfigureBefore注释、@AutoConfigureAfter注释来定义这些配置类的载入顺序。

二、属性映射

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

@ConfigurationProperties(prefix = "")
public class MongoProperties {

    private String host;
    private int port = ;
    private String uri = "mongodb://localhost/test";
    private String database;

    // ... getters/ setters omitted
}

@ConfigurationProperties注释将POJO关联到指定前缀的每一个属性。例如,属性将映射到这个类的端口属性。 
强烈建议Spring Boot开发者使用这种方式来删除与配置属性相关的瓶颈代码。

三、@Conditional注释

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

  • @ConditionalOnBean
  • @ConditionalOnClass
  • @ConditionalOnExpression
  • @ConditionalOnMissingBean
  • @ConditionalOnMissingClass
  • @ConditionalOnNotWebApplication
  • @ConditionalOnResource
  • @ConditionalOnWebApplication

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

@Conditional()
@Retention()
@Target({ ,  })
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类中定义:

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) (expression, expressionContext);

        // ...
        // here we create a message the user will see when debugging

        return new ConditionOutcome(result, ());
    }
}

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

四、应用程序上下文初始化器

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

五、调试Spring Boot自动配置

Spring Boot的官方文档(/spring-boot/docs/current/reference/htmlsingle/#howto-troubleshoot-auto-configuration)有助于理解自动配置期间发生了什么。当以调试模式运行时,Spring Boot会产生一个报告,如下:

Positive matches:
-----------------

   MessageSourceAutoConfiguration
      - @ConditionalOnMissingBean (types: ; SearchStrategy: all) found no beans (OnBeanCondition)

   JmxAutoConfiguration
      - @ConditionalOnClass classes found:  (OnClassCondition)
      - SpEL expression on : ${:true} (OnExpressionCondition)
      - @ConditionalOnMissingBean (types: ; SearchStrategy: all) found no beans (OnBeanCondition)

   DispatcherServletAutoConfiguration
      - found web application StandardServletEnvironment (OnWebApplicationCondition)
      - @ConditionalOnClass classes found:  (OnClassCondition)


Negative matches:
-----------------

   DataSourceAutoConfiguration
      - required @ConditionalOnClass classes not found:  (OnClassCondition)

   DataSourceTransactionManagerAutoConfiguration
      - required @ConditionalOnClass classes not found: , (OnClassCondition)

   MongoAutoConfiguration
      - required @ConditionalOnClass classes not found:  (OnClassCondition)

   FallbackWebSecurityAutoConfiguration
      - SpEL expression on : !${:true} (OnExpressionCondition)

   SecurityAutoConfiguration
      - required @ConditionalOnClass classes not found:  (OnClassCondition)

   
      - required @ConditionalOnClass classes not found: , (OnClassCondition)

   #localeResolver
      - @ConditionalOnMissingBean (types: ; SearchStrategy: all) found no beans (OnBeanCondition)
      - SpEL expression: '${:}' != '' (OnExpressionCondition)

   WebSocketAutoConfiguration
      - required @ConditionalOnClass classes not found: , (OnClassCondition)

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

六、结论

Spring Boot很好地利用了Spring 4框架的功能,可以创建一个自动配置的可执行JAR。 
不要忘记,可以使用自己的配置来替代自动配置,见: /spring-boot/docs/current/reference/htmlsingle/#using-boot-replacing-auto-configuration 
over!