SpringBoot的自动配置能力源码分析

@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 {


@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};


@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};


@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};

}

@SpringBootApplication主要被三个注解修饰:

@SpringBootConfiguration:被@Configuration 修饰,这说明我们可以在SpringBootApplication修饰的类里面声明@Bean
@ComponentScan:声明扫描的包路径,其中scanBasePackages,scanBasePackageClasses属性被该注解解析,没有配置使用注解所在包修饰的的路径为根路径。(这也是为什么启动类不在根路径下需要特别声明scanBasePackages,否则会报错)

@EnableAutoConfiguration: 自动配置类,excludeName,exclude被该注解解析

注意:@AliasFor注解为了声明可以等效替换的属性和类,如下面代码声明了scanBasePackageClasses属性等效于ComponentScan注解中的basePackageClasses属性

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};  

@EnableAutoConfiguration:

@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

/**
 * Exclude specific auto-configuration class names such that they will never be
 * applied.
 * @return the class names to exclude
 * @since 1.3.0
 */
String[] excludeName() default {};

}

@EnableAutoConfiguration借助EnableAutoConfigurationImportSelector将所有符合条件的@Configuration配置加载到IOC中,通常还要借助@EnableConfigurationProperties,@ConditionalOnBean等注解
核心方法:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	try {
	    //加载自动配置的信息,"META-INF/spring-autoconfigure-metadata.properties中所有的key value键值对
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
				//加载注解中配置的属性 即exclude和excludeName
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
	    //获取到自动配置类的列表 META-INF/spring.factories所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置类列表
		List<String> configurations =
		getCandidateConfigurations(annotationMetadata,
				attributes);
				//去重,排除不同的jar中spring.factories中配置了相同的配置类
		configurations = removeDuplicates(configurations);
		//排序
		configurations = sort(configurations, autoConfigurationMetadata);
		//获得 exclude和excludeName列表
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		 //  再次校验,排除exclusions 已经加载的类
		checkExcludedClasses(configurations, exclusions);
		//排除掉
		configurations.removeAll(exclusions);
		//过滤配置,此处会加载spring.factories中key为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的配置类
		configurations = filter(configurations, autoConfigurationMetadata);
			//初始化监听器,此处会加载spring.factories中key为	org.springframework.boot.autoconfigure.AutoConfigurationImportListener的配置类
	
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return configurations.toArray(new String[configurations.size()]);
	}
	catch (IOException ex) {
		throw new IllegalStateException(ex);
	}
}


	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
		AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
			getSpringFactoriesLoaderFactoryClass(), 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;
}

SpringFactoriesLoader.java 的 loadFactoryNames

....
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	try {
		Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		List<String> result = new ArrayList<String>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
			
			 //根据key获取value 
			String factoryClassNames = properties.getProperty(factoryClassName);
			result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
		}
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
				"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

以上说明了springboot的自动配置其实就是加载META-INF/spring.factories下的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的@Configration类,置于要不要初始化还需要看具体的类的配置,举个栗子

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

该类使用了@ConditionalOnClass注解修饰,当修饰的Class存在时 该Configration类的配置才会被真正加载,至于我们在项目的application.properties中,配置了name多属性都被那些类使用,则使用@EnableConfigurationProperties注解。

参考DataSourceProperties的源码

//prefix 代表配置的前缀,即该类中的所有属性在配置文件中都以spring.datasource开头
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties
	implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {

private ClassLoader classLoader;

private Environment environment;

/**
 * Name of the datasource.
 */
private String name = "testdb";

/**
 * Generate a random datasource name.
 */
private boolean generateUniqueName;

/**
 * Fully qualified name of the connection pool implementation to use. By default, it
 * is auto-detected from the classpath.
 */
private Class<? extends DataSource> type;

/**
 * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
 */
private String driverClassName;

/**
 * JDBC url of the database.
 */
private String url;

/**
 * Login user of the database.
 */
private String username;

/**
 * Login password of the database.
 */
private String password;

/**
 * JNDI location of the datasource. Class, url, username & password are ignored when
 * set.
 */
private String jndiName;

/**
 * Populate the database using 'data.sql'.
 */
private boolean initialize = true;

/**
 * Platform to use in the DDL or DML scripts (e.g. schema-${platform}.sql or
 * data-${platform}.sql).
 */
private String platform = "all"; 
}

