本文同享自华为云社区《Spring高手之路16——解析XML装备映射为BeanDefinition的源码》,作者:砖业洋__。

1. BeanDefinition阶段的剖析

Spring结构中操控反转(IOC)容器的BeanDefinition阶段的详细进程,首要触及到Bean的界说、加载、解析,并在后面进行编程式注入和后置处理。这个阶段是Spring结构中Bean生命周期的早期阶段之一,关于了解整个Spring结构十分要害

  • 加载装备文件、装备类

在这一步,Spring容器经过装备文件或装备类来了解需求办理哪些Bean。关于根据XML的装备,一般运用ClassPathXmlApplicationContext或许FileSystemXmlApplicationContext

  • 解析装备文件、装备类并封装为BeanDefinition

Spring结构经过运用BeanDefinitionReader实例(如XmlBeanDefinitionReader)来解析装备文件。解析后,每个Bean装备会被封装成一个BeanDefinition方针,这个方针包含了类名、效果域、生命周期回调等信息。

  • 编程式注入额定的BeanDefinition

除了装备文件界说的Bean,也能够经过编程的办法动态添加BeanDefinitionIOC容器中,这添加了灵活性。

  • BeanDefinition的后置处理

BeanDefinition的后置处理是指容器答应运用BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor来对解析后的BeanDefinition做进一步处理,例如修正Bean的特点等。

2. 加载xml装备文件

2.1 XML装备文件中加载bean的代码示例

先给出最简单的代码示例,然后逐渐剖析

全部代码如下:

package com.example.demo.bean;
// HelloWorld.java
public class HelloWorld {
    private String message;
    public void setMessage(String message) {
        this.message = message;
    }
    public void sayHello() {
        System.out.println("Hello, "   message   "!");
    }
}

主程序:

package com.example.demo;
import com.example.demo.bean.HelloWorld;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        // 创立Spring上下文(容器)
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("ApplicationContext.xml");
        // 从容器中获取bean,假设咱们有一个名为 'helloWorld' 的bean
        HelloWorld helloWorld = context.getBean("helloWorld", HelloWorld.class);
        // 运用bean
        helloWorld.sayHello();
        // 封闭上下文
        context.close();
    }
}

xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- Bean界说 -->
    <bean id="helloWorld" class="com.example.demo.bean.HelloWorld">
        <!-- 设置特点 -->
        <property name="message" value="World"/>
    </bean>
</beans>

运转成果:

万字解析XML装备映射为BeanDefinition的源码

接着咱们就从这段代码开端剖析

2.2 setConfigLocations – 设置和保存装备文件途径

咱们还是以Spring 5.3.7的源码为例剖析

// 创立Spring上下文(容器)
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("ApplicationContext.xml");

这段代码,咱们利用idea点击去剖析,终究在ClassPathXmlApplicationContext的重载办法里看到调用了setConfigLocations设置装备文件的途径。

万字解析XML装备映射为BeanDefinition的源码

接着看看setConfigLocations办法

万字解析XML装备映射为BeanDefinition的源码

setConfigLocations() 办法的首要效果是设定 Spring 容器加载 Bean 界说时所需求读取的装备文件途径。这些途径能够是类途径下的资源、文件体系中的资源或许其他任何经过URL定位的资源。该办法保证一切供给的装备途径都被保存并在稍后的容器改写操作中运用。

源码提出来剖析:

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        // 运用Spring的Assert类来校验,保证传入的装备方位数组中没有null元素。
        Assert.noNullElements(locations, "Config locations must not be null");
        // 依据传入的装备方位数量,初始化内部存储装备方位的数组。
        this.configLocations = new String[locations.length];
        // 遍历传入的装备方位数组。
        for(int i = 0; i < locations.length;   i) {
            // 调用resolvePath办法处理每一个装备方位(或许进行必要的途径解析,如解析占位符)。
            // trim()用于移除字符串首尾的空格,保证保存的途径是净化的。
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        // 假如传入的装备方位是null,清除掉一切已设定的装备方位。
        this.configLocations = null;
    }
}

在上下文被改写的时分,这些装备文件方位会被读取,而且Spring容器将解析其间界说的beans并将它们注册到容器中。setConfigLocations() 办法只是设置了这些方位,而实践的加载和注册进程是在上下文改写时完结的。

这个setConfigLocations办法一般不是由用户直接调用的,而是在ApplicationContext初始化的进程中被结构调用,例如在根据XML的装备中,咱们会在初始化ClassPathXmlApplicationContextFileSystemXmlApplicationContext时供给装备文件的途径。

debug的时分,能够看到把测验代码中设置的 xml 装备文件的途径保存了。

万字解析XML装备映射为BeanDefinition的源码

2.3 refresh – 触发容器改写,装备文件的加载与解析

咱们上面看到ClassPathXmlApplicationContext办法里边,履行完setConfigLocations后,紧接着有个refresh办法,咱们来看看。

万字解析XML装备映射为BeanDefinition的源码

万字解析XML装备映射为BeanDefinition的源码

Spring结构中,refresh()办法是十分要害的,它是ApplicationContext接口的一部分。这个办法的首要功用是改写运用上下文,加载或许从头加载装备文件中界说的Bean,初始化一切的单例,装备消息资源,事情发布器等。

代码提出来剖析:

