beanfactory篇-(十五)context自定义标签的源码解析过程-九零后大叔的技术博客

沙海 2021年4月24日09:21:09Java评论25字数 13492阅读44分58秒阅读模式
摘要

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

文章源自JAVA秀-https://www.javaxiu.com/19916.html

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

文章源自JAVA秀-https://www.javaxiu.com/19916.html

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

我们先看看context标签的常用用法如下:文章源自JAVA秀-https://www.javaxiu.com/19916.html

<!-- 配置注解扫描 -->
<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的源码:文章源自JAVA秀-https://www.javaxiu.com/19916.html

@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等等子标签。这些具体标签用法,感兴趣的同学可以自行学习使用。文章源自JAVA秀-https://www.javaxiu.com/19916.html

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

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

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));
}

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

我们先看看自定义标签的解析核心模板方法,看看当前的方法是如何一步一步的解析自定义标签的。内容非常的抽象,大家先关注这个抽象处理过程。文章源自JAVA秀-https://www.javaxiu.com/19916.html

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这个空间解析对象。文章源自JAVA秀-https://www.javaxiu.com/19916.html

@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类的实例对象才是真正来解析各个子标签的。这个类很像是一个管理者,而不是一个真正的业务执行者。文章源自JAVA秀-https://www.javaxiu.com/19916.html

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

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

从上面的方法,我们能看出是使用context标签解析器实例对象正式解析context标签的内容(准确的说是解析Element对象,因为context标签已经被转化成为了Element对象)。文章源自JAVA秀-https://www.javaxiu.com/19916.html

ContextNamespaceHandler 继承至 NamespaceHandlerSupport抽象类文章源自JAVA秀-https://www.javaxiu.com/19916.html

//当前方法是在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篇-(十五)context自定义标签的源码解析过程-九零后大叔的技术博客 文章源自JAVA秀-https://www.javaxiu.com/19916.html

更多知识请关注公众号文章源自JAVA秀-https://www.javaxiu.com/19916.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定