Spring源码阅读目录
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第三章 Spring源码阅读之假如让我来写IOC容器——解析配置文件篇 已经实现了加载不同的配置文件,接下来重点看下如何加载XML的配置文件吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大要求A君在一周内开发个简单的IOC容器
前情提要:在A君抽取BeanDefinition之后,老大又提出XML文件加载成DOM可以有多种方式,不能把他写死,希望定义个接口,来支持不同的实现
第六版 抽取DOC加载、注册接口
接口!接口!又是接口!A君从未像这时一样痛恨过接口。之前的版本被打回来,大多数都是因为这个原因
既然老大说加载DOM可以有多种实现,那就再定义个DocumentLoader接口,用以支持不同形式的DOM加载方式,DocumentLoader接口代码如下:
import com.hqd.ch03.v6.io.Resource;
import org.w3c.dom.Document;
/**
* xml dom加载接口
*/
public interface DocumentLoader {
/**
* 加载dom
*
* @param resource
* @return
*/
Document loadDocument(Resource resource);
}
再提供个DefaultDocumentLoader类作为默认的实现,代码如下:
import com.hqd.ch03.v6.io.Resource;
import com.hqd.ch03.v6.reader.xml.doc.DocumentLoader;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class DefaultDocumentLoader implements DocumentLoader {
@Override
public Document loadDocument(Resource resource) {
try {
InputSource inputSource = new InputSource(resource.getInputStream());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
return docBuilder.parse(inputSource);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
经历过这么多次,A君已经麻了,已经预感到老大会说什么了:从DOM解析成BeanDefinition 也可能有多种实现,巴拉巴拉。A君索性一步到位,又定义了BeanDefinitionDocumentReader接口,用以规范从Dom解析成BeanDefinition的注册过程。接口代码如下:
import com.hqd.ch03.v6.registry.BeanDefinitionRegistry;
import org.w3c.dom.Document;
public interface BeanDefinitionDocumentReader {
void registerBeanDefinitions(Document doc, BeanDefinitionRegistry registry);
}
老样子,提供个DefaultBeanDefinitionDocumentReader作为默认实现,负责具体解析xml标签。代码如下:
import com.hqd.ch03.v6.config.BeanDefinition;
import com.hqd.ch03.v6.config.MutablePropertyValues;
import com.hqd.ch03.v6.reader.xml.parse.BeanDefinitionDocumentReader;
import com.hqd.ch03.v6.registry.BeanDefinitionRegistry;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
public static final String DESCRIPTION_ELEMENT = "description";
public static final String NAME_ATTRIBUTE = "name";
public static final String BEAN_ELEMENT = "bean";
public static final String ID_ATTRIBUTE = "id";
public static final String CLASS_ATTRIBUTE = "class";
public static final String SCOPE_ATTRIBUTE = "scope";
public static final String LAZY_INIT_ATTRIBUTE = "lazy-init";
public static final String INIT_METHOD_ATTRIBUTE = "init-method";
public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method";
public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean";
public static final String REF_ATTRIBUTE = "ref";
public static final String VALUE_ATTRIBUTE = "value";
private static final String SINGLETON_ATTRIBUTE = "singleton";
@Override
public void registerBeanDefinitions(Document doc, BeanDefinitionRegistry registry) {
parseBeanDefinitions(doc.getDocumentElement(), registry);
}
protected void parseBeanDefinitions(Element root, BeanDefinitionRegistry registry) {
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;
processBeanDefinition(ele, registry);
}
}
}
private void processBeanDefinition(Element ele, BeanDefinitionRegistry registry) {
BeanDefinition bd = new BeanDefinition();
String id = ele.getAttribute(ID_ATTRIBUTE);
bd.setId(id);
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
bd.setLazyInit(StringUtils.isBlank(lazyInit) ? true : Boolean.valueOf(lazyInit));
bd.setBeanClass(ele.getAttribute(CLASS_ATTRIBUTE));
bd.setProperties(parsePropertyElements(ele.getChildNodes()));
registry.registerBeanDefinition(bd.getId(), bd);
}
/**
* 转换bean下的property节点
*
* @param nodes
* @return
*/
private MutablePropertyValues parsePropertyElements(NodeList nodes) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
if (nodes.getLength() != 0) {
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element element = (Element) node;
propertyValues.addProperty(element.getAttribute(NAME_ATTRIBUTE), element.getAttribute(VALUE_ATTRIBUTE));
}
}
}
return propertyValues;
}
}
由于DOM的加载、解析已经从XmlBeanDefinitionReader剥离出去,所以还需要对其进行修改,删除解析xml相关操作,直接调用具体实现即可。改造如下:
完整代码:
import com.hqd.ch03.v6.io.Resource;
import com.hqd.ch03.v6.reader.BeanDefinitionReader;
import com.hqd.ch03.v6.reader.xml.doc.DocumentLoader;
import com.hqd.ch03.v6.reader.xml.doc.support.DefaultDocumentLoader;
import com.hqd.ch03.v6.reader.xml.parse.BeanDefinitionDocumentReader;
import com.hqd.ch03.v6.reader.xml.parse.spport.DefaultBeanDefinitionDocumentReader;
import com.hqd.ch03.v6.registry.BeanDefinitionRegistry;
import org.w3c.dom.Document;
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
private final BeanDefinitionRegistry beanDefinitionRegistry;
private DocumentLoader documentLoader = new DefaultDocumentLoader();
private BeanDefinitionDocumentReader documentReader = new DefaultBeanDefinitionDocumentReader();
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
@Override
public void loadBeanDefinition(Resource resource) {
Document doc = documentLoader.loadDocument(resource);
registerBeanDefinitions(doc);
}
private void registerBeanDefinitions(Document doc) {
this.documentReader.registerBeanDefinitions(doc, beanDefinitionRegistry);
}
public void setDocumentLoader(DocumentLoader documentLoader) {
this.documentLoader = (documentLoader != null ? documentLoader : new DefaultDocumentLoader());
}
public void setDocumentReader(BeanDefinitionDocumentReader documentReader) {
this.documentReader = documentReader;
}
}
在做个简单的测试,测试代码如下:
@Test
public void v6() {
System.out.println("############# 第六版:提取Document加载接口 #############");
SpringImitationV6 beanFactory = new SpringImitationV6Xml("classpath:v6/bean.xml");
User user = beanFactory.getBean("user", User.class);
System.out.println("基于类路径加载:" + user);
}
输出如下:
“哼哼,这次看老大还有什么好说的”。看到测试结果后,A君心里暗想。正想着,老大突然就杀了过来,A君心里咯噔一下,顿觉不妙
老大笑着拍了拍A君的肩膀,说:A君,用户提了个需要,他们想要自定义XML标签,这个你一起回去想一想
第七版 自定义XML标签
接到新需求后,虽然A君心里早有预感,但心里瞬间千万只草尼玛奔腾而过。还不个简单的需求,自定义个锤子自定义,又得开始加班了,害
对于自定义XML标签,A君之前接触甚少,不经挠头,无从下手。几经波折(不停的百度),A君决定先从XML约束开始入手
不论撸代码,还是其他,总是先有规范,再有具体实现,这样就不会变成一堆乱麻,难以维护。XML也是一样的道理,用XML描述一个类,十个人可能会有十种不同的写法。所以,为了方便维护和实现,必须对XML格式进行约束
对于XML的约束,主要有两种:xsd、dtd。举个栗子,以下面的XML为例,如下:
<beans>
<user id="testbean" name="testbean" age="222"/>
</beans>
dtd格式
如果是dtd,则XML如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.lexueba.com/schema/user/dtd/user-test.dtd">
<beans>
<user age="22" name="user" id="user"/>
</beans>
dtd约束文件如下:
<!ELEMENT beans (
(user)*
)>
<!ELEMENT user EMPTY>
<!ATTLIST user id CDATA #IMPLIED>
<!ATTLIST user name CDATA #IMPLIED>
<!ATTLIST user age CDATA #IMPLIED>
xsd格式
如果是xsd,则XML如下:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.lexueba.com/schema/user"
xsi:schemaLocation="http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user-test.xsd
">
<user id="testbean" name="testbean" age="222"/>
</beans>
xsd约束文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lexueba.com/schema/user"
elementFormDefault="qualified">
<element name="beans">
<complexType>
<sequence>
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="age" type="int"/>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
了解完两者的区别后(这里具体看xsd),A君又一阵头大。这才开始而已,自定义标签又可以分为几种:
- 自定义<bean>节点同类型的标签
- 自定义<bean>节点的子标签
- 自定义<bean>节点的属性
用户具体要怎么定义标签,A君不得而知。但是作为容器的开发者,A君可以定义接口让用户实现,而后回调即可
因为存在多种情况,A君先定义BeanDefinitionParser接口负责解析<bean>节点同类型的标签,其代码如下:
import com.hqd.ch03.v7.config.BeanDefinition;
import org.w3c.dom.Element;
/**
* 自定义标签解析
*/
public interface BeanDefinitionParser {
BeanDefinition parse(Element element);
}
再定义一个BeanDefinitionDecorator接口,负责解析<bean>节点下自定义的子节点和属性,其代码如下:
import com.hqd.ch03.v7.config.BeanDefinition;
import org.w3c.dom.Node;
/**
* 解析<bean>的自定义子节点和自定义属性
*/
public interface BeanDefinitionDecorator {
BeanDefinition decorate(Node node, BeanDefinition beanDefinition);
}
有了这两个接口后,用户可以个性化实现,但是此时,容器还无法感知到用户实现类。还需要有个注册接口,可以把用户实现的类注册到容器中去。而后,A君有定义了负责注册(NamespaceHandler)接口,代码如下:
import com.hqd.ch03.v6.config.BeanDefinition;
import org.w3c.dom.Element;
import com.hqd.ch03.v7.config.BeanDefinition;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* 标签解析注册接口
*/
public interface NamespaceHandler {
void init();
/**
* 解析BeanDefinition自定义标签,eg:
* <beans>
* * <a></a>
* </beans>
*/
BeanDefinition parse(Element element);
/**
* 解析bean子标签或者属性,eg:
* <beans>
* * <bean name="xxx" a="xxx">
* * <a></a>
* * </bean>
* </beans>
*/
BeanDefinition decorate(Node source, BeanDefinition definition);
}
为了方便用户使用,A君又定义了NamespaceHandlerSupport抽象类,做个简单的封装。代码如下:
import com.hqd.ch03.v7.config.BeanDefinition;
import com.hqd.ch03.v7.reader.xml.namespace.NamespaceHandler;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionDecorator;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionParser;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.HashMap;
import java.util.Map;
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
/**
* BeanDefinition自定义标签解析器,eg:
* <beans>
* * <a></a>
* </beans>
*/
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
/**
* bean子标签解析器,eg:
* <beans>
* * <bean>
* * <a></a>
* * </bean>
* </beans>
*/
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
/**
* 自定义属性,eg:
* <beans>
* * <bean name="xx" a="xx"></bean>
* </beans>
*/
private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<>();
@Override
public BeanDefinition parse(Element element) {
BeanDefinitionParser parser = findParserForElement(element);
return (parser != null ? parser.parse(element) : null);
}
protected BeanDefinitionParser findParserForElement(Element element) {
String localName = element.getLocalName();
return this.parsers.get(localName);
}
@Override
public BeanDefinition decorate(Node node, BeanDefinition definition) {
BeanDefinitionDecorator decorator = findDecoratorForNode(node);
return (decorator != null ? decorator.decorate(node, definition) : null);
}
private BeanDefinitionDecorator findDecoratorForNode(Node node) {
String localName = node.getLocalName();
if (node instanceof Element) {//自定义子节点
return this.decorators.get(localName);
} else if (node instanceof Attr) {//自定义标签属性
return this.attributeDecorators.get(localName);
} else {
throw new RuntimeException(String.format("【%s】节点解析异常", node));
}
}
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
this.decorators.put(elementName, dec);
}
protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
this.attributeDecorators.put(attrName, dec);
}
}
做完这些之后,A君又开始犯难了:
- 自定义标签要怎么和用户实现类在xml解析前就关联上?
- xml约束定义的是http,如果网络连接慢,甚至断网了怎么办?
针对上面的问题,A君设想了以下解决方案
- 自定义标签可以通过URI关联NamespaceHandler实现类,可以通过配置文件,来维护URI和NamespaceHandler实现类的关系
- 约束定义的是http,可以通过本地文件,实现的方式类似,用配置文件来维护URI和xsd约束文件的关系
有了思路之后,A君继续撸袖子忙活了起来,分别定义了两个文件:spring.handlers、spring.schemas,
spring.handlers文件用来维护URI和实现类的关系(key是URI,value是类全路径),spring.schemas用来维护URI和约束的关系(key是URI,value是xsd路径)
确定了配置文件之后,接下来就是针对两个配置文件的解析。先来看下spring.handlers文件的解析,老规矩,定义个NamespaceHandlerResolver接口,用来注册用户配置的实现类,接口代码如下:
/**
* 读取自定义的解析类
*/
public interface NamespaceHandlerResolver {
NamespaceHandler resolve(String namespaceUri);
}
接着是默认实现类DefaultNamespaceHandlerResolver, 这个类只需加载spring.handlers 文件,初始化相关配置实现类后放进缓存就行了,代码如下:
import com.hqd.ch03.v7.reader.xml.namespace.NamespaceHandler;
import com.hqd.ch03.v7.reader.xml.namespace.NamespaceHandlerResolver;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
private Map<String, NamespaceHandler> handlerMappings = new HashMap<>();
public DefaultNamespaceHandlerResolver() {
this.init();
}
protected void init() {
Properties properties = new Properties();
try {
/**
* 加载配置文件
*/
properties.load(this.getClass().getClassLoader().getResourceAsStream(DEFAULT_HANDLER_MAPPINGS_LOCATION));
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
String uri = keys.nextElement().toString();
String handlerClass = properties.getProperty(uri);
/**
* 初始化配置的类
*/
NamespaceHandler handler = (NamespaceHandler) Class.forName(handlerClass).getConstructor().newInstance();
handler.init();
/**
* 放入缓存
*/
handlerMappings.put(uri, handler);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public NamespaceHandler resolve(String namespaceUri) {
return this.handlerMappings.get(namespaceUri);
}
}
接着是spring.schemas文件的解析。不过在此之前,需要先改造下DocumentLoader接口,代码如下:
import com.hqd.ch03.v7.io.Resource;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
/**
* xml dom加载接口
*/
public interface DocumentLoader {
/**
* 加载dom
*
* @param resource xml资源
* @param entityResolver 本地xml验证器
* @param errorHandler xml解析失败处理器
* @return
*/
Document loadDocument(Resource resource, EntityResolver entityResolver, ErrorHandler errorHandler);
}
在修改下实现类DefaultDocumentLoader,代码如下:
import com.hqd.ch03.v7.io.Resource;
import com.hqd.ch03.v7.reader.xml.doc.DocumentLoader;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class DefaultDocumentLoader implements DocumentLoader {
protected static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
protected static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
protected InputSource getInputSource(Resource resource) throws Exception {
return new InputSource(resource.getInputStream());
}
@Override
public Document loadDocument(Resource resource, EntityResolver resolver, ErrorHandler errorHandler) {
try {
InputSource inputSource = getInputSource(resource);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
/**
* 设置为true,获取namespaceURI
* dtd需要设置成false
*/
factory.setNamespaceAware(true);
/**
* 开启验证模式
*/
factory.setValidating(true);
/**
* xsd需要设置,dtd不需要
*/
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
DocumentBuilder docBuilder = factory.newDocumentBuilder();
/**
* 设置验证失败处理器
*/
docBuilder.setErrorHandler(errorHandler);
/**
* 指定xml验证器
*/
docBuilder.setEntityResolver(resolver);
return docBuilder.parse(inputSource);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
之所以这么修改,是因为验证XML,默认会去读取XML文档上的声明,即HTTP请求去下载约束文件。如果要改成读取本地约束文件,则需要提供个EntityResolver实现类。另外A君虽然定义约束,但是架不住用户乱写或者写错了,出现这种情况时A君希望解析XML时候就报错,毕竟错误越早暴露越好嘛,于是A君再提供个ErrorHandler 实现类用来处理验证异常的情况
完成了DocumentLoader接口的相关修改,再来就是ErrorHandler 和EntityResolver的实现了,由于ErrorHandler 比较简单,先来实现ErrorHandler 接口,实现类SimpleSaxErrorHandler代码如下:
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* xml验证异常处理实现类
*/
public class SimpleSaxErrorHandler implements ErrorHandler {
@Override
public void warning(SAXParseException exception) throws SAXException {
exception.printStackTrace();
}
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
}
接着就是EntityResolver的实现了。前面被老大折腾了这么久,A君也有点明白套路了:由于XML约束存在DTD和XSD,先定义一个总的入口,然后再交给具体的处理类。就像上头把需求分给老大,而实际干活的却是A君。。。
想到这里,A君定义了ResourceEntityResolver作为入口类,包含了dtd和xsd的验证,代码如下:
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;
/**
* xml校验入口。提供dtd、xsd校验
* 具体校验委托给具体实现类
*/
public class ResourceEntityResolver implements EntityResolver {
public static final String DTD_SUFFIX = ".dtd";
public static final String XSD_SUFFIX = ".xsd";
private final EntityResolver dtdResolver;
private final EntityResolver schemaResolver;
public ResourceEntityResolver() {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver();
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
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);
}
}
return null;
}
}
有了入口类之后,接下来看下spring.schemas文件的解析,A君定义了 PluggableSchemaResolver 作为xsd约束的具体处理类(这里重点说下xsd)。这个实现也简单,解析spring.schemas文件存进map缓存,根据uri获取对应路径即可。代码如下:
import com.hqd.ch03.v7.io.Resource;
import com.hqd.ch03.v7.io.support.ClassPathResource;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 读取本地xsd校验文件进行校验
*/
public class PluggableSchemaResolver implements EntityResolver {
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
private final String schemaMappingsLocation;
private volatile Map<String, String> schemaMappings;
public PluggableSchemaResolver(String schemaMappingsLocation) {
this.schemaMappingsLocation = schemaMappingsLocation;
}
public PluggableSchemaResolver() {
this(DEFAULT_SCHEMA_MAPPINGS_LOCATION);
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
String resourceLocation = getSchemaMappings().get(systemId);
/**
* systemId以https开头,且资源不存在,尝试以http的形式获取
*/
if (resourceLocation == null && systemId.startsWith("https:")) {
resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
}
/**
* 资源存在,则尝试加载
*/
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
} catch (FileNotFoundException ex) {
throw new RuntimeException(ex);
}
}
}
return null;
}
/**
* 读取指定目录下的配置文件,缓存起来
*
* @return
*/
private Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
synchronized (this) {
schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
try {
Properties mappings = new Properties();
mappings.load(this.getClass().getClassLoader().getResourceAsStream(schemaMappingsLocation));
Set<Object> objects = mappings.keySet();
schemaMappings = new ConcurrentHashMap<>(objects.size());
for (Object key : objects) {
String keyStr = key.toString();
String value = mappings.getProperty(keyStr);
schemaMappings.put(keyStr, value);
}
this.schemaMappings = schemaMappings;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
return schemaMappings;
}
}
这些都做好之后,接着需要对DefaultBeanDefinitionDocumentReader进行一番改造,以支持xml约束和解析自定义标签。这个可以根据标签的URI进行区分是用默认的标签解析器还是用户自定义的标签解析器。先是parseBeanDefinitions方法改造,如下:
isDefaultNamespace方法
parseCustomElement方法
再是processBeanDefinition方法相关改造,需要添加解析自定义标签。如下:
decorateBeanDefinitionIfRequired方法
decorateIfRequired方法
完整代码:
import com.hqd.ch03.v7.config.BeanDefinition;
import com.hqd.ch03.v7.config.MutablePropertyValues;
import com.hqd.ch03.v7.reader.xml.namespace.NamespaceHandler;
import com.hqd.ch03.v7.reader.xml.namespace.NamespaceHandlerResolver;
import com.hqd.ch03.v7.reader.xml.namespace.support.DefaultNamespaceHandlerResolver;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionDocumentReader;
import com.hqd.ch03.v7.registry.BeanDefinitionRegistry;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.*;
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public static final String DESCRIPTION_ELEMENT = "description";
public static final String NAME_ATTRIBUTE = "name";
public static final String BEAN_ELEMENT = "bean";
public static final String ID_ATTRIBUTE = "id";
public static final String CLASS_ATTRIBUTE = "class";
public static final String SCOPE_ATTRIBUTE = "scope";
public static final String LAZY_INIT_ATTRIBUTE = "lazy-init";
public static final String INIT_METHOD_ATTRIBUTE = "init-method";
public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method";
public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean";
public static final String REF_ATTRIBUTE = "ref";
public static final String VALUE_ATTRIBUTE = "value";
private static final String SINGLETON_ATTRIBUTE = "singleton";
protected NamespaceHandlerResolver resolver = new DefaultNamespaceHandlerResolver();
@Override
public void registerBeanDefinitions(Document doc, BeanDefinitionRegistry registry) {
parseBeanDefinitions(doc.getDocumentElement(), registry);
}
/**
* 解析xml标签为BeanDefinition
*
* @param root
* @param registry
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionRegistry registry) {
if (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 (isDefaultNamespace(ele)) {
processBeanDefinition(ele, registry);
} else {//自定义标签
parseCustomElement(ele, registry);
}
}
}
} else {
parseCustomElement(root, registry);
}
}
/**
* 转换bean底下的自定义标签和属性
*
* @param ele
* @param originalDef
* @return
*/
private BeanDefinition decorateBeanDefinitionIfRequired(
Element ele, BeanDefinition originalDef) {
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
originalDef = decorateIfRequired(node, originalDef);
}
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
originalDef = decorateIfRequired(node, originalDef);
}
}
return originalDef;
}
private BeanDefinition decorateIfRequired(Node node, BeanDefinition originalDef) {
String namespaceURI = node.getNamespaceURI();
if (!isDefaultNamespace(namespaceURI)) {
NamespaceHandler handler = resolver.resolve(namespaceURI);
if (handler != null) {
return handler.decorate(node, originalDef);
}
}
return originalDef;
}
/**
* 处理bean标签
*
* @param ele
* @param registry
*/
private void processBeanDefinition(Element ele, BeanDefinitionRegistry registry) {
BeanDefinition bd = new BeanDefinition();
String id = ele.getAttribute(ID_ATTRIBUTE);
bd.setId(id);
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
bd.setLazyInit(StringUtils.isBlank(lazyInit) ? true : Boolean.valueOf(lazyInit));
bd.setBeanClass(ele.getAttribute(CLASS_ATTRIBUTE));
bd.setProperties(parsePropertyElements(ele.getChildNodes()));
/**
* 解析自定义标签
*/
BeanDefinition beanDefinition = decorateBeanDefinitionIfRequired(ele, bd);
registry.registerBeanDefinition(bd.getId(), beanDefinition);
}
/**
* 转换bean下的property节点
*
* @param nodes
* @return
*/
private MutablePropertyValues parsePropertyElements(NodeList nodes) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
if (nodes.getLength() != 0) {
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element element = (Element) node;
propertyValues.addProperty(element.getAttribute(NAME_ATTRIBUTE), element.getAttribute(VALUE_ATTRIBUTE));
}
}
}
return propertyValues;
}
/**
* 调用自定义解析器
*
* @param ele
* @param registry
*/
private void parseCustomElement(Element ele, BeanDefinitionRegistry registry) {
NamespaceHandler resolve = resolver.resolve(getNamespaceURI(ele));
BeanDefinition bd = resolve.parse(ele);
registry.registerBeanDefinition(bd.getId(), bd);
}
/**
* 判断是否是spring-bean的标签
*
* @param namespaceUri
* @return
*/
public boolean isDefaultNamespace(String namespaceUri) {
return StringUtils.isBlank(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}
/**
* 判断是否是spring-bean的标签
*
* @param element
* @return
*/
public boolean isDefaultNamespace(Element element) {
return isDefaultNamespace(getNamespaceURI(element));
}
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}
}
对应的代码改造已经完成,还需要测试一下来验证努力的成果
A君新创建了个Student实体类,代码如下:
import lombok.Data;
@Data
public class Student extends User {
private String school;
private String hobbies;
@Override
public String toString() {
return "User{" +
"name='" + getName() + '\'' +
", age=" + getAge() +
", school='" + school + '\'' +
", hobbies=" + hobbies +
'}';
}
}
在定义个xsd约束文件,xsd文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns:loc="http://www.lexueba.com/schema/user"
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lexueba.com/schema/user"
elementFormDefault="qualified">
<attribute name="school"/>
<element name="hobbies">
<complexType>
<attribute name="value" type="string"/>
</complexType>
</element>
<complexType name="userType">
<attribute name="id" type="string"/>
<attribute name="name" type="string"/>
<attribute name="age" type="int"/>
</complexType>
<element name="user" type="loc:userType"/>
<element name="beans">
<complexType>
<sequence>
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="loc:user"/>
</choice>
</sequence>
</complexType>
</element>
</schema>
重新写个包含自定义标签的bean.xml文件,并加入spring的相关约束,如下:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:custom="http://www.lexueba.com/schema/user"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd
">
<custom:user id="testbean" name="testbean" age="222"/>
<bean id="student" class="com.hqd.ch03.bean.Student" custom:school="南山幼儿园">
<property name="name" value="法外狂徒-张三"/>
<property name="age" value="14"/>
<custom:hobbies value="唱,跳,rap"/>
</bean>
</beans>
准备完xml后,还要实现相关接口来完成解析。先是自定义<bean>同级标签的解析类UserBeanDefinitionParser,代码如下:
import com.hqd.ch03.bean.User;
import com.hqd.ch03.v7.config.BeanDefinition;
import com.hqd.ch03.v7.config.MutablePropertyValues;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionParser;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;
public class UserBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element) {
BeanDefinition bd = new BeanDefinition();
String id = element.getAttribute("id");
String userName = element.getAttribute("name");
String age = element.getAttribute("age");
bd.setBeanClass(User.class.getName());
bd.setId(id);
MutablePropertyValues propertyValues = new MutablePropertyValues();
if (StringUtils.isNotBlank(userName)) {
propertyValues.addProperty("name", userName);
}
if (StringUtils.isNotBlank(age)) {
propertyValues.addProperty("age", age);
}
bd.setProperties(propertyValues);
return bd;
}
}
<bean>自定义子标签解析类,代码如下:
import com.hqd.ch03.v7.config.BeanDefinition;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionDecorator;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.HashMap;
import java.util.Map;
public class HobbiesBeanDefinitionDecorator implements BeanDefinitionDecorator {
private Map<String, String> parsePropertyValues(Element ele) {
String hobbiesStr = ele.getAttribute("value");
if (StringUtils.isNoneBlank(hobbiesStr)) {
HashMap<String, String> map = new HashMap<>();
map.put("hobbies", hobbiesStr);
return map;
}
return null;
}
@Override
public BeanDefinition decorate(Node node, BeanDefinition definition) {
if (node instanceof Element
&& StringUtils.equalsIgnoreCase(node.getLocalName(), "hobbies")) {
Element ele = (Element) node;
Map<String, String> map = parsePropertyValues(ele);
if (map != null) {
definition.getProperties()
.addPropertyValues(map);
}
}
return definition;
}
}
最后是<bean>自定义属性解析类,代码如下:
import com.hqd.ch03.v7.config.BeanDefinition;
import com.hqd.ch03.v7.reader.xml.parse.BeanDefinitionDecorator;
import com.sun.org.apache.xerces.internal.dom.DeferredAttrNSImpl;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Node;
public class SchoolBeanDefinitionDecorator implements BeanDefinitionDecorator {
@Override
public BeanDefinition decorate(Node node, BeanDefinition definition) {
if (node instanceof DeferredAttrNSImpl
&& StringUtils.equalsIgnoreCase(node.getLocalName(), "school")) {
DeferredAttrNSImpl attrNS = (DeferredAttrNSImpl) node;
definition.getProperties()
.addProperty(attrNS.getLocalName(), attrNS.getValue());
}
return definition;
}
}
准备完xml和标签实现类后,还要实现NamespaceHandler接口注册这几个实现类,MyNamespaceHandler代码如下:
import com.hqd.ch03.v7.reader.xml.namespace.support.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
registerBeanDefinitionDecorator("hobbies", new HobbiesBeanDefinitionDecorator());
registerBeanDefinitionDecoratorForAttribute("school", new SchoolBeanDefinitionDecorator());
}
}
这些都做完之后,还需要配置下spring.handlers和spring.schemas,将定义的xsd和注册类与URI关联起来
spring.handlers配置如下:
http\://www.lexueba.com/schema/user=com.hqd.test.MyNamespaceHandler
spring.schemas配置如下:
http\://www.lexueba.com/schema/user.xsd=schema/xsd/user.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=schema/xsd/spring-beans.xsd
好了,现在一切都准备完了,可以进行测试了,测试代码如下:
/**
* 增加自定义标签
*/
@Test
public void v7() {
System.out.println("############# 第七版:增加自定义标签 #############");
SpringImitationV7 beanFactory = new SpringImitationV7Xml("classpath:v7/bean.xml");
User user = beanFactory.getBean("student", User.class);
System.out.println("加载默认标签:" + user);
User testbean = beanFactory.getBean("testbean", User.class);
System.out.println("加载自定义标签:" + testbean);
}
输入如下:
总算做好了,A君喜悦之情溢于言表。为了需要求,A君都不知道掉了多少头发哩!升职加薪,指日可待,嘿嘿
老大看着代码,难得没再提什么建议了,反复看了几遍,说道:A君,干得不错,准备上线吧
总结
历经千辛万苦,项目终于可以上线了,A君后边还会遇到什么问题,让我们拭目以待吧。欲知后事如何,请看下回分解(✪ω✪)