public void refresh() throws BeansException, IllegalStateException {
    // 同步块,保证容器改写进程的线程安全
    synchronized(this.startupShutdownMonitor) {
        // 开端上下文改写的进程记载,用于监控和诊断
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 准备改写进程,设置开端时间,状况标志等
        this.prepareRefresh();
        // 获取新的BeanFactory,假如是第一次改写则创立一个BeanFactory
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 装备BeanFactory,注册忽略的依靠接口等
        this.prepareBeanFactory(beanFactory);
        try {
            // 答应BeanFactory的后置处理器对其进行修正
            this.postProcessBeanFactory(beanFactory);
            // 开端Bean工厂的后置处理进程的监控
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 调用BeanFactoryPostProcessors
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 注册BeanPostProcessors到BeanFactory
            this.registerBeanPostProcessors(beanFactory);
            // Bean后置处理进程完毕
            beanPostProcess.end();
            // 初始化MessageSource组件,用于国际化等功用
            this.initMessageSource();
            // 初始化事情播送器
            this.initApplicationEventMulticaster();
            // 留给子类掩盖的定制办法
            this.onRefresh();
            // 注册监听器
            this.registerListeners();
            // 初始化剩下的单例Bean
            this.finishBeanFactoryInitialization(beanFactory);
            // 完结改写进程,告诉生命周期处理器lifecycleProcessor改写进程,发布ContextRefreshedEvent事情
            this.finishRefresh();
        } catch (BeansException var10) {
            // 捕获BeansException,记载警告信息,毁掉已创立的Bean
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: "   var10);
            }
            // 毁掉现已初始化的单例Bean
            this.destroyBeans();
            // 撤销改写,重置同步监视器上的标志位
            this.cancelRefresh(var10);
            // 抛出反常,完毕改写进程
            throw var10;
        } finally {
            // 在改写的终究,重置Spring内核中的同享缓存
            this.resetCommonCaches();
            // 完毕上下文改写进程的记载
            contextRefresh.end();
        }
    }
}

这个办法准确履行一系列进程来装备ApplicationContext,包含Bean的加载、注册和初始化。改写进程包含了Bean界说的载入、注册以及Bean的初始化等一系列杂乱的进程。

在现代Spring结构中,ApplicationContext一般在容器发动时改写一次。一旦容器发动而且上下文被改写,一切的Bean就被加载而且创立了。虽然技术上或许存在调用refresh()办法屡次的或许性,但这在实践中并不常见,由于这意味着重置运用上下文的状况并从头开端。这样做将毁掉一切的单例Bean,并从头初始化它们,这在大多数运用中是不可取的,不仅价值贵重而且或许导致状况丢掉、数据不一致等问题。

关于根据xmlApplicationContext(如ClassPathXmlApplicationContext),在调用refresh()办法时会从头读取和解析装备文件,然后从头创立BeanFactoryBean的界说。假如容器现已被改写过,则需求先毁掉一切的单例Bean,封闭BeanFactory,然后从头创立。一般,这个功用用于开发进程中或许测验中,不推荐在出产环境运用,由于它的开支和危险都很大。

咱们来看一下要点,加载装备文件的操作在哪里?这儿图上我标注出来了,obtainFreshBeanFactory办法里边有个refreshBeanFactory办法。

万字解析XML装备映射为BeanDefinition的源码

refreshBeanFactory办法是个笼统办法,咱们来看看完结类是怎样完结的,依据承继联系找到完结类的refreshBeanFactory办法。

万字解析XML装备映射为BeanDefinition的源码

refreshBeanFactory()办法一般在refresh()办法中被调用。这个办法保证当时ApplicationContext含有一个清洁状况的BeanFactory

代码提出来剖析:

protected final void refreshBeanFactory() throws BeansException {
    // 查看当时运用上下文是否现已包含了一个BeanFactory
    if (this.hasBeanFactory()) {
        // 假如现已存在BeanFactory,毁掉它办理的一切bean
        this.destroyBeans();
        // 封闭现有的BeanFactory,开释其或许持有的任何资源
        this.closeBeanFactory();
    }
    try {
        // 创立一个DefaultListableBeanFactory的新实例,这是Spring中ConfigurableListableBeanFactory接口的默许完结
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();
        // 为beanFactory设置一个序列化ID,这个ID后面能够用于反序列化
        beanFactory.setSerializationId(this.getId());
        // 答应子类定制新创立的beanFactory
        this.customizeBeanFactory(beanFactory);
        // 从底层资源(例如XML文件)中加载bean界说到beanFactory
        this.loadBeanDefinitions(beanFactory);
        // 将新的beanFactory赋值给这个上下文的beanFactory特点
        this.beanFactory = beanFactory;
    } catch (IOException var2) {
        // 假如在解析bean界说资源进程中产生I/O反常,将其包装并从头抛出为ApplicationContextException
        throw new ApplicationContextException("I/O过错解析用于"   this.getDisplayName()   "的bean界说源", var2);
    }
}

这个办法在AbstractApplicationContext的详细完结中被重写。它供给了改写bean工厂的模板——假如现已存在一个,则将其毁掉并封闭;然后创立一个新的bean工厂,进行定制,并填充bean界说。在加载bean界说(例如,从XML文件读取)时,假如遇到I/O反常,会抛出一个ApplicationContextException,供给有关过错性质的更多上下文信息。

这段代码咱们能够看到有loadBeanDefinitions办法,是从底层资源(例如XML文件)中加载bean界说到beanFactory,逻辑很杂乱,咱们下面来进行单独剖析。

2.4 loadBeanDefinitions – 详细的BeanDefinition加载逻辑

this.loadBeanDefinitions 办法是在 AbstractApplicationContext 的子类中完结的,这种形式是一个典型的模板办法规划形式的比方。在模板办法规划形式中,一个算法的结构(即一系列的进程)被界说在父类的办法中,可是一些进程的详细完结会推迟到子类中完结。

AbstractApplicationContext 供给了 refreshBeanFactory 办法的结构,这个办法界说了改写 BeanFactory 的进程,可是它将 loadBeanDefinitions 的详细完结留给了子类。子类需求依据详细的存储资源类型(比方 XML 文件、Java 注解、Groovy 脚本等)来完结这个办法。

万字解析XML装备映射为BeanDefinition的源码

子类AbstractXmlApplicationContext完结的loadBeanDefinitions 办法如下:

万字解析XML装备映射为BeanDefinition的源码

loadBeanDefinitions()办法是Spring结构中用于加载、解析并注册Bean界说的中心办法。其基本责任是从一个或多个源读取装备信息,然后将这些信息转换成Spring容器能够办理的Bean界说。这个办法一般在Spring上下文初始化进程中被调用,是Spring容器装载Bean界说的要害进程。

代码提出来剖析:

