springspring源码阅读之xml读取bean注入(BeanFactory)

Posted 淡丶无欲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springspring源码阅读之xml读取bean注入(BeanFactory)相关的知识,希望对你有一定的参考价值。

前言

  此源码其实是在4月中旬就看了,而且当初也写了一份word文档,但不打算直接把word发上来。还是跟着以前的笔记、跟踪代码边看边写吧。

  其实当初看源码的理由很简单,1、才进新公司,比较有空闲。2、面试老是问spring的问题,我理论又不好,所以想看下。

  但现在,我重新看的目的其实不在于其实现原理,而是想学习和写出好的编码风格。(当初大概花了1周看,记得那时把看到的都理解深刻了,但现在基本不记得什么了。毕竟开发用不到)

一、如何开始阅读源码?

  最初我也一头雾水,不知道从哪开始看、怎么看、看源码的目的是什么等等。

  1、从哪开始看?

    针对本文的话,还是可以知道入口是哪的(后面给出demo),所以我还知道从哪开始开起。

  2、怎么看?

    我都是跟踪代码,一步一步的看的。(其效率很慢,但暂时也只会这么看)

  3、目的?

    当初我只关注spring是怎么读取xml配置、怎么如入bean。但发现,即使我当初看的时候理解在深刻,一个月不回想,再过不多久就不知道都是些什么了。

    而且,现在我更想学习的是好的编码风格,如何才能写出好看、好理解、易扩展的代码(即遵循SOLID原则)

    (开发设计原则 此文章是我较早学习整理的,其正文就是我看到较好的一篇blog内容。)

二、入口代码demo  spring版本3.2.16.RELEASE

public class Demo0102Run {
//	static String path = new Demo0102Run().getClass().getClassLoader().getResource("").getPath();
	public static void main(String[] args) {
//		System.out.println(System.getProperty("user.dir"));
		//step1: 读取spring的xml   (spring核心的是BeanFactory)
		/* 把资源文件封装为Spring的Resource
		 * spring把资源文件封装成统一的Resource进行管理,和提供一些基本的方法。 */
		Resource resource = new ClassPathResource("spring-demo0102.xml",Demo0102Run.class);

//		System.out.println(resource.isOpen());
		/* 加载资源文件,把xml中的bean definition注册到:1.把Resource再次封装为EncodedResource
		 *
		 */
		XmlBeanFactory factory = new XmlBeanFactory(resource);
//		BeanFactory factory = new XmlBeanFactory(resource);

		//step2:根据读取的xml配置,注入对应的bean
		/*
		 *  需要了解 BeanFactory和FactoryBean的区别:http://chenzehe.iteye.com/blog/1481476
		 * 备注01: 如果是beanName的命名是"&"开头,ex:"&demo0102Bean" spring会有别的处理。
		 * 		a. 对于beanInstance是FactoryBean的,会返回FactoryBean的的实例。并不是返回通过FactoryBean创建的bean实例
		 */
		Demo0102Bean bean = (Demo0102Bean) factory.getBean("demo0102Bean");
		System.out.println(bean.getResult());
	}
}
public class Demo0102Bean {
	private String result = "Demo0102Bean result String!";

	public String getResult() {  return result;  }
	public void setResult(String result) {  this.result = result;  }
}

  通过demo,可以很好的跟踪进代码,然后一步一步分析。(我是这样看的,不一定可取)

三、spring读取xml (为了好看,源码中部分注释被删除、部分代码被简写)

Resource resource = new ClassPathResource("spring-demo0102.xml",Demo0102Run.class);

  3.1 new ClassPathResource("spring-demo0102.xml",Demo0102Run.class);

    构造一个ClassPathResource(),根据ClassPathResource的类结构可知:实际是构造了Resource资源文件的实例对象

   在后续的资源处理就可以利用Resource提供各种的服务来操作。

    在demo中,有了ClassPathResource(即Resource)就可以对XmlBeanFactory进行初始化new XmlBeanFactory(...)。

/** Resource.class的实现类,使用给定的ClassLoader或Class去加载资源文件。
 *  	顺序是Class、ClassLoader、null,
 *  详见ClassPathResource.getInputStream()
 */
