深入理解Spring IOC(二) 、从xml到BeanDefinition(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 深入理解Spring IOC(二) 、从xml到BeanDefinition(上)

在上一篇文章中,我们讲了Spring的统一资源加载策略,我们知道了xml在使用之前必须先用ResourceLoader加载成Resource,而ResourceLoader每次只能把一个文件加载成Resource,所以当需要同时加载多个资源的时候,就需要使用到ResourcePatternReslover。 那么接下来,在将xml加载成了Resource之后,又是怎样解析的呢,解析后xml里的信息变成了什么呢?本篇将会为你解答这个问题。


BeanDefinition


这是我们在说解析xml之前不得不说的一个东西。我们来看看源码中关于对这个接口的描述:


1686810374113.png



为什么一定要说这个呢?因为BeanDefinition是你真正去阅读源码的基础,不少的扩展基于都是基于这里,也有不少的核心流程需要你理解它。很多人号称自己读过了Spring源码,然而最终却连个最基础的BeanDefinition都不知道。


红框中的英文注释直接翻译过来意思就是,BeanDefinition描述的是一个bean的实例有哪些属性,它的构造器的参数,以及被子类所支持的更多的信息。我们来看看BeanDefinition这个接口的继承体系:


1686810403788.png


重中之重是AbstractBeanDefinition这个抽象类,它里面有对于BeanDefinition大部分方法的默认实现,以及很多的属性,这些属性决定了Spring要怎么去实例化对应的bean。我们先大概来看看AbstractBeanDefinition这个抽象类里面包含了哪些属性:


属性比较多,并不需要记住它,先把大部分看懂即可


代码块1
        // bean 对应的java类
  @Nullable
  private volatile Object beanClass;
  // bean的作用范围,对应scope属性
  @Nullable
  private String scope = SCOPE_DEFAULT;
  // 是否抽象类
  private boolean abstractFlag = false;
  // 是否懒加载
  private boolean lazyInit = false;
  // 自动注入的方式
  private int autowireMode = AUTOWIRE_NO;
  // 依赖检查
  private int dependencyCheck = DEPENDENCY_CHECK_NONE;
  //用来表示一个bean的实例化依靠另一个bean先实例化,对应bean属性depend-on,以及@DependenOn注解
  @Nullable
  private String[] dependsOn;
  // autowire-candidate属性设置为false,这样容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,
        // 但是该bean本身还是可以使用自动装配来注入其他bean的
  private boolean autowireCandidate = true;
  // 自动装配时出现多个bean候选者时,将作为首选者,对应bean属性primary,以及@Primary注解
  private boolean primary = false;
  // 用于记录Qualifier,对应子元素qualifier
  private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
  @Nullable
  private Supplier<?> instanceSupplier;
  private boolean nonPublicAccessAllowed = true;
  // 是否以一种宽松的模式解析构造函数,默认为true
  // 不明白的话先跳过,后边文章会说到这个
  private boolean lenientConstructorResolution = true;
  // 对应bean标签属性factory-bean
  @Nullable
  private String factoryBeanName;
  // 对应bean标签属性factory-method
  @Nullable
  private String factoryMethodName;
  // 记录构造函数注入属性,对应bean属性constructor-arg
  @Nullable
  private ConstructorArgumentValues constructorArgumentValues;
  //普通属性集合
  @Nullable
  private MutablePropertyValues propertyValues;
  //方法重写的持有者,记录lookup-method、replaced-method元素
  @Nullable
  private MethodOverrides methodOverrides;
  //初始化方法,对应bean属性init-method
  @Nullable
  private String initMethodName;
  //销毁方法,对应bean属性destroy-method
  @Nullable
  private String destroyMethodName;
  //是否执行init-method,程序设置
  private boolean enforceInitMethod = true;
  //是否执行destroy-method,程序设置
  private boolean enforceDestroyMethod = true;
  //是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true,程序设置
  private boolean synthetic = false;
  //定义这个bean的应用,APPLICATION:用户,INFRASTRUCTURE:完全内部使用,与用户无关,SUPPORT:某些复杂配置的一部分
        //程序设置
  private int role = BeanDefinition.ROLE_APPLICATION;
  //bean的描述信息
  @Nullable
  private String description;
  //这个bean定义的资源
  @Nullable
  private Resource resource;


为了代码少一些,我只是列出来了大部分的属性。我们可以看到,有很多的属性,我们几乎都可以对着xml看出来它是干嘛的,也有很多我们不熟的属性,别着急,先看看,混个脸熟,后边有需要,我会再回来解释这些东西。作者也不嫌啰嗦的再次提示一下,不用把上面的所有属性都记住,大概看懂就好,我们在这里只需要记住的是,xml的每个bean标签里面的东西,最后都会被解析成这样一个BeanDefinition的某一个子类。


BeanDefinitionReader


我们从类名的字面意思可以看出来,BeanDefinitionReader这个接口的实现类是用来读BeanDefinition的,我们还是按照惯例先来看看它的代码:


代码块2
public interface BeanDefinitionReader {
  BeanDefinitionRegistry getRegistry();
  ResourceLoader getResourceLoader();
  ClassLoader getBeanClassLoader();
  BeanNameGenerator getBeanNameGenerator();
        // 核心的四个方法
  int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
  int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
  int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
  int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}


按照之前说的看接口要看核心方法的思路,我们不难看出来,核心方法是最下面四个方法,即接受不同参数的loadBeanDefinitions方法,他们都是去加载资源,然后返回被加载的资源的数量,所以同样容易理解的是这个接口的实现类的核心作用就是根据资源或者资源的位置来加载解析BeanDefinition。我们再来看看这个接口的继承体系(图来自网络):


1686810451336.png


我们先不关注 EnvironmentCapable 接口,然后从上往下看,先看到的是AbstractBeanDefinitionReader,和其他体系的套路类似,这个类给出了BeanDefinitionReader很多方法的默认实现,我们来看看它对loadBeanDefinitions的实现:


代码块3
        @Override
  public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    int counter = 0;
    for (Resource resource : resources) {
            // 调用了未实现的方法
      counter += loadBeanDefinitions(resource);
    }
    return counter;
  }
  @Override
  public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = 0;
    for (String location : locations) {
            // 调用了下面的方法
      counter += loadBeanDefinitions(location);
    }
    return counter;
  }
  @Override
  public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
          // 调用了下面的方法
    return loadBeanDefinitions(location, null);
  }
        // 这个是个重载方法
  public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
          "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    // 这个if else非常简单,大体意思就是如果本类的resourceLoader属性是个ResourcePatternResolver实例,那么就去用ResourcePatternResolver
    // 批量加载资源然后解析资源,否则就用普通的ResourceLoader来加载单个资源
    if (resourceLoader instanceof ResourcePatternResolver) {
      try {
        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
        int loadCount = loadBeanDefinitions(resources);
        if (actualResources != null) {
          for (Resource resource : resources) {
            actualResources.add(resource);
          }
        }
        if (logger.isDebugEnabled()) {
          logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
        }
        return loadCount;
      }
      catch (IOException ex) {
        throw new BeanDefinitionStoreException(
            "Could not resolve bean definition resource pattern [" + location + "]", ex);
      }
    } else {
      Resource resource = resourceLoader.getResource(location);
      int loadCount = loadBeanDefinitions(resource);
      if (actualResources != null) {
        actualResources.add(resource);
      }
      if (logger.isDebugEnabled()) {
        logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
      }
      return loadCount;
    }
  }