// 运用DefaultListableBeanFactory作为Bean界说注册的方针工厂,加载Bean界说
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 创立一个读取XML Bean界说的读取器,并将工厂传入用于注册界说
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    // 设置环境方针,或许包含特点解析相关的环境装备
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    // 设置资源加载器,答应读取器加载XML资源
    beanDefinitionReader.setResourceLoader(this);
    // 设置实体解析器,用于解析XML中的实体如DTD
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    // 初始化Bean界说读取器,或许设置一些参数,如是否验证XML
    this.initBeanDefinitionReader(beanDefinitionReader);
    // 调用重载的loadBeanDefinitions,依据装备的资源和方位加载Bean界说
    this.loadBeanDefinitions(beanDefinitionReader);
}
// 初始化Bean界说读取器,首要设置是否进行XML验证
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
    // 设置XML验证形式,一般取决于运用上下文的装备
    reader.setValidating(this.validating);
}
// 经过XmlBeanDefinitionReader加载Bean界说
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    // 获取一切装备资源的数组(如XML装备文件)
    Resource[] configResources = this.getConfigResources();
    // 假如装备资源非空,则加载这些资源
    if (configResources != null) {
        reader.loadBeanDefinitions(configResources);
    }
    // 获取一切装备文件方位的数组
    String[] configLocations = this.getConfigLocations();
    // 假如装备文件方位非空,则加载这些方位指定的装备文件
    if (configLocations != null) {
        reader.loadBeanDefinitions(configLocations);
    }
}

loadBeanDefinitions(DefaultListableBeanFactory beanFactory)办法中,首要创立了一个XmlBeanDefinitionReader实例,这个读取器是专门用来解析XML装备文件并把Bean界说加载到DefaultListableBeanFactory中。beanDefinitionReader的相关特点被设置了,包含环境变量、资源加载器和实体解析器。这些设置保证了beanDefinitionReader能正确地解析XML文件并能解析文件中的占位符和外部资源。

接着,经过调用initBeanDefinitionReader办法,能够对XmlBeanDefinitionReader实例进行一些额定的装备,例如设置XML验证。终究,调用loadBeanDefinitions(XmlBeanDefinitionReader reader)办法实践进行加载操作。这个办法会调用读取器来实践地读取和解析XML文件,把Bean界说加载到Spring容器中。

loadBeanDefinitions(XmlBeanDefinitionReader reader)办法中,首要测验从getConfigResources办法获取XML装备文件资源,假如存在这样的资源,则经过reader加载这些界说。其次,测验获取装备文件方位信息,假如存在,则经过reader加载这些方位指定的装备文件。这种规划答应从不同的来源加载装备,如直接从资源文件或许从指定的文件途径。

debug能够看到readerconfigLocations的详细状况

万字解析XML装备映射为BeanDefinition的源码

这儿看到还有一个reader.loadBeanDefinitions(configLocations);这是在做什么呢?下面接着来看!

2.5 loadBeanDefinitions – 由XmlBeanDefinitionReader完结

debug的时分能够看到这儿的readerXmlBeanDefinitionReader,点击盯梢reader.loadBeanDefinitions(configLocations);办法,调用的办法在AbstractBeanDefinitionReader,而XmlBeanDefinitionReader 承继自 AbstractBeanDefinitionReader

万字解析XML装备映射为BeanDefinition的源码

这儿装备文件循环加载,有一个count = this.loadBeanDefinitions(location); 持续盯梢!

万字解析XML装备映射为BeanDefinition的源码

这段代码的逻辑动作大致为:

  1. 依据传入的资源方位字符串,经过资源加载器(ResourceLoader)获取对应的资源。
  2. 假如资源加载器是资源形式解析器(ResourcePatternResolver),它会处理途径中的形式(比方通配符),加载一切匹配的资源。
  3. 读取资源,解析并注册其间界说的一切bean界说。
  4. 假如供给了一个实践资源的调集(actualResources),解分出来的资源将被添加到这个调集中。
  5. 回来加载并注册的bean界说的数量。

咱们还是看要点,持续盯梢里边的loadBeanDefinitions

万字解析XML装备映射为BeanDefinition的源码

代码提出来剖析:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    // 将Resource包装为EncodedResource,答应指定编码,然后持续加载Bean界说
    return this.loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    // 断语传入的EncodedResource不为空
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    // 假如日志等级为trace,则输出盯梢日志
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Loading XML bean definitions from "   encodedResource);
    }
    // 获取当时线程正在加载的资源调集
    Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    // 查看资源是否现已在加载中,假如是,则抛出BeanDefinitionStoreException反常,防止循环加载
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of "   encodedResource   " - check your import definitions!");
    } else {
        int var6; // 这将用来存储加载的Bean界说数量
        try {
            // 翻开资源的InputStream进行读取
            InputStream inputStream = encodedResource.getResource().getInputStream();
            Throwable var4 = null;
            try {
                // 将InputStream封装为InputSource,XML解析器能够接受这个类型
                InputSource inputSource = new InputSource(inputStream);
                // 假如资源编码不为空,设置资源的编码
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 实践加载Bean界说的办法,回来加载的Bean界说数量
                var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } catch (Throwable var24) {
                // 捕获Throwable以便在finally块中处理资源开释
                var4 = var24;
                throw var24;
            } finally {
                // 封闭InputStream资源
                if (inputStream != null) {
                    if (var4 != null) {
                        try {
                            inputStream.close();
                        } catch (Throwable var23) {
                            // 添加被按捺的反常
                            var4.addSuppressed(var23);
                        }
                    } else {
                        inputStream.close();
                    }
                }
            }
        } catch (IOException var26) {
            // 抛出IOException反常,假如解析XML文档失败
            throw new BeanDefinitionStoreException("IOException parsing XML document from "   encodedResource.getResource(), var26);
        } finally {
            // 从当时加载的资源调集中移除该资源
            currentResources.remove(encodedResource);
            // 假如当时加载的资源调集为空,则从ThreadLocal中移除
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
        // 回来加载的Bean界说数量
        return var6;
    }
}

