写点什么

spring 系列之 IOC 容器实例化过程三

作者:王威07325
  • 2023-05-29
    上海
  • 本文字数:14989 字

    阅读完需:约 49 分钟

refresh 方法中的 obtainFreshBeanFactory 方法

文章开始之前先啰嗦几句,写 spring 系列的文章的时候是想着每个星期更新一篇文章,但是上篇文章到现在这篇文章已经过了差不多两个星期,跟自己开始的预期偏离的有点远了,心里还是有点不是那么不得意的,所以趁着春节期间,我争取每天一篇将 spring 系列全部发完。希望各位看完能点点赞



回到正题,上回我们说到 refresh 的 prepareRefresh 方法,今天我们来说 obtainFreshBeanFactory 这个方法。先贴上 refresh 方法的代码:


@Override  public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {      // Prepare this context for refreshing.      prepareRefresh();
// Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);
try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory);
// Initialize message source for this context. initMessageSource();
// Initialize event multicaster for this context. initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses. onRefresh();
// Check for listener beans and register them. registerListeners();
// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event. finishRefresh(); }
catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); }
// Destroy already created singletons to avoid dangling resources. destroyBeans();
// Reset 'active' flag. cancelRefresh(ex);
// Propagate exception to caller. throw ex; }
finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
复制代码


再提一嘴,可能博主用的 spring 的版本和你们的不同,代码跟你们的有些不是那么一致,但是大家需要明白的是,springIOC 的基本流程和结构是不会变的,至少到目前为止,还没有大的变化,因此 spring 版本不同并不影响我们学习 springIOC 的核心原理。


其实从 obtainFreshBeanFactory 这个方法的名称就大概能知道这个方法是获取一个刷新了的 bean 工厂,进入 obtainFreshBeanFactory 方法:


protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {    refreshBeanFactory();    ConfigurableListableBeanFactory beanFactory = getBeanFactory();    if (logger.isDebugEnabled()) {      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);    }    return beanFactory;  }
复制代码


既然是获取一个刷新了的 bean 工厂,第一步调用 refreshBeanFactory 刷新 bean 工厂就显得那么顺理成章,此方法是一个抽象方法,需要子类去实现,最终调用的是 AbstractRefreshableApplicationContext 类中的方法,实现如下:


@Override  protected final void refreshBeanFactory() throws BeansException {    if (hasBeanFactory()) {    //如果已经存在,那么销毁之前的      destroyBeans();      closeBeanFactory();    }    try {    //创建了一个DefaultListableBeanFactory对象,这个需要多加注意,之后这个bean工厂出现的次数有点多      DefaultListableBeanFactory beanFactory = createBeanFactory();      beanFactory.setSerializationId(getId());      customizeBeanFactory(beanFactory);      loadBeanDefinitions(beanFactory);      synchronized (this.beanFactoryMonitor) {        this.beanFactory = beanFactory;      }    }    catch (IOException ex) {      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);    }  }
复制代码


AbstractRefreshableApplicationContext.customizeBeanFactory 方法用于给子类提供一个自由配置的机会,默认实现:


protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {    if (this.allowBeanDefinitionOverriding != null) {        //默认false,不允许覆盖        beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);    }    if (this.allowCircularReferences != null) {        //默认false,不允许循环引用        beanFactory.setAllowCircularReferences(this.allowCircularReferences);    }}
复制代码


AbstractXmlApplicationContext.loadBeanDefinitions,这个便是核心的 bean 加载了:


@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {    // Create a new XmlBeanDefinitionReader for the given BeanFactory.    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);    // Configure the bean definition reader with this context's    // resource loading environment.    beanDefinitionReader.setEnvironment(this.getEnvironment());    beanDefinitionReader.setResourceLoader(this);    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));    // Allow a subclass to provide custom initialization of the reader,    // then proceed with actually loading the bean definitions.    //默认空实现    initBeanDefinitionReader(beanDefinitionReader);    loadBeanDefinitions(beanDefinitionReader);}
复制代码


XmlBeanDefinitionReaderResourceLoaderBeanDefinitionReader 这几个词汇应该都不陌生了,在 springIOC 的容器结构的时候就做了一个详细的介绍,这里就不过多介绍了,我们来说一下 EntityResolver,EntityResolver 接口在 org.xml.sax 中定义。DelegatingEntityResolver 用于 schema 和 dtd 的解析(schema 和 dtd 是 xml 文件的约束,想详细了解的可以去问度娘)。我们再来看看**loadBeanDefinitions(beanDefinitionReader);**方法:


protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {    Resource[] configResources = getConfigResources();    if (configResources != null) {        reader.loadBeanDefinitions(configResources);    }    String[] configLocations = getConfigLocations();    //here    if (configLocations != null) {        reader.loadBeanDefinitions(configLocations);    }}
复制代码


AbstractBeanDefinitionReader.loadBeanDefinitions:


@Overridepublic int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {    Assert.notNull(locations, "Location array must not be null");    int counter = 0;    for (String location : locations) {        counter += loadBeanDefinitions(location);    }    return counter;}
复制代码


之后调用:


//第二个参数为空public int loadBeanDefinitions(String location, Set<Resource> actualResources) {    ResourceLoader resourceLoader = getResourceLoader();    //参见ResourceLoader类图,ClassPathXmlApplicationContext实现了此接口    if (resourceLoader instanceof ResourcePatternResolver) {        // Resource pattern matching available.        try {            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);            int loadCount = loadBeanDefinitions(resources);            if (actualResources != null) {                for (Resource resource : resources) {                    actualResources.add(resource);                }            }            return loadCount;        }        catch (IOException ex) {            throw new BeanDefinitionStoreException(                    "Could not resolve bean definition resource pattern [" + location + "]", ex);        }    }    else {        // Can only load single resources by absolute URL.        Resource resource = resourceLoader.getResource(location);        int loadCount = loadBeanDefinitions(resource);        if (actualResources != null) {            actualResources.add(resource);        }        return loadCount;    }}
复制代码


getResource 的实现在 AbstractApplicationContext:


@Overridepublic Resource[] getResources(String locationPattern) throws IOException {    //构造器中初始化,PathMatchingResourcePatternResolver对象    return this.resourcePatternResolver.getResources(locationPattern);}
复制代码


PathMatchingResourcePatternResolver 是 ResourceLoader 继承体系的一部分。


@Overridepublic Resource[] getResources(String locationPattern) throws IOException {    Assert.notNull(locationPattern, "Location pattern must not be null");    //classpath:    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {        // a class path resource (multiple resources for same name possible)        //matcher是一个AntPathMatcher对象        if (getPathMatcher().isPattern(locationPattern            .substring(CLASSPATH_ALL_URL_PREFIX.length()))) {            // a class path resource pattern            return findPathMatchingResources(locationPattern);        } else {            // all class path resources with the given name            return findAllClassPathResources(locationPattern                .substring(CLASSPATH_ALL_URL_PREFIX.length()));        }    } else {        // Only look for a pattern after a prefix here        // (to not get fooled by a pattern symbol in a strange prefix).        int prefixEnd = locationPattern.indexOf(":") + 1;        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {            // a file pattern            return findPathMatchingResources(locationPattern);        }        else {            // a single resource with the given name            return new Resource[] {getResourceLoader().getResource(locationPattern)};        }    }}
复制代码


isPattern:


@Overridepublic boolean isPattern(String path) {    return (path.indexOf('*') != -1 || path.indexOf('?') != -1);}
复制代码


可以看出配置文件路径是支持 ant 风格的,也就是可以这么写:


new ClassPathXmlApplicationContext("con*.xml");
复制代码


然后就是配置文件的加载:


//加载Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);//解析int loadCount = loadBeanDefinitions(resources);
复制代码


最终逐个调用 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法:


@Overridepublic int loadBeanDefinitions(Resource resource) {    return loadBeanDefinitions(new EncodedResource(resource));}
复制代码


EncodedResource 扮演的其实是一个装饰器的模式,为 InputStreamSource 添加了字符编码(虽然默认为 null)。这样为我们自定义 xml 配置文件的编码方式提供了机会。


之后关键的源码只有两行:


public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {    InputStream inputStream = encodedResource.getResource().getInputStream();    InputSource inputSource = new InputSource(inputStream);    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}
复制代码


InputSource 是 org.xml.sax 的类。doLoadBeanDefinitions:


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {    Document doc = doLoadDocument(inputSource, resource);    return registerBeanDefinitions(doc, resource);}
复制代码


doLoadDocument:


protected Document doLoadDocument(InputSource inputSource, Resource resource) {    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,        getValidationModeForResource(resource), isNamespaceAware());}
复制代码


documentLoader 是一个 DefaultDocumentLoader 对象,此类是 DocumentLoader 接口的唯一实现。getEntityResolver 方法返回 ResourceEntityResolver,上面说过了。errorHandler 是一个 SimpleSaxErrorHandler 对象。


校验模型其实就是确定 xml 文件使用 xsd 方式还是 dtd 方式来校验,忘了的话左转度娘。Spring 会通过读取 xml 文件的方式判断应该采用哪种。


NamespaceAware 默认 false,因为默认配置了校验为 true。


DefaultDocumentLoader.loadDocument:


@Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver,    ErrorHandler errorHandler, int validationMode, boolean namespaceAware) {    //这里就是老套路了,可以看出,Spring还是使用了dom的方式解析,即一次全部load到内存    DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);    DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);    return builder.parse(inputSource);}
复制代码


createDocumentBuilderFactory 比较有意思:


protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware{    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();    factory.setNamespaceAware(namespaceAware);    if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {        //此方法设为true仅对dtd有效,xsd(schema)无效        factory.setValidating(true);        if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {            // Enforce namespace aware for XSD...             //开启xsd(schema)支持            factory.setNamespaceAware(true);             //这个也是Java支持Schema的套路,可以问度娘            factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);        }    }    return factory;}
复制代码


XmlBeanDefinitionReader.registerBeanDefinitions:


public int registerBeanDefinitions(Document doc, Resource resource) {    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();    int countBefore = getRegistry().getBeanDefinitionCount();    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));    return getRegistry().getBeanDefinitionCount() - countBefore;}
复制代码


createBeanDefinitionDocumentReader:


protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {    return BeanDefinitionDocumentReader.class.cast      //反射      (BeanUtils.instantiateClass(this.documentReaderClass));}
复制代码


documentReaderClass 默认是 DefaultBeanDefinitionDocumentReader,这其实也是策略模式,通过 setter 方法可以更换其实现。


注意 cast 方法,代替了强转。


createReaderContext:


public XmlReaderContext createReaderContext(Resource resource) {    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,        this.sourceExtractor, this, getNamespaceHandlerResolver());}
复制代码


problemReporter 是一个 FailFastProblemReporter 对象。


eventListener 是 EmptyReaderEventListener 对象,此类里的方法都是空实现。


sourceExtractor 是 NullSourceExtractor 对象,直接返回空,也是空实现。


getNamespaceHandlerResolver 默认返回 DefaultNamespaceHandlerResolver 对象,用来获取 xsd 对应的处理器。


XmlReaderContext 的作用感觉就是这一堆参数的容器,糅合到一起传给 DocumentReader,并美其名为 Context。可以看出,Spring 中到处都是策略模式,大量操作被抽象成接口。


DefaultBeanDefinitionDocumentReader.registerBeanDefinitions:


@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {    this.readerContext = readerContext;    Element root = doc.getDocumentElement();    doRegisterBeanDefinitions(root);}
复制代码


doRegisterBeanDefinitions:


protected void doRegisterBeanDefinitions(Element root) {    BeanDefinitionParserDelegate parent = this.delegate;    this.delegate = createDelegate(getReaderContext(), root, parent);    //默认的命名空间即    //http://www.springframework.org/schema/beans    if (this.delegate.isDefaultNamespace(root)) {        //检查profile属性        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);        if (StringUtils.hasText(profileSpec)) {            //profile属性可以以,分割            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {                return;            }        }    }    preProcessXml(root);    parseBeanDefinitions(root, this.delegate);    postProcessXml(root);    this.delegate = parent;}
复制代码


delegate 的作用在于处理 beans 标签的嵌套,其实 Spring 配置文件是可以写成这样的:


<?xml version="1.0" encoding="UTF-8"?>    <beans>        <bean class="base.SimpleBean"></bean>    <beans>        <bean class="java.lang.Object"></bean>    </beans></beans>
复制代码


xml(schema)的命名空间其实类似于 java 的报名,命名空间采用 URL,比如 Spring 的是这样:


<?xml version="1.0" encoding="UTF-8"?>    <beans xmlns="http://www.springframework.org/schema/beans"></beans>
复制代码


注意一下 profile 的检查, AbstractEnvironment.acceptsProfiles:


@Overridepublic boolean acceptsProfiles(String... profiles) {    Assert.notEmpty(profiles, "Must specify at least one profile");    for (String profile : profiles) {        if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {            if (!isProfileActive(profile.substring(1))) {                return true;            }        } else if (isProfileActive(profile)) {            return true;        }    }    return false;}
复制代码


原理很简单,注意从源码可以看出,profile 属性支持!取反。


preProcessXml 方法是个空实现,供子类去覆盖,目的在于给子类一个把我们自定义的标签转为 Spring 标准标签的机会, 想的真周到。


DefaultBeanDefinitionDocumentReader.parseBeanDefinitions:


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {    if (delegate.isDefaultNamespace(root)) {        NodeList nl = root.getChildNodes();        for (int i = 0; i < nl.getLength(); i++) {            Node node = nl.item(i);            if (node instanceof Element) {                Element ele = (Element) node;                if (delegate.isDefaultNamespace(ele)) {                    parseDefaultElement(ele, delegate);                } else {                    delegate.parseCustomElement(ele);                }            }        }    } else {        delegate.parseCustomElement(root);    }}
复制代码


可见,对于非默认命名空间的元素交由 delegate 处理。即 import, alias, bean, 嵌套的 beans 四种元素。parseDefaultElement:


private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {    //"import"    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {        importBeanDefinitionResource(ele);    }    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {        processAliasRegistration(ele);    }    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {        processBeanDefinition(ele, delegate);    }    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {        // recurse        doRegisterBeanDefinitions(ele);    }}
复制代码


写法示例:


<import resource="CTIContext.xml" /><import resource="customerContext.xml" />
复制代码


importBeanDefinitionResource 套路和之前的配置文件加载完全一样,不过注意被 import 进来的文件是先于当前文件 被解析的。


加入有一个 bean 名为 componentA-dataSource,但是另一个组件想以 componentB-dataSource 的名字使用,就可以这样定义:


<alias name="componentA-dataSource" alias="componentB-dataSource"/>
复制代码


processAliasRegistration 核心源码:


protected void processAliasRegistration(Element ele) {    String name = ele.getAttribute(NAME_ATTRIBUTE);    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);    getReaderContext().getRegistry().registerAlias(name, alias);    getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));}
复制代码


从前面的源码可以发现,registry 其实就是 DefaultListableBeanFactory,它实现了 BeanDefinitionRegistry 接口。registerAlias 方法的实现在 SimpleAliasRegistry:


@Overridepublic void registerAlias(String name, String alias) {    Assert.hasText(name, "'name' must not be empty");    Assert.hasText(alias, "'alias' must not be empty");    //名字和别名一样    if (alias.equals(name)) {        //ConcurrentHashMap        this.aliasMap.remove(alias);    } else {        String registeredName = this.aliasMap.get(alias);        if (registeredName != null) {            if (registeredName.equals(name)) {                // An existing alias - no need to re-register                return;            }            if (!allowAliasOverriding()) {                throw new IllegalStateException                    ("Cannot register alias '" + alias + "' for name '" +                    name + "': It is already registered for name '" + registeredName + "'.");            }        }        checkForAliasCircle(name, alias);        this.aliasMap.put(alias, name);    }}
复制代码


所以别名关系的保存使用 Map 完成,key 为别名,value 为本来的名字。


bean 节点是 Spring 最最常见的节点了。


DefaultBeanDefinitionDocumentReader.processBeanDefinition:


protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);    if (bdHolder != null) {        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);        try {            // Register the final decorated instance.            BeanDefinitionReaderUtils.registerBeanDefinition                (bdHolder, getReaderContext().getRegistry());        }        catch (BeanDefinitionStoreException ex) {            getReaderContext().error("Failed to register bean definition with name '" +                    bdHolder.getBeanName() + "'", ele, ex);        }        // Send registration event.        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));    }}
复制代码


id & name 处理最终调用 BeanDefinitionParserDelegate.parseBeanDefinitionElement(Element ele, BeanDefinition containingBean),源码较长,分部分说明。


首先获取到 id 和 name 属性,name 属性支持配置多个,以逗号分隔,如果没有指定 id,那么将以第一个 name 属性值代替。id 必须是唯一的,name 属性其实是 alias 的角色,可以和其它的 bean 重复,如果 name 也没有配置,那么其实什么也没做。


String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);List<String> aliases = new ArrayList<String>();if (StringUtils.hasLength(nameAttr)) {    //按,分隔    String[] nameArr = StringUtils.tokenizeToStringArray        (nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);    aliases.addAll(Arrays.asList(nameArr));}String beanName = id;if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {    //name的第一个值作为id    beanName = aliases.remove(0);}//默认nullif (containingBean == null) {    //校验id是否已重复,如果重复直接抛异常    //校验是通过内部一个HashSet完成的,出现过的id都会保存进此Set    checkNameUniqueness(beanName, aliases, ele);}
复制代码


如果 name 和 id 属性都没有指定,那么 Spring 会自己生成一个, BeanDefinitionParserDelegate.parseBeanDefinitionElement:


beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();aliases.add(beanClassName);
复制代码


可见,Spring 同时会把类名作为其别名。


最终调用的是 BeanDefinitionReaderUtils.generateBeanName:


public static String generateBeanName(        BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) {    String generatedBeanName = definition.getBeanClassName();    if (generatedBeanName == null) {        if (definition.getParentName() != null) {            generatedBeanName = definition.getParentName() + "$child";             //工厂方法产生的bean        } else if (definition.getFactoryBeanName() != null) {            generatedBeanName = definition.getFactoryBeanName() + "$created";        }    }    String id = generatedBeanName;    if (isInnerBean) {        // Inner bean: generate identity hashcode suffix.        id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR +             ObjectUtils.getIdentityHexString(definition);    } else {        // Top-level bean: use plain class name.        // Increase counter until the id is unique.        int counter = -1;         //用类名#自增的数字命名        while (counter == -1 || registry.containsBeanDefinition(id)) {            counter++;            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;        }    }    return id;}
复制代码


还是分部分说明(parseBeanDefinitionElement)首先获取到 bean 的 class 属性和 parent 属性,配置了 parent 之后,当前 bean 会继承父 bean 的属性。之后根据 class 和 parent 创建 BeanDefinition 对象


String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {    className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {    parent = ele.getAttribute(PARENT_ATTRIBUTE);}AbstractBeanDefinition bd = createBeanDefinition(className, parent);
复制代码


BeanDefinition 的创建在 BeanDefinitionReaderUtils.createBeanDefinition:


public static AbstractBeanDefinition createBeanDefinition(        String parentName, String className, ClassLoader classLoader) {    GenericBeanDefinition bd = new GenericBeanDefinition();    bd.setParentName(parentName);    if (className != null) {        if (classLoader != null) {            bd.setBeanClass(ClassUtils.forName(className, classLoader));        }        else {            bd.setBeanClassName(className);        }    }    return bd;}
复制代码


之后是解析 bean 的其它属性,其实就是读取其配置,调用相应的 setter 方法保存在 BeanDefinition 中:


parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
复制代码


之后解析 bean 的 decription 子元素


<bean id="b" name="one, two" class="base.SimpleBean">    <description>SimpleBean</description></bean>
复制代码


就仅仅是个描述。


然后是 meta 子元素的解析,meta 元素在 xml 配置文件里是这样的:


<bean id="b" name="one, two" class="base.SimpleBean">    <meta key="name" value="skywalker"/></bean>
复制代码


注释上说,这样可以将任意的元数据附到对应的 bean definition 上。解析过程源码:


public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {    NodeList nl = ele.getChildNodes();    for (int i = 0; i < nl.getLength(); i++) {        Node node = nl.item(i);        if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {            Element metaElement = (Element) node;            String key = metaElement.getAttribute(KEY_ATTRIBUTE);            String value = metaElement.getAttribute(VALUE_ATTRIBUTE);             //就是一个key, value的载体,无他            BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);             //sourceExtractor默认是NullSourceExtractor,返回的是空            attribute.setSource(extractSource(metaElement));            attributeAccessor.addMetadataAttribute(attribute);        }    }}
复制代码


AbstractBeanDefinition 继承自 BeanMetadataAttributeAccessor 类,底层使用了一个 LinkedHashMap 保存 metadata。这个 metadata 具体是做什么暂时还不知道。


lookup-method 解析:


此标签的作用在于当一个 bean 的某个方法被设置为 lookup-method 后,每次调用此方法时,都会返回一个新的指定 bean 的对象。用法示例:


<bean id="apple" class="cn.com.willchen.test.di.Apple" scope="prototype"/><!--水果盘--><bean id="fruitPlate" class="cn.com.willchen.test.di.FruitPlate">    <lookup-method name="getFruit" bean="apple"/></bean>
复制代码


replace-mothod 解析:


此标签用于替换 bean 里面的特定的方法实现,替换者必须实现 Spring 的 MethodReplacer 接口,有点像 aop 的意思。


配置文件示例:


<bean name="replacer" class="springroad.deomo.chap4.MethodReplace" />  <bean name="testBean" class="springroad.deomo.chap4.LookupMethodBean">    <replaced-method name="test" replacer="replacer">        <arg-type match="String" />    </replaced-method>  </bean> 
复制代码


解析之后将数据放在 ReplaceOverride 对象中,里面有一个 LinkedList 专门用于保存 arg-type。


构造参数(constructor-arg)解析:


作用一目了然,使用示例:


<bean class="base.SimpleBean">    <constructor-arg>        <value type="java.lang.String">Cat</value>    </constructor-arg></bean>
复制代码


type 一般不需要指定,除了泛型集合那种。除此之外,constructor-arg 还支持 name, index, ref 等属性,可以具体的指定参数的位置等。构造参数解析后保存在 BeanDefinition 内部一个 ConstructorArgumentValues 对象中。如果设置了 index 属性,那么以 Map<Integer, ValueHolder>的形式保存,反之,以 List 的形式保存。


property 解析:


非常常用的标签,用以为 bean 的属性赋值,支持 value 和 ref 两种形式,示例:


<bean class="base.SimpleBean">    <property name="name" value="skywalker" /></bean>
复制代码


value 和 ref 属性不能同时出现,如果是 ref,那么将其值保存在不可变的 RuntimeBeanReference 对象中,其实现了 BeanReference 接口,此接口只有一个 getBeanName 方法。如果是 value,那么将其值保存在 TypedStringValue 对象中。最终将对象保存在 BeanDefinition 内部一个 MutablePropertyValues 对象中(内部以 ArrayList 实现)。


qualifier 解析:


配置示例:


<bean class="base.Student">    <property name="name" value="skywalker"></property>    <property name="age" value="12"></property>    <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="student" /></bean>  <bean class="base.Student">    <property name="name" value="seaswalker"></property>    <property name="age" value="15"></property>    <qualifier value="student_2"></qualifier></bean><bean class="base.SimpleBean" />
复制代码


SimpleBean 部分源码:


@Autowired@Qualifier("student")private Student student;
复制代码


此标签和 @Qualifier, @Autowired 两个注解一起使用才有作用。@Autowired 注解采用按类型查找的方式进行注入,如果找到多个需要类型的 bean 便会报错,有了 @Qualifier 标签就可以再按照此注解指定的名称查找。两者结合相当于实现了按类型+名称注入。type 属性可以不指定,因为默认就是那个。qualifier 标签可以有 attribute 子元素,比如:


<qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="student">    <attribute key="id" value="1"/></qualifier>
复制代码


貌似是用来在 qualifier 也区分不开的时候使用。attribute 键值对保存在 BeanMetadataAttribute 对象中。整个 qualifier 保存在 AutowireCandidateQualifier 对象中。


再讲个 bean 的注册:BeanDefinitionReaderUtils.registerBeanDefinition:


public static void registerBeanDefinition(    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {    // Register bean definition under primary name.    String beanName = definitionHolder.getBeanName();    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());    // Register aliases for bean name, if any.    String[] aliases = definitionHolder.getAliases();    if (aliases != null) {        for (String alias : aliases) {            registry.registerAlias(beanName, alias);        }    }}
复制代码


registry 其实就是 DefaultListableBeanFactory 对象,registerBeanDefinition 方法主要就干了这么两件事:


@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {    this.beanDefinitionMap.put(beanName, beanDefinition);    this.beanDefinitionNames.add(beanName);}
复制代码


一个是 Map,另一个是 List,一目了然。registerAlias 方法的实现在其父类 SimpleAliasRegistry,就是把键值对放在了一个 ConcurrentHashMap 里。ComponentRegistered 事件触发:默认是个空实现,前面说过了。


这篇文章的篇幅比较长,但是也是比较重要的一票希望大家看完都能有所收获,最后祝大家新年快乐!!!

发布于: 刚刚阅读数: 4
用户头像

王威07325

关注

还未添加个人签名 2021-12-29 加入

还未添加个人简介

评论

发布
暂无评论
spring系列之IOC容器实例化过程三_spring ioc_王威07325_InfoQ写作社区