起步依赖
在Spring Boot项目中,必须继承依赖spring-boot-starter-parent,所以从这个继承依赖开始。
在点击进入spring-boot-starter-parent后,其pom文件又继承了一个依赖spring-boot-dependencies,在看这个依赖前,先来看下这个文件的内容。
- spring-boot-starter-parent
其中有对配置文件的引入配置
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
可以看到,默认maven会去项目src/main/resources下寻找配置文件,可以.yml,.yaml,.properties文件,且加载顺序是先加载yml文件,然后yaml,最后properties,所以如果存在覆盖(即有相同属性的配置),properties文件的配置会覆盖前两者。
- spring-boot-dependencies
在spring-boot-dependencies pom文件中,可以看到该文件管理了所有依赖的版本号;
<properties>
<activemq.version>5.15.9</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.75</appengine-sdk.version>
<artemis.version>2.6.4</artemis.version>
<aspectj.version>1.9.4</aspectj.version>
<assertj.version>3.11.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.9.13</byte-buddy.version>
<caffeine.version>2.6.2</caffeine.version>
...
</properties>
即解决了我们原有项目中可能存在依赖版本冲突的问题,它来真正管理spring boot应用里面的所有依赖版本。
在继承spring-boot-starter-parent的同时,还需要导入相应的spring-boot-starter-web依赖,所以来看看里面的内容。
- spring-boot-starter-web
在spring-boot-starter-web pom文件中
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.8.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
它帮我们导入了web模块正常运行所依赖的组件,这些依赖的版本则有父项目进行管理。
- 总结
在起步依赖上,spring boot帮我们管理了各个依赖的版本,使各个依赖不会出现版本冲突;另外,帮我们打包了各个依赖让我们不用再像之前那样自己导入一大堆的依赖,只要引入起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的用。
自动配置
spring boot要求每一个启动类上都需要加上解@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 {
可以看到它是一个复合annotation,其中最重要的三个annotation是@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan。
- @SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
等同与@Configuration,启动类标注了@SpringBootConfiguration之后,本身其实也是一个ioc容器的配置类。
- @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
SpringBoot自动配置功能开启,是自动装配的核心注解。
首先来看下@AutoConfigurationPackage注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@AutoConfigurationPackage在这里导入了一个AutoConfigurationPackages.Registrar.class的组件,下面是AutoConfigurationPackages.Registrar的源码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
在这个类中,我们关注的是registerBeanDefinitions方法,metadata是注解的源信息,包括@SpringBootConfiguration配置的类名等信息;
register方法会扫描主配置类所在包及其子包下的组件,并注册到IOC容器中。
至此,我们找到了@AutoConfigurationPackage的作用。
说完了@AutoConfigurationPackage的作用,再来探索下@Import(AutoConfigurationImportSelector.class)
首先来看下AutoConfigurationImportSelector源码,主要关注的方法即
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
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 new AutoConfigurationEntry(configurations, exclusions);
}
在这里selectImports根据注解源信息获取需要导入容器组件的全类名,并返回,而获取就在getAutoConfigurationEntry中。
根据Debug发现,这里的configurations就是获取的全类名集合
所以我们重点来看下getCandidateConfigurations是如何获取这些类名的。下面是getCandidateConfigurations方法的源码。
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;
}
继续跟进loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
在loadSpringFactories方法中到了答案
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
看到这个方法后,就比较清晰了,首先读取类路径下的配置文件,然后循环遍历URL集合,并将URL加载为resource后读取成propertis对象,最后将键和值存入LinkedMultiValueMap中并返回。
这里的FACTORIES_RESOURCE_LOCATION即是所有组件的配置文件,它在类路径下的
# 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.CloudServiceConnectorsAutoConfiguration,\
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,\
...
上面配置文件存在大量的以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而 SpringApplication在获取这些类名后再加载。
以HttpEncodingAutoConfiguration为例来分析加载的过程。
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
- @Configuration:表示该类为注解类;
- @EnableConfigurationProperties(HttpProperties.class) :使使用 @ConfigurationProperties 注解的类生效。任何被@EnableConfigurationProperties应用到@ConfigurationProperties类都会被Environment属性配置,并注入到IOC容器中。具体可以看@EnableConfigurationProperties 注解;
- @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET):Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;判断当前应用是否是web应用,如果是,当前配置类生效 ;
- @ConditionalOnClass(CharacterEncodingFilter.class)://判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
- ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true):判断配置文件中是否存在某个配置 spring.http.encoding.enabled;matchlfMissing表示我们配置文件中没有pring.http.encoding.enabled=true,也是默认生效的;
因为在类上标注了@Configuration,所以在扫描到后就会被创建放入IOC容器中,@Configuration类被创建的原理见@Configuration注解的类的加载实现原理,又因为@EnableConfigurationProperties(HttpProperties.class)注解,配置属性类HttpProperties被创建并由配置文件初始化后放入IOC容器中,之后由HttpEncodingAutoConfiguration的构造函数传入,这就是被扫描类被生成的过程,在该类中还有两个@Bean的方法,这些组件在创建后也被放入IOC容器中,在上面CharacterEncodingFilter解决乱码的过滤器类就被创建了。
最后,来看下HttpProperties这个配置类是如何获取属性注入的。
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
private boolean logRequestDetails;
public static class Encoding {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
...
}
}
配置类通过@ConfigurationProperties(prefix = "spring.http")注解,从配置文件中获取值并注入到配置类中,所以如果由需要自定义的值,也可以在类路径下application.properties或application.yml填写。
所以,SpringBoot中xxxxAutoConfigurartion:自动配置类,给容器中添加组件;xxxxProperties:封装配置文件中相关属性;
- 总结
自动配置,第一得益于能扫描主配置类所在包及其子包的组件,将这些组件注册到IOC容器中,第二它能借助spring框架原有的SpringFactoriesLoader的支持,加载META-INF/spring.factories获取组件的全类名并通过反射实例化为对应的标注了@Configuration的JavaConfig形式,并且符合要求@Conditional要求的IOC容器配置类,同时还有需要的Properties类和方法返回的@Bean类。
其他
- @Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
- 怎么知道哪些自动配置类生效;
通过在application.properties配置文件中添加debug=true属性,来让控制台打印自动配置报告。
这样,就可以清楚的知道哪些配置类生效,哪些没有生效,以便自定义设置。