Spring 的 XML 解析原理,这一次全搞懂再走!,springmybatis 整合原理
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //创建 xml 的解析器,这里是一个委托模式 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); //这里传一个 this 进去,因为 ApplicationContext 是实现了 ResourceLoader 接口的 beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); //主要看这个方法 loadBeanDefinitions(beanDefinitionReader);}
首先创建了一个 XmlBeanDefinitionReader 对象,见名知意,这个就是解析 xml 的类,需要注意的是该类的构造方法接收的是 BeanDefinitionRegistry 对象,而这里将 DefaultListableBeanFactory 对象传入了进去(别忘记了这个对象是实现了 BeanDefinitionRegistry 类的),如果你足够敏感,应该可以想到后面会委托给该类去注册。注册什么呢?自然是注册 BeanDefintion。记住这个猜想,我们稍后来验证是不是这么回事。 接着进入 loadBeanDefinitions 方法获取之前保存的 xml 配置文件路径,并委托给 XmlBeanDefinitionReader 对象解析加载:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);} //获取需要加载的 xml 配置文件 String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}}
最后会进入到抽象父类 AbstractBeanDefinitionReader 中:
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // 这里获取到的依然是 DefaultListableBeanFactory 对象 ResourceLoader resourceLoader = getResourceLoader();if (resourceLoader == null) {throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");}
if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //把字符串类型的 xml 文件路径,形如:classpath*:user/**/*-context.xml,转换成 Resource 对象类型,其实就是用流 //的方式加载配置文件,然后封装成 Resource 对象 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //主要看这个方法 int count = loadBeanDefinitions(resources);if (actualResources != null) {Collections.addAll(actualResources, resources);}if (logger.isTraceEnabled()) {logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");}return count;}catch (IOException ex) {throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);}}else { // Can only load single resources by absolute URL. Resource resource = resourceLoader.getResource(location);int count = loadBeanDefinitions(resource);if (actualResources != null) {actualResources.add(resource);}if (logger.isTraceEnabled()) {logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");}return count;}}
这个方法中主要将 xml 配置加载到存中并封装成为 Resource 对象,这一步不重要,可以略过,主要的还是 loadBeanDefinitions 方法,最终还是调用到子类 XmlBeanDefinitionReader 的方法:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {try { //获取 Resource 对象中的 xml 文件流对象 InputStream inputStream = encodedResource.getResource().getInputStream();try { //InputSource 是 jdk 中的 sax xml 文件解析对象 InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());} //主要看这个方法 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {
try { //把 inputSource 封装成 Document 文件对象,这是 jdk 的 API Document doc = doLoadDocument(inputSource, resource); //主要看这个方法,根据解析出来的 document 对象,拿到里面的标签元素封装成 BeanDefinition int count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 创建 DefaultBeanDefinitionDocumentReader 对象,并委托其做解析注册工作 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount(); //主要看这个方法,需要注意 createReaderContext 方法中创建的几个对象 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}
public XmlReaderContext createReaderContext(Resource resource) { // XmlReaderContext 对象中保存了 XmlBeanDefinitionReader 对象和 DefaultNamespaceHandlerResolver 对象的引用,在后面会用到 return new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, getNamespaceHandlerResolver());}
接着看看 DefaultBeanDefinitionDocumentReader 中是如何解析的:
protected void doRegisterBeanDefinitions(Element root) { // 创建了 BeanDefinitionParserDelegate 对象 BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent); // 如果是 Spring 原生命名空间,首先解析 profile 标签,这里不重要 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); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}
preProcessXml(root); //主要看这个方法,标签具体解析过程 parseBeanDefinitions(root, this.delegate);postProcessXml(root);
this.delegate = parent;}
在这个方法中重点关注 preProcessXml、parseBeanDefinitions、postProcessXml 三个方法,其中 preProcessXml 和 postProcessXml 都是空方法,意思是在解析标签前后我们自己可以扩展需要执行的操作,也是一个模板方法模式,体现了 Spring 的高扩展性。然后进入 parseBeanDefinitions 方法看具体是怎么解析标签的:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) { //默认标签解析 parseDefaultElement(ele, delegate);}else { //自定义标签解析 delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}
这里有两种标签的解析:Spring 原生标签和自定义标签。怎么区分这两种标签呢?
// 自定义标签 context:component-scan/ // 默认标签 bean:/
如上,带前缀的就是自定义标签,否则就是 Spring 默认标签,无论哪种标签在使用前都需要在 Spring 的 xml 配置文件里声明 Namespace URI,这样在解析标签时才能通过 Namespace URI 找到对应的 NamespaceHandler。
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/beans
isDefaultNamespace 判断是不是默认标签,点进去看看是不是跟我上面说的一致:
public boolean isDefaultNamespace(Node node) {return isDefaultNamespace(getNamespaceURI(node));}
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";public boolean isDefaultNamespace(@Nullable String namespaceUri) {return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));}
可以看到http://www.springframework.org/schema/beans所对应的就是默认标签。接着,我们进入 parseDefaultElement 方法:
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 标签 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele);}}
这里面主要是对 import、alias、bean 标签的解析以及 beans 的字标签的递归解析,主要看看 bean 标签的解析:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 解析 elment 封装为 BeanDefinitionHolder 对象 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) { // 该方法功能不重要,主要理解设计思想:装饰者设计模式以及 SPI 设计思想 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try { // 完成 document 到 BeanDefinition 对象转换后,对 BeanDefinition 对象进行缓存注册 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());} // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 获取 id 和 name 属性 String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 获取别名属性,多个别名可用,;隔开 List<String> aliases = new ArrayList<>();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.isTraceEnabled()) {logger.trace("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}} //检查 beanName 是否重复 if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);} // 具体的解析封装过程还在这个方法里 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.isTraceEnabled()) {logger.trace("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);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}
return null;} // bean 的解析 public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName)); // 获取 class 名称和父类名称 String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}
try { // 创建 GenericBeanDefinition 对象 AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 解析 bean 标签的属性,并把解析出来的属性设置到 BeanDefinition 对象中 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //解析 bean 中的 meta 标签 parseMetaElements(ele, bd); //解析 bean 中的 lookup-method 标签 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); //解析 bean 中的 replaced-method 标签 parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析 bean 中的 constructor-arg 标签 parseConstructorArgElements(ele, bd); //解析 bean 中的 property 标签 parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));
return bd;}
return null;}
bean 标签的解析步骤仔细理解并不复杂,就是将一个个标签属性的值装入到了 BeanDefinition 对象中,这里需要注意 parseConstructorArgElements 和 parsePropertyElements 方法,分别是对 constructor-arg 和 property 标签的解析,解析完成后分别装入了 BeanDefinition 对象的 constructorArgumentValues 和 propertyValues 中,而这两个属性在接下来 c 和 p 标签的解析中还会用到,而且还涉及一个很重要的设计思想——装饰器模式。 Bean 标签解析完成后将生成的 BeanDefinition 对象、bean 的名称以及别名一起封装到了 BeanDefinitionHolder 对象并返回,然后调用了 decorateBeanDefinitionIfRequired 进行装饰:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder; //根据 bean 标签属性装饰 BeanDefinitionHolder,比如<bean class="xx" p:username="dark"/> NamedNodeMap attributes = ele.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {Node node = attributes.item(i);finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);} //根据 bean 标签子元素装饰 BeanDefinitionHolder\ NodeList children = ele.getChildNodes();for (int i = 0; i < children.getLength(); i++) {Node node = children.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);}}return finalDefinition;}
在这个方法中分别对 Bean 标签的属性和子标签迭代,获取其中的自定义标签进行解析,并装饰之前创建的 BeanDefinition 对象,如同下面的 c 和 p:
// c:和 p:表示通过构造器和属性的 setter 方法给属性赋值,是 constructor-arg 和 property 的简化写法<bean class="com.dark.bean.Student" id="student" p:username="Dark" p:password="111" c:age="12" c:sex="1"/>
两个步骤是一样的,我们点进 decorateIfRequired 方法中:
public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { //根据 node 获取到 node 的命名空间,形如:http://www.springframework.org/schema/p String namespaceUri = getNamespaceURI(node);if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) { // 根据配置文件获取 namespaceUri 对应的处理类,SPI 思想 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler != null) { //调用 NamespaceHandler 处理类的 decorate 方法,开始具体装饰过程,并返回装饰完的对象 BeanDefinitionHolder decorated =handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));if (decorated != null) {return decorated;}}else if (namespaceUri.startsWith("http://www.springframework.org/")) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);}else { // A custom namespace, not to be handled by Spring - maybe "xml:...". if (logger.isDebugEnabled()) {logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");}}}return originalDef;}
这里也和我们之前说的一样,首先获取到标签对应的 namespaceUri,然后通过这个 Uri 去获取到对应的 NamespceHandler,最后再调用 NamespceHandler 的 decorate 方法进行装饰。我们先来看看获取 NamespceHandler 的过程,这涉及到一个非常重要的高扩展性的思想——SPI(有关 SPI,在我之前的文章Dubbo——SPI及自适应扩展原理中已经详细讲解过,这里不再赘述):
public NamespaceHandler resolve(String namespaceUri) { // 获取 spring 中所有 jar 包里面的 "META-INF/spring.handlers"文件,并且建立映射关系 Map<String, Object> handlerMappings = getHandlerMappings(); //根据 namespaceUri:http://www.springframework.org/schema/p,获取到这个命名空间的处理类 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); //调用处理类的 init 方法,在 init 方法中
完成标签元素解析类的注册 namespaceHandler.init();handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;}}} // AOP 标签对应的 NamespaceHandler,可以发现 NamespaceHandler 的作用就是管理和注册与自己相关的标签解析器 public void init() { // In 2.0 XSD as well as in 2.1 XSD. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());}
看到这里我们应该就清楚了 Spring 是如何解析 xml 里的标签了以及我们如果要扩展自己的标签该怎么做。只需要创建一个我们的自定义标签和解析类,并指定它的命名空间以及 NamespaceHandler,最后在 META-INF/spring.handlers 文件中指定命名空间和 NamespaceHandler 的映射关系即可,就像 Spring 的 c 和 p 标签一样:
像这样使用 SPI 的思想设计我们的项目的话,当需要扩展时,不需要改动任何的代码,非常的方便优雅。 接着,我们回到 handler 的 decorate 方法,这里有三个默认的实现类:NamespaceHandlerSupport、SimpleConstructorNamespaceHandler、SimplePropertyNamespaceHandler。第一个是一个抽象类,与我们这里的流程无关,感兴趣的可自行了解,第二个和第三个则分别是 c 和 p 标签对应的 NamespaceHandler,两个装饰的处理逻辑基本上是一样的,我这里进入的是 SimpleConstructorNamespaceHandler 类:
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {if (node instanceof Attr) {Attr attr = (Attr) node;String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));String argValue = StringUtils.trimWhitespace(attr.getValue());
ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();boolean ref = false; // handle -ref arguments if (argName.endsWith(REF_SUFFIX)) {ref = true;argName = argName.substring(0, argName.length() - REF_SUFFIX.length());}
ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);valueHolder.setSource(parserContext.getReaderContext().extractSource(attr)); // handle "escaped"/"_" arguments if (argName.startsWith(DELIMITER_PREFIX)) {String arg = argName.substring(1).trim(); // fast default check if (!StringUtils.hasText(arg)) {cvs.addGenericArgumentValue(valueHolder);
评论