Spring -- 加载BeanDefinition

把spring的源码拉下来,就可以开始学习之路了,为了走通一个过程。就拿org.springframework.context.support.ClassPathXmlApplicationContextTests#testGenericApplicationContextWithXmlBeanDefinitions 这个方法入手进行debug

@Test
    public void testGenericApplicationContextWithXmlBeanDefinitions() {
        // 创建context
        GenericApplicationContext ctx = new GenericApplicationContext();
        // 创建reader用于解析资源
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
        // 加载资源并解析为BeanDefinition
        reader.loadBeanDefinitions(new ClassPathResource(CONTEXT_B, getClass()));
        reader.loadBeanDefinitions(new ClassPathResource(CONTEXT_C, getClass()));
        reader.loadBeanDefinitions(new ClassPathResource(CONTEXT_A, getClass()));
        ctx.refresh();
        assertTrue(ctx.containsBean("service"));
        assertTrue(ctx.containsBean("logicOne"));
        assertTrue(ctx.containsBean("logicTwo"));
        ctx.close();
    }

方法首先创建了XMLBeanDefinitionReader用于解析资源
创建reader后调用loadBeanDefinitions()方法解析资源为BeanDefinition并且注册到factory中。

/**
     * Create a new AbstractBeanDefinitionReader for the given bean factory.
     * <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry
     * interface but also the ResourceLoader interface, it will be used as default
     * ResourceLoader as well. This will usually be the case for
     * {@link org.springframework.context.ApplicationContext} implementations.
     * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a
     * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
     * environment will be used by this reader.  Otherwise, the reader will initialize and
     * use a {@link StandardEnvironment}. All ApplicationContext implementations are
     * EnvironmentCapable, while normal BeanFactory implementations are not.
     * @param registry the BeanFactory to load bean definitions into,
     * in the form of a BeanDefinitionRegistry
     * @see #setResourceLoader
     * @see #setEnvironment
     */
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

我们看看创建XMLBeanDefinitionReader执行了什么,它的父类AbstractBeanDefinitionReader构造方法中,接收了一个Registry参数,并设置了这个类的registy,也就是当解析成为BeanDefinition后要用这个registry将其注册到BeanFactory中。
然后设置了resourceLoader,用于加载资源文件

/** System environment property source name: {@value}. */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value}. */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


    /**
     * Customize the set of property sources with those appropriate for any standard
     * Java environment:
     * <ul>
     * <li>{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}
     * <li>{@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
     * </ul>
     * <p>Properties present in {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} will
     * take precedence over those in {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}.
     * @see AbstractEnvironment#customizePropertySources(MutablePropertySources)
     * @see #getSystemProperties()
     * @see #getSystemEnvironment()
     */
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

最后设置了environment,这个就是spring的环境配置了,它干了两件事,一个是加载系统环境配置,一个是加载JVM系统配置。然后说是JVM系统配置的优先级比系统环境配置要高。举个例子把,就是说你在启动容器时配置JVM参数的优先级高于配置文件的优先级,也就是JVM参数会覆盖掉配置文件里的。配置文件当然是指.properties配置文件。spring boot 现在还有.yml的配置文件。当然配置文件的形式在这里不重要。


GenericApplicationContext

