九零后大叔
技术人的点点滴滴

beanfactory篇-(十四)自定义标签是如何奏效的

nonecoding阅读(164)

回顾:通过前面一个章节的了解,我们大概清楚的了解了spring默认标签的解析过程。那么本章节和后面一个章节,我将来细致的看看自定义标签如何使用以及自定义标签是如何成功的被解析的。


一、自定义标签的基础知识

1.spring的默认标签与自定义标签
[1] 常用的默认标签和使用如下:

<import resource="applicationContext-dao.xml"/>

<alias name="sqlSessionFactory" alias="sqlSessionFactory1"/>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 配置数据源 -->
    <property name="dataSource" ref="dataSourceMaster"/>
    <!-- 加载mybatis的配置文件 -->
    <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"/>
    <!-- 自动扫描mapping.xml文件,**表示迭代查找 -->
    <property name="mapperLocations" value="classpath*:com/minesoft/tutorial/mapper/**/*.xml" />
</bean>

[2] 常用的自定义标签和使用如下:

<!-- 配置注解扫描 -->
<context:component-scan base-package="com.minesoft.tutorial,com.minesoft.tutorial.controller">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<!-- 属性文件配置 -->
<context:property-placeholder location="classpath*:properties/*.properties" ignore-unresolvable="true" order="2"/>

<!-- 事物处理 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>

<!-- 配置切面处理 -->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

<!--配置静态资源访问路径-->
<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
<mvc:resources location="/WEB-INF/css/" mapping="/css/**"/>

2.自定义标签的相关概念

[1] 标签命名空间和标签格式校验文件介绍。首先我们先看看applicationContext.xml文件头信息:

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:mvc="http://www.springframework.org/schema/mvc" 
   xmlns:aop="http://www.springframework.org/schema/aop"

   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
   http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

每一个自定义标签都归属到一个命名空间名下,而applicationContext.xml文件的头部的这些信息就是该文件内部所有使用自定标签所对应的命名空间。很类似于我们在使用某个java类的时候,我们需要在文件的头部import这个我们需要使用的java类的全限定名;同样的道理,我们想要使用某个自定义标签的时候,我们也需要在文件的头部引入这个自定义标签的命名空间。

不同于java的地方在于,除了引入这个命名空间之外,我们还需要多一步操作,就是指定一个自定义标签格式校验文件,也就是xsd文件。也就是在xsi:schemaLocation=后面添加自己所使用的自定义标签的格式校验文件。上述的例子中,我们看到了有spring-beans.xsd、spring-context.xsd、spring-mvc.xsd、spring-aop.xsd等等的标签格式校验文件,spring-context.xsd用于校验context标签,spring-mvc.xsd用于校验mvc标签,spring-aop.xsd用于校验aop标签。如果我们想要创建一个属于我们自己的自定义标签,那么我们也一定要创建一个xsd文件。

[2] spring.handlers和spring.schemas文件
我们在翻看其它的jar包的时候,经常发现别人的jar包里面经常会有两个文件,那么这两个文件是做什么的呢?二者都可以理解为一个Map信息文件,里面存储的信息都是键值对。所不同的是:
spring.handlers文件的键值对是哪个自定义标签由哪个业务类进行解析。
spring.schemas文件的键值对是哪个自定义标签由哪个路径下的xsd文件校验该标签是否合法。

[3] 到此为止我们知道,我们要创建一个属于自己的自定义标签,有三步必不可少(无先后顺序关系)。
第一步:我们要创建一个自定义标签合法性校验文件
第二步:我们要创建一个自定义标签与该标签解析类的绑定关系文件
第三步:我们要创建一个自定义标签解析类文件


二、如何创建一个自定义标签并使用

1.创建一个jar包工程如下(不含任何文件):

2.创建需要扩展的组件,创建一个Custom类文件。

@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Custom {

    private Long id;
    private String name;
    private String mobile;
    private String bankcard;
    private String identity;

}

3.在resources文件夹的META-INF目录下,定义XSD文件描述组件内容

<xsd:schema xmlns="http://www.minesoft.com/schema/minesoft"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            targetNamespace="http://www.minesoft.com/schema/minesoft">

    <xsd:element name="minesoft">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:long"/>
            <xsd:attribute name="name" type="xsd:string"/>
            <xsd:attribute name="mobile" type="xsd:string"/>
            <xsd:attribute name="bankcard" type="xsd:string"/>
            <xsd:attribute name="identity" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

4.创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义

public class MinesoftBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return Custom.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {

        Long id = Long.valueOf(element.getAttribute("id"));
        String name = element.getAttribute("name");
        String mobile = element.getAttribute("mobile");
        String bankcard = element.getAttribute("bankcard");
        String identity = element.getAttribute("identity");

        if (id != null) {
            builder.addPropertyValue("id", id);
        }

        if (StringUtils.hasText(name)) {
            builder.addPropertyValue("name", name);
        }

        if (StringUtils.hasText(mobile)) {
            builder.addPropertyValue("mobile", mobile);
        }

        if (StringUtils.hasText(bankcard)) {
            builder.addPropertyValue("bankcard", bankcard);
        }

        if (StringUtils.hasText(identity)) {
            builder.addPropertyValue("identity", identity);
        }
    }
}

5.创建Handler文件,扩展至NamespaceHandlerSupport,目的是将组件注册到Spring容器

public class MinesoftNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("minesoft", new MinesoftBeanDefinitionParser());
    }
}

6.编写spring.handlers和spring.schemas文件
spring.handlers文件内容如下

http\://www.minesoft.com/schema/minesoft=com.minesoft.custom.schema.MinesoftNamespaceHandler

spring.schemas文件内容如下

http\://www.minesoft.com/schema/minesoft/minesoft.xsd=/META-INF/minesoft.xsd

将上面的六个步骤弄完之后,我们在命令行或者在idea左侧工具栏使用install功能,将当前的工程打成jar包部署到本地仓库。

7.开始测试

首先我们新创建一个测试工程,并在该测试工程的applicationContext.xml文件中添加下面的自定义标签(另外记得在新测试工程pom文件里面依赖前面打的jar包)。

<minesoft:minesoft id="1000" name="sam" mobile="16878765436" identity="4305229387366524X" bankcard="7265382763772"/>

其次我们在测试工程的单元测试类执行如下代码:

ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Custom custom = cac.getBean(Custom.class);
System.out.println(JSONObject.toJSONString(custom));
cac.close();

执行上面的代码,我们会发现,我们在applicationContext.xml中使用的自定义标签被spring成功的解析生效了。该标签被解析成了一个Custom业务bean对象,该自定义标签的使用和bean默认标签的使用非常类似。

更多知识请关注公众号

beanfactory篇-(十五)context自定义标签的源码解析过程

nonecoding阅读(159)

前一章节回顾: 前一章节我们讲解了自定义标签的一些相关概念,并介绍了自定义标签的基本使用。本章节,我们来细致的聊一聊自定义标签是如何一步一步的被spring解析的。本章节,我们以context标签作为案例,详细的讲讲这个自定义标签是如何被spring解析的(里面也会进一步提到component-scan是如何将多个文件夹下被注解的类添加到解析并加载到spring容器中的)。