上面这些就是我们平时熟悉的配置
//prefix 代表配置的前缀,即该类中的所有属性在配置文件中都以spring.datasource开头
@ConfigurationProperties(prefix = “spring.datasource”)

另外还支持map和类嵌套属性的配置,子类的属性配置需要使用@NestedConfigurationProperty 修饰,例如我之前自己做的SSO插件的配置,

   /**
     * 自定义验证配置器所需要的Cookie的name与认证url的映射,该UrL接收POST请求,content-Type为application/json,解析参数为token,默认为空.
     */
    private Map<String,String> auth = new HashMap<String,String>(2);
    /**
     * 处理用户信息时的自定义策略,包括缓存类型策略,缓存key的组装策略,用户信息的定义策略
     */
    @NestedConfigurationProperty
    private Strategy strategy = new Strategy();

编译完成后会target中生成一个spring-configuration-metadata.json,例如mybatis-spring-boot-autoconfigure下的配置内容如下,只截取一部分:

    {
  "groups": [
    {
      "name": "mybatis",
      "type": "org.mybatis.spring.boot.autoconfigure.MybatisProperties",
      "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties"
    }
  ],
  "properties": [
    {
      "name": "mybatis.check-config-location",
      "type": "java.lang.Boolean",
      "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties"
    },
    {
      "name": "mybatis.check-config-location",
      "type": "java.lang.Boolean",
      "description": "Check the config file exists.",
      "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties",
      "defaultValue": false
    },
    
    {
      "name": "mybatis.type-handlers-package",
      "type": "java.lang.String",
      "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties"
    },
    {
      "name": "mybatis.type-handlers-package",
      "type": "java.lang.String",
      "description": "Package to scan handlers.",
      "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties"
    }
  ],
  "hints": []
}

groups:组,可以理解为一个配置文件为一组

 "name":  前缀(prefix)
 "type":类的全路径名
 "sourceType": 类的全路径名

properties: 类中的属性

  "name": "mybatis.check-config-location", //配置属性的key,加前缀,小驼峰以-连接
  "type": "java.lang.Boolean", //数据类型
  "description": "Check the config file exists.",//javadoc注释
  "sourceType": "org.mybatis.spring.boot.autoconfigure.MybatisProperties", //类的全路径
  "defaultValue": false //默认值

这样我们在配置这些属性的时候就会有提示了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot2的启动流程是通过@SpringBootApplication注解自动配置来实现的。该注解包含了多个注解的组合,其中包括@ComponentScan、@EnableAutoConfiguration和@Configuration等。通过这些注解,Spring Boot会自动扫描并加载配置类,并根据自动配置规则来配置应用程序。 具体而言,当应用程序启动时,Spring Boot会创建一个Spring应用程序上下文。在创建上下文的过程中,会先加载主配置类(通常是包含main方法的类),然后根据@ComponentScan注解扫描指定包下的所有组件。 接下来,Spring Boot会根据@EnableAutoConfiguration注解自动配置应用程序。这个注解会根据classpath和条件匹配的规则,加载配置类,并将它们注册到应用程序上下文中。这些配置类使用了@Configuration注解,会声明一些Bean,并根据条件来决定是否生效。 最后,Spring Boot会启动应用程序,并执行相应的事件处理器。这些事件处理器可以通过自定义ApplicationListener来实现。在应用程序运行期间,Spring Boot会触发不同的事件,并调用相应的事件处理器。 参考文献: 引用:SpringBoot2 | @SpringBootApplication注解 自动配置流程源码分析(三) [2] 引用:SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六) 引用:该系列主要还是Spring的核心源码,不过目前Springboot大行其道,所以就从Springboot开始分析。最新版本是Springboot2.0.4,Spring5,所以新特性本系列后面也会着重分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值