在这段代码中,loadBeanDefinitions 首要将Resource转换为EncodedResource,这答应它保存关于资源编码的信息。然后,它测验将资源加载为InputStream并将其转换为InputSource,这是XML解析所需求的。接着它调用doLoadBeanDefinitions办法,实践上担任解析XML并注册Bean界说。

在这个进程中,代码保证了不会循环加载相同的资源,而且在加载资源时,假如产生反常,会适当地整理资源并陈述过错。加载的Bean界说数量在完结后被回来。

咱们来要点看下这段代码的要点进程:doLoadBeanDefinitions办法!

2.6 doLoadBeanDefinitions – 读取并解析XML装备文件内容

万字解析XML装备映射为BeanDefinition的源码

doLoadBeanDefinitions办法做了什么?

详细进程如下:

  1. 运用doLoadDocument办法将给定的InputSource解析为 DOM Document方针。这个Document方针代表了 XML 文件的结构。

  2. 经过调用registerBeanDefinitions办法,将解析得到的Document中的 Bean 界说注册到 SpringBean 工厂中。这个办法回来注册的 Bean 界说的数量。

  3. 假如日志等级设置为 DEBUG,则会记载加载的 Bean 界说数量。

这儿要点是registerBeanDefinitions办法,持续盯梢代码

万字解析XML装备映射为BeanDefinition的源码

持续看要点,终究追到doRegisterBeanDefinitions办法

万字解析XML装备映射为BeanDefinition的源码

doRegisterBeanDefinitions(Element root) 办法是 Spring 结构中用于解析 XML 装备文件中的 Bean 界说并注册它们到 Spring 容器的办法。这个办法一般在 XML 文件读取并转换成 DOM(Document Object Model)树之后调用,此刻 XML 文件的根元素经过参数 root 传递给这个办法。

代码提出来剖析:

protected void doRegisterBeanDefinitions(Element root) {
    // 保存旧的解析署理(delegate),以便之后能够康复
    BeanDefinitionParserDelegate parent = this.delegate;
    // 创立新的解析署理(delegate),用于处理当时XML根节点的解析
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    // 假如当时节点运用的是Spring默许的XML命名空间
    if (this.delegate.isDefaultNamespace(root)) {
        // 获取根节点的"profile"特点
        String profileSpec = root.getAttribute("profile");
        // 查看"profile"特点是否有文本内容
        if (StringUtils.hasText(profileSpec)) {
            // 按逗号、分号和空格分隔"profile"特点值,得到指定的profiles数组
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            // 假如当时环境不接受任何指定的profiles,则不加载该Bean界说文件
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                // 假如日志等级是DEBUG,则记载跳过文件的信息
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipped XML bean definition file due to specified profiles ["   profileSpec   "] not matching: "   this.getReaderContext().getResource());
                }
                // 退出办法,不进行后续处理
                return;
            }
        }
    }
    // 在解析XML前进行预处理,可被重写的办法
    this.preProcessXml(root);
    // 解析XML根节点下的Bean界说
    this.parseBeanDefinitions(root, this.delegate);
    // 在解析XML后进行后处理,可被重写的办法
    this.postProcessXml(root);
    // 康复旧的解析署理(delegate)
    this.delegate = parent;
}

上述代码片段是Spring结构用于注册Bean界说的内部办法。该办法在解析XML装备文件并注册Bean界说到Spring容器时被调用。它包含处理profile特点以依据运转时环境决议是否加载特定Bean界说的逻辑,以及前后处理钩子,答应在解析前后进行自界说操作。终究,它保证解析署理(delegate)被重置为之前的状况,以维护正确的状况。

接着,咱们要看看是怎么解析xml的,要点关注下parseBeanDefinitions办法

2.7 parseBeanDefinitions – 解析XML中的BeanDefinition元素

万字解析XML装备映射为BeanDefinition的源码

parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 办法的首要意图是遍历 XML 装备文件的根节点,解析并注册其间界说的一切 Bean。该办法担任区分不同类型的元素,即默许命名空间下的规范元素和自界说命名空间下的自界说元素,并对它们进行相应的处理。