一、context自定义标签的基础知识

我们先看看context标签的常用用法如下:

<!-- 配置注解扫描 -->
<context:component-scan base-package="com.minesoft.tutorial,com.minesoft.tutorial.controller">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<!-- 属性文件配置 -->
<context:property-placeholder location="classpath*:properties/*.properties" ignore-unresolvable="true" order="2"/>

我们在applicationContext.xml文件中配置了上述的两个标签之后,就可以使用@Autowired等等bean的注解,也可以将我们的mysql,redis,kafka的常量信息写到一个properties文件,然后在applicationContext.xml文件中进行引用了。事实上context标签是一个父标签,component-scan和property-placeholder信息是context的子标签。我们翻一下spring的源码:

@Override
public void init() {
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

我们发现context除了支持上述的component-scan和property-placeholder两个子标签,还支持property-override、annotation-config、load-time-weaver、spring-configured、mbean-export、mbean-server等等子标签。这些具体标签用法,感兴趣的同学可以自行学习使用。


一、自定义标签的解析前准备过程

我们之前说过,在下面的方法里面开始进行标签的解析的工作。之前我们在beanfactory篇-(十一)spring的核心过程一之bean描述信息的注册这篇文章中是顺着默认标签的解析进行了讲解,这次我们从自定义分支开始讲解:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        //这个for循环用于完成所有的bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
        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)) {
                    //如果是import、alias、bean、beans等默认标签,调用parseDefaultElement方法完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
                    parseDefaultElement(ele, delegate);
                }
                else {
                    //如果是contex,dubbo,mvc,aop,transation等自定义标签,调用parseCustomElement方法完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。自定义标签的解析,不在本文中描述,且在后面的文章中讲解。
                    //本行代码就是我们自定义标签的解析代码的起始点
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

//本方法不做具体的自定义标签的解析处理,直接委托给另外一个方法处理
public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

//到这个方法就是自定义标签的核心解析方法了,这个方法虽然代码比较少,深入到每一个方法调用都包含比较多的内容
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}


二、自定义标签的解析核心模板方法说明

我们先看看自定义标签的解析核心模板方法,看看当前的方法是如何一步一步的解析自定义标签的。内容非常的抽象,大家先关注这个抽象处理过程。

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    //此时的context自定义标签信息已经被解析到Element对象当中,所以后面我们用Element对象指代context标签。因为解析context自定义标签信息的时候,把这个标签所属的命名空间信息也放置到了Element对象当中,所以我们从这里拿出context自定义标签的命名空间信息-“http://www.springframework.org/schema/context”。
    String namespaceUri = getNamespaceURI(ele);
    //自定义标签的解析核心模板过程一:根据命名空间信息,拿到能解析context这个自定义标签的命名空间解析器对象,其实就是ContextNamespaceHandler对象。
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    //自定义标签的解析核心模板过程二:使用ContextNamespaceHandler这个空间解析对象,解析context自定义标签。
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}


三、自定义标签的解析核心模板过程一:自定义标签所有子标签解析类搜集

NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

从上面的方法,我们能看出是根据标签命名空间拿到ContextNamespaceHandler这个空间解析对象。下面我们看看是如何一步一步的拿到ContextNamespaceHandler这个空间解析对象。

@Override
public NamespaceHandler resolve(String namespaceUri) {
    //1.首先我们拿到自定义标签的命名空间信息与自定义标签解析器类名的映射关系对象,这里是整个项目的齐全的映射关系,具体细节我们暂时先不讨论,感兴趣的朋友可以自行进去了解。
    Map<String, Object> handlerMappings = getHandlerMappings();
    //2.然后我们用context自定义标签的命名空间信息拿到context的自定义标签解析器类名
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        String className = (String) handlerOrClassName;
        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            //3.使用BeanUtils工具类构造出context的自定义标签解析器实例对象(此时的context标签解析器实例对象还不具备解析的能力)
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            //4.调用context自定义标签解析器实例对象的init方法(执行完下面的一行代码后,context标签解析器实例对象就具备解析context自定义标签的能力)
            namespaceHandler.init();
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

//下面我们看看为什么执行完成namespaceHandler的init方法,context标签解析器实例对象具备了解析context标签的能力的。
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        //我们看看init初始化,是将context标签的每一个子标签解析类的实例全部添加到ContextNamespaceHandler实例的parsers属性(容器)中,到这一步ContextNamespaceHandler实例对象就具备了解析context标签的每一个子标签的能力了。
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }

}

从上面的过程我们发现,ContextNamespaceHandler类实际上是一个context自定义标签的全局解析器,他本身不做任何的具体的解析的工作。他持有PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser、PropertyPlaceholderBeanDefinitionParser类的实例对象才是真正来解析各个子标签的。这个类很像是一个管理者,而不是一个真正的业务执行者。


四、自定义标签的解析核心模板过程二:

handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

从上面的方法,我们能看出是使用context标签解析器实例对象正式解析context标签的内容(准确的说是解析Element对象,因为context标签已经被转化成为了Element对象)。

ContextNamespaceHandler 继承至 NamespaceHandlerSupport抽象类

//当前方法是在NamespaceHandlerSupport里面
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    //根据element里面的子标签类型(也就是component-scan)拿到该子标签对应的子标签解析器类实例对象(也就是ComponentScanBeanDefinitionParser类的实例),正式解析子标签(解析component-scan子标签)。
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

//当前方法是在ComponentScanBeanDefinitionParser里面
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    //拿到component-scan子标签所指定的扫描路径信息
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Actually scan for bean definitions and register them.
    //正式扫描指定路径下的所有的类,凡是存在@Controller,@Service,@Dao,@Component注解的类,都将这些类的注解信息封装成相应的bean描述信息,放置到Map中去。如果有100个类存在上述的注解,那么就会解析出来100个bean描述信息,存放在Map中。如果我们不使用扫描的方式,我们可能需要在applicationContext.xml文件中定义很多bean标签信息;有了scan之后,就大大的减轻了我们开发的工作。
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

    return null;
}

//上面所说的Map我们在前面的文章中提到过,相当于一个bean描述信息存放容器,可以理解为是一个bean档案容器。有了这个容器里存放的所有bean的描述信息,我们就知道每一个bean要被创建成什么样了。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    //component-scan标签是可以配置多个路劲信息的,不过我们一般只配置一个,多路径配置用的比较少;springboot中多路径配置比较多。
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        //找出每个路径下需要注册到Map的所有bean描述信息,然后挨个儿将bean信息放置到Map中去。
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                //核心代码:将每一个@Controller,@Service,@Dao,@Component注解的类,所对应的bean描述信息注册到Map中去。
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    //核心代码:将每一个被注解的类所对应的bean描述信息注册到Map中去。
    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);
        }
    }
}

