简介
SpringBoot 这款框架几乎是现在企业级开发的标配,使用SpringBoot进行开发,能够大量减少xml配置文件的编写,并且能为我们提供一站式服务。SpringBoot我们只需要导入相关模块的starter,就可以使用相关功能,但是我们几乎没有写配置,那么SpringBoot是如何帮我们配置的呢?
又比如说我们可以在application.properties或者application.yml 配置文件中进行一些配置,比如设置 server.port=8081,那么我们是如何知道可以设置哪些属性呢?我们往往是通过记忆,或者通过IDEA提示,或者是官方文档,但是这些都不太行,只有我们真正研究透原理,才能随心所欲的去应用。
下面就来谈谈SpringBoot的自动装配原理。
原理
一切的开始,都要从启动类上的@SpringBootApplication
注解开始。
@SpringBootApplication
public class AutoconfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigurationApplication.class, args);
}
}
这个注解又是一个合成注解,点进去看看。
@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 {}
SpringBoot启动时,加载主配置类,并且开启了自动配置功能。也就是这个注解: @EnableAutoConfiguration
那么这注解的作用是什么呢?点进去看看
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
可以看到,主要利用到了一个底层的 @Import注解,导入一个选择器,根据选择器,给Spring的容器中导入一些组件。
那么导入哪些组件呢?点进这个 AutoConfigurationImportSelector
类看看。该类实现了 ImportSelect
接口,重写了这个方法:
String[] selectImports(AnnotationMetadata importingClassMetadata);
该方法返回的是一个String类型的数组,该数组其实就是一个全类名的数组,SpringBoot会将这些全类名对应的类加入到IOC容器中。重写如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// selectImports方法中调用了getAutoConfigurationEntry方法。
protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
可以看到,在 getAutoConfigurationEntry(),有这么一行代码
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
通过getCandidateConfigurations方法,翻译过来是获取候选的配置,返回一个configurations对象。
那么这个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;
}
调用了 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); 这个方法传入了EnableAutoConfiguration.class以及类加载器。点进这个方法看看。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
分析一下上述代码,在loadFactoryNames方法中,其实起作用的是loadSpringFactories方法,在loadSpringFactories方法中,先通过类加载器去获取资源:classLoader.getResources(FACTORIES_RESOURCE_LOCATION);这个FACTORIES_RESOURCE_LOCATION是一个常量,表示 META-INF/spring.factories 这个路径。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
所以说,当项目启动时,会去扫描所有jar包中的类路径下的META-INF/spring.factories 文件,获取文件对应的url。然后再遍历每一个url,最终将这些扫描到的文件的内容,包装成一个Properties对象。然后在从Properties中获取的值加入到返回的结果里面。这个结果就是要交给容器的所有组件的全类名。
这里的factoryType就是传过来的EnableAutoConfiguration.class。所以会从properties中获取这个类名对应的值,然后把他们添加到容器中。
一共131个。
这131给 xxxAutoConfiguration 都是容器中的一个组件,都会加入到容器中,这些自动配置类本质都是配置类,用他们来进行自动配置。
就拿HttpEncodingAutoConfiguration为例,来解释自动配置原理。
@Configuration(proxyBeanMethods = false) // 表示是一个配置类,可以给容器中添加组件
@EnableConfigurationProperties(ServerProperties.class) // 启动指定类的 @ConfigurationProperties 功能,让指定的类和 properties 文件进行属性映射,并将ServerProperties加入到容器中,给其他组件使用。
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)// @Condition,表示如果是web应用,才生效
@ConditionalOnClass(CharacterEncodingFilter.class) // 判断当前项目有没有这个类,这个类是MVC中进行乱码解决的过滤器
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) // 判断配置文件中是否存在 server.servlet.encoding 这个配置,即使不存在也判断成立。即使不配置,也默认生效。
public class HttpEncodingAutoConfiguration {
// 这个properties已经和配置文件映射了。
private final Encoding properties;
// 只有一个有参构造器,这个参数的值就会从容器中拿,
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
}
点进 ServerProperties 这个类看看,可以看到,标注了@ConfigurationProperties
注解,该注解的作用就是从配置文件中获取指定的值和bean的属性进行绑定。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
所以说,我们想要知道配置文件中去配置什么,就可以看 @ConfigurationProperties
注解的prefix 前缀。 可以看到,我们常用的server.port就是这么来的。
也就是说,所有的在配置文件中配置的属性都是在xxxProperties中封装着。 配置文件能配什么,就可以参照某个功能对应的属性类。
HttpEncodingAutoConfiguration类上标注了很多的 @Condition 注解,如果这些条件都成立,配置类才会生效。如果生效,就会向容器中@Bean,添加组件,这些值的的某些属性,需要从properties中获取。
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
我们在application.properteis 中直接点击我们配置的属性,也会跳转到绑定的properties文件中。