代码提出来剖析:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 判别根节点是否运用的是Spring的默许命名空间
    if (delegate.isDefaultNamespace(root)) {
        // 获取一切子节点
        NodeList nl = root.getChildNodes();
        // 遍历一切子节点
        for (int i = 0; i < nl.getLength();   i) {
            Node node = nl.item(i);
            // 只处理Element类型的节点(过滤掉文本节点等其他类型)
            if (node instanceof Element) {
                Element ele = (Element)node;
                // 假如子元素节点也是默许命名空间,则调用parseDefaultElement办法解析
                if (delegate.isDefaultNamespace(ele)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    // 假如子元素节点不是默许命名空间,则调用parseCustomElement办法解析
                    // 这一般标明节点界说了自界说的行为,或许是用户自界说的标签或许是Spring扩展的标签
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 假如根节点不是默许命名空间,那么它或许是一个自界说标签的顶级元素
        // 在这种情况下,直接调用parseCustomElement进行解析
        delegate.parseCustomElement(root);
    }
}

这段代码的效果是解析XML文件中界说的bean。它查看每个XML元素(包含根元素和子元素),并依据这些元素是否归于Spring的默许命名空间(一般是”http://www.springframework.org/schema/beans“),调用不同的处理办法。假如元素归于默许命名空间,那么它将调用parseDefaultElement来解析规范的Spring装备元素,例如<bean>。假如元素不归于默许命名空间,那么将认为它是一个自界说元素,并调用parseCustomElement来解析。自界说元素一般是由开发人员界说或Spring扩展供给的,以添加结构的功用。

这儿能够看到是一个循环处理Element节点,解析的动作首要是parseDefaultElement办法,持续来看看。

万字解析XML装备映射为BeanDefinition的源码

parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 办法是 Spring 结构解析 XML 装备文件中默许命名空间(也便是没有前缀的 Spring 命名空间)元素的办法。这个办法专门处理 <import>, <alias>, <bean>, 和 <beans> 这几种标签。

“没有前缀的 Spring 命名空间” 是指那些元素?它们归于 Spring 的默许命名空间,但在运用时不需求指定命名空间前缀。如 <bean>, <property><constructor-arg> ,这些元素都是没有前缀的,它们归于 Spring 默许界说的 XML 形式命名空间,默许命名空间一般在 XML 文件的顶部经过 xmlns 特点声明。

代码提出来剖析:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 判别当时元素节点称号是否是"import"
    if (delegate.nodeNameEquals(ele, "import")) {
        // 假如是"import",则导入其他装备文件
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        // 假如节点是"alias",则处理别号界说,为一个bean界说一个或多个别号
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        // 假如节点是"bean",则处理bean界说,这是界说Spring bean的中心元素
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        // 假如节点是"beans",意味着有嵌套的beans界说,需求递归地注册其间的bean界说
        this.doRegisterBeanDefinitions(ele);
    }
}

这段代码的功用是依据元素的称号来决议对XML装备文件中的不同标签进行不同的处理操作。它处理Spring结构默许命名空间下的四种首要标签:

  1. <import>:导入其他Spring XML装备文件到当时的装备文件中。
  2. <alias>:为一个现已界说的bean供给一个或多个别号。
  3. <bean>:界说一个Spring办理的bean,是最常用的元素,包含了bean的详细装备。
  4. <beans>:界说一个beans的调集,一般是装备文件中的顶层元素,但也能够是嵌套界说,标明一个新的效果域或许上下文。

这样,Spring能够依据这些元从来构建运用上下文中的bean工厂。

调试能够发现,xml现已解分出开端的雏形了

万字解析XML装备映射为BeanDefinition的源码

在这儿好像没看到bean元素,这是怎样解析的呢?让咱们一步一步来,在上面提到的parseDefaultElement办法中有调用processBeanDefinition办法,来看看这是干嘛的。

2.8 processBeanDefinition – 对

标签进行详细解析和处理


万字解析XML装备映射为BeanDefinition的源码

processBeanDefinition办法是 Spring 结构中用于处理 <bean> XML 装备元素的办法。其意图是将 <bean> 元素中描述的信息转换为 Spring 内部运用的BeanDefinition方针,并将其注册到 Spring IoC 容器中。这是 Spring bean 生命周期中的一个要害进程,由于在这儿界说的 bean 会在容器发动时被实例化和办理

代码提出来剖析:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 运用代了解析bean界说元素,这触及将XML界说的<bean>元素转换成Spring的BeanDefinitionHolder方针,
    // 该方针包含了bean界说和称号。
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    // 查看解析是否回来了BeanDefinitionHolder方针。
    if (bdHolder != null) {
        // 如有需求,对bean界说进行装饰。这或许触及运用任何额定的特点或嵌套元素,
        // 这些都是bean界说的一部分,但不是规范<bean> XML装备的一部分。
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // 在注册中心注册bean界说。注册中心一般是持有一切bean界说的Spring IoC容器。
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException var5) {
            // 假如在bean注册进程中出现反常,陈述过错上下文并抛出反常。
            // 过错上下文包含bean的称号和引起问题的XML元素。
            this.getReaderContext().error("Failed to register bean definition with name '"   bdHolder.getBeanName()   "'", ele, var5);
        }
        // 在成功注册后,告诉任何监听器一个新的bean界说已被注册。
        // 这是Spring事情机制的一部分,答应对容器内的特定动作作出呼应。
        this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
    // 注意:假如bdHolder为空,则意味着bean界说元素没有被正确解析
    // 或许它不是要被注册的(例如,在笼统界说的情况下)。
    // 因此,在这种情况下,该办法不履行任何操作。
}

该办法一般在Spring结构的bean界说解析进程中运用,它处理根据供给的XML元素创立和注册bean界说的逻辑。BeanDefinitionParserDelegate 是一个协助类,担任处了解析特定Spring XML结构的细节。

debug这个类的时分,发现现已解分出这个beanclassid

万字解析XML装备映射为BeanDefinition的源码

有人会好奇了,这是怎么将 xml 元素封装为 BeanDefinitionHolder

万字解析XML装备映射为BeanDefinition的源码

parseBeanDefinitionElement办法是用来解析 Spring 装备文件中 <bean> 元素的界说,并生成对应的 BeanDefinitionHolder 方针。BeanDefinitionHolder 是一个包装类,它封装了 BeanDefinition 实例和该界说的称号(即beanid)以及别号(假如有的话)。

代码提出来剖析:

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    // 调用重载办法parseBeanDefinitionElement,并将BeanDefinition设置为null
    return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);
}
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 获取元素的id特点
    String id = ele.getAttribute("id");
    // 获取元素的name特点
    String nameAttr = ele.getAttribute("name");
    // 创立别号列表
    List<String> aliases = new ArrayList();
    if (StringUtils.hasLength(nameAttr)) {
        // 假如name特点非空,则运用分隔符切割name字符串,并将成果添加到别号列表
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
        aliases.addAll(Arrays.asList(nameArr));
    }
    // 默许情况下bean的称号运用id特点的值
    String beanName = id;
    if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
        // 假如id为空且别号列表非空,则运用别号列表中的第一个作为bean称号,并从列表中移除它
        beanName = aliases.remove(0);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No XML 'id' specified - using '"   beanName   "' as bean name and "   aliases   " as aliases");
        }
    }
    if (containingBean == null) {
        // 假如不是嵌套bean界说,则查看bean称号和别号的仅有性
        this.checkNameUniqueness(beanName, aliases, ele);
    }
    // 解析bean界说元素,回来AbstractBeanDefinition方针
    AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            // 假如bean称号为空,则测验生成bean称号
            try {
                if (containingBean != null) {
                    // 假如是内部bean,则运用特定的生成战略
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    // 不然运用默许战略
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        // 假如bean类名不为空,且生成的bean称号以类名最初,且未被运用,则将类名添加到别号列表
                        aliases.add(beanClassName);
                    }
                }
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name ["   beanName   "]");
                }
            } catch (Exception var9) {
                // 在称号生成进程中捕获反常,并记载过错
                this.error(var9.getMessage(), ele);
                return null;
            }
        }
        // 将别号列表转换为数组
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        // 创立并回来BeanDefinitionHolder方针
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    } else {
        // 假如bean界说为空,则回来null
        return null;
    }
}