public class ClassPathResource extends AbstractFileResolvingResource {
	private final String path;
	private ClassLoader classLoader;
	private Class<?> clazz;
	/**
	创建一个 new ClassPathResource();
           Create a new {@code ClassPathResource} for {@code Class} usage.The path can be relative to the given class,
           or absolute within the classpath via a leading slash.
	@param path relative or absolute path within the class path
	@param clazz the class to load resources with
	@see java.lang.Class#getResourceAsStream
	*/
	public ClassPathResource(String path, Class<?> clazz) {// 还有别的构造函数,详见源码; 区别是,是否设置了class、classLoader 
		Assert.notNull(path, "Path must not be null");
		this.path = StringUtils.cleanPath(path);
		this.clazz = clazz;
	}	
           public InputStream getInputStream() throws IOException {
		InputStream is;
		//if else判断根据用Class\\ClassLoader去读取资源文件.构造函数中设置
		if (this.clazz != null) is = this.clazz.getResourceAsStream(this.path);
		else if (this.classLoader != null) is = this.classLoader.getResourceAsStream(this.path);
		else is = ClassLoader.getSystemResourceAsStream(this.path);
		
		if (is == null) throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		return is;
	}
}

image

    通过ClassPathResource的UML可知道其继承关系(本来显示全名的,但不知道怎么弄)。

    根据此关系及ClassPathResource实际源码,可以发现ClassPathResource实现了Resource中部分抽象方法。

    (在ClassPathResource源码,并未实现全部的Resource。一部分是AbstractResource中实现的,但AbstractResource部份不是真实的实现。e.g.: isOpen();AbstractResource总是返回false。 )

  3.1.1 Resource资源是如何被封装的?

    在demo中是通过new ClassPathResource(...); 通过Resource的关系图,也可以用别的来封装。

image

    image

  3.1.2 Spring为什么需要封装Resource?

    Spring用Resource接口对其内部要用到的资源进行统一的处理。及定义一些基本方法。

    clip_image001

  3.1.3 Spring中的Resource及 InputStreamSource

    通过图Resource类的UML可以看出,对于不同来源的资源文件都有相应的Resource实现:

    • 文件                   :FileSystemResource.class
    • Classpath资源     :ClassPathResource.class
    • URL资源             :UrlResource.class
    • InputStream资源 :InputStreamResource.class
    • Byte数组            :ByteArrayResource.class
    • 等等
/**
 * Interface for a resource descriptor that abstracts from the actual
 * type of underlying resource, such as a file or class path resource.
 *
 * <p>An InputStream can be opened for every resource if it exists in
 * physical form, but a URL or File handle can just be returned for
 * certain resources. The actual behavior is implementation-specific.
*/
public interface Resource extends InputStreamSource {

	/** 返回资源是否存在 */
	boolean exists();
	/** 返回资源内容是否可读 */
	boolean isReadable();

/** 返回这个资源是否有已打开流的处理。
* 如果为true,则此InputStream就不能被多次读取,
*而且只能被读取和关闭以避免资源泄漏
	*/
	boolean isOpen();

	/** 转换Resource到URL
	 * Return a URL handle for this resource.
	 * @throws IOException if the resource cannot be resolved as URL,
	 * i.e. if the resource is not available as descriptor
	 */
	URL getURL() throws IOException;

	/** 转换Resource到URI
	 * Return a URI handle for this resource.
	 * @throws IOException if the resource cannot be resolved as URI,
	 * i.e. if the resource is not available as descriptor
	 */
	URI getURI() throws IOException;

	/** 转换Resource到File
	 * Return a File handle for this resource.
	 * @throws IOException if the resource cannot be resolved as absolute file path, i.e. if the resource is not available in a file system
	 */
	File getFile() throws IOException;

	/** 返回该资源的内容长度
	 * Determine the content length for this resource.
	 * @throws IOException if the resource cannot be resolved
	 * (in the file system or as some other known physical resource type)
	 */
	long contentLength() throws IOException;

	/**
	 * Determine the last-modified timestamp for this resource.
	 * @throws IOException if the resource cannot be resolved
	 * (in the file system or as some other known physical resource type)
	 */
	long lastModified() throws IOException;

	/** 创建资源相对于这个资源
	 * Create a resource relative to this resource.
	 * @param relativePath the relative path (relative to this resource)
	 * @return the resource handle for the relative resource
	 * @throws IOException if the relative resource cannot be determined
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * Determine a filename for this resource, i.e. typically the last
	 * part of the path: for example, "myfile.txt".
	 * <p>Returns {@code null} if this type of resource does not
	 * have a filename.
	 */
	String getFilename();

