Spring源码解密之默认标签的解析

前言

紧跟上篇 Spring解密 - XML解析 与 Bean注册 ,我们接着往下分析源码,话不多说了,来一起看看详细的介绍吧。

解密

在 Spring 的 XML 配置里面有两大类声明,一个是默认的如 <bean id="person" class="com.battcn.bean.Person"/> ,另一类就是自定义的如<tx:annotation-driven /> ,两种标签的解析方式差异是非常大的。parseBeanDefinitions 方法就是用来区分不同标签所使用的解析方式。通过 node.getNamespaceURI() 方法获取命名空间,判断是默认命名空间还是自定义命名空间,并与 Spring 中固定的命名空间 http://www.springframework.org/schema/beans 进行比对,如果一致则采用parseDefaultElement(ele, delegate);否则就是delegate.parseCustomElement(ele);

默认标签的解析

parseDefaultElement 对 4 种不同的标签 import、alias、bean、beans 做了不同的处理,其中 bean 标签的解析最为复杂也最为重要,所以我们将从 bean 开始深入分析,如果能理解此标签的解析过程,其他标签的解析自然会迎刃而解。上一篇中只是简单描述了一下,本篇我们围绕解析模块详细的探讨一下

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
 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);
 }
 // import 标签解析
 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
 // beans标签解析 递归方式
 doRegisterBeanDefinitions(ele);
 }
 }
}

首先我们来分析下当类中的 processBeanDefinition(ele, delegate)

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 // 委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析
 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 if (bdHolder != null) {
 // 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析
 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 try {
 // 解析完成后需要对解析后的bdHolder进行注册,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法
 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
 }
 catch (BeanDefinitionStoreException ex) {
 getReaderContext().error("Failed to register bean definition with name '" +
 bdHolder.getBeanName() + "'", ele, ex);
 }
 // 最后发出响应事件,通知相关监听器这个bean已经被加载
 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
 }
}

这段代码中:

  • 首先委托 BeanDefinitionParseDelegate 对节点做了解析,并返回了一个 BeanDefinitionHolder 的实例,在这个实例中已经包含了配置文件中配置的各种属性了
  • 如果在当前子节点中存在自定义属性,则还需要对自定义标签进行解析
  • 解析完成后,需要对解析后的 bdHolder 进行注册,同样注册操作委托给了 BeanDefinitionReaderUtils
  • 最后发出响应事件,通知相关监听器这个 bean 已经被加载

下面我们详细分析下,Spring 是如何解析各个标签和节点的

bean 标签解析

public class BeanDefinitionParserDelegate {
 @Nullable
 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
 // 获取Bean标签的ID属性
 String id = ele.getAttribute(ID_ATTRIBUTE);
 // 获取Bean标签的Name属性
 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 List<String> aliases = new ArrayList<>();
 if (StringUtils.hasLength(nameAttr)) {
 // 将name属性的值通过,; 进行分割 转为字符串数字(即在配置文件中如配置多个name 在此做处理)
 String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
 aliases.addAll(Arrays.asList(nameArr));
 }
 String beanName = id;
 // 如果ID为空 使用配置的第一个name属性作为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) {
 // 校验beanName和aliases的唯一性
 // 内部核心为使用usedNames集合保存所有已经使用了的beanName和alisa
 checkNameUniqueness(beanName, aliases, ele);
 }
 // 进一步解析其他所有属性到GenericBeanDefinition对象中
 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
 if (beanDefinition != null) {
 // 如果bean没有指定beanName 那么使用默认规则为此Bean生成beanName
 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);
 // 将信息封装到BeanDefinitionHolder对象中
 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
 }
 return null;
 }
}

该方法主要处理了 id、name、alias 等相关属性,生成了 beanName,并且在重载函数 parseBeanDefinitionElement(ele, beanName, containingBean)方法中完成核心的标签解析。

接下来重点分析parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean)

看下它是如何完成标签解析操作的

bean 节点与属性解析