这段代码担任解析XML中的<bean>元素,提取idname特点,并处理或许的别号。然后它创立一个AbstractBeanDefinition,这是Springbean界说的笼统表现形式。假如没有指定bean的称号,它会测验生成一个仅有的称号,并在必要时添加别号。终究,它回来一个包含一切这些信息的BeanDefinitionHolder。假如在解析进程中遇到任何问题,会记载过错并回来null

在这段代码中,会调用另一个重载办法,this.parseBeanDefinitionElement(ele, beanName, containingBean);这段代码里有封装 <bean> 其它特点的 parseBeanDefinitionAttributes 办法,咱们来看下

万字解析XML装备映射为BeanDefinition的源码

万字解析XML装备映射为BeanDefinition的源码

办法 parseBeanDefinitionAttributes 用于解析 Spring 装备文件中 <bean> 元素的特点,并将这些特点运用到传入的 AbstractBeanDefinition 方针上。这个进程是为了设置bean的效果域、是否推迟初始化、主动装配形式、依靠联系、是否作为主动装配的候选、是否是优先考虑的beanprimary)、初始化办法、毁掉办法、工厂办法和工厂bean称号等特点。办法处理了特点的默许值以及处理了一些特点的遗留格式(如 singleton)。

直接提出代码剖析:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
    // 查看是否运用了已抛弃的singleton特点,假如存在,则报错提示应该升级到scope特点
    if (ele.hasAttribute("singleton")) {
        this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    // 假如存在scope特点,则设置bean的效果域
    } else if (ele.hasAttribute("scope")) {
        bd.setScope(ele.getAttribute("scope"));
    // 假如没有设置scope特点可是有包含bean,则设置为包含bean的效果域
    } else if (containingBean != null) {
        bd.setScope(containingBean.getScope());
    }
    // 假如设置了abstract特点,依据该特点的值设置bean界说是否为笼统
    if (ele.hasAttribute("abstract")) {
        bd.setAbstract("true".equals(ele.getAttribute("abstract")));
    }
    // 解析lazy-init特点,默许运用装备的默许值,假如设置了则掩盖
    String lazyInit = ele.getAttribute("lazy-init");
    if (this.isDefaultValue(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit("true".equals(lazyInit));
    // 解析autowire特点,将字符串值转换为相应的主动装配形式
    String autowire = ele.getAttribute("autowire");
    bd.setAutowireMode(this.getAutowireMode(autowire));
    // 解析depends-on特点,将字符串值转换为数组,并设置为bean界说的依靠
    if (ele.hasAttribute("depends-on")) {
        String dependsOn = ele.getAttribute("depends-on");
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, ",; "));
    }
    // 解析autowire-candidate特点,设置bean是否可作为主动装配的候选者
    String autowireCandidate = ele.getAttribute("autowire-candidate");
    if (this.isDefaultValue(autowireCandidate)) {
        String defaultValue = this.defaults.getAutowireCandidates();
        if (defaultValue != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(defaultValue);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    } else {
        bd.setAutowireCandidate("true".equals(autowireCandidate));
    }
    // 解析primary特点,设置bean是否为primary
    if (ele.hasAttribute("primary")) {
        bd.setPrimary("true".equals(ele.getAttribute("primary")));
    }
    // 解析init-method特点,设置bean的初始化办法
    String initMethodName = ele.getAttribute("init-method");
    if (ele.hasAttribute("init-method")) {
        bd.setInitMethodName(initMethodName);
    // 假如没有设置可是有默许值,则运用默许值
    } else if (this.defaults.getInitMethod() != null) {
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }
    // 解析destroy-method特点,设置bean的毁掉办法
    String destroyMethodName = ele.getAttribute("destroy-method");
    if (ele.hasAttribute("destroy-method")) {
        bd.setDestroyMethodName(destroyMethodName);
    // 假如没有设置可是有默许值,则运用默许值
    } else if (this.defaults.getDestroyMethod() != null) {
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }
    // 解析factory-method特点,设置bean的工厂办法
    if (ele.hasAttribute("factory-method")) {
        bd.setFactoryMethodName(ele.getAttribute("factory-method"));
    }
    // 解析factory-bean特点,设置bean的工厂bean名
    if (ele.hasAttribute("factory-bean")) {
        bd.setFactoryBeanName(ele.getAttribute("factory-bean"));
    }
    // 回来装备好的bean界说
    return bd;
}

这段代码的中心功用是将XML装备文件中的特点转换为BeanDefinition方针的特点。关于每个特点,它首要查看该特点是否存在,假如存在,则读取其值并设置到BeanDefinition方针中。假如存在默许值,而且XML中没有供给特定值,则运用默许值。经过这种办法,Spring容器能够依据装备文件创立和办理bean

2.9 总结

从读取XML装备文件到注册BeanDefinition的完好流程:

万字解析XML装备映射为BeanDefinition的源码

加载装备文件

  • 图中”创立上下文”进程对应于实例化ClassPathXmlApplicationContext,这时会传入XML文件途径。

  • ClassPathXmlApplicationContext接受一个或多个XML文件途径作为构造参数。

初始化BeanFactory并进行改写

  • 在图中”履行refresh“进程标明refresh()办法被调用,这个办法会发动容器的初始化和改写进程。

  • refresh()办法中初始化BeanFactory,并准备对装备文件进行解析。

读取XML装备文件

  • 图中”加载Bean界说”进程代表XmlBeanDefinitionReader的效果,它担任读取和加载XML装备文件。

  • XmlBeanDefinitionReader 担任读取传入的XML装备文件。