	/** 返回该资源的描述,用于错误处理中打印信息。
	 * Return a description for this resource,
	 * to be used for error output when working with the resource.
	 * <p>Implementations are also encouraged to return this value
	 * from their {@code toString} method.
	 * @see Object#toString()
	 */
	String getDescription();

}
Resource类源码
Resource类源码
public interface InputStreamSource {
	/**
	 * @return the input stream for the underlying resource (must not be null)
	 */
	InputStream getInputStream() throws IOException;
}
InputStreamSource类源码

  3.1.4 Resource读取资源的具体实现

    以getInputStream()为例,在ClassPathResource中的实现方式是通过class或者classLoader提供的底层方法调用的,返回InputStream。

    当通过Resource完成了对配置文件进行封装后,配置文件的读取工作就完全交给了XmlBeanDefinitionReader来处理。

	/**
	 * This implementation opens an InputStream for the given class path resource.
	 * @see java.lang.ClassLoader#getResourceAsStream(String)
	 * @see java.lang.Class#getResourceAsStream(String)
	 */
	public InputStream getInputStream() throws IOException {
		InputStream is;
		// if else 判断根据用Class 、 ClassLoader去读取资源文件;在构造函数中设置
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}

  3.2 new XmlBeanFactory(resource)

public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	/**
	 * Create a new XmlBeanFactory with the given resource,
	 * which must be parsable using DOM.
	 * @param resource XML resource to load bean definitions from
	 * @throws BeansException in case of loading or parsing errors
	 */
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	/**
	 * Create a new XmlBeanFactory with the given input stream,
	 * which must be parsable using DOM.
	 * @param resource XML resource to load bean definitions from
	 * @param parentBeanFactory parent bean factory
	 * @throws BeansException in case of loading or parsing errors
	 */
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		/* 调用父类的构造函数,进行了一个中要处理。
		 * 在 AbstractAutowireCapableBeanFactory.class构造函数中有如下设置:
		 * 	ignoreDependencyInterface(BeanNameAware.class);
		 * 	ignoreDependencyInterface(BeanFactoryAware.class);
		 * 	ignoreDependencyInterface(BeanClassLoaderAware.class);
		 *
		 *  ignoreDependencyInterface(...)方法描述: 忽略给定依赖接口的自动装配功能
		 *     具体看方法。
		 */
		super(parentBeanFactory);//此处调用的是:AbstractAutowireCapableBeanFactory.class

		/*
		 * XmlBeanFactory方式整个资源加载的核心
		 */
		this.reader.loadBeanDefinitions(resource);
	}

}
XmlBeanFactory类源码
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
		super(parentBeanFactory);
	}
}
DefaultListableBeanFactory类部分源码
image

3.2.1 为什么要ignoreDependencyInterface(...)?

  当A中有属性B时,在spring获取A的Bean的时候,如果属性B还没有初始化,则spring会自动初始化B(这是spring的一个重要特性);

  但是,某些情况下B不会被初始化,其中一种情况就是B实现了BeanNameAware接口。

  ignoreDependencyInterface中注释表明了,忽略给定依赖接口的自动装配功能。典型的是通过其他方式解析ApplicationContext。

  比如:BeanFactory通过BeanFactoryAware进行注入,或者ApplicationContext通过ApplicationContextAware进行注入。

  (这是当初记下的,现在回头看。完全不知道说的什么…>.<!)

3.2.2 (核心)new XmlBeanDefinitionReader(this).loadBeanDefinitions(resource); XmlBeanDefinitionReader方式整个资源加载的核心。

