热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

这一次搞懂Spring的XML解析原理说明

这篇文章主要介绍了这一次搞懂Spring的XML解析原理说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

前言

Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率。同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规范,但因其体系庞大、设计复杂对于刚开始阅读源码的人来说是非常困难的。所以在此之前首先你得下定决心,不管有多困难都得坚持下去;其次,最好先把设计模式掌握熟练;然后在开始阅读源码时一定要多画UML类图和时序图,多问自己为什么要这么设计?这样设计的好处是什么?还有没有更好的设计?当然,晕车是难免的,但还是那句话,一定要持之以恒(PS:源码版本5.1.3.RELEASE)。

正文

熟悉IOC体系结构

要学习Spring源码,我们首先得要找准入口,那这个入口怎么找呢?我们不妨先思考一下,在Spring项目启动时,Spring做了哪些事情。这里我以最原始的xml配置方式来分析,那么在项目启动时,首先肯定要先定位——找到xml配置文件,定位之后肯定是加载——将我们的配置加载到内存,最后才是根据我们的配置实例化(本篇文章只讲前两个过程)。那么Spring是如何定位和加载xml文件的呢?涉及到哪些类呢?我们先来看张类图:

该图是IOC的体系图,整体上你需要有一个大概的印象,可以看到所有的IOC都是有继承关系的,这样设计的好处就是任何一个子类IOC可以直接使用父类IOC加载的Bean,有点像JVM类加载的双亲委派机制;而红色方框圈起来的是本篇涉及到的重要类,需要着重记忆它们的关系。

图中最重要的两个类是BeanFactory和ApplicationContext,这是所有IOC的父接口。其中BeanFactory提供了最基本的对bean的操作:

而ApplicationContex继承了BeanFactory,同时还继承了MessageSource、ResourceLoader、ApplicationEventPublisher等接口以提供国际化、资源加载、事件发布等高级功能。我们应该想到平时Spring加载xml文件应该是ApplicationContext的子类,从图中我们可以看到一个叫ClassPathXmlApplicationContext的类,联想到我们平时都会 将xml放到classPath下,所以我们直接从这个类开始就行,这就是优秀命名的好处。

探究配置加载的过程

在ClassPathXmlApplicationContext中有很多构造方法,其中有一个是传入一个字符串的(即配置文件的相对路径),但最终是调用的下面这个构造:

 public ClassPathXmlApplicationContext(
  String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
  throws BeansException {

 super(parent);

 //创建解析器,解析configLocations
 setConfigLocations(configLocations);
 if (refresh) {
  refresh();
 }
 }

首先调用父类构造器设置环境:

 public AbstractApplicationContext(@Nullable ApplicationContext parent) {
 this();
 setParent(parent);
 }
 
 public void setParent(@Nullable ApplicationContext parent) {
 this.parent = parent;
 if (parent != null) {
  Environment parentEnvirOnment= parent.getEnvironment();
  if (parentEnvironment instanceof ConfigurableEnvironment) {
  getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
  }
 }
 }

然后解析传入的相对路径保存到configLocations变量中,最后再调用父类AbstractApplicationContext的refresh方法刷新容器(启动容器都会调用该方法),我们着重来看这个方法:

public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
  //为容器初始化做准备
  prepareRefresh();
  
  // 解析xml
  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

  // Prepare the bean factory for use in this context.
  prepareBeanFactory(beanFactory);

  try {
  // Allows post-processing of the bean factory in context subclasses.
  postProcessBeanFactory(beanFactory);

  // Invoke factory processors registered as beans in the context.
  invokeBeanFactoryPostProcessors(beanFactory);

  // Register bean processors that intercept bean creation.
  registerBeanPostProcessors(beanFactory);

  // Initialize message source for this context.
  initMessageSource();

  // Initialize event multicaster for this context.
  initApplicationEventMulticaster();

  // Initialize other special beans in specific context subclasses.
  onRefresh();

  // Check for listener beans and register them.
  registerListeners();

  // Instantiate all remaining (non-lazy-init) singletons.
  finishBeanFactoryInitialization(beanFactory);

  // Last step: publish corresponding event.
  finishRefresh();
  }

  catch (BeansException ex) {
  if (logger.isWarnEnabled()) {
   logger.warn("Exception encountered during context initialization - " +
    "cancelling refresh attempt: " + ex);
  }

  // Destroy already created singletons to avoid dangling resources.
  destroyBeans();

  // Reset 'active' flag.
  cancelRefresh(ex);

  // Propagate exception to caller.
  throw ex;
  }

  finally {
  // Reset common introspection caches in Spring's core, since we
  // might not ever need metadata for singleton beans anymore...
  resetCommonCaches();
  }
 }
 }

这个方法是一个典型的模板方法模式的实现,第一步是准备初始化容器环境,这一步不重要,重点是第二步,创建BeanFactory对象、加载解析xml并封装成BeanDefinition对象都是在这一步完成的。

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
 refreshBeanFactory();
 return getBeanFactory();
 }

点进去看是调用了refreshBeanFactory方法,但这里有两个实现,应该进哪一个类里面呢?

