参考资料:
《Spring IoC源码学习:parseCustomElement 详解》
前文:
《Spring IOC:obtainFreshBeanFactory调用链》
写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。
目录
一、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);
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)
二、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
这里会使用指定的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);
}
}
}
经过几个步骤的校验,我们使用无参构造函数实例化了处理器,并调用其对应的init方法进行初始化。这里的namespaceHandler实际上就是我们获取到的处理器的引用,当命名空间为context时,对应的即为ContextNamespaceHandler类的init方法。
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
四、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());
}
这里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内的实现。
// 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配置文件中的自定义命名空间节点,读取spring.handlers文件加载命名空间与解析器的映射关系,再针对当前节点确认其解析器,为下一步操作做准备。(注意,这里叫自定义命名空间节点,是因为我们不仅可以处理spring自定义的节点,还可以配置我们自己个性化的命名空间节点,有兴趣的可以看这篇文章的最后部分《传送门》)。