解析XML文件

  • 图中的”解析XML“进程标明DefaultBeanDefinitionDocumentReader处理XML文件,这包含解析顶层<beans>标签。

  • DefaultBeanDefinitionDocumentReader 开端处理XML文件,解析<beans>这样的顶层标签。

  • 关于<bean>元素的解析,首要查看元素是否在默许命名空间。假如是,进行默许元素的解析;假如不是,默许命名空间之外的元素被认为是自界说元素,并交由delegate.parseCustomElement(ele)处理。

Bean界说的解析和注册

  • 图中的”注册Bean界说”、“处理别号”、“处理Bean”和“处理导入”进程对应于BeanDefinitionParserDelegate的各种解析活动,它触及解析beanidname、别号、特点、子元素等,以及将解析成果注册到BeanDefinitionRegistry

  • 运用BeanDefinitionParserDelegate来解析<bean>元素的细节,包含beanidname、别号等。

  • 解析<bean>元素的特点,如scopelazy-init等,并将这些值设置到BeanDefinition实例中。

  • 假如<bean>元素包含子元素(如<property><constructor-arg>),它们也将被解析并以相应的元数据形式加入到BeanDefinition中。

  • 生成的BeanDefinition将会注册到BeanDefinitionRegistry中,运用BeanDefinitionReaderUtils.registerBeanDefinition办法。

  • 假如解析进程中产生任何过错,会经过error办法记载过错信息。

事情发布

  • 在注册BeanDefinition后,ApplicationContext会发布一个组件注册事情,以告诉相关的监听器。这个进程答应完结了ApplicationListener接口或运用@EventListener注解的组件接收到这个事情,并依据需求进行呼应。例如,能够运用这个事情来触发某些自界说的逻辑,如额定的装备查看、发动某些后处理操作等。

这个详细流程显示了从加载装备文件到解析并注册BeanDefinition所触及的杂乱进程,它展现了Spring结构处理Bean声明和依靠联系的内部机制。这是Spring依靠注入中心功用的根底,保证了Bean能够依照界说被实例化和办理。

3. 源码阅览练习题

1. XML装备文件解析:

  • 解析Spring装备文件时,Spring容器运用了哪些组件?

Spring容器在解析装备文件时首要运用了 XmlBeanDefinitionReader 类。此外,还用到了 BeanDefinitionDocumentReader 来进行详细的文档读取。

  • BeanDefinitionReader 在装备文件解析中扮演什么人物?

BeanDefinitionReader 担任从XML文件读取bean界说并转换为Spring内部的 BeanDefinition 方针。

  • parseBeanDefinitionElement 办法是在什么时分被调用的?它的输出是什么?

parseBeanDefinitionElementXML元素被读取时调用,它的输出是 BeanDefinitionHolder 方针,其间包含了bean界说以及称号和别号。

2. Bean界说解析:

  • 描述一个bean界说从读取XML元素开端,到生成 BeanDefinition 方针的进程。

BeanDefinition 方针是经过读取XML中的 <bean> 元素并提取相关特点来创立的。这些特点包含bean的类名、效果域、生命周期回调等。

  • parseBeanDefinitionAttributes 办法在整个解析进程中的效果是什么?

parseBeanDefinitionAttributes 办法用于提取bean元素上的特点,并设置到 AbstractBeanDefinition 方针中。

  • 哪些XML特点会被 parseBeanDefinitionAttributes 办法处理,并怎么影响生成的 BeanDefinition 方针?

parseBeanDefinitionAttributes 办法处理的特点包含 scopelazy-initautowire 等,这些特点会决议bean的行为和它怎么与其他bean交互。

3. Bean称号与别号:

  • 假如XML元素中没有供给beanidnameSpring是怎么处理的?

假如没有供给idnameSpring会主动生成一个仅有的bean称号。它或许根据类名加上一定的序列号。提示:剖析parseBeanDefinitionElement办法时有说过。

  • 别号(alias)在Spring中有何用途?在 parseBeanDefinitionElement 办法中,别号是怎么被处理的?

别号可认为bean供给额定的称号,这在需求引用相同的bean但在不同上下文中运用不同称号时很有用。在 parseBeanDefinitionElement 办法中,别号是经过解析 name 特点并以逗号、分号或空格作为分隔符来处理的。

4. Bean效果域与生命周期特点:

  • 怎么界说一个bean的效果域(scope)?singletonprototype 有什么不同?

经过设置 <bean> 元素的 scope 特点界说bean的效果域。singleton 标明全局仅有实例,而 prototype 标明每次请求都创立一个新的实例。

  • lazy-initinit-methoddestroy-method 这些特点对bean的生命周期有什么影响?

lazy-init 特点确认bean是否应该在发动时推迟初始化,init-methoddestroy-method 界说了bean的初始化和毁掉时调用的办法。

5. Bean注册:

  • 一旦 BeanDefinition 方针被创立,Spring是怎么将其注册到容器中的?

BeanDefinition 方针在解析后,经过 DefaultListableBeanFactory.registerBeanDefinition 办法注册到Spring容器中。

  • 注册进程中,假如发现bean称号冲突,Spring会怎么处理?

假如发现称号冲突,会抛出 BeanDefinitionStoreException。假如是在不同的装备文件中界说相同称号的bean,后者一般会掩盖前者。

6. 反常处理:

  • XML装备不正确或运用了不合法的特点时,Spring是怎么反馈给用户的?

Spring会经过抛出 BeanDefinitionStoreException 来奉告用户装备过错。反常信息会详细阐明过错的原因和方位。

  • 剖析Spring中的过错处理机制,它关于开发者调试装备有何协助?

Spring的过错处理机制包含反常的详细信息和准确的定位,这关于开发者快速辨认装备过错十分有协助。

4. 常见疑问

4.1 在refresh进程中,Bean的生命周期是怎样的?每个Bean的状况是怎么被办理的?

实例化BeanFactory

refresh办法开端时,Spring会实例化一个新的BeanFactory,一般是DefaultListableBeanFactory,作为容器用于创立Bean实例。

加载Bean界说

然后,refresh调用loadBeanDefinitions来加载和注册Bean的界说。这些界说能够来源于XML装备文件、Java装备类或许扫描的注解。

