SpringBoot自动配置原理
本次演示使用版本:JDK1.8、maven3.6.0、idea2018
1、创建一个maven工程,直接点击下一步即可,无需勾选任何选项
2、填写GAV(groupId、artifactId、version),填写完毕之后点击下一步
3、直接点击完成即可
4、因为我们需要创建一个SpringBoot的工程,所以我们要指定当前的parent为springBoot的子模块(开篇我们使用的是springBoot的2.2.2版本,一会儿为了比较与1版本的区别另外演示)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
5、引入web的启动器(可以不引入,直接演示自动配置原理,但是为了将springBoot的starter知识引入进来,所以在此演示)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
6、自己手动创建包结构,且【随意】定义一个类来作为启动类,放在任意包下均可,图中类SpringBootAutoConfigApplication.java为我随意起的
7、启动类头上加一个@SpringBootApplication
注解,在该类中添加一个main方法。
代码如下:
package cn.cmysz.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootAutoConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAutoConfigApplication.class, args);
}
}
8、添加一个Controller到一个包下(但是这个包要在刚刚启动类的同级包或者是其子包下,原因何故稍后解释),写上几行代码:
package cn.cmysz.springboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController//该注解为@Controller 与 @ResponseBody的组合注解
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "Hello";
}
}
9、点击启动类的 运行按钮发现控制台的输出
发现2.2.2版本的SpringBoot内置Tomcat,且版本为9.0.29,contextPath为’ ',端口号为8080
其中contextPath与端口号均可以在配置文件中配置。
总结:以上就是一个简单的SpringBoot工程的快速入手,接下来说明他的自动配置原理
首先如果你使用的是idea开发工具,那么按住Ctrl+鼠标左键点击刚才在启动类上面添加的@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 {
前四个注解为java自带的元注解。
下方的@SpringBootConfiguration
注解表示该类是一个配置类,当然感兴趣的可以点进去这个注解内部,就会发现这个注解的头上除了有java的元注解之外,看见了Spring的@Configuration注解的存在。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
再下方的@EnableAutoConfiguration
这个注解是整个自动配置的核心。
点进这个注解之后发现除了一样的有java自带的元注解之外的两个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
核心注解:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
其一:
点击@AutoConfigurationPackage
注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
发现其为我们导入了AutoConfigurationPackages.Registrar.class这个静态内部类。
明显看见该内部类中的两个方法:
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(AnnotationMetadata metadata,
BeanDefinitionRegistry registry)
其内部调用了register方法,将断点打在此处。debug运行主方法。
步进该方法
我们可以通过两种方式看见我们所创建的包
1、在控制台下方的变量中
2、选中ClassUtils.getPackageName(metadata.getClassName()右键计算表达式
总结:SpringBoot在此处将我们所写的SpringBootAutoConfigApplication这个主启动类所在包及其子包下的所有类上被标注@Controller,@Service,@Repository,@Component这四个注解的类加载到IOC容器中。所以我们方才所写的Controller类要放在启动类同级包或者子包下。
注:此功能在SpringBoot1版本与2版本中相差甚微,故只做一次解释
其二:@Import(AutoConfigurationImportSelector.class)
该注解是为我们导入了一个自动配置导入选择器类AutoConfigurationImportSelector
点击AutoConfigurationImportSelector这个类之后发现此时会察觉到SpringBoot的1版本与2版本有所差距,现在我们先来介绍2.2.2版本情况:
点进来会看到有一个静态内部类AutoConfigurationGroup,在此类中有一个方法process
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
将断点打在此处。点击getAutoConfigurationEntry这个方法
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);
}
在此处打下断点。点击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<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
点击FACTORIES_RESOURCE_LOCATION这个常量,即可看见:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
此时就是使用的类加载器加载所有在META-INF目录下/spring.factories这个文件中的自动配置类,并且将他们加载进IOC容器中。
举例:
我们在继承parent模块时为我们引入的spring-boot-autoconfigure-2.2.2.RELEASE的META-INF下的spring.factories文件中所有的自动配置类都会将其加载进IOC文件,由此,便完成了SpringBoot的自动配置。
总结:以上是2版本的自动配置过程,接下来说明1版本。
首先将parent换成1版本,此处演示的是SpringBoot的1.5.10.RELEASE版本。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
在@EnableAutoConfiguration这个注解的上方2版本引入的是:AutoConfigurationImportSelector,1版本引入的是:EnableAutoConfigurationImportSelector
在此类中我们可以看出:
@Deprecated
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
起作用的是它的父类AutoConfigurationImportSelector,是不是有点眼熟,就是2版本的AutoConfigurationImportSelector,但是在此类中两个静态内部类全无。
在此类中起作用的是selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
将断点打在此处,并且点击进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, 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));
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);
}
}
此处又可看见FACTORIES_RESOURCE_LOCATION
这个变量
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
自此以后流程全部一样都是将META-INF包下/spring.factories文件中的所有的自动配置类加载进IOC文件。
留下疑问:
1、我们知道SpringBoot是约定大于配置,那么它给我们默认配置了哪些?
2、我们可不可以修改?
3、那么多的自动配置类,我们要想修改为自己独特配置怎么做?假如我要修改mapper映射文件的地址怎么做?
4、我应该怎么写?就像开篇提到的contextPath和端口号,该怎么修改?
5、我还看见了:No active profile set, falling back to default profiles: default,这是什么?
6、自动配置类都放在了META-INFO/spring.factories文件中,我想自己定义一个属于自己的启动器该怎么做。
7、等等一系列问题。。请听下回。。。。