spring源码

Easter79
• 阅读 885

Spring IOC容器 源码解析系列,建议大家按顺序阅读,欢迎讨论

(spring源码均为4.1.6.RELEASE版本)

  1. Spring源码-IOC容器(一)-构建简单IOC容器
  2. Spring源码-IOC容器(二)-Bean的定位解析注册
  3. Spring源码-IOC容器(三)-GetBean
  4. Spring源码-IOC容器(四)-FactoryBean
  5. Spring源码-IOC容器(五)-Bean的初始化
  6. Spring源码-IOC容器(六)-bean的循环依赖
  7. Spring源码-IOC容器(七)-ApplicationContext
  8. Spring源码-IOC容器(八)-NamespaceHandler与自定义xml
  9. Spring源码-IOC容器(九)-Component-Scan源码解析
  10. Spring源码-IOC容器(十)-@Autowired解析

前言

在spring的xml配置文件中,当我们想使用spring-context或者spring-aop的配置时,都是有前缀的标签。如:

<context:component-scan/>
<aop:config/>

而普通的<bean>标签则是没有前缀的,为什么会有这样的区别呢?

因为spring的xml文件默认的名称空间(namespace)是http://www.springframework.org/schema/beans,只支持bean,import,alias等基本标签,而要支持context,aop,mvc等其他名称空间,就需要在xml文件中单独声明。

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

那么如果想自定义xml标签,用来支持一些特殊的功能或者集成一个框架到spring中去,应该怎么去实现呢?

就是通过NamespaceHandler接口。来看他的定义:

Base interface used by the DefaultBeanDefinitionDocumentReader for handling custom namespaces in a Spring XML configuration file.

DefaultBeanDefinitionDocumentReader中处理xml配置文件中自定义命名空间的基本接口

接口中定义了三个方法,一般常用的只有两个

public interface NamespaceHandler {

    // 初始化操作,一般都是注册子标签的解析器BeanDefinitionParser
    void init();

    // 解析标签的具体操作
    BeanDefinition parse(Element element, ParserContext parserContext);
}

下面通过一个demo来看如何使用自定义xml标签

自定义xml标签

我们想实现一个类似于<bean>标签功能的自定义标签,像这样的:

<sample:entity id="sampleBean" class="com.lcifn.spring.demo.namespace.SampleBean"/>

id就是bean的名称,class是bean的全路径包名。然后通过ApplicationContext.getBean("sampleBean")就可以获取bean的实例对象。

首先要写一个sample.xsd文件,用来定义xml的组织结构。

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lcifn.com/sample"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lcifn.com/sample"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">
    <xsd:element name="entity">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" />
            <xsd:attribute name="class" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

这里声明了命名空间为http://www.lcifn.com/sample,并定义了元素entity,以及它的两个属性id和class。

然后在classpath下创建一个META-INF文件夹,并创建spring.handlers和spring.schemas两个文件,这两个文件是模仿spring自己的jar包下的写法,打开spring-beans或者spring-context的jar包,都会有一个META-INF的文件夹,其中就有spring.handlers和spring.schemas两个文件。

spring.schemas用来定义xml文件schemaLocation声明的xsd和本地xsd文件的关系,比如spring中context的schemaLocation如下

xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"

通常解析xml是会去请求http://www.springframework.org/schema/context/spring-context-3.0.xsd这个远程文件,但往往程序运行时未必能访问网络,如果可以访问本地的xsd文件就可以解决这个问题。于是就有了spring.schemas文件。比如spring-context的jar包下的spring.schemas定义了context的xsd文件关系。

http\://www.springframework.org/schema/context/spring-context-4.1.xsd=org/springframework/context/config/spring-context-4.1.xsd

这里的value值时classpath下本地xsd文件的包路径名。

刚刚写的sample.xsd我放在了com.lcifn.spring.demo.xsd包路径下,因此我们的spring.schemas如下:

http\://www.lcifn.com/sample.xsd=com/lcifn/spring/demo/xsd/sample.xsd

spring.handlers是spring定义的xml命名空间和其对应的处理器的映射,比如context的:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

ContextNamespaceHandler继承了NamespaceHandlerSupport类,用来对context命名空间的xml标签进行解析,并与spring ioc容器进行交互(如注册BeanDefinition等)。因此我们也需要定义一个NamespaceHandlerSupport的子类SampleNamespaceHandler,并在spring.handlers中与sample的命名空间进行关联。

SampleNamespaceHandler.java

package com.lcifn.spring.demo.namespace;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class SampleNamespaceHandler extends NamespaceHandlerSupport{

    public void init() {
        registerBeanDefinitionParser("entity", new EntityBeanDefinitionParse());
    }

}

实现init方法,注册entity标签的解析器EntityBeanDefinitionParse。

EntityBeanDefinitionParse.java

package com.lcifn.spring.demo.namespace;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class EntityBeanDefinitionParse implements BeanDefinitionParser{

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String id = element.getAttribute("id");
        String beanClassName = element.getAttribute("class");
        BeanDefinition definition = new RootBeanDefinition();
        definition.setBeanClassName(beanClassName);
        BeanDefinitionReaderUtils.registerBeanDefinition(new BeanDefinitionHolder(definition, id), parserContext.getRegistry());
        return definition;
    }
}

EntityBeanDefinitionParse实现spring的BeanDefinitionParser接口,在parse方法里获取entity的id和class属性,并实例化一个RootBeanDefinition,然后注册到spring容器中。

定义一个SampleBean类,只有一个print方法

package com.lcifn.spring.demo.namespace;

public class SampleBean {

    public void print(){
        System.out.println("sample bean works");
    }
}