如果你还记得前面的继承体系,那你就会毫不犹豫的进入AbstractRefreshableApplicationContext类中,所以在阅读源码的过程中一定要记住类的继承体系。

 protected final void refreshBeanFactory() throws BeansException {

 //如果BeanFactory不为空,则清除BeanFactory和里面的实例
 if (hasBeanFactory()) {
  destroyBeans();
  closeBeanFactory();
 }
 try {
  //创建DefaultListableBeanFactory
  DefaultListableBeanFactory beanFactory = createBeanFactory();
  beanFactory.setSerializationId(getId());

  //设置是否可以循环依赖 allowCircularReferences
  //是否允许使用相同名称重新注册不同的bean实现.
  customizeBeanFactory(beanFactory);

  //解析xml,并把xml中的标签封装成BeanDefinition对象
  loadBeanDefinitions(beanFactory);
  synchronized (this.beanFactoryMonitor) {
  this.beanFactory = beanFactory;
  }
 }
 catch (IOException ex) {
  throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
 }
 }

在这个方法中首先会清除掉上一次创建的BeanFactory和对象实例,然后创建了一个DefaultListableBeanFactory对象并传入到了loadBeanDefinitions方法中,这也是一个模板方法,因为我们的配置不止有xml,还有注解等,所以这里我们应该进入AbstractXmlApplicationContext类中:

 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 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 

这里有两种标签的解析:Spring原生标签和自定义标签。怎么区分这两种标签呢?

// 自定义标签


// 默认标签


如上,带前缀的就是自定义标签,否则就是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 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,比如
 NamedNodeMap attributes = ele.getAttributes();
 for (int i = 0; i 

在这个方法中分别对Bean标签的属性和子标签迭代,获取其中的自定义标签进行解析,并装饰之前创建的BeanDefinition对象,如同下面的c和p:

// c:和p:表示通过构造器和属性的setter方法给属性赋值,是constructor-arg和property的简化写法

两个步骤是一样的,我们点进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 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<&#63;> 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标签一样:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

像这样使用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 &#63; 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);
  }
  // assume an index otherwise
  else {
   int index = -1;
   try {
   index = Integer.parseInt(arg);
   }
   catch (NumberFormatException ex) {
   parserContext.getReaderContext().error(
    "Constructor argument '" + argName + "' specifies an invalid integer", attr);
   }
   if (index <0) {
   parserContext.getReaderContext().error(
    "Constructor argument '" + argName + "' specifies a negative index", attr);
   }

   if (cvs.hasIndexedArgumentValue(index)) {
   parserContext.getReaderContext().error(
    "Constructor argument '" + argName + "' with index "+ index+" already defined using ." +
    " Only one approach may be used per argument.", attr);
   }

   cvs.addIndexedArgumentValue(index, valueHolder);
  }
  }
  // no escaping -> ctr name
  else {
  String name = Conventions.attributeNameToPropertyName(argName);
  if (containsArgWithName(name, cvs)) {
   parserContext.getReaderContext().error(
    "Constructor argument '" + argName + "' already defined using ." +
    " Only one approach may be used per argument.", attr);
  }
  valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
  cvs.addGenericArgumentValue(valueHolder);
  }
 }
 return definition;
 }

很简单,拿到c标签对应的值,封装成ValueHolder,再添加到BeanDefinition的ConstructorArgumentValues属性中去,这样就装饰完成了。

讲到这里你可能会觉得,这和平时看到装饰器模式不太一样。其实,设计模式真正想要表达的是各种模式所代表的思想,而不是死搬硬套的实现,只有灵活的运用其思想才算是真正的掌握了设计模式,而装饰器模式的精髓就是动态的将属性、功能、责任附加到对象上,这样你再看这里是否是运用了装饰器的思想呢?

装饰完成后返回BeanDefinitionHolder对象并调用BeanDefinitionReaderUtils.registerBeanDefinition方法将该对象缓存起来,等待容器去实例化。这里就是将其缓存到DefaultListableBeanFactory的beanDefinitionMap属性中,自己看看代码也就明白了,我就不贴代码了。至此,Spring的XML解析原理分析完毕,下面是我画的时序图,可以对照看看:

总结

本篇是Spring源码分析的第一篇,只是分析了refresh中的obtainFreshBeanFactory方法,我们可以看到仅仅是对XML的解析和bean定义的注册缓存,Spring就做了这么多事,并考虑到了各个可能会扩展的地方,那我们平时做的项目呢?看似简单的背后是否有深入思考过呢?

以上这篇这一次搞懂Spring的XML解析原理说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了电流源并联合并的方法,以及谐振电路的原理。谐振电路具有很强的选频能力,通过将电感和电容连接在一起,电流和电压会产生震荡。谐振频率的大小取决于电感和电容的大小,而电路中的电阻会逐渐降低震荡的幅度。电阻和电容组成的电路中,当电容放完电后,电阻两端的电压为0,电流不再流过电容。然而,电感是一种特殊的器件,当有电流流过时,线圈会产生感应磁场,阻止电流的流动,从而使电流不会减小。 ... [详细]
  • 标题: ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
author-avatar
红星闪闪的电灯泡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有