springboot利用了spring的注解:包括@Bean、@Conditional、@Configuration等,将原有对spring繁杂的xml抛弃掉,使用以AutoConfiguration为后缀的来@Bean对象,以Properties为后缀的类充当属性类,代替了xml文件的配置。
SpringBoot自动配置
先从主程序入口来看,我刚创建好springboot项目看见这俩货脑袋顶好几个问号
//为什么要加这个注解
@SpringBootApplication
public class AlllApplication {
public static void main(String[] args) {
//这个run方法是怎么回事?
SpringApplication.run(AlllApplication.class, args);
}
}
不管了,挑一个就下手,先分析注解@SpringBootApplication,鼠标放上ctrl+左键,走你~
@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 {...}
东西真多啊,还好咱spring注解也多少会点,直接看跟springboot相关的
- @SpringBootConfiguration
关于这个类源码描述为
Indicates that a class provides Spring Boot application,碰巧咱工地英语也不会,直接百度:表示类提供了Spring引导应用程序
???
咦,引导程序,莫非…
继续看看这个注解的其他信息
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
都是熟人,都是熟悉的元注解:修饰的范围、生命周期、声明此类的注释加进javadoc中、作为一个配置类
看到这明白了上面的那句,这个注解@SpringBootConfiguration表明是一个springboot的配置类的存在
问题1 : 既然是配置类不是表明他修饰的@SpringBootApplication 是一个配置类么?那么这个加在主启动类上的注解到底配置了哪些好玩的东西呢?
带着疑问看这个注解类的内部
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
一个默认为返回为true的proxyBeanMethods()的函数,至于为什么我叫他函数而不是方法,是个人会把只有一个方法的接口叫函数,大家随意。
@AliasFor(annotation = Configuration.class)这个注解也是spring为我们提供的,表明这个函数的是跟Configuration.class注解对应的,作用是起别名,确保不会跟@Configuration发生冲突
------现在,这个注解总体上我们看出来,表示“我@SpringBootConfiguration ”在谁头上谁就是springBoot的配置类
到这里,我刚才的疑问又出来了,你到底要配置什么?
回到@SpringBootApplication中,继续看这货脑袋上的元注解
我这次把目光放在了 @EnableAutoConfiguration 上,来,ctrl+左键进去,哎~舒服…咳咳,我的意思是看到这类中的内容就激动了
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
先从注解下手,也都熟悉:修饰的范围、生命周期、将注释加进javadoc、可继承该注解,下一个不认识的注解@AutoConfigurationPackage,话不多说点进去看看
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
使用了spring注解的@Import方式来注册这个抽象类AutoConfigurationPackages中的静态内部类Registrar
问题2: Registrar 是干什么的,为什么要注册它?
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
/*
重写了ImportBeanDefinitionRegistrar接口中的方法,在这里是先调用外部方法register(),这个方法都干什么了呢?
1. 先以LinkedHashMap来保存一条数据[K-V][0-new PackageImport(metadata).getPackageName()]:[ConstructorArgumentValues]
2. 最后以[k-v]形式注册的是:regist.registerBeanDefinition(AutoConfigurationPackages.class.getName(),GenericBeanDefinition)
(注册这需要了解spring以@Import形式注册的三种方式)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
/*
将注解的元信息包装成PackageImport类并返回
*/
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
@AutoConfigurationPackage总结:向容器中注册了Registrar这个静态内部类,对这个类的描述是:
{@link ImportBeanDefinitionRegistrar} to store the base package from the importing configuration.
主要作用还是注册了GenericBeanDefinition这个类
这我又来疑问了
问题3: 这个GenericBeanDefinition类究竟是干嘛的呢?
GenericBeanDefinition 类的描述是:
GenericBeanDefinition is a one-stop shop for standard bean definition purposes.
GenericBeanDefinition是用于标准bean定义的一站式服务。
熟悉spring应该知道这个类表示一个bean标签,它的父类AbstractBeanDefinition中的所有属性就是bean标签中包含的内容,包括beanClass、scope、lazyInit懒加载、initMethodName生命周期等,一个id对应一个GenericBeanDefinition
缓口气,看了半天,我是谁、我在哪、我在干什么,就这种感觉,不过接下来就是解决自动配置的主要注解,提起裤子(精神),继续搞起
********************** 重点吸收************************
回到@EnableAutoConfiguration中接着看@AutoConfigurationPackage下面的注解 @Import(AutoConfigurationImportSelector.class)
又导入了一个类AutoConfigurationImportSelector,并且是实现了ImportSelector接口注册的类,这样我们就明确了目标,直接杀进重写方法的selectImports(AnnotationMetadata),看一下到底注册了什么
@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());
}
1. 先来看看这行代码loadMetadata(this.beanClassLoader)
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
对AutoConfigurationMetadataLoader类的描述是:
Internal utility used to load {@link AutoConfigurationMetadata}.
用于加载{@link AutoConfigurationMetadata}的内部实用程序。
深入看看类的内部,哇哦~(在这里只V了主要代码,对这个类感兴趣可以动手自己看看,提高自己)
进入这个类,问题就铺面而来啊,
final class AutoConfigurationMetadataLoader {
//定义一个路径,嗯?定义一个什么路径,定义这个路径干什么,还有个properties文件,这是个springboot配置?这货在哪找?(我疑问当时是真多[捂脸])
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
//这个我熟,调用一个返回值为AutoConfigurationMetadata的loadMetadata方法
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
/*无论进来的classLoader是否为null,都可以使用一个classLoader类加载器来加载这个path,将这个parh对应的properties文件中的内容以一个Enumeration的这么个容器进行返回
我就好奇了,你这文件保存的是URL,难道是www.csdn.com?还是说www.github.com让我自己clone,看一看究竟是配置了什么。
*/
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
/*
将urls集合中的内容保存到Properties 中,下面这个方法返回的是一个静态内部类PropertiesAutoConfigurationMetadata,在Properties 之上进一步包装
*/
return loadMetadata(properties);//返回PropertiesAutoConfigurationMetadata
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
}
(重点 | 将spring.properties内容引入)2 . 再看这段代码getAutoConfigurationEntry()
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
深入进去,ctrl+左键走你~
/*
先从参数分析,可以爱着打一个断点来查看
autoConfigurationMetadata: 这个参数是包装了Properties的PropertiesAutoConfigurationMetadata类
annotationMetadata: StandardAnnotationMetadata对象,表示这个@Import最终是哪个类的头上,得到那个类的元数据,就是注解的信息,在这里是使用反射得到主启动类的元数据
(元数据:注解的属性等,这里我们主启动类上只有这个@SpringBootApplication,如果有其他的注解,可以自行打断点看看,如增加个事务管理器、dubbo等)
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//返回一个的层是LinkedHashMap结构的元数据集合,就是注释属性的键值对
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//当我debugF8到这后显示这个list元素有124个,那么这些内容是怎么来的呢?
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);
}
疑问1: 这个Enumeration是什么?
java.util中的一个接口,类似于迭代器,这里Enumeration实际返回的是它的实现类CompoundEnumeration(在Enumeration基础上加了一个游标)
疑问2:path到底在哪?
spring-boot-autoconfig2.2.5RELEASE包下的META-INFO就可以找打properties文件
疑问3:configurations 集合显示有124个,那么这些元素是从哪里来的呢?
一直debug到SpringFactoriesLoader 类中
public final class SpringFactoriesLoader {
//资源的路径,同疑问2中的路径相同
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
/*
这下面的代码就第二次见了,将指定properties中的内容封装到Enumeration<URL>,最终以LinkedMultiValueMap结构进行返回,
目的就是得到"META-INF/spring.factories"内容,将内容进行封装,保存到cache中,返回result,可以看出springboot增加了很多的map结构:LinkedHashMap、juc中的map等
*/
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的结构是ConcurrentReferenceHashMap,这个结构实现了ConcurrentHashMap接口,K是classLoader-V是list
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
疑问4:得到的URL是什么,有什么作用?
得到是以"org.springframework.boot.autoconfigure.EnableAutoConfiguration="为K,以其中一个元素"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,"来看这是一个webMvc的自动配置类,如果我们的项目是一个web项目那么springboot中的容器就会得到这个了类(为什么会得到这个自动配置类?每个自动配置类都有@Configuration,跟我们自定义config同理),省掉了我们原本需要在xml配置的步骤。
这些自动配置类发部分已经设置好了默认值,可以根据项目进行变更。
对于属性的值来讲,有些自动配置类是以···@EnableConfigurationProperties(HttpProperties.class)来引入一个对应的properties文件,这里以HttpEncodingAutoConfiguration举例
···直接在自动配置类中设置属性以WebMvcAutoConfiguration举例,它就没有对应的properties
自动配置类的作用是向容器注入我们需要的对象,感兴趣可以看看在使用web+JDBC等项目springboot都向我们@Bean了哪些类
当然这么多的url是需要过滤的,根据spring的@Conditional进行过滤。
写一篇比看一遍源码要慢的多啊,最后的自动配置这有补充的我再加(暂时先这么多,springboot真省时省力,爱了)