@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 oldBeanDefinition;

    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    if (oldBeanDefinition != null) {
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                    "': There is already [" + oldBeanDefinition + "] bound.");
        }
        else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        oldBeanDefinition + "] with [" + beanDefinition + "]");
            }
        }
        else if (!beanDefinition.equals(oldBeanDefinition)) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        //核心代码:到这一步,终于完成了将每一个被注解的类所对应的bean描述信息注册到Map中去的工作。默认标签最后也是到这一步,自定义标签最后也是到这一步,殊途同归。
        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<String>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<String>(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 (oldBeanDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

更多知识请关注公众号

beanfactory篇-(十三)默认标签的解析过程

nonecoding阅读(153)

回顾:通过前面一章节的了解,我们基本上知道了applicationContext.xml文件的整体的反复的封装过程。在beanfactory篇-(十一)spring的核心过程一之bean描述信息的注册 这篇文章里面,有提到关键的一些代码,就是将bean描述信息注册到beanDefinitionMap中。但是中间有一步,我们直接就跳过去了,没有细致的进行追踪了解。本章节我们对该分支逻辑进行详细的了解。


1. bean标签解析过程的整体理解

在下面的代码中,我们知道代码行BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());是关键核心代码,主要是把bean描述信息注册到beanDefinitionMap中。

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.
            //关键核心代码,把bean描述信息(也就是BeanDefinitionHolder对象)注册到beanDefinitionMap中。
            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));
    }
}

然而本章节我们不关注bean描述信息的注册,而是关注bean描述信息的具体转换,也就是下面这行代码的过程。

BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

通过前面的一章节,我们知道了一个bean描述信息通过了一个漫长的解析过程,终于从下面的标签信息

<bean id="userService" name="userService" class="com.minesoft.tutorial.service.UserServiceImpl"
      scope="singleton"
      autowire="byType"
      abstract="false"
      lazy-init="true"
      factory-bean="userServiceXmlFactoryBean"
      factory-method="createUserService"
/>

被解析封装成为了Element对象。那么Element对象,是如何被转换成为一个BeanDefinitionHolder对象的呢?下面我们跟踪代码,一步一步解开这个疑问。


注意:不管是上面的一段bean标签信息,还是Element对象,还是BeanDefinitionHolder对象,本质上都是一个东西,那就是bean描述信息,只不过给人展现的形态不一样而已。这种抽象的思想,是一个开发者需要多加理解的。


2. Element对象到BeanDefinitionHolder对象解析过程

[1] 首先将转换成为一个BeanDefinitionHolder对象的工作委托给delegate对象(BeanDefinitionParserDelegate)

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    //将转换工作整体委托给BeanDefinitionParserDelegate对象处理
    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));
    }
}

[2] 其次我们看看BeanDefinitionParserDelegate的parseBeanDefinitionElement方法的核心解析过程

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    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()) {
        beanName = aliases.remove(0);
        if (logger.isDebugEnabled()) {
            logger.debug("No XML 'id' specified - using '" + beanName +
                    "' as bean name and " + aliases + " as aliases");
        }
    }

    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }

    //首先将Element对象转换成为AbstractBeanDefinition对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(
                            beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    // Register an alias for the plain bean class name, if still possible,
                    // if the generator returned the class name plus a suffix.
                    // This is expected for Spring 1.2/2.0 backwards compatibility.
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null &&
                            beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                            !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Neither XML 'id' nor 'name' specified - " +
                            "using generated bean name [" + beanName + "]");
                }
            }
            catch (Exception ex) {
                error(ex.getMessage(), ele);
                return null;
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        //其次通过AbstractBeanDefinition对象,来初始化成BeanDefinitionHolder对象(也就是需要注册到beanDefinitionMap中的对象)。
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

[3] 我们进一步跟踪一下parseBeanDefinitionElement方法是如何将Element对象转换成为AbstractBeanDefinition对象的

public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        //首先创建一个不含任何具体信息的初始化的AbstractBeanDefinition对象
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        //其次我们将Element对象的属性信息提取出来,初始化到AbstractBeanDefinition对象
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        //然后我们将Element对象的meta标签信息提取出来,初始化到AbstractBeanDefinition对象
        parseMetaElements(ele, bd);

        //然后我们将lookup-method和replace-method信息从Element对象中提取出来,初始化到AbstractBeanDefinition对象
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        //然后我们将构造参数信息从Element对象中提取出来,初始化到AbstractBeanDefinition对象
        parseConstructorArgElements(ele, bd);

        //然后我们将属性信息从Element对象中提取出来,初始化到AbstractBeanDefinition对象
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    }
    catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    }
    catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    }
    finally {
        this.parseState.pop();
    }

    return null;
}

[4] 我们进一步跟踪一下parseBeanDefinitionAttributes方法是如何将Element对象信息,逐步的提取到AbstractBeanDefinition对象的

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
        BeanDefinition containingBean, AbstractBeanDefinition bd) {

    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
        error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }
    else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
        bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }
    else if (containingBean != null) {
        // Take default from containing bean in case of an inner bean definition.
        bd.setScope(containingBean.getScope());
    }

    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
        bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }

    //从Element对象提取lazyInit信息,并初始化到AbstractBeanDefinition对象
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));

    //从Element对象提取autowire信息,并初始化到AbstractBeanDefinition对象
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));

    //从Element对象提取dependencyCheck信息,并初始化到AbstractBeanDefinition对象
    String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
    bd.setDependencyCheck(getDependencyCheck(dependencyCheck));

    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
        String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
        bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }

    //从Element对象提取autowireCandidate信息,并初始化到AbstractBeanDefinition对象
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
        String candidatePattern = this.defaults.getAutowireCandidates();
        if (candidatePattern != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    }
    else {
        bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }

    //从Element对象提取PRIMARY_ATTRIBUTE信息,并初始化到AbstractBeanDefinition对象
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
        bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }

    //从Element对象提取init-method信息,并初始化到AbstractBeanDefinition对象
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
        String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
        if (!"".equals(initMethodName)) {
            bd.setInitMethodName(initMethodName);
        }
    }
    else {
        if (this.defaults.getInitMethod() != null) {
            bd.setInitMethodName(this.defaults.getInitMethod());
            bd.setEnforceInitMethod(false);
        }
    }

    //从Element对象提取destroy-method信息,并初始化到AbstractBeanDefinition对象
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
        String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
        bd.setDestroyMethodName(destroyMethodName);
    }
    else {
        if (this.defaults.getDestroyMethod() != null) {
            bd.setDestroyMethodName(this.defaults.getDestroyMethod());
            bd.setEnforceDestroyMethod(false);
        }
    }

    //从Element对象提取factory-method信息,并初始化到AbstractBeanDefinition对象
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
        bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
        bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }

    return bd;
}

通过上述逐步调用,我们终于了解了Element对象是如何被转换成为一个BeanDefinitionHolder对象的。

更多知识请关注公众号

beanfactory篇-(十二)bean描述信息的封装抽象过程

nonecoding阅读(173)

回顾:通过前面的文章,我们基本上知道了spring的核心代码的整体脉络,一块是XmlBeanFacotry对象的创建,也就是spring容器的启动过程。但是中间的applicationContext.xml的解析过程,没有重点跟踪,本章节我们将重点跟踪一下applicationContext.xml的解析过程。