大家不要被这么长的代码吓到了,这段代码属于纸老虎来的,认真琢磨很容易就明白了。我们可以很容易的看到,AbstractBeanDefinitionReader没有实现BeanDefinitionReader中的loadBeanDefinitions(Resource resource)这个方法,而是实现了的其他三个方法以及增加了一个重载方法,但是,其他的实现最终都是要调用这个没有实现的方法(重载方法也要调用),由此我们可以看出来,解析具体的某个资源,还是要靠loadBeanDefinitions(Resource resource)这个方法,接下来就需要看我们本篇的重点即AbstractBeanDefinitionReader的子类XmlBeanDefinitionReader(至于为什么选的是这个,因为我们正篇到现在为止,讲的都是基于xml的配置方式)。我们直接来看看XmlBeanDefinitionReader对loadBeanDefinitions(Resource resource)的实现:


代码块4
        @Override
  public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
  }


然后我们在看loadBeanDefinitions(EncodeResource encodeResource):(接下来代码会比较多,不要害怕,看我的注释即可,如果还是看不懂建议去我的github中的源码中跟一下,一路代码都有注释)


代码块5
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
                //参数不能为空的校验
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }
    // 1.主要是import标签的循环引用的检查,可以理解成检查一个xml和另外一个xml是否通过import标签相互引用
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
      currentResources = new HashSet<EncodedResource>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // 2.如果相互引用了就报异常了
    // 1和2没看懂可以直接看3,后边再回过头看很容易就会明白了
    if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
          "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    // 3.这里的核心其实就是把Resource转化成了InputSource,然后交给真正的doLoadBeanDefinitions去做。
    try {
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
          inputSource.setEncoding(encodedResource.getEncoding());
        }
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
        inputStream.close();
      }
    }
    catch (IOException ex) {
      throw new BeanDefinitionStoreException(
          "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
        this.resourcesCurrentlyBeingLoaded.remove();
      }
    }
  }