@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
 Element ele, String beanName, @Nullable BeanDefinition containingBean) {
 this.parseState.push(new BeanEntry(beanName));
 // 获取Bean标签的class属性
 String className = null;
 if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
 className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
 }
 // 获取Bean标签的parent属性
 String parent = null;
 if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
 parent = ele.getAttribute(PARENT_ATTRIBUTE);
 }
 try {
 // 创建用于承载属性的AbstractBeanDefinition
 AbstractBeanDefinition bd = createBeanDefinition(className, parent);
 // 获取bean标签各种属性
 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
 // 解析description标签
 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
 // 解析meta标签
 parseMetaElements(ele, bd);
 // 解析lookup-method标签
 parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
 // 解析replaced-method标签
 parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
 // 解析constructor-arg标签
 parseConstructorArgElements(ele, bd);
 // 解析property标签
 parsePropertyElements(ele, bd);
 // 解析qualifier标签
 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;
}

进一步解析其他属性和元素(元素和属性很多,所以这是一个庞大的工作量)并统一封装至 GenericBeanDefinition 中, 解析完成这些属性和元素之后,如果检测到 bean 没有指定的 beanName,那么便使用默认的规则为 bean 生成一个 beanName。

// BeanDefinitionParserDelegate.java
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
 throws ClassNotFoundException {
 return BeanDefinitionReaderUtils.createBeanDefinition(
 parentName, className, this.readerContext.getBeanClassLoader());
}
public class BeanDefinitionReaderUtils {
 public static AbstractBeanDefinition createBeanDefinition(
 @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
 GenericBeanDefinition bd = new GenericBeanDefinition();
 // parentName可能为空
 bd.setParentName(parentName);
 // 如果classLoader不为空
 // 则使用传入的classLoader同一虚拟机加载类对象 否则只记录classLoader
 if (className != null) {
 if (classLoader != null) {
 bd.setBeanClass(ClassUtils.forName(className, classLoader));
 }
 else {
 bd.setBeanClassName(className);
 }
 }
 return bd;
 }
}

BeanDefinition 是 <bean> 在容器中的内部表示形式,BeanDefinition 和 <bean> 是一一对应的。同时 BeanDefinition 会被注册到 BeanDefinitionRegistry 中,BeanDefinitionRegistry 就像 Spring 配置信息的内存数据库。

至此 createBeanDefinition(className, parent); 已经说完了,而且我们也获得了 用于承载属性的AbstractBeanDefinition,接下来看看 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); 是如何解析 bean 中的各种标签属性的

public class BeanDefinitionParserDelegate {
 public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
 @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
 // ...省略详细代码,该部分代码主要就是通过 if else 判断是否含有指定的属性,如果有就 bd.set(attribute);
 return bd;
 }
}
```

`bean` 标签的完整解析到这就已经全部结束了,其中 `bean` 标签下的元素解析都大同小异,有兴趣的可以自己跟踪一下源代码看看 `qualifier、lookup-method` 等解析方式(*相对 `bean` 而言不复杂*)。自定义标签内容较多会在下一章详细介绍。

最后将获取到的信息封装到 `BeanDefinitionHolder` 实例中

``` java
// BeanDefinitionParserDelegate.java
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
 // ...
 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

注册解析的 BeanDefinition

在解析完配置文件后我们已经获取了 bean 的所有属性,接下来就是对 bean 的注册了