注意:看下面的代码的过程,细心的读者会发现好像这个过程和前一节的文章很类似啊,但是我们这里关注的重点是applicationContext.xml文件资源的一路演变过程。applicationContext.xml文件是我们整个spring容器未启动时候的BeanDefinition对象的最初的信息承载者,而spring容器启动的过程就是要把applicationContext.xml文件的若干Bean描述信息,一步一步转换成为若干BeanDefinition对象,然后存放到beanDefinitionMap中。


1. 查看applicationContext.xml这个资源的变化流程

//主干代码
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

new ClassPathResource("applicationContext.xml")

public ClassPathResource(String path) {
    this(path, (ClassLoader) null);
}

//我们先看看第一次演变-经过new ClassPathResource的封装,这个时候的applicationContext.xml文件资源由"applicationContext.xml"字符串变成了ClassPathResource对象,此时的文件任然没有读取。
public ClassPathResource(String path, ClassLoader classLoader) {
    Assert.notNull(path, "Path must not be null");
    String pathToUse = StringUtils.cleanPath(path);
    if (pathToUse.startsWith("/")) {
        pathToUse = pathToUse.substring(1);
    }
    this.path = pathToUse;
    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}


public XmlBeanFactory(Resource resource) throws BeansException {
    //回到主干代码
    this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    //进一步深入主干代码
    this.reader.loadBeanDefinitions(resource);
}

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    //第二次演变-此时的applicationContext.xml文件资源由ClassPathResource对象变成了EncodedResource对象了。
    return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
        logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        //第三次演变-此时的applicationContext.xml文件资源由EncodedResource对象变成InputStream对象了。
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            //第四次演变-此时的applicationContext.xml文件资源由InputStream对象变成InputSource对象了。
            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();
        }
    }
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        //第五次演变-此时的applicationContext.xml文件资源InputStream对象变成Document对象
        Document doc = doLoadDocument(inputSource, resource);
        //主干代码
        return registerBeanDefinitions(doc, resource);
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
    catch (SAXParseException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    }
    catch (SAXException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "XML document from " + resource + " is invalid", ex);
    }
    catch (ParserConfigurationException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Parser configuration exception parsing XML from " + resource, ex);
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "IOException parsing XML document from " + resource, ex);
    }
    catch (Throwable ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(),
                "Unexpected exception parsing XML document from " + resource, ex);
    }
}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    //主干代码
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    //第六次演变-此时的applicationContext.xml文件资源由Document对象变成了Element对象
    Element root = doc.getDocumentElement();
    //主干代码
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            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;
}


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        //第七次演变-此时的applicationContext.xml文件资源由Element对象变成了一个NodeList对象(或者说是一堆Node对象)
        NodeList nl = root.getChildNodes();
        //此时的Node对象可以理解为就是对默认标签(import,alias,bean,beans)或者自定义标签(mvc,context,aop,transaction,dubbo)信息的封装,而下面的循环时将所有的Node信息解析成为BeanDefinition对象放置到一个Map中去,前面的章节已经提到了这里不细说。
        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);
    }
}

抽取applicationContext.xml文件的整个演变成为BeanDefinition对象的关键步骤

//第一次演变-经过new ClassPathResource的封装,这个时候的applicationContext.xml文件资源由"applicationContext.xml"字符串变成了ClassPathResource对象。
new ClassPathResource("applicationContext.xml")
//第二次演变-此时的applicationContext.xml文件资源由ClassPathResource对象变成了EncodedResource对象了。
new EncodedResource(resource)
//第三次演变-此时的applicationContext.xml文件资源由EncodedResource对象变成InputStream对象了。
InputStream inputStream = encodedResource.getResource().getInputStream();
//第四次演变-此时的applicationContext.xml文件资源由InputStream对象变成InputSource对象了。
InputSource inputSource = new InputSource(inputStream);
//第五次演变-此时的applicationContext.xml文件资源InputStream对象变成Document对象
Document doc = doLoadDocument(inputSource, resource);
//第六次演变-此时的applicationContext.xml文件资源由Document对象变成了Element对象
Element root = doc.getDocumentElement();
//第七次演变-此时的applicationContext.xml文件资源由Element对象变成了一个NodeList对象(或者说是一堆Node对象)
NodeList nl = root.getChildNodes(); 

启动一个spring容器所需的所有的bean描述信息的承载者,从”applicationContext.xml”字符串,到ClassPathResource对象,一步一步到一堆Node对象,最后变成一批BeanDefinition对象,放置到了beanDefinitionMap对象中去。通过上面的这些步骤,我们可以看到我们的applicationContext.xml文件资源,由非常抽象的一个”applicationContext.xml”字符串,一步一步的被解析,从而变的越来越具象,越来越具体了。


2. applicationContext文件资源通过输入流读取


通过前面的一些分析,我们大致了解了applicationContext.xml资源是如何被一步一步转换成为BeanDefinition对象的。但是好像有一个步骤我们丢失了,就是applicationContext文件资源到底是如何被读取的?答案在第二次演变InputStream inputStream = encodedResource.getResource().getInputStream();的getInputStream()方法,我们看看下面的方法便知道了。

@Override
public InputStream getInputStream() throws IOException {
    InputStream is;
    if (this.clazz != null) {
        is = this.clazz.getResourceAsStream(this.path);
    }
    else if (this.classLoader != null) {
        //在这里applicationContext.xml文件资源被转换成了InputStream流对象
        is = this.classLoader.getResourceAsStream(this.path);
    }
    else {
        is = ClassLoader.getSystemResourceAsStream(this.path);
    }
    if (is == null) {
        throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
    }
    return is;
}

更多知识请关注公众号

beanfactory篇-(十一)spring的核心过程一之bean描述信息的注册

nonecoding阅读(199)

回顾:通过前面一个章节的了解,我们大概对spring有了一个感性的认识。XmlBeanFactory这个低配版的spring的整体工作过程分两个核心步骤:一、创建一个XmlBeanFactory对象(创建这个对象的实质是将所有的bean的描述信息注册到一个Map中去);二、通过getBean方法,创建bean对象(实质上是根据一的Map种bean描述信息创建对应的bean对象)。大概有了一个总体的认识之后,我们首先看看第一步,也就是创建一个XmlBeanFactory对象(实际上也就是将所有的bean的描述信息注册到一个Map中去)。


1. spring源码解读先导介绍


注意:1.我们做任何框架的源码解读的时候,一定要注意一点,不要去扣所有的细节,而是要关注他的核心脉络。因为如果总是去扣每一个细节,我们会陷入到无穷的细节代码中去,最后导致自己手撕源码失败。在搞清楚核心框架的脉络之后,我们再去扣细枝末节的技术点。2.我们了解一个框架的工作原理的时候,一定要首先找到这个框架的最小功能集或者说核心功能集,待我们了解原理之后(也可以说是搞清楚框架的核心脉络之后),在去查看它的扩展集功能。

上面我们说过,spring的核心工作过程有两个:1.创建一个spring容器;2.创建好spring容器后,通过spring容器去获取bean对象。对于1创建一个spring容器的核心处理在上一章节我们说过,是把bean描述信息封装成对象放置到一个map中去,而通过spring容器去获取bean对象的核心处理我们在上一章节我们也说过,就是根据bean描述信息来创建bean信息并把bean之间的引用关系处理好。


