@SpringBootApplication包含的注解
@Target(ElementType.TYPE) //指定在何处写入注释的合法位置 @Retention(RetentionPolicy.RUNTIME) //RetentionPolicy这个枚举类型的常量描述保留注释的各种策略,它们与元注释(@Retention)一起指定注释要保留多长时间 @Documented //表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分。 @Inherited //它指明被注解的类会自动继承 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), //扫描器 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //扫描器的过滤方式
一、@SpringBootConfiguration包含的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Configuration 1、表明该注解是一个配置类,可以用@Configuration注解来代替以前传统的spring.xml配置文件;
2、@Configuration注解的类会自动加入到spring容器中。
二、@EnableAutoConfiguration :使spring boot 实现自动配置。(约定优于配置)
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage:实现自动将包放入到扫描中,它有一个注解
@Import(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)); } }
metadata可以获取被@SpringBootApplication注解的类,然后通过该类获取包名。通过这个方法将该包以及其子包所包含的类全部放入到spring容器中,这也是为什么spring boot的代码都要放在@SpringBootApplication注解的类的包或其子包中,才能被spring容器识别的原因。
其中@EnableAutoConfiguration本身也有一个@Import(AutoConfigurationImportSelector.class),其中有一个方法:
@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()); }
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); }
通过DEBUG可以发现configurations中通过LinkedList链表数据结构,放入我们第三方依赖的jar包,这种链表结构使依赖的jar包增删速度很快。
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)又是怎么引入jar包的呢?
进入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:
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; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
可以很明显看到FACTORIES_RESOURCE_LOCATION这个枚举类型就是引入jar包的关键,继续进入:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
发现jar包最终是由这个配置文件:spring-boot-autoconfigure-2.1.13.RELEASE.jar中META-INF/spring.factories,来进行声明,然后通过@EnableAutoConfiguration来开启使用。
三、具体看看自动装配原理
以spring-boot-autoconfigure-2.1.13.RELEASE.jar中META-INF/spring.factories中的org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration举例,进入该类看其注解信息:
@Configuration //标识为配置类,将其纳入spring容器 @EnableConfigurationProperties(HttpProperties.class) //进入该注解,发现将默认编码设置为UTF_8 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //如果是servlet类型则该类生效 @ConditionalOnClass(CharacterEncodingFilter.class) //项目中存在字节码过滤器则成立 @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //当属性满足要求时,此条件成立 public class HttpEncodingAutoConfiguration {... }
3.1、进入@EnableConfigurationProperties(HttpProperties.class):
@ConfigurationProperties(prefix = "spring.http")
/**
* Configuration properties for http encoding.
*/
发现设置有“ "spring.http"”这个前缀,注释信息为encoding,加上属性信息Charset将其写入到application.properties中:
spring.http.encoding.charset=US_ASCII
发现可以通过此前缀来更改其约定。
3.2、解读@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
prefix = "spring.http.encoding", value = "enabled":前缀为spring.http.encoding,名为enabled;
matchIfMissing :当前述中的属性不存在时成立。
即在application.properties中没有配置spring.http.encoding.enabled这个属性,则该类生效。
总结:即当自动配置类中的@ConditionalOnXXXX 注解中的条件都满足时,自动装配即生效。也可从@ConditionalOnProperty中prefix+value 得知更改该自动装配全局配置文件中的key。
附:
tip: 在全局配置文件中设置 debug=true,可以在控制台快速获取自动装配的信息。
Positive matches 列表表示spring boot开启的自动装配;
Negative matches 列表表示spring boot没有开启的自动装配。