public class BeanDefinitionReaderUtils {
 public static void registerBeanDefinition(
 BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
 throws BeanDefinitionStoreException {
 // 使用 beanName 做唯一标识符
 String beanName = definitionHolder.getBeanName();
 // 注册bean的核心代码
 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
 // 为bean注册所有的别名
 String[] aliases = definitionHolder.getAliases();
 if (aliases != null) {
 for (String alias : aliases) {
 registry.registerAlias(beanName, alias);
 }
 }
 }
}

以上代码主要完成两个功能,一是使用 beanName 注册 beanDefinition,二是完成了对别名的注册

BeanName 注册 BeanDefinition

public class 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 {
 // 注册前的最后一次校验,这里的校验不同于XML文件校验
 // 主要是对于AbstractBeanDefinition属性中的methodOverrides校验
 // 校验methodOverrides是否与工厂方法并存或者methodOverrides对于的方法根本不存在
 ((AbstractBeanDefinition) beanDefinition).validate();
 }
 catch (BeanDefinitionValidationException ex) {
 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
 "Validation of bean definition failed", ex);
 }
 }
 BeanDefinition oldBeanDefinition;
 // 获取缓存中的 beanDefinition
 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 + "]");
 }
 }
 // 如果允许覆盖,保存beanDefinition到beanDefinitionMap中
 this.beanDefinitionMap.put(beanName, beanDefinition);
 }
 else {
 // 判断是否已经开始创建bean
 if (hasBeanCreationStarted()) {
 // Cannot modify startup-time collection elements anymore (for stable iteration)
 synchronized (this.beanDefinitionMap) {
 // 保存beanDefinition到beanDefinitionMap中
 this.beanDefinitionMap.put(beanName, beanDefinition);
 // 更新已经注册的beanName
 List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
 updatedDefinitions.addAll(this.beanDefinitionNames);
 updatedDefinitions.add(beanName);
 this.beanDefinitionNames = updatedDefinitions;
 if (this.manualSingletonNames.contains(beanName)) {
 Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
 updatedSingletons.remove(beanName);
 this.manualSingletonNames = updatedSingletons;
 }
 }
 }
 else {
 // 还没开始创建bean
 this.beanDefinitionMap.put(beanName, beanDefinition);
 this.beanDefinitionNames.add(beanName);
 this.manualSingletonNames.remove(beanName);
 }
 this.frozenBeanDefinitionNames = null;
 }
 if (oldBeanDefinition != null || containsSingleton(beanName)) {
 // 重置beanName对应的缓存
 resetBeanDefinition(beanName);
 }
 }
}
  • 对 AbstractBeanDefinition 的校验,主要是针对 AbstractBeanDefinition 的 methodOverrides 属性的
  • 对 beanName 已经注册的情况的处理,如果设置了不允许 bean 的覆盖,则需要抛出异常,否则直接覆盖
  • 使用 beanName 作为 key,beanDefinition 为 Value 加入 beanDefinitionMap 存储
  • 如果缓存中已经存在,并且该 bean 为单例模式则清楚 beanName 对应的缓存

注册别名

注册好了 beanDefinition,接下来就是注册 alias。注册的 alias 和 beanName 的对应关系存放在了 aliasMap 中,沿着类的继承链会发现 registerAlias 的方法是在 SimpleAliasRegistry 中实现的

public class SimpleAliasRegistry {
 /** Map from alias to canonical name */
 private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
 public void registerAlias(String name, String alias) {
 Assert.hasText(name, "'name' must not be empty");
 Assert.hasText(alias, "'alias' must not be empty");
 if (alias.equals(name)) {
  // 如果beanName与alias相同的话不记录alias 并删除对应的alias
  this.aliasMap.remove(alias);
 } else {
  String registeredName = this.aliasMap.get(alias);
  if (registeredName != null) {
  if (registeredName.equals(name)) {
   // 如果别名已经注册过并且指向的name和当前name相同 不做任何处理
   return;
  }
  // 如果alias不允许被覆盖则抛出异常
  if (!allowAliasOverriding()) {
   throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
  }
  }
  // 校验循环指向依赖 如A->B B->C C->A则出错
  checkForAliasCircle(name, alias);
  this.aliasMap.put(alias, name);
 }
 }
}

通过 checkForAliasCircle() 方法来检查 alias 循环依赖,当 A -> B 存在时,若再次出现 A -> C -> B 则会抛出异常:

protected void checkForAliasCircle(String name, String alias) {
 if (hasAlias(alias, name)) {
 throw new IllegalStateException("Cannot register alias '" + alias +
 "' for name '" + name + "': Circular reference - '" +
 name + "' is a direct or indirect alias for '" + alias + "' already");
 }
}
public boolean hasAlias(String name, String alias) {
 for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
 String registeredName = entry.getValue();
 if (registeredName.equals(name)) {
 String registeredAlias = entry.getKey();
 return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));
 }
 }
 return false;
}