// 整个xml加载的核心代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
	/**
	 * Load bean definitions from the specified XML file.
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	  /*
	   * 1:把Resource进一步封装为EncodedResource;
	   * 原因:考虑到Resource可能对编码有要,所以EncodedResource可以指定encoding编码、charset字符集。
	   * EncodedResource中的重要方法是getReader(),返回InputStreamReader.class(java.io.Reader的子类)
	   * 2:再调用加载资源文件的方法。
	   */
	  return loadBeanDefinitions(new EncodedResource(resource));
	}
           //记录已经加载过的资源。注意:用了ThreadLocal
           private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
			new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");

	/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	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());
		}
                       // 通过类属性resourcesCurrentlyBeingLoaded 记录已加载的资源。
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		//添加新资源到类属性;最后finally中有remove() 处理循环加载exception
		if (!currentResources.add(encodedResource)) {
			//循环加载exception
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource
					+ " - check your import definitions!");
		}
		try {
                                // 封装的EncodeResource先获取Resource,在通过Resource获取InputStream 
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
                                // 把InputStream封装为InputSource;org.xml.sax.InputSource不属于Spring		
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// doLoadBeanDefinitions才是核心加载资源文件
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}catch (IOException ex) {
			throw new BeanDefinitionStoreException("IOException parsing XML document from "+ encodedResource.getResource(), ex);
		}
                       // 问题01:finally的作用是什么? 原以为resourcesCurrentlyBeingLoaded类似缓存的作用,但在finally看来应该不是,那resourcesCurrentlyBeingLoaded实际作用是什么?
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

	private DocumentLoader documentLoader = new DefaultDocumentLoader();
	private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);

	/** 实际上真正的去加载bean的定义,从指定的xml文件中
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
		try {
			// 注册核心01: 获取对xml文件的验证模式
			int validationMode = getValidationModeForResource(resource);
			/* 注册核心02:利用spring核心的DocumentLoader加载xml,得到对应的org.w3c.dom.Document
			 * 	inputSource:就是通过Resource.getInputStream(),在用InputStream构造InputSource
			 *  getEntityResolver():得到一个实体解析器(org.xml.sax.EntityResolver),可以通过AbstractBeanDefinitionReader.setResourceLoader()设置。
			 *  this.errorHandler:xml错误处理。(org.xml.sax.ErrorHandler,不属于spring)
			 *  validationMode:xml验证模式
			 *  isNamespaceAware():Return whether or not the XML parser should be XML namespace aware.
			 */
			Document doc = this.documentLoader.loadDocument(
					inputSource,
					getEntityResolver(),
					this.errorHandler,
					validationMode,
					isNamespaceAware());
			/*
			 * 注册核心03:根据返回的Document注册bean的信息。(重点,解析配置文件,然后注册bean信息)
			 * (理解注册、注入,lyn个人理解:
			 *  	注册:好比创建了一个登陆帐号,保存到了数据库
			 *	注入:根据输入的登陆信息,去数据库找,找到就返回(把bean实例化)
			 * )
			 */
			return registerBeanDefinitions(doc, resource);
		}
		catch (*Exception ex) {
			throw new *Exception(...);
		}
	}
	/** 获取指定的Resource的验证模式,如果没有配置明确的验证模式,则使用自动检测
	 * Gets the validation mode for the specified {@link Resource}. If no explicit validation mode has been configured then the validation mode is
	 * {@link #detectValidationMode detected}.
	 *
	 * <p>Override this method if you would like full control over the validation mode, even when something other than {@link #VALIDATION_AUTO} was set.
	 */
	protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
		// 如果手动指定了验证模式,则使用指定的
		//(可在XmlBeanDefinitionReader.setValidationMode()方法设置)
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		// 如果未指定,则使用自动检测
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		// Hmm, we didn\'t get a clear indication... Let\'s assume XSD,
		// since apparently no DTD declaration has been found up until
		// detection stopped (before finding the document\'s root tag).
		return VALIDATION_XSD;
	}

	private boolean namespaceAware = false; // 默认false
	/**
	 * Return whether or not the XML parser should be XML namespace aware.
	 */
	public boolean isNamespaceAware() {
		return this.namespaceAware;
	}
	/** 返回EntityResolver的使用,构建一个默认的EntityResolver(实体解析器)
	 * Return the EntityResolver to use, building a default resolver
	 * if none specified. 如果未指定
	 * getResourceLoader()、getBeanClassLoader()都是AbstractBeanDefinitionReader.class中的方法。
	 */
	protected EntityResolver getEntityResolver() {
		if (this.entityResolver == null) {
			// 确定默认使用的EntityResolver(实体解析器) 
			// Determine default EntityResolver to use.
			ResourceLoader resourceLoader = getResourceLoader();
			if (resourceLoader != null) this.entityResolver = new ResourceEntityResolver(resourceLoader);
			else this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
		}
		return this.entityResolver;
	}
}
/** 对Resource进行封装,考虑到Resource可能对编码有要,所以EncodedResource可以指定encoding编码、charset字符集。
 * 当指定后,获取Reader会根据charset、encoding、default的顺序去获取InputStreamReader,看具体代码getReader();
 * Holder that combines a Resource descriptor with a specific encoding
 * or Charset to be used for reading from the resource.
 *
 * <p>Used as an argument for operations that support reading content with a specific encoding, typically via a java.io.Reader.
*/
public class EncodedResource {
	private final Resource resource;
	private final String encoding;
	private final Charset charset;

