springbootapplication_Springboot学习(三)启动过程@SpringBootApplication详解

springboot一定要注意1.5.17.RELEASE才可以打断点在注解类上

2.X后的版本都无法打断点。(搞了很久也不知道原因)

springboot的启动过程,要看看面试题,重新整理不然太乱

@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 {
    ...
} 

实际上就是下方三个注解的整合:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

@SpringBootConfiguration

实际上就是复用了@Configuration,表示带有该注释的类是一个Spring的配置类。

//这是实现注解需要的配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//这是复合了其他注解的功能
@Configuration
public @interface SpringBootConfiguration {
} 

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//import表示导入某个类
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
} 

@EnableAutoConfiguration 注解启用自动配置,其可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中

@import的三种使用方式

@AutoConfigurationPackage

@Import({Registrar.class})

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //获取到元信息的包名传入注册器
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }
  • metadata 该注解所在的启动类信息
  • registry 用作注册的Bean注册器

v2-36b20d75d45be166e5d2b8cc840b649b_b.jpg
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

获取到该启动类所在路径的包名,根据传入的register和包名packageName注册该包名下的所有需要注册并实例化的Bean(包括@Component @Service @Mapper @Repository等)。

@Import(AutoConfigurationImportSelector.class)

   public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            try {
				//获取META-INF/spring-autoconfigure-metadata.properties 键值对,配置类的加载条件(需要依赖的class之类的条件)
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                
				//获取@EnableAutoConfiguration 中的属性值,exclude/excludeName,用于过滤自动配置类
				AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                //获取META-INF/spring.factories 键值对,"EnableAutoConfiguration"对应的值,也就是所有自动装配类名
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                
				//移除list中的重复自动配置类名
				configurations = this.removeDuplicates(configurations);
				//通过读取@AutoConfigureOrder、@link AutoConfigureBefore、@AutoConfigureAfter注解对自动配置类名进行优先级排序
                configurations = this.sort(configurations, autoConfigurationMetadata);
				//获得排除项,该值为@EnableAutoConfiguration 中的属性值,目前为空,因此exclusions为空
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
				//移除候选配置类当中的的所有排除项(不需要自动配置的类);
                configurations.removeAll(exclusions);
				
				//根据自动装配类和配置类加载条件进行条件判断,从而排除掉不满足的配置类
                configurations = this.filter(configurations, autoConfigurationMetadata);
				
                //通过AutoConfigurationImportListener监听器触发自动配置导入事件;
				this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return (String[])configurations.toArray(new String[configurations.size()]);
            } catch (IOException var6) {
                throw new IllegalStateException(var6);
            }
        }
    }

@ComponentScan

@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

没有配置basePackages,默认扫描该类所在的包下所有的配置类

这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:类cn.shiyujun.Demo类上标注了@ComponentScan 注解,则cn.shiyujun.controllercn.shiyujun.service等等包下的类都可以被扫描到
这个注解一共包含以下几个属性:

basePackages:指定多个包名进行扫描
basePackageClasses:对指定的类和接口所属的包进行扫
excludeFilters:指定不扫描的过滤器
includeFilters:指定扫描的过滤器
lazyInit:是否对注册扫描的bean设置为懒加载
nameGenerator:为扫描到的bean自动命名
resourcePattern:控制可用于扫描的类文件
scopedProxy:指定代理是否应该被扫描
scopeResolver:指定扫描bean的范围
useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测

?它与@AutoConfigurationPackage 不是重复了吗:

it will be used when scanning for code @Entity classes. It is generally recommended that you place EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.


@ComponentScan是先于@AutoConfigurationPackage 进行检索。

@Controller/@Service/@Component/@Repository这些注解是由ComponentScan来扫描并加载的。

除此之外,还有一些Spring Data注解 比如说,你用了Spring Data JPA,可能会在实体类上写@Entity注解。这个@Entity注解由@AutoConfigurationPackage扫描并加载。

简单理解:这二者扫描的对象是不一样的。


总结

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

因为自动装配的存在,所以才体现了"约定大于配置"