2. bean描述信息的注册过程

//spring核心过程一:启动一个spring容器
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource 
("applicationContext.xml"));

[1] XmlBeanFactory构造方法,完成初始化一个XmlBeanFactory对象的操作

跳转至XmlBeanFactory类

public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    //this.reader是XmlBeanDefinitionReader类,调用loadBeanDefinitions方法,即可完成所有的bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。此时的applicationcontext.xml文件已经被转换成为一个ClassPathResource对象,这个对象实现了Resource接口。
    this.reader.loadBeanDefinitions(resource);
}

[2] 将有的bean描述信息注册的操作,委托给XmlBeanDefinitionReader类

跳转至XmlBeanDefinitionReader类
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
        logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }

    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        //此时applicationContext.xml文件由Resource对象再次转换成为了InputStream对象。
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            //此时applicationContext.xml文件再次由InputStream对象变成了InputSource对象
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //调用doLoadBeanDefinitions方法,即可完成所有的bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
            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();
        }
    }
}

[3] 将有的bean描述信息注册的操作,委托给DefaultBeanDefinitionDocumentReader类

跳转至DefaultBeanDefinitionDocumentReader类

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    //再次将注册bean描述信息到Map的处理委托给本类的doRegisterBeanDefinitions方法
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }

    preProcessXml(root);
    //调用parseBeanDefinitions方法,即可完成所有的bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        //这个for循环用于完成所有的bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
        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)) {
                    //如果是import、alias、bean、beans等默认标签,调用parseDefaultElement方法完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。
                    parseDefaultElement(ele, delegate);
                }
                else {
                    //如果是contex,dubbo,mvc,aop,transation等自定义标签,调用parseCustomElement方法完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)。自定义标签的解析,不在本文中描述,且在后面的文章中讲解。
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //import标签的解析
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    //alias标签的解析
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    //bean标签的解析(bean描述信息注册操作)
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

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方法,完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)
            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));
    }
}

public static void registerBeanDefinition(
    BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
    throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    调用registry类的registerBeanDefinition方法,完成某一个bean描述信息注册的操作(即bean描述信息封装成对象后,放置到Map中去)
    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);
        }
    }
}

[3] 将有的bean描述信息注册的操作,委托给DefaultListableBeanFactory类

跳转至DefaultListableBeanFactory类      

@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 oldBeanDefinition;

    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    if (oldBeanDefinition != null) {
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                    "': There is already [" + oldBeanDefinition + "] bound.");
        }
        else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        oldBeanDefinition + "] with [" + beanDefinition + "]");
            }
        }
        else if (!beanDefinition.equals(oldBeanDefinition)) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        //终于到这关键的一步,每一个applicationContext.xml的bean的描述信息,最终都被封装成为了一个beanDefinition对象,然后放置到了DefaultListableBeanFactory类的一个beanDefinitionMap对象中去。这个对象的定义如下:
        //private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
        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<String>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<String>(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 (oldBeanDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

我们把上面的创建BeanFactory对象的关键行代码提取出来,我们可以清晰的看到整个spring容器启动(就是创建一个XmlBeanFactory对象)的核心抽象过程。[10]步骤有一个循环处理,用于处理所有的bean描述信息的注册,从代码调用看,循环里面执行的就是[11]-[17]步。而[11]-[17]步骤用于完成每一个bean描述信息的注册过程。

    [1] BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory"));
    [2] XmlBeanFactory -> XmlBeanFactory -> this.reader.loadBeanDefinitions(resource);
    [3] XmlBeanDefinitionReader -> loadBeanDefinitions -> this.loadBeanDefinitions(new EncodedResource(resource));
    [4] XmlBeanDefinitionReader -> loadBeanDefinitions -> var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    [5] XmlBeanDefinitionReader -> doLoadBeanDefinitions -> int validationMode = this.getValidationModeForResource(resource); 此处是读取xml配置文件的格式,然后校验该格式是否正确,一般有两种格式,一种是DTD,一种是XSD的。
    [6] XmlBeanDefinitionReader -> doLoadBeanDefinitions -> Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware()); 此处的意思是将xml文件读取成为一个Document内存对象,该对象就是一个树形结构,里面包含了很多的xml中的节点的对象。
    [7] XmlBeanDefinitionReader -> doLoadBeanDefinitions -> this.registerBeanDefinitions(doc, resource); 
    [8] XmlBeanDefinitionReader -> registerBeanDefinitions -> documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource)); 此处才是真正的开始进行注册Bean的操作的过程了。
    [9] DefaultBeanDefinitionDocumentReader -> registerBeanDefinitions -> Element root = doc.getDocumentElement(); 
    [10] DefaultBeanDefinitionDocumentReader -> registerBeanDefinitions -> this.doRegisterBeanDefinitions(root);
    [11] DefaultBeanDefinitionDocumentReader -> doRegisterBeanDefinitions -> this.parseBeanDefinitions(root, this.delegate); 本调用内部就是进行递归便利的方式来进行注册bean的操作了,因为root对象可以获取下面的所有数据
    [12] DefaultBeanDefinitionDocumentReader -> parseBeanDefinitions -> this.parseDefaultElement(ele, delegate); 注册spring的各种bean信息 
    [13] DefaultBeanDefinitionDocumentReader -> parseBeanDefinitions -> delegate.parseCustomElement(ele); 注册dubbo,aop,transaction,mvc等自定义的各种bean信息
    [14] DefaultBeanDefinitionDocumentReader -> parseDefaultElement -> this.processBeanDefinition(ele, delegate);
    [15] DefaultBeanDefinitionDocumentReader -> processBeanDefinition -> BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
    [16] BeanDefinitionReaderUtils -> registerBeanDefinition -> registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    [17] DefaultListableBeanFactory -> registerBeanDefinition -> this.beanDefinitionMap.put(beanName, beanDefinition); 其实前面的所有的步骤,都是为了将所有的xml配置文件中的bean信息注册到DefaultListableBeanFactory中的beanDefinitionMap对象中去。

总结:new一个beanfactory对象的核心业务处理,实际上就是把所有的bean描述信息封装成beanDefinition对象,然后统一放置到DefaultListableBeanFactory所持有的一个beanDefinitionMap对象中去。那么这么做的意义是什么呢?就是为了调用getBean方法创建bean对象做准备,有了bean描述信息就能将一个bean创建出来了。

更多知识请关注公众号

beanfactory篇-(十)spring框架的感性认识

nonecoding阅读(116)

回顾:通过前面一系列的文章的了解,对于spring的常用和非常用用法都有了一定的了解。本章节我们不探讨任何一些spring的功能的使用,也暂时不阅读任何的源码。我们先总体的回顾和总结一下前面的知识,对于spring有一个感性的认识和了解。


1. spring的重要概念的总结

[1]BeanFactory是基础版或者标配版的spring,提供spring最核心最基础的功能;ApplicationContext是高配版或者豪华办的spring,提供了除核心和基础功能外的常用的扩展功能。

[2]既然ApplicationContext是对BeanFactory的扩展,那么是不是ApplicationContext的底层间接的继承至BeanFactory或者间接的持有BeanFactory呢?我们看看下面的的过程就明白了。