上面代码中最重要的,其实也就是步骤3,然后我们来看看doLoadBeanDefinitions方法:


代码块6
        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
    try {
      // 获取Document对象(Document代表一个html或是一个xml),其实到了这步,spring才知道你的Resource是个xml
      Document doc = doLoadDocument(inputSource, resource);
      // 解析xml注册BeanDefinitions
      return registerBeanDefinitions(doc, resource);
    }
    catch (BeanDefinitionStoreException ex) {
      throw ex;
    }
    ...


doLoadBeanDefinitions这个方法的代码行数很多,但其实核心就是上面的代码,其他的都是catch语句块,我们首先来看看第一句代码中的doLoadDocument方法


代码块7
        // 我特地把documentLoader放到这个位置来
        private DocumentLoader documentLoader = new DefaultDocumentLoader();
  protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
          //加载Document
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
        getValidationModeForResource(resource), isNamespaceAware());
  }


从上面的代码我们可以看出来,documentLoader是DefaultDocumentLoader的实例(我特地把成员属性放到这个位置来了),在这个loadDocument方法中,最重要的是第四个参数,在上面的代码中,这个参数是通过getValidationModeForResource来获取的,我们首先来看这个方法:


代码块8
        protected int getValidationModeForResource(Resource resource) {
    // 拿到已经设置好的验证模式,在ClassPathXmlApplicationoContext的情况下,这个值
    // 是1,如果用上一篇开始的代码去debug,validationModeToUse的值是1
    int validationModeToUse = getValidationMode();
    // VALIDATION_AUTO的值是1,因此不走这个if
    //(VALIDATION_AUTO代表的是由spring来发现应该去用什么样的校验资源的方式)
    if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
    }
    // 通过代码去验证,用上一篇文章开始的代码debug后detectedMode的返回值是3
    // 如果我们用的是个DTD格式的文件,那么这里将会返回2
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
    }
    // 返回XSD校验模式,其实也是3
    return VALIDATION_XSD;
  }


然后之前的loadDocument方法拿到这个参数之后,主要根据这个参数来把第一个参数的inputSource解析成了一个xml格式的Document对象,限于篇幅,这里就不放这个方法的代码了。然后我们看XmlBeanDefinitionReader中的registerBeanDefinitions(Document doc, Resource resource)方法,其实也就是从代码块6中的第二行代码:


代码块9
        @SuppressWarnings("deprecation")
  public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 1.用反射创建DefaultBeanDefinitionDocumentReader实例对象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 2.设置环境
    documentReader.setEnvironment(getEnvironment());
    // 3.拿到已经注册的BeanDefinition的数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 4.真正注册BeanDefinition,即正儿八经解析xml的过程
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 返回注册的BeanDefinition的数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
  }


重点是第四行代码了,我们可以看到,真正解析xml还是交给了BeanDefinitionDocumentReader这个类的registerBeanDefinitions方法去做了,这个方法有两个参数,第一个就是我们之前已经被我们转换好的xml格式的Document对象,第二个其实是个XmlReaderContext(这个放到扩展篇讲,这里先忽略),我们来看看这个registerBeanDefinitions方法:


代码块10
        @Override
  public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    // 就是拿个个root节点然后交给真正的doRegisterBeanDefinitions去解析而已
    // spring源码中还有很多类似风格的代码,doXXX才是真正干活的地方。。。
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
  }


紧接着看真正干活的doRegisterBeanDefinitions方法


代码块11
        protected void doRegisterBeanDefinitions(Element root) {
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    // 这个if代码块是处理profile相关的(忽略)
    if (StringUtils.hasText(profileSpec)) {
      String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
          profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
        return;
      }
    }
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    // 由于beans标签里面可以嵌套beans标签,递归调用时候root不一样,所以需要创建一个新的delegate
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(this.readerContext, root, parent);
    preProcessXml(root);
    // 从root节点开始解析,(核心逻辑)
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);
    this.delegate = parent;
  }


来看核心逻辑的parseBeanDefinitions


代码块12
        protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 就是在看标签中有没有"http://www.springframework.org/schema/beans",如果有,说明不是开发者自己定义的xml,就走if
    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);
    }
  }