AutoConfigurationImportSelector.class 详细说明如下

AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)

这一步就是将META-INF/spring-autoconfigure-metadata.properties文件中的键值对放入AutoConfigurationMetadata的Properties对象中,共有485个元素。

org.springframework.boot:spring-boot-autoconfigure:1.5.17RELEASE/META-INF下

org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration

AnnotationAttributes attributes =this.getAttributes(annotationMetadata);

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
        String name = this.getAnnotationClass().getName();
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
        Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?");
        return attributes;
    }
  • 注意的是这里的name并没有使用annotationMetadata,而是使用了this

v2-c3711b4bc267707493628e8d76b1b3a8_b.png
  • 因此它的作用是获取@EnableAutoConfiguration 中的属性值

v2-816647c496a5898f2add3f35aeaf29ed_b.jpg

因为我们并没有设置值,所以它使用默认值为空

v2-b097c2cacff1b0e12cbb892cf4fd6f43_b.jpg

List<String> configurations =this.getCandidateConfigurations(annotationMetadata, attributes);

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL 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 var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

核心在于

springFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader());
  • this.getSpringFactoriesLoaderFactoryClass():EnableAutoConfiguration.class;
  • this.getBeanClassLoader():AppClassLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL 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 var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }
  • classLoader.getResources("META-INF/spring.factories"):通过类加载器获取jar包中所有META-INF/spring.factories并获取其中的内容

会把当前加载器和父类加载器都去扫描

v2-ed374d8b1a0d1a0df610e40aa6ef24be_b.jpg

?但是我没懂这些变量并没有找到文件所在的jar包路径

v2-a21ced62a65ec19bc128bfcaafd63b2f_b.jpg
  • while(urls.hasMoreElements())
    • 遍历文件路径url=jar:file:/C:/Users/Alan/.m2/repository/org/springframework/boot/spring-boot/1.5.17.RELEASE/spring-boot-1.5.17.RELEASE.jar!/META-INF/spring.factories
    • 获得文件内容键值对properties
    • 获得"org.springframework.boot.autoconfigure.EnableAutoConfiguration"对应的值
    • 该值以','作为连接符,因此需要进行逗号切割
    • 至此获得所有自动装配的类名

configurations = this.filter(configurations, autoConfigurationMetadata);

  • configurations:读取MEAT-INF/spring.factories文件得到的经过排除得到的自动配置类名的list集合,96个值
  • autoConfigurationMetadata:读取META-INF/spring-autoconfigure-metadata.properties文件得到的485个键值对。

该方法,就是判断configurattiaions是否满足autoConfigurationMetadata的类加载条件,比如说

匹配失败原因:@ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice'
由于项目没有引入Aop的相关依赖,导致类路径中没有Aspect和Advice类,因此AopAutoConfiguration这个自动配置类匹配失败,无法进行自动配置。

(1) META-INF/spring-autoconfigure-metadata.properties文件中的Aop内容:

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=
org.springframework.context.annotation.EnableAspectJAutoProxy,org.aspectj.lang.annotation.Aspect,org.aspectj.lang.reflect.Advice

(2) META-INF/spring.factories中的Aop内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=...,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,...

能否成功配置,关键还要看是否已经被排除以及是否满足对于这个@ConditionXXX,也就是说是否导入了XXXstarter依赖,否则就会缺少一些类,就不满足条件。

若不满足,则该类是会从自动配置类列表中排除,这样能加快springboot的启动速度

总结

导入所有的自动配置类,然后根据当前不同的条件判断,觉得这个配置类是否生效,一旦配置类生效,这个配置类就会给容器中添加各自组件,这些组件的属性是从对应的properties类中获取,这些类里面的每一个属性又是与配置文件绑定的。

挖坑

一定要弄懂一下@Configuration 为什么就可以注入自定义属性了!

https://juejin.im/post/5e689b49e51d4527143e5e2f

源码分析来源集合

https://www.jianshu.com/p/9dc7a385d19e

https://juejin.im/post/5dce352351882510337e27a6

https://juejin.im/post/5d84269d5188257ae63ebbe6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值