至此,注册别名也完成了,主要完成了以下几个工作

  • 如果 beanName 与 alias 相同的话不记录 alias 并删除对应的 alias
  • 如果别名已经注册过并且指向的name和当前name相同 不做任何处理
  • 如果别名已经注册过并且指向的name和当前name不相同 判断是否允许被覆盖
  • 校验循环指向依赖 如A->B B->C C->A则出错

发送通知

通知监听器解析及注册完成

//DefaultBeanDefinitionDocumentReader.java
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 // Send registration event.
 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}

通过 fireComponentRegistered 方法进行通知监听器解析及注册完成工作,这里的实现只为扩展,当程序开发人员需要对注册 BeanDefinition事件进行监听时,可以通过注册监听器的方式并将处理逻辑写入监听器中,目前 Spring 中并没有对此事件做任何处理

其中 ReaderContext 是在类 XmlBeanDefinitionReader 中调用 createReaderContext 生成的,然后调用 fireComponentRegistered()

alias 标签解析

Spring 提供了 <alias name="person" alias="p"/> 方式来进行别名的配置,该标签解析是在 processAliasRegistration(Element ele) 方法中完成的

public class DefaultBeanDefinitionDocumentReader {
 protected void processAliasRegistration(Element ele) {
 // 获取 alisa 标签 name 属性
 String name = ele.getAttribute(NAME_ATTRIBUTE);
 // 获取 alisa 标签 alias 属性
 String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
 boolean valid = true;
 if (!StringUtils.hasText(name)) {
  getReaderContext().error("Name must not be empty", ele);
  valid = false;
 } if (!StringUtils.hasText(alias)) {
  getReaderContext().error("Alias must not be empty", ele);
  valid = false;
 }
 if (valid) {
  try {
  // 进行别名注册
  getReaderContext().getRegistry().registerAlias(name, alias);
  } catch (Exception ex) {
  getReaderContext().error("Failed to register alias '" + alias +
   "' for bean with name '" + name + "'", ele, ex);
  }
  // 别名注册后告知监听器做相应处理
  getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
 }
 }
}

首先对 alias 标签属性进行提取校验,校验通过后进行别名注册,别名注册和 bean 标签解析中的别名注册一直,此处不再赘述

import 标签解析

public class DefaultBeanDefinitionDocumentReader {
 protected void importBeanDefinitionResource(Element ele) {
 // 获取import标签的resource属性
 String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
 // 如果不存在则不做任何处理
 if (!StringUtils.hasText(location)) {
  getReaderContext().error("Resource location must not be empty", ele);
  return;
 }
 // 解析占位符属性 格式如"${user.dir}"
 location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
 Set<Resource> actualResources = new LinkedHashSet<>(4);
 // 判断资源是绝对路径还是相对路径
 boolean absoluteLocation = false;
 try {
  absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
 } catch (URISyntaxException ex) {
  // cannot convert to an URI, considering the location relative
  // unless it is the well-known Spring prefix "classpath*:"
 }

 // 如果是绝对路径则直接根据地址加载对应的配置文件
 if (absoluteLocation) {
  try {
  int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
  if (logger.isDebugEnabled()) {
   logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
  }
  } catch (BeanDefinitionStoreException ex) {
  getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex);
  }
 } else {
  try {
  int importCount;
  // 根据相对路径加载资源
  Resource relativeResource = getReaderContext().getResource().createRelative(location);
  if (relativeResource.exists()) {
   importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
   actualResources.add(relativeResource);
  } else {
   String baseLocation = getReaderContext().getResource().getURL().toString();
   importCount = getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
  }
  if (logger.isDebugEnabled()) {
   logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
  }
  } catch (IOException ex) {
  getReaderContext().error("Failed to resolve current resource location", ele, ex);
  } catch (BeanDefinitionStoreException ex) {
  getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex);
  }
 }
 // 解析后进行监听器激活处理
 Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
 getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
 }
}