首先从下图,我们可以看到XmlBeanFactory继承至DefaultListableBeanFactory类

其次从下两张图,我们可以看到ClassPathXmlApplicationContext继承至AbstractXmlApplicationContext,而AbstractXmlApplicationContext继承至AbstractRefreshableConfigApplicationContext,而AbstractRefreshableConfigApplicationContext继承至AbstractRefreshableApplicationContext,而AbstractRefreshableApplicationContext类持有一个DefaultListableBeanFactory对象。


从上述两组图,我们基本上可以确定DefaultListableBeanFactory类是我们的spring的最小核心实现,不管是XmlBeanFactory继承至DefaultListableBeanFactory,还是ClassPathXmlApplicationContext持有DefaultListableBeanFactory,都是对DefaultListableBeanFactory扩展而已。只不过XmlBeanFactory基本上没有做很多功能的扩展,只是简单的继承了一下DefaultListableBeanFactory类,而ClassPathXmlApplicationContext通过持有DefaultListableBeanFactory的方式,来对DefaultListableBeanFactory进行了大量的功能扩展。

[3]一个XmlBeanFactory对象或者ClassPathXmlApplicationContext对象,就是一个spring容器。而创建一个XmlBeanFactory对象或者ClassPathXmlApplicationContext对象的过程,就可以理解为启动spring容器或者启动spring框架的过程。

    //使用低配版的spring开发业务(不太常用)
    XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    UserService userService = beanFactory.getBean(UserService.class);
    User user = userService.findUserById(100L);
    System.out.println(JSONObject.toJSONString(user));

    //使用高配版的spring框架来开发业务(除了web类服务外,像定时服务,消息消费服务一般都是这种方式启动一个spring服务的)
    ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = cac.getBean(UserService.class);
    User user = userService.findUserById(100L);
    System.out.println(JSONObject.toJSONString(user));

[4]XmlBeanFactory这个低配版的spring的整体工作过程分两个核心步骤:一、创建一个XmlBeanFactory对象(创建这个对象的实质是将所有的bean的描述信息注册到一个Map中去);二、通过getBean方法,创建bean对象(实质上是根据一的Map种bean描述信息创建对应的bean对象)


2. spring的的核心过程的一些问题

前面我们刚刚提到了spring的整体工作过程分两个部分,一个是注册bean描述信息,一个是根据这些bean描述信息创建对象。要理解前面这两个部分,我们首先要将下面的三个问题要先捋清楚。

问题一:什么是bean描述信息?
bean描述信息又成为bean元数据,其实更应该称为bean描述信息,因为这样称呼更加贴切。bean描述信息和我们人的档案非常的类似,我们的档案详细描述了我们一个人从出生到目前为止,所有的就学和工作经历等等。同样的,每一个bean都有一份描述信息(或者说“档案”),决定了这个bean应该被创建成为什么样(如bean的名称,父bean的名称,bean的scope范围,bean是否是延迟加载,bean是否由其它的factorybean生成的等等信息,bean有哪些其它属性)。有了这些信息之后
,我们的spring容器就能根据这些描述信息创建对应的bean了。

bean描述信息在不同的阶段,是被不通的载体所承载的。在源码阶段,一段applicationContext.xml的bean配置信息就是我们的某个bean描述信息,如下所示:

<bean id="userService" name="userService" class="com.minesoft.tutorial.service.UserServiceImpl"
      scope="singleton"
      autowire="byType"
      abstract="false"
      lazy-init="true"
      factory-bean="userServiceXmlFactoryBean"
      factory-method="createUserService"
/>

而当我们的spring容器启动成功后(也就是一个XmlBeanFactory对象被创建成功后),上面的这段xml描述信息也就被封装成了一个RootBeanDefinition(继承至AbstractBeanDefinition)对象。如下图所示:

而从一段bean标签信息被一步一步的解析并最终封装成为一个RootBeanDefinition(继承至AbstractBeanDefinition)对象的过程,也就是spring容器的启动过程。

问题二:Map在哪儿,被什么对象所持有?
我们刚刚提到了一段bean标签信息被一步一步的解析并最终封装成为一个BeanDefinition对象的过程,那么这个对象总得存放到一个Map也好,List也好,Set也好,总之得存放到一个容器中去。不然后续使用getBean方法创造一个bean的时候,从何处拿到bean描述信息呢?从下图我们知道最终存放到了DefaultListableBeanFactory对象所持有的beanDefinitionMap对象中。

问题三:创建bean对象是通过new的方式创建的,还是通过反射的方式创建的,还是其它方式创建的?
bean对象的创建方式有多种多样,有的是spring通过反射创建的,有的是spring调用用户的一些扩展代码实现的,具体实现方式得看每一个bean描述信息的具体配置。通过前面的文章的学习,我们知道了至少三种方式能创建bean对象(xml配置或者注解最底层都是通过反射的方式创建,开发者实现FactoryBean接口自定义创建,开发者实现InstantiationAwareBeanPostProcessor接口并在postProcessBeforeInstantiation方法中创建)。


更多知识请关注公众号

beanfactory篇-(九)BeanDefinitionRegistryPostProcessor接口的使用

nonecoding阅读(150)

回顾:通过前面一个章节的了解,我们学会了InstantiationAwareBeanPostProcessor接口的基本用法。实现这个接口,可以使得开发者在创建某个bean的前前后后的各个环节都有机会介入进来,对该bean做各种定制处理。这种处理有可能是替换,有可能是包装,有可能是进一步的初始化。总之spring提供了强大的扩展功能给开发者,方便他们的实现自己的定制需求。本章节,我们学习一个新的接口,就是BeanDefinitionRegistryPostProcessor接口。


1. BeanDefinitionRegistryPostProcessor接口的基本概念介绍

BeanDefinitionRegistryPostProcessor接口继承至BeanFactoryPostProcessor接口,并在其基础上扩展了功能,我们看看具体的使用。


2. BeanDefinitionRegistryPostProcessor接口的基本使用

第一步:创建一个User实体类

@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class User implements Serializable {

    private static final long serialVersionUID = 4800166777883697833L;

    private Long id;
    private String name;
    private String identity;
    private String mobile;
    private String bankcard;
    private Integer age;
    private Integer gender;

}

第二步:我们创建一个接口和接口实现类

public interface UserService {

    User findUserById(Long id);

}

public class UserServiceImpl implements UserService {

    @Override
    public User findUserById(Long id) {

        User user = new User();
        user.setId(id);
        user.setName("张山");
        user.setIdentity("张山");
        user.setBankcard("36457736355363");
        user.setMobile("16752652625");
        user.setGender(2);
        user.setAge(18);

        return user;
    }

}

第三步:创建BeanDefinitionRegistryPostProcessor接口的实现类如下:

@Component
public class GlobalBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println("Execute postProcessBeanDefinitionRegistry.");

        //除了xml配置,@Service外,这里是第三种注册bean信息的方式,也就是代码中做注册
        GenericBeanDefinition gbd = new GenericBeanDefinition();
        gbd.setBeanClass(UserServiceImpl.class);
        gbd.setScope(BeanDefinition.SCOPE_SINGLETON);
        gbd.setAutowireCandidate(true);
        registry.registerBeanDefinition("userService", gbd);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("Execute postProcessBeanFactory.");
    }
}

