自学笔记记录:Spring Boot 2.5.0源码学习【2、自动配置原理】

【“没有什么是一个断点不能解决的”系列】

一、Spring Boot自动配置原理

@SpringBootApplication
public class SpringbootLearningApplication {
    public static void main(String[] args) {
        // 1、返回我们的 IOC 容器,整个应用一启动就会给我们返回应用内的容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootLearningApplication.class, args);

        // 2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        // 获取容器内组件数量
        int beanDefinitionCount = run.getBeanDefinitionCount();
        System.out.println(beanDefinitionCount);

    }
}

Ⅰ、引导加载自动配置类

@SpringBootApplication 注解表示这是一个 Spring Boot 应用。

进入源码分析可得,相当于以下这三个注解。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.*******.*******")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
1、@SpringBootConfiguration

底层用了 @Configuration 注解,代表只是一个配置类。

@Configuration
@Indexed
public @interface SpringBootConfiguration {}
2、@ComponentScan

包扫描注解,指定要扫描哪些。

3、@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
3.1、@AutoConfigurationPackage
@Import({Registrar.class})		// 给容器中导入一个组件 AutoConfigurationPackages.Registrar.class
public @interface AutoConfigurationPackage {}

利用 Registrar 给容器中导入了一系列组件。

那么是导入哪些组件呢?

  • 我们进入到 Registrar 中 找到 registerBeanDefinitions 方法,可见传入了两个参数 注解元信息AnnotationMetadata metadata, BeanDefinitionRegistry registry。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
  • 先来看 AnnotationMetadata metadata,因为该 Register 所在的 Import 注解标在 @AutoConfigurationPackage 注解上,而 @AutoConfigurationPackage 注解标在 @EnableAutoConfiguration 注解上,@EnableAutoConfiguration 注解最终又标在 XxxxxApplication上,因此分析得该注解最终是标在 XxxxxApplication上的。所以可以看到 metadata 注解元信息是标在应用程序类 XxxxxApplication 上。