完成了对 import 标签的处理,首先就是获取 <import resource="beans.xml"/> resource 属性所表示的路径,接着解析路径中的属性占位符 如 ${user.dir} ,然后判定 location 是绝对路径还是相对路径,如果是绝对路径则递归调用 bean 的解析过程(loadBeanDefinitions(location, actualResources);) ,进行另一次解析,如果是相对路径则计算出绝对路径并进行解析,最后通知监听器,解析完成

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走…

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

说点什么

全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1(本地下载)

您可能感兴趣的文章:

  • Java开发框架spring实现自定义缓存标签
  • Spring MVC---数据绑定和表单标签详解
  • SpringMVC实现数据绑定及表单标签
  • SpringMVC表单标签使用详解
  • 基于Spring开发之自定义标签及其解析
  • springmvc常用注解标签详解
  • SpringMVC表单标签知识点详解
(0)

相关推荐

  • SpringMVC表单标签知识点详解

    本篇我们来学习Spring MVC表单标签的使用,借助于Spring MVC提供的表单标签可以让我们在视图上展示WebModel中的数据更加轻松. 一.首先我们先做一个简单了例子来对Spring MVC表单表单标签的使用有一个大致的印象,然后再结合例子对各个标签介绍一下如何使用. 1.首先,在com.demo.web.models包中添加一个模型TagsModel内容如下: package com.demo.web.models; import java.util.List; import ja

  • 基于Spring开发之自定义标签及其解析

    Spring框架是现在Java最流行的开源框架之一,并且Spring下的各种子项目对某些特定问题的解决有很好的支持.因此,如果能在Spring 基础上实现搭建自己的一套框架(基于XML配置).就必然需要实现一些自定义的标签,主要是方便使用我们框架的人能够快速.简单进行配置. 1. XML Schema 要想自定义标签,首先第一步需要写自己的XML Schema.XML Schema的个人感觉比较复杂,网上的教程比较简单,因此可以参照spring-beans.xsd依葫芦画瓢.这里就按照我自己的理

  • Spring MVC---数据绑定和表单标签详解

    数据绑定和表单标签 数据绑定 数据绑定是将用户输入绑定到领域模型的一种特性,在Spring MVC的controller和view数据传递中,基于HTTP请求的特性,所有HTTP请求参数的类型均为字符串,如果模型领域需要绑定的类型为double或int,则需要手动进行类型转换,而有了数据绑定后,就不需要手动将HTTP请求中的String类型转换为模型需要的类型了,数据绑定的另一个好处是,当输入验证失败时,会重新生成一个HTML表单,无需重新填写输入字段. 表单标签库 表单标签库中包含了可以用在J

  • SpringMVC表单标签使用详解

    在使用SpringMVC的时候我们可以使用Spring封装的一系列表单标签,这些标签都可以访问到ModelMap中的内容.下面将对这些标签一一介绍. 在正式介绍SpringMVC的表单标签之前,我们需要先在JSP中声明使用的标签,具体做法是在JSP文件的顶部加入以下指令: <%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %> 1.1.form标签 使用Sprin

  • SpringMVC实现数据绑定及表单标签

    首先理解数据绑定 为什么要使用数据绑定 基于HTTP特性,所有的用户输入的请求参数类型都是String,比如下面表单: 但我们提交后,为了将请求信息映射到模型中,还需要手动进行格式转换,此外还借助了一个中转对象productForm,其字段名称和Product一模一样,只是类型为String. @RequestMapping(value = "/product_save",method = RequestMethod.POST) public String saveProduct(Pr

  • springmvc常用注解标签详解

     1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示.在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@Request

  • Java开发框架spring实现自定义缓存标签

    自从spring3.1之后,spring引入了抽象缓存,可以通过在方法上添加@Cacheable等标签对方法返回的数据进行缓存.但是它到底是怎么实现的呢,我们通过一个例子来看一下.首先我们定义一个@MyCacheable package caching.springaop; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.

  • Spring源码解密之默认标签的解析

    前言 紧跟上篇 Spring解密 - XML解析 与 Bean注册 ,我们接着往下分析源码,话不多说了,来一起看看详细的介绍吧. 解密 在 Spring 的 XML 配置里面有两大类声明,一个是默认的如 <bean id="person" class="com.battcn.bean.Person"/> ,另一类就是自定义的如<tx:annotation-driven /> ,两种标签的解析方式差异是非常大的.parseBeanDefinit

  • Spring源码解密之自定义标签与解析

    前言 在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析.话不多说了,来一起看看详细的介绍吧. 自定义标签 在讲解 自定义标签解析 之前,先看下如何自定义标签 定义 XSD 文件 定义一个 XSD 文件描述组件内容 <?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="

  • Idea 搭建Spring源码环境的超详细教程

    本篇主要讲解如何使用Ideal 搭建Spring的源码环境,想必大家都会多多少少去看过Spring的部分源码,一般我们都是直接点进某个Spring类 然后Idea上面去下载 ,但是确实比较麻烦,而且不能添加自己对源码的注释 理解 ,本篇就来解决这个问题,手把手使用Idea 搭建Spring framework ,并且直接在Spring framework项目中添加我们自己的module 来验证环境是否正确. 本过程会比较耗时 而且容易出错 慢慢来吧. 1. clone spring-framew

  • Spring源码学习之动态代理实现流程

    注:这里不阐述Spring和AOP的一些基本概念和用法,直接进入正题. 流程   Spring所管理的对象大体会经过确定实例化对象类型.推断构造方法创建对象(实例化).设置属性.初始化等等步骤.在对象初始化阶段,Spring为开发者提供了一个BeanPostProcessor接口,它会在对象初始化之前和初始化之后被调用(初始化,不是实例化,对应实例化的是InstantiationAwareBeanPostProcessor接口). public interface BeanPostProcess

  • Spring源码解析之Configuration

    一.@Configuration 1.1 未加@Configuration <!--logback-test.xml,配置不打印日志--> <?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml" /> <

  • Spring源码解析之事务传播特性

    一.使用方式 可以采用Transactional,配置propagation即可. 打开org.springframework.transaction.annotation.Transactional可见默认传播特性是REQUIRED. /** * The transaction propagation type. * <p>Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.inte

  • Spring源码解析之推断构造方法

    Spring推断构造方法 贴个测试代码直接开干,这只是个样例,其他情况自行分析 @Component public class OrderService { public OrderService() { System.out.println("无参构造方法"); } @Autowired(required = false) public OrderService(UserService userService) { System.out.println("一个参数的构造方法

  • Spring源码剖析之Spring处理循环依赖的问题

    前言 你是不是被这个骚气的标题吸引进来的,_ 喜欢我的文章的话就给个好评吧,你的肯定是我坚持写作最大的动力,来吧兄弟们,给我一点动力 Spring如何处理循环依赖?这是最近较为频繁被问到的一个面试题,在前面Bean实例化流程中,对属性注入一文多多少少对循环依赖有过介绍,这篇文章详细讲一下Spring中的循环依赖的处理方案. 什么是循环依赖 依赖指的是Bean与Bean之间的依赖关系,循环依赖指的是两个或者多个Bean相互依赖,如: 构造器循环依赖 代码示例: public class BeanA

  • 从Spring源码解析事务失效的原因

    目录 一.前言 二.方法不是 public 的 三.内部方法间调用导致事务失效 四.异常类型是否配置正确 五.异常被catch住 一.前言 1.Bean是否是代理对象 2.入口函数是否是public的 3.数据库是否支持事务(Mysql的Mvlsam不支持事务),行锁才支持事务 4.切点是否配置正确 5.内部方法间调用导致事务失效 因为this不是代理对象,可以配置 expose-proxy="true",就可以通过AopContext.currentProxy()获取到当前类的代理对

  • Spring源码BeanFactoryPostProcessor详解

    Spring源码分析-BeanFactoryPostProcessor BeanFactoryPostProcessor接口是Spring提供的对Bean的扩展点,它的子接口是BeanDefinitionRegistryPostProcessor @FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory b

随机推荐