第四步:我们在applicationContext.xml文件中配置相应的信息

<context:component-scan base-package="com.minesoft.tutorial" />

完成上述的步骤之后,我们运行一下下面的代码片段

XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
UserService userService = beanFactory.getBean(UserService.class);
User user = userService.findUserById(100L);
System.out.println(JSONObject.toJSONString(user));

控制台成功的输出了User信息,本接口功能和BeanFactoryPostProcessor接口基本类似。如果业务中创建两个类GlobalBeanDefinitionRegistryPostProcessor和GlobalBeanFactoryPostProcessor分别实现
BeanDefinitionRegistryPostProcessor接口和BeanFactoryPostProcessor接口,那么GlobalBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry和postProcessBeanFactory方法会被先调用,而GlobalBeanFactoryPostProcessor类的postProcessBeanFactory方法会被后调用。


更多知识请关注公众号

beanfactory篇-(八)BeanFactoryPostProcessor接口的使用

nonecoding阅读(114)

回顾:通过前面的章节的了解,我们又了解了InstantiationAwareBeanPostProcessor接口的基本使用,当我们想要介入一个bean的实例化和初始化的各个环节的时候,只需要写一个创建一个类实现InstantiationAwareBeanPostProcessor接口,即可灵活的进行一些自定义的业务逻辑处理。本章节我们将学习一个新的接口,就是BeanFactoryPostProcessor接口


1. BeanFactoryPostProcessor接口的基本介绍

一、通过前面的一些章节的知识介绍,我们基本上可以确定一个观念了,那就是BeanFactory是spring落地到代码层面的基础实现,也就是标配版本的spring,提供spring最基础和最核心的功能;而ApplicationContext是spring落地到代码层面的丰富实现,也就是豪华版本的spring,不但提供spring最基础和最核心的功能,还提供一些我们业务开发过程中的常用扩展功能。

二、在spring的源码中,实际上ApplicationContext是间接的持有一个BeanFactory对象的。也就是说基于ApplicationContext的spring容器,在启动该过程中,一定是要创建并且初始化一个BeanFactory对象的。那么问题来了,在beanFactory对象被创建后,开发者希望自己能介入到beanFactory的
初始化过程中去,这个也是可以的。BeanFactoryPostProcessor接口就是spring提供的一个扩展机制,允许开发者在spring的启动过程中,有机会参与到beanFactory对象的初始化过程中去。一个常用的使用场景就是我们基于代码动态的注册一个bean信息到beanFactory中去,下面我们看看这个到底是如何使用的。

三、本接口主要包含一个重要方法:postProcessBeanFactory方法。该方法是beanFactory对象被创建后,给开发者一个扩展或者修改beanFactory对象的机会,下面我就看看如何使用这个接口。



2. BeanFactoryPostProcessor接口的基本使用

第一步:创建一个User类如下:

@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class User implements Serializable {

    private static final long serialVersionUID = 4800166777883697833L;

    private Long id;
    private String name;
    private String identity;
    private String mobile;
    private String bankcard;
    private Integer age;
    private Integer gender;

}

第二步:创建一个接口和接口实现类如下:

public interface UserService {

    User findUserById(Long id);

}

//注意,这个地方不需要这个注解了
//@Service
public class UserServiceImpl implements UserService {

    private Map<Integer, String> blackListMap = new HashMap<>();

    @Override
    public User findUserById(Long id) {

        User user = new User();
        user.setId(id);
        user.setName("张山");
        user.setIdentity("张山");
        user.setBankcard("36457736355363");
        user.setMobile("16752652625");
        user.setGender(2);
        user.setAge(18);

        return user;
    }

}

第三步:创建一个GlobalBeanFactoryPostProcessor类,实现了BeanFactoryPostProcessor接口

@Component
public class GlobalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("Execute postProcessBeanFactory.");

        //除了xml配置,@Service外,这里是第三种注册bean信息的方式,也就是代码中做注册
        BeanDefinitionRegistry bdr = (BeanDefinitionRegistry)beanFactory;
        GenericBeanDefinition gbd = new GenericBeanDefinition();
        gbd.setBeanClass(UserServiceImpl.class);
        gbd.setScope(BeanDefinition.SCOPE_SINGLETON);
        gbd.setAutowireCandidate(true);
        bdr.registerBeanDefinition("userService", gbd);

    }

}

第四步:配置applicationContext.xml文件内容:

<context:component-scan base-package="com.minesoft.tutorial" />

第五步:最后我们运行下面的代码

    ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = cac.getBean(UserService.class);
    User user = userService.findUserById(100L);
    System.out.println(JSONObject.toJSONString(user));

运行完成上面的代码,我们会发现一个奇怪的事实,就是我们的UserServiceImpl这个bean,既不需要在applicationContext.xml文件中配置,也不需要加上@Service注解,居然也能被spring创建并管理起来,能够正常的使用。其实这种使用方式并不奇怪,只不过我们不常用而已,这种用法正是BeanFactoryPostProcessor接口的常用用法。

总结:从上面我们看到了除了xml配置,和@Service注解之外的第三种注册bean信息的方式,也就是通过实现BeanFactoryPostProcessor接口,在代码中动态的去注册bean信息。那么可能有同学好奇了,前面的两种和这种本质上有啥区别呢?答案是本质上无任何区别。前两种方式最终底层也是执行了第三步的postProcessBeanFactory方法的那段代码,即将bean描述信息注册到beanfactory中去。至于这个解析过程,后面我们会详细的提到。本章节我们先了解这些基础的功能用法。


3. BeanPostProcessor接口和BeanFactoryPostProcessor接口的区别

首先BeanFactory是一个spring的容器,也就是一个容器级别的对象,这个容器对象里面管理着多个bean对象。基于这一点,我们看看下面的二者的不同。

一、二者的量级就不一样,从名称上就能分辨。BeanFactoryPostProcessor接口作用于BeanFactory对象的初始化过程,而BeanPostProcessor接口只能作用Bean对象的初始化过程。一个用于拦截或者介入容器级对象(即BeanFactory对象)的初始化过程;一个用于拦截或者介入bean对象的初始化过程。

二、一个BeanFactoryPostProcessor接口的实现类在spring容器的启动过程中,一般只会被调用一次;而BeanPostProcessor接口的实现类在spring容器的启动过程中,一般会被调用N次(N与这个spring容器所创建和管理的Bean对象一样)。

更多知识请关注公众号

beanfactory篇-(七)InstantiationAwareBeanPostProcessor接口的使用

nonecoding阅读(131)

回顾:通过前面的章节的了解,我们又了解了BeanPostProcessor接口的基本使用。当我们想要在一个bean被创建好之后,开发者能有机会参与到这个bean的初始化过程的时候,我们只需要实现BeanPostProcessor接口,并在接口方法里面选择性的初始化自己的bean对象即可。本章我们将新学习一个接口,这个接口就是InstantiationAwareBeanPostProcessor接口。


