Spring IOC:parseCustomElement调用链

参考资料:

《Spring IoC源码学习:parseCustomElement 详解》

前文:

  《Spring IOC:obtainFreshBeanFactory调用链》

写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

目录

一、parseCustomElement方法

二、getHandlerMappings方法

三、resolve方法

四、init方法

五、parse方法

总结:       


一、parseCustomElement方法

        在前文中,parseBeanDefinitions调用parseCustomElement方法处理自定义命名空间节点。

public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}
 
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    // 拿到节点ele的命名空间,例如常见的:
    // <context> 节点对应命名空间: http://www.springframework.org/schema/context
    // <aop> 节点对应命名空间: http://www.springframework.org/schema/aop
    String namespaceUri = getNamespaceURI(ele);
    // 拿到命名空间对应的的handler, 例如:http://www.springframework.org/schema/context 对应 ContextNameSpaceHandler
    // getNamespaceHandlerResolver: 拿到namespaceHandlerResolver
    // resolve: 使用namespaceHandlerResolver解析namespaceUri, 拿到namespaceUri对应的NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 使用拿到的handler解析节点
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

        这里获取的readerContext即我们在前文中创建XmlReaderContext时会创建一个默认的DefaultNamespaceHandlerResolver,这边拿到的就是该对象。

NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

Spring IOC:parseCustomElement调用链

public XmlReaderContext createReaderContext(Resource resource) {
    // 根据 resource 构建一个 XmlReaderContext,用于存放解析时会用到的一些上下文信息
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}
 
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        // 创建默认的 DefaultNamespaceHandlerResolver
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}
 
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
 
// DefaultNamespaceHandlerResolver.java
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
    this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
 
public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
    Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
    // handlerMappingsLocation有个重要属性handlerMappings
    // 用于存放命名空间和该命名空间handler类的映射
    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    this.handlerMappingsLocation = handlerMappingsLocation;
}

         然后,调用DefaultNamespaceHandlerResolver的resolve方法解析namespaceUri,拿到对应的handler。

        例如<context> 节点对应命名空间http://www.springframework.org/schema/context)Spring IOC:parseCustomElement调用链

二、getHandlerMappings方法

        这里将getHandlerMappings方法放到前面,因为这是resolve方法的第一步,是所有后续流程的基础。

// DefaultNamespaceHandlerResolver.java
private Map<String, Object> getHandlerMappings() {
    // 如果handlerMappings已经加载过,则直接返回
    if (this.handlerMappings == null) {
        synchronized (this) {
            // 如果handlerMappings还没加载过,则进行加载
            if (this.handlerMappings == null) {
                try {
                    // 使用给定的类加载器从指定的类路径资源加载所有属性
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                    // 将Properties转换成Map, mappings -> handlerMappings
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    // 将加载到的所有命名空间映射放到缓存
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return this.handlerMappings;
}

         此方法中,我们拿到handlerMappingsLocation属性,默认是“META-INF/spring.handlers”。这里取得是spring框架包下的配置文件。

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

         在spring-context包中,可以看到下面这些命名空间对应的解析器。

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

Spring IOC:parseCustomElement调用链

         这里会使用指定的classLoader加载该配置文件,并使用Properties来存放 spring.handlers 文件中的内容(即命名空间和 handler 的键值对)。然后将 Properties 转成 Map。其中 key 为命名空间,value 为命名空间对应的 handler,最后将转换后的 handlerMappings 放到缓存。

三、resolve方法

        在第一节中,我们拿到了DefaultNamespaceHandlerResolver对象,然后调用resolve方法,在第二节中,我们获得了命名空间与处理器的映射关系。现在来看下后续的处理流程。

// DefaultNamespaceHandlerResolver.java
@Override
public NamespaceHandler resolve(String namespaceUri) {
    // 拿到配置文件的所有命名空间和对应的handler
    // 例如:"http://www.springframework.org/schema/aop" -> "org.springframework.aop.config.AopNamespaceHandler"
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 拿到当前命名空间对应的handler (可能是handler的className,也可能是已经实例化的handler)
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        // 如果不存在namespaceUri对应的handler,则返回null
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        // 如果是已经实例化的handler,则直接强转返回
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 如果是handler的className
        String className = (String) handlerOrClassName;
        try {
            // 根据className,使用类加载器拿到该类
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            // 校验是否是继承自NamespaceHandler(所有的handler都继承自NamespaceHandler)
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            // 使用无参构造函数实例化handlerClass类
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 调用handler类的初始化方法(将命名空间下的节点名和对应的解析器注册到parsers缓存中)
            namespaceHandler.init();
            // 将实例化的handler放到缓存,替换原来的className
            // 原来为: namespaceUri -> handler的className,会被覆盖成: namespaceUri -> 实例化的handler
            handlerMappings.put(namespaceUri, namespaceHandler);
            // 返回实例化后的handler对象
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

Spring IOC:parseCustomElement调用链

        

        经过几个步骤的校验,我们使用无参构造函数实例化了处理器,并调用其对应的init方法进行初始化。这里的namespaceHandler实际上就是我们获取到的处理器的引用,当命名空间为context时,对应的即为ContextNamespaceHandler类的init方法。

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();

        Spring IOC:parseCustomElement调用链

四、init方法

        上文中我们的调用链调用了ContextNamespaceHandler类的init方法,此方法调用registerBeanDefinitionParser方法对context后不同的节点属性进行注册。

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

Spring IOC:parseCustomElement调用链

         这里registerBeanDefinitionParser方法将节点与BeanDefinitionParser进行绑定并置入映射关系中。

// NamespaceHandlerSupport.java
private final Map<String, BeanDefinitionParser> parsers =
			new HashMap<String, BeanDefinitionParser>();
	
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		this.parsers.put(elementName, parser);
	}

五、parse方法

        parseCustomElement方法的结尾,对于返回的解析器,调用其parse方法。

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

         这里handler即ContextNamespaceHandler,parse调用的是其父类NamespaceHandlerSupport内的实现。

Spring IOC:parseCustomElement调用链

// NamespaceHandlerSupport.java
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // findParserForElement: 给element寻找对应的BeanDefinition解析器
    // 使用BeanDefinition解析器解析element节点
    return findParserForElement(element, parserContext).parse(element, parserContext);
}
 
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 拿到节点的localName,例如:annotation-config、component-scan
    String localName = parserContext.getDelegate().getLocalName(element);
    // 从parsers缓存中,拿到localName对应的解析器, 例如: component-scan -> ComponentScanBeanDefinitionParser
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

        parsers从缓存中查找element节点对应的BeanDefinition解析器,这里的解析器对应的就是第四节中init方法中置入map的映射。 然后,使用拿到的BeanDefinition解析器解析elemen 节点。例如:使用 ComponentScanBeanDefinitionParser 解析器解析 component-scan 节点。

Spring IOC:parseCustomElement调用链

Spring IOC:parseCustomElement调用链

总结:       

        这一步其实内容较少,主要是针对spring配置文件中的自定义命名空间节点,读取spring.handlers文件加载命名空间与解析器的映射关系,再针对当前节点确认其解析器,为下一步操作做准备。(注意,这里叫自定义命名空间节点,是因为我们不仅可以处理spring自定义的节点,还可以配置我们自己个性化的命名空间节点,有兴趣的可以看这篇文章的最后部分《传送门》)。

    


 

上一篇:spring整合mybathis配置文件


下一篇:springboot2 集成kafka