BeanFactoryPostProcessor的履行

在一切Bean界说加载完结之后,但在Bean实例化之前,Spring会调用BeanFactoryPostProcessor。这些处理器能够对Bean界说(装备元数据)进行修正。

BeanPostProcessor的注册

接下来,Spring注册BeanPostProcessor实例。这些处理器能够对Bean的实例(创立和初始化后的方针)进行修正。

单例Bean的预实例化

随后,Spring会预实例化单例Bean。关于单例效果域的BeanSpring会创立并装备这些Bean,然后将它们放入缓存中。

依靠注入

Bean实例化后,Spring会进行依靠注入。此刻,Bean的特点将被设置,相关的依靠将被注入。

Bean初始化

之后,Bean将被初始化。假如Bean完结了InitializingBean接口,afterPropertiesSet办法会被调用;或许假如界说了init-method,指定的办法也会被调用。

Aware接口的调用

假如Bean完结了任何Aware接口,如ApplicationContextAwareBeanNameAware,它们将在初始化之前被调用。

BeanPostProcessor的后处理

BeanPostProcessor的前置处理(postProcessBeforeInitialization)和后置处理(postProcessAfterInitialization)办法在Bean初始化之前和之后被调用,它们能够进一步定制Bean

事情发布

一旦一切单例Bean都被初始化,Spring会发布ContextRefreshedEvent,标明ApplicationContext已被改写。

运用Bean

此刻,一切的Bean都准备就绪,并能够用于运用程序的其他部分。

封闭容器

当运用上下文被封闭时,假如Bean完结了DisposableBean接口,destroy办法会被调用;或许界说了destroy-method办法,它也会被履行来整理资源。

在整个生命周期进程中,每个Bean的状况被ApplicationContextBeanFactory盯梢和办理,从创立、依靠注入、初始化,到毁掉,保证Bean在正确的时机被创立和整理。

4.2 refresh办法是主动触发的吗?假如不是,那么是什么条件下需求手动触发?

在Spring中的refresh办法:

1. 何时触发:

  • 主动触发: 在初始化ApplicationContext的时分,比方在运用程序中运用new ClassPathXmlApplicationContext("config.xml")Spring容器发动进程中会主动调用refresh办法。

  • 手动触发: 假如在运用程序运转时需求从头加载装备(或许是修正了装备文件),能够手动调用refresh办法来完结。但这一般在开发或测验阶段用于特殊场景,由于它会导致整个运用上下文重建,包含一切的Bean方针。

2. 为什么需求手动触发:

一般情况下,Spring容器在发动时只需求加载一次装备,初始化一次每个Bean。除非有特殊需求,例如动态调整日志等级,从头加载装备文件中的特定Bean,不然不需求手动触发。

在Spring Boot中的refresh办法:

Spring Boot大大简化了Spring运用的装备和发动进程。它主动装备了SpringApplicationContext并在合适的时分调用了refresh办法。

1. 主动触发:

当运用Spring BootSpringApplication.run()办法发动运用时,Spring Boot会主动创立ApplicationContext,并在内部调用refresh办法。这个进程是主动的,开发者一般不需求关怀。

2. 或许的手动触发场景:

Spring Boot供给了actuator模块,其间/refresh端点能够用来从头加载装备(一般是与Spring Cloud Config结合运用)。这不是传统意义上的调用ApplicationContextrefresh办法,而是一种触发从头加载部分装备的机制,特别是标注了@RefreshScopeBean,它们能够在不从头发动整个运用的情况下更新。

一般情况下的主张:

  • 关于开发者来说,不该该在出产环境中随意手动调用refresh办法。由于这会导致整个运用的从头加载,影响性能并或许导致服务中止。

  • 假如需求动态更新装备,应当运用Spring Cloud ConfigSpring Boot Actuator/refresh端点,这是一种愈加安全和操控的办法来更新装备。

4.3 在Spring Boot中,refresh办法的行为是否有所不同?Spring Boot是否供给了更优的办法来处理运用上下文的改变?

Spring Boot中,refresh办法的基本行为保持不变,由于Spring Boot建立在Spring之上,遵循相同的基本原则。不过,Spring Boot确实为运用上下文的办理和改写供给了更多的主动化和便利性:

主动装备:

Spring Boot特有的主动装备特性减少了需求手动改写的场景。在发动时,它会主动装配Bean,一般不需求显式调用refresh

外部化装备:

Spring Boot支持强壮的外部化装备机制,答应经过装备文件、环境变量等办法来注入装备。这使得改变装备而不需求从头改写上下文成为或许。

条件改写:

Spring Boot运用条件注解(如@ConditionalOnClass@ConditionalOnBean等),这答应上下文依据环境或许特定条件动态调整其装备,减少了需求手动触发refresh的场景。

生命周期办理:

经过SpringApplication类,Spring Boot为运用生命周期供给了额定的办理能力。它处理了许多在传统Spring运用中需求手动完结的使命,如初始化和改写运用上下文。

Actuator endpoints:

关于运转中的运用,Spring Boot Actuator供给了一系列办理和监控的端点,其间一些能够用来改写装备(如/refresh端点)或许重启上下文(如/restart端点),这在某些情况下能够代替完好的运用重启。

装备更改监听:

运用Spring Cloud Config的运用能够在装备改变时主动改写上下文。在装备服务器上的改变能够被监听,而且能够触发客户端上下文的主动改写,而不需求手动干涉。

过错处理:

Spring Boot有一套默许的过错处理机制,特别是在Web运用程序中,它会供给默许的过错页面和/error端点。此外,开发者能够定制过错处理,以适应详细需求。

综上所述,Spring Boot供给了更为主动化的办法来处理运用上下文的改变,很多时分无需手动调用refresh办法。不过,假如需求在运转时动态改变Bean的装备,并期望这些改变立即收效,那么或许还需求运用Spring供给的refresh办法或经过Spring Boot Actuator的相关端点来达到这一意图。

点击关注,第一时间了解华为云新鲜技术~