SpringBoot自动装配
有一定经验的开发使用过纯Spring开发,相信Spring的配置文件一定让你很头疼。SpringBoot的理念是约定大于配置
。通俗说,就是Spring Boot为我们提供了一套默认的配置,不需要我们再去手动配置XML配置文件,只有SpringBoot默认配置不满足要求时,才去修改配置。
@1、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)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication注解包含两个主要注解:@SpringBootConfiguration
、@EnableAutoConfiguration
;
@1.1、SpringBootConfiguration
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
在@SpringBootConfiguration注解内,包含@Configuration注解。
- 表示该注解是一个配置类,SpringBoot建议我们使用该注解来代替传统的XML配置文件。
- @Configuration包含了@Component,所以加@Configuration注解的类会自动纳入Spring容器(但@Configuration注解标记的类须在@SpringBootApplication注解标记的类所在类路径之下)。
@1.2、EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
在@EnableAutoConfiguration注解内,该注解包含了@AutoConfigurationPackage,并导入一个AutoConfigurationImportSelector类。
1.2.1、@AutoConfigurationPackage
以前使用Spring进行传统方式的开发时,需要在配置文件中定义一个scan扫描器,在xml配置这个标签后,Spring才可以自动去扫描base-pack下面或者子包下面的Java文件,如果扫描到@Component @Controller@Service等这些注解的类,则把这些类注册为bean。在SpringBoot中,我们只需要将我们自己写的Java文件,放到@SpringBootApplication所在类的包下,SpringBoot就可以自动的帮我们完成扫描,不需要我们再去手动配置扫描器,简化了我们的开发流程。SpringBoot就是使用@AutoConfigurationPackage这个注解,完成了自动扫描。
在@AutoConfigurationPackage内导入一个名为Registrar的内部类。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 这个方法是SpringBoot通过@SpringBootApplication注解所在类的包,并将该包与它的子包全部注册到Spring容器中进行管理。
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
1.2.2、AutoConfigurationImportSelector
开发过程中,除编写的代码外,还需要引用很多第三方的依赖,SpringBoot也可以通过自动配置,帮助引入第三方的依赖。其原理就是用过@EnableAutoConfiguration这个注解导入的AutoConfigurationImportSelector类来实现的。
AutoConfigurationImportSelector类通过SpringFactoriesLoader.loadFactoryNames这个方法来加载第三方配置。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
-------------------------------------------------------------------
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);
}
-------------------------------------------------------------------
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;
}
-------------------------------------------------------------------
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
loadFactoryNames()方法的第一个参数getSpringFactoriesLoaderFactoryClass(),直接返回EnableAutoConfiguration这个类。
进入SpringFactoriesLoader类后可以看到,loadFactoryNames方法又去调用了loadSpringFactories这个方法。
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());
}
在这个方法里,SpringFactoriesLoader会去加载jar包中的META-INF/spring.factories。如果找到这个文件,SpringFactoriesLoader就会搜索文件中的一个配置org.springframework.boot.autoconfigure.EnableAutoConfiguration,并读取后面的值。
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 {
// 加载jar包中的META-INF/spring.factories
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;
}
当然不是所有的AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件,再进一步判断是否加载。下篇文章我们通过HttpEncodingAutoConfiguration实例来分析整个自动配置的过程。