微信截图_20210608174938
  • 把注解元信息拿到后,获取它所在的包名, (new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()。
image-20210608175751967
  • 然后把该包名封装在数组中,取数组中第一个元素,再 register 注册进去,相当于将该包下所有的组件批量注入。

因此是将 XxxxxApplication 程序所在的包下所有的组件批量导入进去。

这也解释了为什么默认的包路径是 XxxxxApplication 所在的包路径。

3.2、@Import({AutoConfigurationImportSelector.class})

进入 AutoConfigurationImportSelector,找到 selectImports 方法来分析要批量导入哪些组件,会返回要导入的组件的 String 数组。

在 selectImports 方法中利用 getAutoConfigurationEntry(annotationMetadata) 方法给容器中批量导入组件。之后将返回得到的 autoConfigurationEntry 再进一步得到所有的配置 autoConfigurationEntry.getConfigurations(),再转成数组作为 selectImports 方法的返回结果。

因此着重来分析 getAutoConfigurationEntry(annotationMetadata) 方法。

  1. 利用 getAutoConfigurationEntry(annotationMetadata) 方法给容器中批量导入组件;

  2. 在该方法中调用 List configurations = this.getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入到容器中的配置类(组件);

在这里插入图片描述

  1. getCandidateConfigurations -> loadFactoryNames -> loadSpringFactories。

    利用工厂加载器 Map<String, List> loadSpringFactories(ClassLoader classLoader) 得到所有的组件;

  2. Enumeration urls = classLoader.getResources(“META-INF/spring.factories”);

    又从 META-INF/spring.factories 位置加载文件。默认扫描当前系统里面所有 META-INF/spring.factories 位置的文件;

    spring-boot-autoconfigure-2.5.0.jar 包中 META-INF/spring.factories 文件中就写有 131项(由第2步 getCandidateConfigurations 方法获取的 数组长度为 131 的 configurations),标明了要自动加载哪些自动配置类。

    因此在此处 spring.factories 文件中写死了 spring-boot 一启动就要给容器中加载的所有配置类

在这里插入图片描述

虽然这131个场景的所有自动配置在项目启动的时候默认全部加载,但按照条件装配规则(@Conditional),最终会按需配置。

Ⅱ、按需开启自动配置项

  1. 虽然这131个场景的所有自动配置在项目启动的时候默认全部加载进容器
  2. 但按照条件装配规则(@Conditional),最终会按需配置,才会生效

可以暂时理解为带有 XxxxxAutoConfiguration 后缀的配置类文件在 spring-boot 一启动能够默认自动被加载进来到容器中。

1、以 AopAutoConfiguration 为例
微信截图_20210609133746

首先来看 AopAutoConfiguration 类的注解

@Configuration 注解表示这是一个配置类;

@ConditionalOnProperty(prefix = “spring.aop”, name = {“auto”}, havingValue = “true”, matchIfMissing = true) 注解用来判断配置文件中是否存在 spring.aop.auto 为 true 的配置,如果存在的话该配置类就生效,并且 matchIfMissing = true 表示就算没有配值也默认表示 spring.aop.auto 为 true。

然后在 AopAutoConfiguration 类中,再来看 AspectJAutoProxyingConfiguration 类

@Configuration 表示其还是一个配置类;

@ConditionalOnClass({Advice.class}) 表示我们需要导入 org.aspectj.weaver.Advice 这个类的场景,AspectJAutoProxyingConfiguration {}类才能够生效

再来看 ClassProxyingConfiguration 类

@Configuration 表示是一个配置类;

@ConditionalOnMissingClass({“org.aspectj.weaver.Advice”}) 表示当系统中没有 org.aspectj.weaver.Advice 这个类时就会生效;

@ConditionalOnProperty( prefix = “spring.aop”, name = {“proxy-target-class”}, havingValue = “true”, matchIfMissing = true)表示配置文件中如果配了 spring.aop.proxy-target-class 为 true 的这个属性的话,那么就会生效,并且同样 matchIfMissing = true 表示就算没有配值也默认表示 spring.aop.proxy-target-class 为 true。

相当于最终我们的 ClassProxyingConfiguration 配置类,也开启了 aop 功能,开启的是简单的 aop 功能,而不是 AspectJ 的 aop。

2、以 CacheAutoConfiguration 为例

同样 CacheAutoConfiguration 也是 spring-boot 一启动默认自动加载进来的。

首先看 CacheAutoConfiguration 类

@Configuration 表示是一个配置类;

@ConditionalOnClass({CacheManager.class}) 表示在系统应用内有 org.springframework.cache.CacheManager 这个类文件导入情况下,才能生效;

@ConditionalOnBean({CacheAspectSupport.class}) 表示判断容器中是否有 CacheAspectSupport 类型的组件,可以通过以下代码来验证判断,发现容器中没有 CacheAspectSupport 类型的组件,因此以下都不会生效了;

String[] beanNamesForType = run.getBeanNamesForType(CacheAspectSupport.class);
System.out.println("==========="+beanNamesForType.length); // 0
总结

Spring Boot 底层的设计模式之一:Spring Boot 默认会在底层配好所有的组件,但是如果用户自己配置了,以用户的优先。

@Bean
@ConditionalOnMissingBean  // 如果系统中没有配 CharacterEncodingFilter,Spring Boot就会帮我们配
public CharacterEncodingFilter characterEncodingFilter() {}
public class MyConfig {
    /**
     * 自己配置 CharacterEncodingFilter,Spring Boot 会以我们自己配的优先
     */
    @Bean
    public CharacterEncodingFilter characterEncodingFilter() {
        return null;
    }
}

总结:

  • Spring Boot 先加载所有的自动配置类;XxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效(@Conditional);默认都会绑定配置文件 properties 文件指定的值;XxxxxProperties里面拿;XxxxxProperties 和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于就有了这些组件当中的功能
  • 定制化配置
    • 用户直接自己写个 @Bean 配置类组件替换底层的组件,会以用户配置的优先
    • 用户查看这个组件获取的是配置文件中的什么值,直接去 properties 配置文件中进行修改

自动导入 XxxxxAutoConfiguration —> 按需配置组件 —> 从 XxxxxProperties 拿值 —> application.properties 中绑定了对应的值

具体也可以参照官网查看Common Application Properties

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
rar包内含有spring2.5.6源码,解压即可使用 源代码分析,是一件既痛苦又快乐的事情,看别人写的代码是通过的,但当你能够看明白的时候,相信快乐也会随之而来,为了减少痛苦,更快的带来快乐,在这里希望通过这篇文章对觉得困难的朋友有一个帮助。 本文以spring框架的XmlBeanFactory为入手点进行分析,希望能够以尽量简洁明了的方式给予有需要的朋友一定的帮助。 首先来打开该类的代码,我们将看到如下代码: Java代码 public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } } public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } } 这个类的代码很简单,一个成员对象加两个构造函数,从这里我们可以看出,最重要的地方在于最后一个构造函数: Java代码 super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); 第一句就是将父亲工厂交给父类的构造函数,实际上最后也就是把父工厂保存到类的parentBeanFactory成员对象中,这个对象是在AbstractBeanFactory抽象类中定义的,而这个父工厂也会一直传递到该抽象类进行保存。第二句就是整个类中最重要的地方了,顾名思义,它的目的是通过XmlBeanDefinitionReader这个XML的Reader从资源resource中(也就是你的配置文件)读取bean的定义。接下来我们打开XmlBeanDefinitionReader的loadBeanDefinitions方法,我们可看到在这个方法里代码就一行,调用了一个同名不同参的方法,而参数是EncodedResource的一个实例,这个类实际上是Resource的一个包装类,用来保存资源的Encode的,那接下来我们再看被调用的loadBeanDefinitions方法,这个方法里最主要的部分就是: Java代码 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 这里的目的是将资源包装成一个InputSource,连同Resource作为参数传递到doLoadBeanDefinitions方法 Java代码 DocumentBuilderFactory factory = createDocumentBuilderFactory(); if (logger.isDebugEnabled()) { logger.debug("Using JAXP implementation [" + factory + "]"); } DocumentBuilder builder = createDocumentBuilder(factory); Document doc = builder.parse(inputSource); return registerBeanDefinitions(doc, resource); DocumentBuilderFactory factory = createDocumentBuilderFactory(); if (logger.isDebugEnabled()) { logger.debug("Using JAXP implementation [" + factory + "]"); } DocumentBuilder builder = createDocumentBuilder(factory); Document doc = builder.parse(inputSource); return registerBeanDefinitions(doc, resource); 这个方法的目的一目了然,就是为了将资源解释成为Document对象,然后调用registerBeanDefinitions方法,这里不做详细解释,不了解的话请去看看关于JAXP的介绍。接下来我们打开registerBeanDefinitions方法: Java代码 public int registerBeanDefinitions(Document doc, Resource resource) throws BeansException { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } public int registerBeanDefinitions(Document doc, Resource resource) throws BeansException { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } 这里创建了一个XmlBeanDefinitionParser接口的实现,这个接口的具体类是DefaultXmlBeanDefinitionParser,这个接口很简单,只有registerBeanDefinitions一个方法,这个方法的作用也很明了,就是用来注册Bean的定义的,所以说类和方法的名字一定要起得有意义,这样可以让人一看就大概了解其作用,减少了很多阅读代码的痛苦。废话不多说,我们打开DefaultXmlBeanDefinitionParser的registerBeanDefinitions方法,这个类就是解释XML配置文件的核心类了,打开registerBeanDefinitions方法后我们看到如下代码: Java代码 public int registerBeanDefinitions(BeanDefinitionReader reader, Document doc, Resource resource) throws BeanDefinitionStoreException { this.beanDefinitionReader = reader; this.resource = resource; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); //初始化根元素 initDefaults(root); if (logger.isDebugEnabled()) { logger.debug("Default lazy init '" + getDefaultLazyInit() + "'"); logger.debug("Default autowire '" + getDefaultAutowire() + "'"); logger.debug("Default dependency check '" + getDefaultDependencyCheck() + "'"); } preProcessXml(root);//一个空方法用于扩展 int beanDefinitionCount = parseBeanDefinitions(root);//解释配置的主要方法 if (logger.isDebugEnabled()) { logger.debug("Found " + beanDefinitionCount + " elements in " + resource); } postProcessXml(root); //一个空方法用于扩展 return beanDefinitionCount; } public int registerBeanDefinitions(BeanDefinitionReader reader, Document doc, Resource resource) throws BeanDefinitionStoreException { this.beanDefinitionReader = reader; this.resource = resource; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); //初始化根元素 initDefaults(root); if (logger.isDebugEnabled()) { logger.debug("Default lazy init '" + getDefaultLazyInit() + "'"); logger.debug("Default autowire '" + getDefaultAutowire() + "'"); logger.debug("Default dependency check '" + getDefaultDependencyCheck() + "'"); } preProcessXml(root);//一个空方法用于扩展 int beanDefinitionCount = parseBeanDefinitions(root);//解释配置的主要方法 if (logger.isDebugEnabled()) { logger.debug("Found " + beanDefinitionCount + " elements in " + resource); } postProcessXml(root); //一个空方法用于扩展 return beanDefinitionCount; } 在这个方法当中,主要用于解释定义的有两个方法,一个是initDefaults,一个是parseBeanDefinitions,第一个方法是用来解释根元素的属性的,例如lazy-init, autowire等,而parseBeanDefinitions就是用来解释具体的bean定义了,方法代码如下: Java代码 protected int parseBeanDefinitions(Element root) throws BeanDefinitionStoreException { NodeList nl = root.getChildNodes(); int beanDefinitionCount = 0; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (IMPORT_ELEMENT.equals(node.getNodeName())) { importBeanDefinitionResource(ele); } else if (ALIAS_ELEMENT.equals(node.getNodeName())) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); this.beanDefinitionReader.getBeanFactory().registerAlias(name, alias); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } } } return beanDefinitionCount; } protected int parseBeanDefinitions(Element root) throws BeanDefinitionStoreException { NodeList nl = root.getChildNodes(); int beanDefinitionCount = 0; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (IMPORT_ELEMENT.equals(node.getNodeName())) { importBeanDefinitionResource(ele); } else if (ALIAS_ELEMENT.equals(node.getNodeName())) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); this.beanDefinitionReader.getBeanFactory().registerAlias(name, alias); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } } } return beanDefinitionCount; } 其他标签具体如何被解释这里就不多说,相信大家也能看得懂,这里主要讲一下解释bean的的处理,我们注意以下代码: Java代码 else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } 这里是当碰到一个bean标签的时候所进行的处理,也既是对bean的定义进行解释,可以看到parseBeanDefinitionElement方法的第一个参数就是bean则个元素,第二个参数表示该bean是否为内置的bean,从这里进行解释的bean都不可能是内置的,所以这里直接以false为参数,打开parseBeanDefinitionElement方法,就可以看到这个方法里就是对bean的内部的解释,也很简单,也不多讲了,呵呵(下班时间已经到了,所以就写这么多了,基本的流程也就这样,没什么特别难的地方。),对了,最后还有一点就是解释完后,bean的定义将会被保存到beanFactory中,这个beanFactory的实现就是XmlBeanFactory了,该beanFactory是在new的时候被传递到reader中的,就是该类中以下这行代码: Java代码 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 好了,就这么多了,本文只作为参考,只讲解了如何加载bean定义这块,只作为一个参考,希望对其他朋友能有所帮助吧,因为时间匆忙,有错漏的地方请指正。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值