我们本篇先只看parseDefaultElement,这个方法是spring ioc 这部分自己的标签,自定义标签的我会单独弄一篇出来讲:


代码块13
    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);
    }
    // 解析beans标签,其实就是递归了。。。
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
    }
  }


其实可以看到除过在spring的xml中,只有import 、alias 、bean  、beans这几个标签是ioc本身的,其他的都是属于自定义标签。我们先来看看解析import标签的importBeanDefinitionResource方法:


代码块14
// 解析import标签
protected void importBeanDefinitionResource(Element ele) {
    // 拿到location属性值并且校验是否为空(新版本属性名已经变成resource)
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    if (!StringUtils.hasText(location)) {
      getReaderContext().error("Resource location must not be empty", ele);
      return;
    }
    // 如果location里带了诸如("${user.dir}")这类的东西的话需要先解析这个
    location = getEnvironment().resolveRequiredPlaceholders(location);
    Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
    // 判断 location 是相对路径还是绝对路径
    boolean absoluteLocation = false;
    try {
      // 这里面的逻辑其实就是看location是不是以classpath*:开头 或者classpath:开头 或者是不是url 或者根据uri的scheme来判断你是个绝对还是相对路径
      absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    } catch (URISyntaxException ex) {
    }
    // 如果是绝对路径,则直接使用xmlBeanDefinitionReader中的loadBeanDefinitions加载解析Resource
    // 如果不是绝对路径,那么计算相对路径,然后再用xmlBeanDefinitionReader中的loadBeanDefinitions来加载解析Resource
    if (absoluteLocation) {
      try {
        // 注意这里的 getReaderContext().getReader()返回的其实就是之前的xmlBeanDefinitionReader对象
        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 {
      // 解析相对路径的Resource
      try {
        int importCount;
        Resource relativeResource = getReaderContext().getResource().createRelative(location);
        if (relativeResource.exists()) {
                // 这里既然找到了相对路径的Resource,那么就去使用上面的loadBeanDefinitions方法去解析
          importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
          actualResources.add(relativeResource);
        }
        else {
                // 如果以相对路径的方式解析失败,则尝试以URL的方式去解析
          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);
      }
    }
    // 解析成功后,进行监听器激活处理,但其实这个版本的这里,P事都没干,因为最终调用的方法是个空实现,所以这里不用管
    Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
  }


这个方法看起来很长,也有一定的复杂性,总结下就是去拿import标签的location属性然后校验,校验完了判断location是个相对路径还是绝对路径,然后根据路径的不同采取不同的解析方式,但是最终都是要使用之前说的那个loadBeanDefinitions(Resource resource),然后在这个方法中调用的loadBeanDefinitions(EncodedResource encodedResource)中的import循环引用的检查指的就是这里的,代码块5中已经提过。

解析import标签的看完了,我们再看看解析alias标签的代码:


代码块15
        protected void processAliasRegistration(Element ele) {
    // 拿到alias标签中的alias属性和name属性
    String name = ele.getAttribute(NAME_ATTRIBUTE);
    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
    // 校验name属性,如果是空的,就会在error方法中抛出异常
    boolean valid = true;
    if (!StringUtils.hasText(name)) {
      getReaderContext().error("Name must not be empty", ele);
      valid = false;
    }
    // 校验alias属性,如果是空的,就会在error方法中抛出异常
    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));
    }
  }


至于beans标签是用的之前提到的doRegisterBeanDefinitions方法,也就是代码块11啦,其实也就是递归解析。接下来看最重要的processBeanDefinition方法,这个也是最重要的方法,它将一个bean标签解析成了一个具体的BeanDefinition对象,方法代码如下


代码块16
        protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
                // 解析bean标签,也是最重要的逻辑
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
      // 1.解析默认标签下的自定义标签
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
        // 2.注册beanDefiniton实例.
        BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
        getReaderContext().error("Failed to register bean definition with name '" +
            bdHolder.getBeanName() + "'", ele, ex);
      }
      // Send registration event.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
  }


这里面我们先看第一行由委派去解析bean标签的方法,这个也是最重要的,它里面是直接调用了自己类里的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)。只不过第二个参数传的是null,我们直接看这个方法:


代码块17
        // 开始解析<bean>标签
  public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    // 1.拿到bean标签的id和name属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
                // 把name放入集合,后续会将name注册成别名
    List<String> aliases = new ArrayList<String>();
    if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
    }
    // 2.如果没有id属性,则用name属性中的第一个来作为bean在容器中的唯一标识名字
    String beanName = 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");
      }
    }
    // 3.检查bean名字是否唯一 
    if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
    }
    // 4.到这里解析成正儿八经的beanDefinition
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
      // 如果到这里beanName还为空或者是空字符串就按规则生成beanName(非重点)
      if (!StringUtils.hasText(beanName)) {
        try {
          if (containingBean != null) {
            beanName = BeanDefinitionReaderUtils.generateBeanName(
                beanDefinition, this.readerContext.getRegistry(), true);
          }
          else {
            beanName = this.readerContext.generateBeanName(beanDefinition);
            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);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
    return null;
  }


上面的代码块分为了四个部分,前三个部分都很简单,我们来看看第四个部分的parseBeanDefinitionElement(ele, beanName, containingBean)这个重载方法,注意此时第三个参数依然传的是null:


代码块18
        // 正儿八经解析bean标签,参数containingBean是null
  public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, BeanDefinition containingBean) {
    this.parseState.push(new BeanEntry(beanName));
    // 1.拿到bean的类名全路径
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    try {
      // 2.拿parent bean的id
      String parent = null;
      if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
      }
      // 3.到目前为止,找到了parent和class的全路径
      // 这里的bd只有class以及parentName和
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
      // 4.在给bd set 标签中各种属性
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      parseMetaElements(ele, bd);
      // 5.lookup-method和replace-method,
      // lookup-method对应的是注解@lookup,这个在搭业务框架的时候很有用,大家感兴趣可以百度下
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      // 6.处理constructor-arg 、property、qualifier 三个标签
      parseConstructorArgElements(ele, bd);
      parsePropertyElements(ele, bd);
      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;
  }


都是解析各个标签的方法了,我们先看看上面代码块中第3处最终调用的方法:


代码块19
        public static AbstractBeanDefinition createBeanDefinition(
      String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException {
    GenericBeanDefinition bd = new GenericBeanDefinition();
    // 父beanDefnition名字
    bd.setParentName(parentName);
    if (className != null) {
      if (classLoader != null) {
        bd.setBeanClass(ClassUtils.forName(className, classLoader));
      }
      else {
        bd.setBeanClassName(className);
      }
    }
    return bd;
  }


我们可以看到,我们的bean标签,最终是被解析成了一个GenericBeanDefinition的实例,而这个GenericBeanDefinition几乎所有的成员属性都是从AbstractBeanDefinition来的,这里面的属性我们在本篇文章开头的时候已经提到过,如果不明白的话,可以翻上去看看。然后在看代码块18第四处的代码:


代码块20
        public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
      BeanDefinition containingBean, AbstractBeanDefinition bd) {
    // scope属性
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
      this.readerContext.warning("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
      bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }else if (containingBean != null) {
      bd.setScope(containingBean.getScope());
    }
    // abstract 属性
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
      bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }
    // lazy-init属性
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (DEFAULT_VALUE.equals(lazyInit)) {
      lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));
    // 这个属性从3.0已经过时,用构造器注入即可
    String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
    bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
    // depend-on 属性,对应现在的注解DependOn(注意他的作用)
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
      String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
      bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }
    // autowire-candidate 属性,指的是这个bean可不可以作为注入其他bean的对象
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
      String candidatePattern = this.defaults.getAutowireCandidates();
      if (candidatePattern != null) {
        String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
        bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
      }
    }
    else {
      bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }
    // paimary,指的是自动装配优先使用这个bean
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
      bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
    // init-method属性
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
      String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
      if (!"".equals(initMethodName)) {
        bd.setInitMethodName(initMethodName);
      }
    }
    else {
      if (this.defaults.getInitMethod() != null) {
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
      }
    }
    // destory 属性
    // 其实init method 和destory属性等同于post construct 和 pre Destory注解
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
      String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
      if (!"".equals(destroyMethodName)) {
        bd.setDestroyMethodName(destroyMethodName);
      }
    }
    else {
      if (this.defaults.getDestroyMethod() != null) {
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
      }
    }
    // factory-method属性
    if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
      bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    // factory-bean 属性
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
      bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
    return bd;
  }


代码块18的第5和第6处方法的代码很简单,为了让文章短点就不贴了。在使用委派的parseBeanDefinitionElement解析完了之后,xml中的属性已经都被读到beanDefinition对象上面了。接下来就是对自定义属性和自定义标签的装饰,我会在自定义标签中说这个。过了对自定义的装饰后,就去注册拿到BeanDefinition对象,至此,对xml的解析已完成。

目录
相关文章
|
1月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
84 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
19天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
11天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
24 0
|
1月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
29 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
79 0
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
45 0
|
3月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
5月前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
47 1
|
2月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
212 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)