1. InstantiationAwareBeanPostProcessor接口的基本介绍


一、实际上来说,对于一个bean的生命周期而言,有两个非常重要的过程:bean的实例化和bean的初始化。 实例化就是创建一个bean,而初始化是对已经创建好的bean进行初始化。
[1]bean的实例化有三种方式:xml配置后spring容器创建,@Service等注解后spring创建,实现FactoryBean接口后用户代码自己创建。
[2]bean的初始化有三种方式:配置init-method属性方式,实现InitializingBean接口方式,实现BeanPostProcessor接口方式。

二、了解了上面的一些重要的概念之后,我们再来了解InstantiationAwareBeanPostProcessor接口就好理解了。
因为InstantiationAwareBeanPostProcessor接口是继承至BeanPostProcessor接口的,所以这个接口是对BeanPostProcessor接口的功能的扩展,BeanPostProcessor接口所有的功能他都有。那么问题是都扩展了哪些功能呢?就是扩展了postProcessBeforeInstantiation、postProcessAfterInstantiation、postProcessPropertyValues三个方法。下面我们重点介绍一下这三个方法:

[1] postProcessBeforeInstantiation方法:调用本方法之前某个业务bean还未被创建,开发者是否需要自己创建这个bean(用法与FactoryBean接口有点类似)。如果开发者在该方法内放置如下代码,则spring启动过程中不会自己通过反射的方式创建这个bean,而是执行下面的代码创建一个bean。

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {

    //如果bean类型是UserServiceImpl,则该bean由开发者自己的代码创建
    if(beanClass.equals(UserServiceImpl.class)) {
        UserService userService = new UserServiceImpl();
        return userService;
    }

    return null;
}

一旦该bean由上面的自定义代码段创建,那么下面的[2],[3]就不会被调用了。spring框架这么处理的目的很简单,就是如果开发者需要自己实例化一个业务bean,那么就在这里将初始化的工作一并处理了,无需再其它方法里面去做实例化。如果运行过程中去掉上面的自定义代码段,那么下面的[2]一定会执行。

[2] postProcessAfterInstantiation方法:调用本方法主要是spring创建某个业务bean之后,给开发者一个介入的机会进行自定义的一些业务处理。除此之外,返回值的true和false直接决定了[3]方法是否执行。

[3] postProcessPropertyValues方法:如果[2]中的方法返回的是true,那么本方法就会被调用,开发者可以再此处初始化一些自己的属性。

总的来说,这个接口的几个方法重点归为两组接口:postProcessBeforeInstantiation方法,postProcessAfterInstantiation方法是一组;postProcessBeforeInitialization方法和postProcessAfterInitialization方法是一组。第一组是spring在实例化某个bean之前和实例化某个bean之后,给开发者一个介入的机会,用于做某些自定义的业务处理;第二组是spring在初始化某个bean之前和初始化某个bean之后,给开发者一个介入的机会,用于做某些自定义的业务处理。注意前面的区别,第一组是实例化,第二组是初始化。


2. InstantiationAwareBeanPostProcessor接口的基本使用

第一步:创建一个User类如下:

@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class User implements Serializable {

    private static final long serialVersionUID = 4800166777883697833L;

    private Long id;
    private String name;
    private String identity;
    private String mobile;
    private String bankcard;
    private Integer age;
    private Integer gender;

}

第二步:创建一个接口和接口实现类如下:

public interface UserService {

    User findUserById(Long id);

}

@Service
public class UserServiceImpl implements UserService {

    public String ip;

    public String port;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    @Override
    public User findUserById(Long id) {

        User user = new User();
        user.setId(id);
        user.setName("张山");
        user.setIdentity("张山");
        user.setBankcard("36457736355363");
        user.setMobile("16752652625");
        user.setGender(2);
        user.setAge(18);

        System.out.println("ip: " + ip);
        System.out.println("port: " + port);

        return user;
    }

}

第三步:创建一个实现InstantiationAwareBeanPostProcessor接口的实现类如下:

@Component
public class GlobalInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {

        //测试一:开发者自己实例化UserServiceImpl这个bean,不要让spring通过反射的方式创建。
        if(beanClass.equals(UserServiceImpl.class)) {
            UserService userService = new UserServiceImpl();
            System.out.println("address1" + userService);
            return userService;
        }

        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {

        //测试二:spring通过反射的方式创建UserServiceImpl这个bean,然后返回一个true或者false。
        //返回true的意思是告诉spring,需要调用下面的postProcessPropertyValues方法做进一步的处理。
        //返回false的意思是告诉spring,不需要再调用下面的postProcessPropertyValues方法做处理了。
        if(bean instanceof UserServiceImpl) {
            return true;
        }

        return false;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

        //测试二:如果在上面的方法里面返回了true,那么本方法会被调用,开发者可以做一些自定义的业务处理。
        //举例来说:比如说spring服务启动后需要将该服务部署的机器的ip地址和端口注入到某个bean中去,那么
        //就可以用下面的代码段做处理。
        if(bean instanceof UserServiceImpl) {
            MutablePropertyValues mpvs = (MutablePropertyValues) pvs;
            mpvs.addPropertyValue(new PropertyValue("ip", "192.168.1.110"));
            mpvs.addPropertyValue(new PropertyValue("port", "8080"));
        }

        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        //在本方法内主要是三种用法:一个是对bean做自定义的一些初始化或者修改的工作;一个是返回一个bean的代理对象;
        // 一个是创建一个全新的bean,替换spring创建的bean。至于哪种用法,完全由开发者自己业务需求决定。
        System.out.println("Execute postProcessBeforeInitialization.");

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        //再本方法内主要是三种用法:一个是对bean做自定义的一些初始化或者修改的工作;一个是返回一个bean的代理对象;
        // 一个是创建一个全新的bean,替换spring创建的bean。至于哪种用法,完全由开发者自己业务需求决定。
        System.out.println("Execute postProcessAfterInitialization.");

        return bean;
    }
}

第四步:配置applicationContext.xml文件内容:

<context:component-scan base-package="com.minesoft.tutorial" />

第五步:开始下面的测试。注意:第三步中的测试一和测试二是互斥的,做测试的时候选择测试一需要注释测试二的代码,选择测试二需要注释测试一的代码。

    ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = cac.getBean(UserService.class);
    System.out.println("address2" + userService);

运行上述测试一所对应的测试代码,控制台会打印如下的日志

address1com.minesoft.tutorial.service.UserServiceImpl@3b2cf7ab
address2com.minesoft.tutorial.service.UserServiceImpl@3b2cf7ab

两个相同的内存地址表明postProcessBeforeInstantiation中创建的bean和后面通过getBean方法拿到的就是同一个bean。也就说明了UserServiceImpl这个bean是开发者自己创建的,而不是spring创建的。

    ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserService userService = cac.getBean(UserService.class);
    userService.findUserById(100L);

运行上述测试二的所对应的测试代码,控制台会打印如下的日志:

ip: 192.168.1.110
port: 8080

说明UserServiceImpl的ip和port属性都被成功的初始化了。


更多知识请关注公众号

九零后大叔的技术博客

联系我们联系我们