@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 //默认值
这样我们在配置这些属性的时候就会有提示了