	/**
	 * Create a new EncodedResource for the given Resource,
	 * not specifying an explicit encoding or Charset.
	 * @param resource the Resource to hold; never null
	 */
	public EncodedResource(Resource resource) {
		this(resource, null, null);
	}

	/**
	 * Create a new EncodedResource for the given Resource,
	 * using the specified encoding.
	 * @param resource the Resource to hold; never null
	 * @param encoding the encoding to use for reading from the resource
	 */
	public EncodedResource(Resource resource, String encoding) {
		this(resource, encoding, null);
	}

	/**
	 * Create a new {@code EncodedResource} for the given {@code Resource},
	 * using the specified {@code Charset}.
	 * @param resource the {@code Resource} to hold; never {@code null}
	 * @param charset the {@code Charset} to use for reading from the resource
	 */
	public EncodedResource(Resource resource, Charset charset) {
		this(resource, null, charset);
	}

	private EncodedResource(Resource resource, String encoding, Charset charset) {
		super();
		Assert.notNull(resource, "Resource must not be null");
		this.resource = resource;
		this.encoding = encoding;
		this.charset = charset;
	}

	/**
	 * Return the {@code Resource} held by this {@code EncodedResource}.
	 */
	public final Resource getResource() {
		return this.resource;
	}

	/**
	 * Return the encoding to use for reading from the {@linkplain #getResource() resource},
	 * or {@code null} if none specified.
	 */
	public final String getEncoding() {
		return this.encoding;
	}

	/**
	 * Return the {@code Charset} to use for reading from the {@linkplain #getResource() resource},
	 * or {@code null} if none specified.
	 */
	public final Charset getCharset() {
		return this.charset;
	}

	/**
	 * Determine whether a {@link Reader} is required as opposed to an {@link InputStream},
	 * i.e. whether an {@linkplain #getEncoding() encoding} or a {@link #getCharset() Charset}
	 * has been specified.
	 * @see #getReader()
	 * @see #getInputStream()
	 */
	public boolean requiresReader() {
		return (this.encoding != null || this.charset != null);
	}

	/** 当设置了encoding或者charset后,会通过指定的去获取InputStreamReader
	 * Open a {@code java.io.Reader} for the specified resource, using the specified
	 * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
	 * (if any).
	 * @throws IOException if opening the Reader failed
	 * @see #requiresReader()
	 * @see #getInputStream()
	 */
	public Reader getReader() throws IOException {
		if (this.charset != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
		else if (this.encoding != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
		else {
			return new InputStreamReader(this.resource.getInputStream());
		}
	}

	/**
	 * Open a java.io.InputStream for the specified resource, ignoring any specified {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}.
	 * @throws IOException if opening the InputStream failed
	 * @see #requiresReader()
	 * @see #getReader()
	 */
	public InputStream getInputStream() throws IOException {
		return this.resource.getInputStream();
	}


	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		if (obj instanceof EncodedResource) {
			EncodedResource that = (EncodedResource) obj;
			return (this.resource.equals(that.resource)
		&& ObjectUtils.nullSafeEquals(this.charset, that.charset) 			&& ObjectUtils.nullSafeEquals(this.encoding, that.encoding));
		}
		return false;
	}

	@Override
	public int hashCode() {
		return this.resource.hashCode();
	}

	@Override
	public String toString() {
		return this.resource.toString();
	}

}
EncodedResource类部分源码

3.2.3 注册核心01: 获取对xml文件的验证模式

  为了保证xml文件的正确性,一般对xml的验证模式有2种:

  (1) DTD:Document Type Definition ,类型定义

  (2) XSD:XML Schemas Definition ,XML结构定义

  问题02DTD和XSD验证XML文件的区别,待看。

  在spring,XmlBeanDefinitionReader中的setValidationMode(int x);可以设置xml的验证方式。如果未设置,则用自动检测模式。

  protected int XmlBeanDefinitionReader.getValidationModeForResource(Resource resource) 可以获取xml的验证方式。

  在其内部又把自动检测detectValidationMode(Resource resource)交给了专门用于处理xml验证的类XmlValidationModeDetector.class