而我们传入的是GenericApplicationContext对象。这个对象既实现了ResourceLoader,又实现了EnvironmentCapable。所以registry、resourceLoader、environment都是GenericApplicationContext对象。当然如果没有实现这些接口的话,会创建一个默认的PathMatchingResourcePatternResolver和StandardEnvironment对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <import resource="import1.xml"/>

    <import resource="classpath:org/springframework/context/support/test/*/import2.xml"/>

    <context:property-override location="org/springframework/context/support/override.properties"/>

    <bean id="messageSource" class="org.springframework.context.support.StaticMessageSource"/>

    <bean class="org.springframework.context.support.FactoryBeanAndApplicationListener"/>

    <bean name="service" class="org.springframework.context.support.Service">
        <property name="resources" value="/org/springframework/context/support/test/context*.xml"/>
    </bean>

    <bean name="service2" class="org.springframework.context.support.Service" autowire="byName" depends-on="service">
        <property name="resources" value="/org/springframework/context/support/test/context*.xml"/>
    </bean>

    <bean name="autowiredService" class="org.springframework.context.support.AutowiredService" autowire="byName"/>

    <bean name="autowiredService2" class="org.springframework.context.support.AutowiredService" autowire="byType"/>

    <bean name="wrappedAssemblerOne" class="org.springframework.context.support.TestProxyFactoryBean">
        <property name="target" ref="assemblerOne"/>
    </bean>

    <bean name="wrappedAssemblerTwo" class="org.springframework.context.support.TestProxyFactoryBean">
        <property name="target" ref="assemblerTwo"/>
    </bean>
</beans>
/**
     * Load bean definitions from the specified XML file.
     * @param encodedResource the resource descriptor for the XML file,
     * allowing to specify an encoding to use for parsing the file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            // 获取资源的输入流
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                // 构建资源
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 解析资源
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                // 关闭流
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            // 解析完成后移除资源
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

然后调用 loadBeanDefinitions() 方法加载为BeanDefinition对象。具体的工作是在XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource)中进行的。
在XmlBeanDefinitionReader的首先做的是将当前资源保存到资源集合中,解析完成后再移除。然后做解析的准备工作。在最后调用doLoadBeanDefinitions()方法进行解析。


doLoadBeanDefinitions

doLoadBeanDefinitions()主要干了两件事,一个是将资源解析为Document文档对象,然后注册BeanDefinition。


image.png
/**
     * Register the bean definitions contained in the given DOM document.
     * Called by {@code loadBeanDefinitions}.
     * <p>Creates a new instance of the parser class and invokes
     * {@code registerBeanDefinitions} on it.
     * @param doc the DOM document
     * @param resource the resource descriptor (for context information)
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of parsing errors
     * @see #loadBeanDefinitions
     * @see #setDocumentReaderClass
     * @see BeanDefinitionDocumentReader#registerBeanDefinitions
     */
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // 创建reader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // 保存已经注册的BeanDefinition数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        // 解析Document对象为BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

在registerBeanDefinitions()方法中首先创建了一个BeanDefinitionDocumentReader对象,实际上就是DefaultBeanDefinitionDocumentReader对象。然后在第524行调用DefaultBeanDefinitionDocumentReader#registerBeanDefinitions注册


解析调用过程

上图的调用过程在此就不详细解析了。直接上图,以后填坑。


DefaultListableBeanFactory

注册BeanDefinition调用的是DefaultListableBeanFactory#registerBeanDefinition方法。DefaultListableBeanFactory可以说是默认的BeanFactory实现类了。
下图是DefaultListableBeanFactory实现的接口和继承的类,大概能知道这个BeanFactory有什么功能了。
@Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                // 打日志
            }
            else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    // 打日志
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    // 打日志
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            }
            else {
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

DefaultListableBeanFactory#registerBeanDefinition方法的大概调用过程就是这样了。被注释为打日志的那一部分的代码被我删掉了。因为占位置。
这段代码挺简单易懂的。大概就是先检查是否存在这个BeanDefinition,如果存在又允许重写的话,就重新覆盖之前的BeanDefinition。不然的话就抛异常了。
不存在的话就先检查是否有bean正在实例化。有的话就先对beanDefinitionMap加锁,防止bean实例化过程出错,然后放进beanDefinitionMap,更新beanDefinitionNames和manualSingletonNames,至于为什么要先重新拷贝一份呢,我想应该是为了防止并发导致的一些问题吧。没有的话直接操作。
最后,如果已经存在了BeanDefinition或者存在这个单例,就要重置一下里面的缓存。因为这个BeanDefinition有可能是beanDefinitionMap中其他BeanDefinition的BeanDefinition,所以都要刷新一次。


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容