spring4.0源码分析之━━━DefaultNamespaceHandlerResolver类

      之所以讲解DefaultNamespaceHandlerResolver类,是因为这个类跟解析xml的时候有着莫大的关联。并且从中也可以学到一些构建代码方法,先看一下我们的配置文件,一般我们会以这样的开头

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/mvc   
            http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

。。。。。。。

      其中的http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/context等这些是xml的命名空间。而spring解析xml的时候就是采用的方法和管理方法有点类似,就是分而治之,给你画一块地,这个就归你管理了,该怎么办就怎么办。spring解析xml的时候就是不同的xml名称空间用不同的类来解析,这样也是解耦了代码,同时更加容易扩展。下面就慢慢的揭开spring解析xml的面纱。。。。。。

     如果 还没看前面一篇解析xml的文章,建议先看一下!spring解析xml到DefaultBeanDefinitionDocumentReader中的以下代码执行:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		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;
//解析Beans标签等
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
//解析其他的命名空间						
delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

    

     如果是其他的命名空间会到BeanDefinitionParserDelegate,这个类就是负责根据不同的xml名称空间分发解析xml了,就像公司的Boss,他负责给每个部门的老大任务。根据什么样的方式来分配任务呢?且看下面:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
//这里的getNamespaceHandlerResolver就像是秘书,根据不同的任务找到不同部门
//这里的readerContext就是Boss了
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
//这里的readerContext就是解析上下文,用于不同类解析完成后像容器注册BeanDefinition
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

      公司的Boss想了一下,你们每个部门不的都有名称么,每个部门自己的职能Boss自己不知道就找秘书,根据这些来分配任务了。spring也是一样,你xml标签不是都有自己的名称空间么,好我就以名称空间给你一亩地。你就负责好管理你这块地了。但是Boss怎么知道下面的每个部门完成任务了呢?是不是要实时的监控下,给每个部门老大办公室安装一个视频监控器。Boss就每天坐家里看你们有没有完成。要是这样那估计部门老大干完一天就走人了,大爷我不干了。你这监视我,就是不信任我啊,再说我还哪有自由可言,还没隐私了。不干了。呵呵,Boss想了一下不能这么干。high不了啊。好,那我就给你一个接口,你干好了,你就自动提交任务吧。这里的readerContext就是这样的接口了。这次我也完全信用你了,都不给你时间限制。你就慢慢玩吧。这样的Boss,估计每个部门老大都很high了。

     但是Boss在什么地方去根据什么样的任务分配给部门呢?Boss肯定都有一个秘书,找秘书吧。(怎么创建Boss和秘书的就要看解析xml了)嗯,秘书就乖乖给Boss一个本子,记录着呢,都在DefaultNamespaceHandlerResolver这个记事薄上,在下面的一页:

 

public NamespaceHandler resolve(String namespaceUri) {
//秘书从资料夹上得到资料
		Map<String, Object> handlerMappings = getHandlerMappings();
		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<?> 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);
//这个是部门创建后招员工的方法,会在下面讲解				
namespaceHandler.init();
				handlerMappings.put(namespaceUri, namespaceHandler);
				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);
			}
		}
	}

	/**
	 * Load the specified NamespaceHandler mappings lazily.
	 */
	private Map<String, Object> getHandlerMappings() {
//不幸,秘书第一次找到是空的,那就初始化吧
		if (this.handlerMappings == null) {
			synchronized (this) {
				if (this.handlerMappings == null) {
					try {
//秘书通过工具PropertiesLoaderUtils查找资料吧,不能手工啊,多麻烦
//这里的资料都记在叫META-INF/spring.handlers这个名字的架上,并且这样的架还挺多的
						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());
						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;
	}

       秘书就从资料架上找啊找,发现都记录在META-INF/spring.handlers这个名字的架上,然后通过PropertiesLoaderUtils工具全部找出来,记录在资料本某一页上。其中工具就是这样执行的。

public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
		Assert.notNull(resourceName, "Resource name must not be null");
		ClassLoader clToUse = classLoader;
		if (clToUse == null) {
			clToUse = ClassUtils.getDefaultClassLoader();
		}
		Properties props = new Properties();
		Enumeration urls = clToUse.getResources(resourceName);
		while (urls.hasMoreElements()) {
			URL url = (URL) urls.nextElement();
			URLConnection con = url.openConnection();
			ResourceUtils.useCachesIfNecessary(con);
			InputStream is = con.getInputStream();
			try {
				if (resourceName != null && resourceName.endsWith(XML_FILE_EXTENSION)) {
					props.loadFromXML(is);
				}
				else {
					props.load(is);
				}
			}
			finally {
				is.close();
			}
		}
		return props;
	}

 

       把所有classpath路径下META-INF/spring.handlers都找到并返回。这样秘书就可以回馈给Boss了。也可以交任务了。所以你到spring下的lib下看,会发现bean,aop,context,tx,jdbc等都会有 META-INF/spring.handlers文件。Boss根据秘书把任务分配好了,这里就要看部门老大怎么完成任务呢。这里讲一个实例,看其中的一个aop名称空间的解析,先看其jar文件下的META-INF/spring.handlers文件内容。发现啊aop的部门老大呢为AopNamespaceHandler类。
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
 这里先看AopNamespaceHandler部门老大的部门结构如下:
 
spring4.0源码分析之━━━DefaultNamespaceHandlerResolver类
       它继承了抽象类NamespaceHandlerSupport,暂且任务这个是公司为部门提供的一个公共的工具吧,每个部门都可以使用。其中的parse工具就是具体完成任务的了。在这个工具中呢,部门老大当然是用自己部门的人员来完成了,也就是解析xml的时候又要根据标签名来解析,不是所有的名称空间下的标签都一个类来解析了。又分了一下。部门老大通过findParserForElement找到自己部门的人,特定的任务就给特定的人来做,A会数据库,那他就做DBA吧,B会JAVA,那他写后台。这里部门在创建的时候呢,就是通过公司提供给部门一个招人的方法registerBeanDefinitionParser,招聘完成了就都放一个房间里parsers,这里的parsers就是房间。以后通过标签来找人,就在这个房间里找了。那就看AopNamespaceHandler这个部门老大是怎么招人的呢?他说我通过校园招聘,校园招聘好啊,人才多还便宜。他创建的时候会调用下面的代码:
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());
	}
       看吧,AopNamespaceHandler老大就把config这个标签任务分给了ConfigBeanDefinitionParser,把aspectj-autoproxy分给了AspectJAutoProxyBeanDefinitionParser。就像把这个位置分给A,命名config。以后遇到config的任务就都你来吧等待。都是校园招聘慧眼识珠找到的人才啊。那必须得用起来啊,公司可不是养猪的地方啊,得干活。
  • 总结

       spring代码中很多用到java的动态基础和继承。所以没有很好的基础看起来特别的费力。就像AopNamespaceHandler一样,通过继承NamespaceHandlerSupport,java多态的表现啊。这个大家就可以公用了是吧。同时spring也很好的用了模块,这就是分而治之的方法了。不同xml名称空间不同类来解析,不同标签不同类来解析。同事也很好的用了上下文Context,不然怎么贯连起来呢。

你可能感兴趣的:(namespace)