  在XmlValidationModeDetector.class内部,判断xml验证模式的标准是:xml中是否包含"DOCTYPE",如果包含则 DTD验证,否则用XSD;

<!-- 包含"DOCTYPE",用 DTD验证 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!-- 未包含"DOCTYPE",用 XSD验证 -->
<?xml version="1.0" encoding="UTF-8"?>
<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:mvc="http://www.springframework.org/schema/mvc"
	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.1.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.1.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"
	>
xml验证模式
public class XmlValidationModeDetector {
	public int detectValidationMode(InputStream inputStream) throws IOException {
		// Peek into the file to look for DOCTYPE. 在读取文件中查找"DOCTYPE"
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		try {
			boolean isDtdValidated = false;
			String content;
			while ((content = reader.readLine()) != null) {
				content = consumeCommentTokens(content);
				//读取的行是null 或者 是注释 则略过
				if (this.inComment || !StringUtils.hasText(content)) { continue; }
				//如果包含DOCTYPE 则验证模式是DTD,否则XSD
				if (hasDoctype(content)) { isDtdValidated = true;break; }
				//读取到<开始符号,验证模式会在开始符号之前(分析xml结构)
				if (hasOpeningTag(content)) {
					// End of meaningful data...
					break;
				}
			}
			return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
		}
		catch (CharConversionException ex) {
			// Choked on some character encoding...
			// Leave the decision up to the caller.
			return VALIDATION_AUTO;
		}
		finally {
			reader.close();
		}
	}
	/**
	 * Does the supplied content contain an XML opening tag. If the parse state is currently in an XML comment then this method always returns false. It is expected that all comment tokens will have consumed for the supplied content before passing the remainder to this method.
	 */
	private boolean hasOpeningTag(String content) {
		if (this.inComment) { return false;}
		int openTagIndex = content.indexOf(\'<\');
		// Character.isLetter 确定字符是字母,是字母返回true
		return (openTagIndex > –1 && (content.length() > openTagIndex + 1)
                                                 && Character.isLetter(content.charAt(openTagIndex + 1))
				);
	}
}

  结合上面的2种xml理解 hasOpeningTag(content):

    因为都是行读取reader.readLine();

  第一行:<?xml version="1.0" encoding="UTF-8"?>

    openTagIndex > -1 ,0 > -1:true

    (content.length() > openTagIndex + 1) :true

    Character.isLetter(“?”) :?不是字母false

  第二行:<beans xmlns="http://www.springframework.org/schema/beans"

    openTagIndex > -1 ,0 > -1:true

    (content.length() > openTagIndex + 1) :true

    Character.isLetter(“b”) :true

    此时会结束while循环,执行

      return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);

3.2.4 最核心加载xml

        /* 注册核心02:利用spring核心的DocumentLoader加载xml,得到对应的org.w3c.dom.Document

         * inputSource:就是通过Resource.getInputStream(),在用InputStream构造InputSource

         * getEntityResolver():得到一个实体解析器(org.xml.sax.EntityResolver),可以通过AbstractBeanDefinitionReader.setResourceLoader()设置。

         * this.errorHandler:xml错误处理。(org.xml.sax.ErrorHandler,不属于spring)

         * validationMode:xml验证模式

         * isNamespaceAware():Return whether or not the XML parser should be XML namespace aware.

         */

         Document doc = this.documentLoader.loadDocument( inputSource,

                                                                                       getEntityResolver(),

                                                                                       this.errorHandler,

                                                                                       validationMode,

                                                                                       isNamespaceAware());

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
	// DocumentLoader是一个接口,spring唯一实现类是DefaultDocumentLoader;
	private DocumentLoader documentLoader = new DefaultDocumentLoader();

	/** 可以自己实现DocumentLoader, 然后通过该方法设置documentLoader
	 * Specify the {@link DocumentLoader} to use.
	 * <p>The default implementation is {@link DefaultDocumentLoader}
	 * which loads {@link Document} instances using JAXP.
	 */
	public void setDocumentLoader(DocumentLoader documentLoader) {
		this.documentLoader = (documentLoader != null ? documentLoader : 以上是关于springspring源码阅读之xml读取bean注入(BeanFactory)的主要内容,如果未能解决你的问题,请参考以下文章

TSharding源码阅读-MapperShardingInitializer之生成mapper xml

SpringSpring系列5之Spring支持事务处理

spring源码阅读 Bean加载之默认标签加载

Mybatis源码阅读之--本地(一级)缓存实现原理分析

SpringSpring&WEB整合原理及源码分析

SpringSpring之事务处理