定义namespace-handler.xml配置文件,要声明xmlns:sample,以及在xsi:schemaLocation中指定sample的xsd

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:sample="http://www.lcifn.com/sample"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.lcifn.com/sample
    http://www.lcifn.com/sample.xsd">
    
    <sample:entity id="sampleBean" class="com.lcifn.spring.demo.namespace.SampleBean"/>
</beans>

再写一个Test类

package com.lcifn.spring.demo.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.lcifn.spring.demo.namespace.SampleBean;

public class SampleNamespaceHandlerTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("namespace-handler.xml");
        SampleBean sampleBean = context.getBean("sampleBean",SampleBean.class);
        sampleBean.print();
    }
}

运行后显示:

sample bean works

表示我们自定义的entity被spring解析并加载到容器中了。

源码分析

spring源码-IOC容器(二)-Bean的定位解析注册中讲解了spring是如何解析xml文件的,但主要就bean标签的解析进行了详解。这里我们来分析自定义标签的解析过程。

1.加载spring.schemas

spring在创建DOM解析的DocumentBuilder时,设置了EntityResolver的实现类ResourceEntityResolver

XmlBeanDefinitionReader.getEntityResolver()

ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
    this.entityResolver = new ResourceEntityResolver(resourceLoader);
}

DefaultDocumentLoader.createDocumentBuilder()

DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
    docBuilder.setEntityResolver(entityResolver);
}

而ResourceEntityResolver中的resolveEntity方法先调用其父类DelegatingEntityResolver的方法

DelegatingEntityResolver.resolveEntity()

if (systemId != null) {
    if (systemId.endsWith(DTD_SUFFIX)) {
        return this.dtdResolver.resolveEntity(publicId, systemId);
    }
    else if (systemId.endsWith(XSD_SUFFIX)) {
        return this.schemaResolver.resolveEntity(publicId, systemId);
    }
}

现在的spring都使用xsd文件,而这里的schemaResolver在构造函数中默认设置为PluggableSchemaResolver

this.schemaResolver = new PluggableSchemaResolver(classLoader);

而PluggableSchemaResolver的resolveEntity方法中调用getSchemaMappings()返回xsd文件的路径

PluggableSchemaResolver.resolveEntity()

if (systemId != null) {
    String resourceLocation = getSchemaMappings().get(systemId);
    if (resourceLocation != null) {
        Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
        try {
            InputSource source = new InputSource(resource.getInputStream());
            source.setPublicId(publicId);
            source.setSystemId(systemId);
            if (logger.isDebugEnabled()) {
                logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
            }
            return source;
        }
        catch (FileNotFoundException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
            }
        }
    }
}

第一次调用getSchemaMappings()时会去加载所有META-INF/spring.schemas配置,其中的schemaMappingsLocation默认为DEFAULT_SCHEMA_MAPPINGS_LOCATION

PluggableSchemaResolver    

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

private Map<String, String> getSchemaMappings() {
    if (this.schemaMappings == null) {
        synchronized (this) {
            if (this.schemaMappings == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                }
                try {
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded schema mappings: " + mappings);
                    }
                    Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
                    CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
                    this.schemaMappings = schemaMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                }
            }
        }
    }
    return this.schemaMappings;
}

至此spring.schemas在xml解析前就已经配置到DocumentBuilder中

2.加载spring.handlers

在DefaultBeanDefinitionDocumentReader类parseBeanDefinitions方法中,解析每一个element,也就是每一个标签时,会先判断element是否属于默认的命名空间。

DefaultBeanDefinitionDocumentReader

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

如果不是默认的命名空间,就调用BeanDefinitionParserDelegate的parseCustomElement方法,来解析出自定义命名空间的处理器,即NamespaceHandler的实现类。

BeanDefinitionParserDelegate.java

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

spring默认的NamespaceHandlerResolver为DefaultNamespaceHandlerResolver,而在resolve方法中调用getHandlerMappings方法,然后匹配传入的namespaceUri来获取NamespaceHandler的实例或是其className。

DefaultNamespaceHandlerResolver.java

public NamespaceHandler resolve(String namespaceUri) {
    Map<String, Object> handlerMappings = getHandlerMappings();
    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");
            }
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            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);
        }
    }
}

getHandlerMappings方法第一次调用时,加载META-INF/spring.handlers配置

DefaultNamespaceHandlerResolver.java

private Map<String, Object> getHandlerMappings() {
    if (this.handlerMappings == null) {
        synchronized (this) {
            if (this.handlerMappings == null) {
                try {
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return this.handlerMappings;
}

至此就解析到所有的spring.handlers并拿到自定义的NamespaceHandler实现类的实例。

3.NamespaceHandlerSupport

在BeanDefinitionParserDelegate的parseCustomElement方法中,调用NamespaceHandler的parse方法,并传入要解析的element和上下文信息(其中包含BeanFactory的实例)。

spring为我们封装了一个支持类NamespaceHandlerSupport,对parse方法进行了处理,拿到子标签的名称(比如上面sample:entity的entity),然后匹配到自定义的BeanDefinitionParser实现类(比如上面的EntityBeanDefinitionParse),调用其parse方法进行真正的解析。

NamespaceHandlerSupport.java

public BeanDefinition parse(Element element, ParserContext parserContext) {
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

其中的this.parsers就是BeanDefinitionParser的一个Map集,通过在init方法中注册进来。比如上面SampleNamespaceHandler的init方法

public void init() {
    registerBeanDefinitionParser("entity", new EntityBeanDefinitionParse());
}

而registerBeanDefinitionParser就是将自定义的BeanDefinitionParser实现扔到parsers中去

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
}

至此整个自定义xml的流程就讲解完了。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k