Spring読み取りXMLのソースコード解析

14583 ワード


Springでは、プロファイルの主なフォーマットはXMLであり、spring自体はjms、aopなど、多くのxml namespaceの構成を提供しています.また、Springは、ユーザーが自分の構成を実現するために多くの拡張点を提供していますが、これは一体どうやって実現されますか?最後まで調べてみましょう.
 
Xml BenFactoryから始めましょう.このクラスで:
 
 
public class XmlBeanFactory extends DefaultListableBeanFactory {

	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}

	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}

}
 
 
springはXmlBenDefinitionReaderを使ってxmlファイルを読み、解析します.Xml BenDefinitionReaderはBenDefinitionReaderインターフェースの実現です.BenDefinitionReaderはspring読み取りbean定義のインターフェースを定義しています.このインターフェースにはいくつかのloadBenDefinitions方法があります.これらの方法による署名から、springはbean構成を読み取るソースをResourceインターフェースとして抽象化しています.BeanDefinitionReaderインターフェースは二つの具体的な実装があります.その一つは、xmlファイルから構成のXmlBenDefinitionReaderを読み取り、もう一つはjava propertiesファイルから構成のProptiesBenDefinitionReaderを読み取ります.開発者は自身のBeanDefinitionReaderを提供して実現し、自分の必要に応じてspring bean定義の構成を読み取ることもできます.XmlBenFactoryでXmlBenDefinitionReaderの例を作成し、XmlBenFactoryの構造方法でXml BenDefinitionReaderのloadBenDefinitions方法を呼び出して、LaadBenDefinitionsメソッドによってbean配置をロードしてXlBeans Factoryに登録します.
 
 
 XmlBenFactoryはXmlBenDefinitionReaderを使ってXMLファイルを読み込むものと見られます.この実際の読み取り転送はXml BeanDefinitionReaderのloadBenDefinitions方法に転送されます.
 
 
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
……
	
private DocumentLoader documentLoader = new DefaultDocumentLoader();
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
……
try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				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 {
			int validationMode = getValidationModeForResource(resource);
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
			return registerBeanDefinitions(doc, resource);
		}
……
}

……
}
   loadBenDefinitionsメソッドは、まずResourceインターフェースを通じてxmlプロファイルを読み取り、Documentオブジェクトに読んで解析するために使用されます.この動作は、インターフェースDcumentLoaderによって実現されます.springはデフォルトでDefault DockmentLoaderを実現しています.
 
 
 
    上記ではdocumentLoaderが定義されています.明らかに、Default Dockment Loaderに矛先が向けられています.
 
public class DefaultDocumentLoader implements DocumentLoader {
private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";

	
	private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isDebugEnabled()) {
			logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(namespaceAware);

		if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
			factory.setValidating(true);

			if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
				// Enforce namespace aware for XSD...
				factory.setNamespaceAware(true);
				try {
					factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
				}
				catch (IllegalArgumentException ex) {
					ParserConfigurationException pcex = new ParserConfigurationException(
							"Unable to validate using XSD: Your JAXP provider [" + factory +
							"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
							"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
					pcex.initCause(ex);
					throw pcex;
				}
			}
		}

		return factory;
	}

protected DocumentBuilder createDocumentBuilder(
			DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
			throws ParserConfigurationException {

		DocumentBuilder docBuilder = factory.newDocumentBuilder();
		if (entityResolver != null) {
			docBuilder.setEntityResolver(entityResolver);
		}
		if (errorHandler != null) {
			docBuilder.setErrorHandler(errorHandler);
		}
		return docBuilder;
	}

}
 
どのようにxmlファイルを読み込むかについては、Doccumentオブジェクトとして知られています.DoccumentBuider Factoryを作成し、Doccument Buider FacotyによってDcumentを作成し、DcumentBuiderのparseメソッドを呼び出してファイルまたはストリームをDcumentとして解析します.確かにspringもそうですが、忘れないでください.springはxml schemaを使ってxmlを検証します.springはjaxp 1.2で提供されたxml schemaの検証方式を使っています.jaxp 1.3に導入されたSchemaオブジェクトを使って検証していません.Default Doccument Loaderは、DcumentBulder Factoryオブジェクトを作成した後、現在xml schemaを使って検証されているかどうかを判断します.もしそうであれば、DcumentBuider Factoryに属性を設定します.この属性名は「Xml schema」です.http://java.sun.com/xml/jaxp/properties/schemaLanguageを選択します.この属性をhttp://www.w3.org/2001/XMLSchemaを選択しますjaxpではxml schemaを使ってxmlドキュメントを検証します.このような検証方式を使うと、EnttyResolaverの実現を提供します.springはEntityResolaverの実現を提供しています.この実現もspringを拡張する鍵となります.
 
 
 
    ResourceからDcumentへの変換を完了した後、Dockmentから各beanの配置を解析しました.このためにspringはまた一つのインターフェースBenDefinitionDcument Readerを抽象化しました.その名前からこのインターフェースはDcumentからbeanの定義を読み取ります.このインターフェースは一つの方法しか定義されていません.springはDefaultBenDefinitionDcuntReaderのデフォルト実装も提供しています.
 
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
……
return registerBeanDefinitions(doc, resource);
……
}

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		// Support old XmlBeanDefinitionParser SPI for backwards-compatibility.
		if (this.parserClass != null) {
			XmlBeanDefinitionParser parser =
					(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
			return parser.registerBeanDefinitions(this, doc, resource);
		}
		// Read document based on new BeanDefinitionDocumentReader SPI.
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
……}
 
    Default BeanDefinitionDocmentReaderは主に二つのことを完成して、元素を解析して、springの元素を拡張するために適切な解析器を探して、対応する元素を解析器に渡します.最初のミッションは、要素を解析し、このspringのコア機能およびIoCまたはDIをspring自身で処理します.この仕事はBenDefinitionParsdelegateを専門的な委託類で処理し、これによって要素を解析し、解析の結果をBenDefinitionRegistryに登録します.
 
 
 
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;

		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();

		BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

		preProcessXml(root);
		parseBeanDefinitions(root, delegate);
		postProcessXml(root);
	}
	protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
		BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
		delegate.initDefaults(root);
		return delegate;
	}

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
			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;
					String namespaceUri = ele.getNamespaceURI();
					if (delegate.isDefaultNamespace(namespaceUri)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
……
}
 
 
では、springはどのようにしてbean元素と他の拡張要素を区別しますか?自然に元素名を使うと考えられます.確かに元素名を使って処理できますが、これによってこのような状況が現れます.プログラマAはspringを拡張して、元素名をcと決めます.同じプログラマB拡張springもcという元素を定義しています.この時は区別ができません.実際にはspringはxml namespaceによって区別されており、拡張要素の解析器もxml namespaceによって処理されている.springはルートの元素から始めて、各元素を解析する時、先に元素のnamespace uriを調べて、もし元素のnamespace uriはhttp://www.springframework.org/schema/beansこれらの要素はbeans、bean、import、aliasを含み、namespace uriがそうでない場合、spring IoCによって解析処理される.http://www.springframework.org/schema/beansを選択しますNamespace HandlerResolaverを使ってNamespace Handlerを解析して、Namespace Handlerを使ってこの元素を解析して処理します.Namespace Handler ResloverとNamespace Handlerはspringを拡張する秘密です.Namespace HandlerResolaverはインターフェースであり、springはEnttityResolaverと同じ策略を使って実現します.このステップが完了すると、springも読み込み解析xmlの設定が完了します.
 
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

	public boolean isDefaultNamespace(String namespaceUri) {
		return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
	}

	public BeanDefinition parseCustomElement(Element ele) {
		return parseCustomElement(ele, null);
	}

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
	}

	public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = ele.getNamespaceURI();
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
 
 
 
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;

public static final String ALIAS_ELEMENT = "alias";

public static final String IMPORT_ELEMENT = "import";
 
springのソースコードのディレクトリには二つの特殊なファイルがあります.spring.schemasとspring.handers、この二つのファイルとspringの中でEnttityResolaverとNamespace HandlerResolaverのPluggable SchemaResolaverとDefault NafaultNaspace Handlerolverはsplargを拡張するためのsplingの重要なところです.実はspring.schemasとspring.handlesファイルは標準的なjava propertiesファイルです.この二つのファイルはspring jarパッケージのMETA-INFディレクトリに大包されています.Pluggable SchmaResolaverはspring.schemasファイルを読み込むことによって、xmlファイルの本体のsystem idによってこれらのエンティティを解析します.spring.schemasファイルのkeyを見てください.(実は私もsystem idとpublic idが何か分かりません.知っている友達は文後の返事にメッセージをください.ありがとうございます.)、Default Namespace HandlerResolaverは元素のnamespace uriによってspring.handersファイルの中で具体的なNamespace Handlerを探しています.
 
 
public interface NamespaceHandlerResolver {

	/**
	 * Resolve the namespace URI and return the located {@link NamespaceHandler}
	 * implementation.
	 * @param namespaceUri the relevant namespace URI
	 * @return the located {@link NamespaceHandler} (may be <code>null</code>)
	 */
	NamespaceHandler resolve(String namespaceUri);

}
 
 
 
上記のように、springを拡張するには、次のいくつかの作業を完了する必要があります.xml schemaを定義し、対応するspring.schemasファイルを作成し、Namespace Handlerインターフェースを実現します.必要に応じてBenDefinitionParterやBenDefinitionDecoratorなどのインターフェースを実現する必要があります.より詳細な情報は、springのreferenceまたはspring関連の他のspringを参照することができます.文書
 
開源コミュニティではどの神人が開発したのか分かりません.  xbean このようなフレームワークです.このフレームワークは具体的に何をしていますか?主に三つのことを完成しています.最初にソースコードの中のいくつかの特殊なdocletからxml schemaを生成します.activemqのソースコードを見てみてください.多くの種類のjavadocにこのようなものが多く存在していることが分かります.一つのxml schemaを生成します.xbeanで完成した二つ目のことは、springを拡張するために必要ないくつかの構成を生成します.三つ目は、XmlBenDefinitionReaderを拡張したように、いくつかのspring中の代替コンポーネントを再実現しました.xxbeanを使用するとxbeanで実現されるAppplicationConteextを使用しなければなりません.xbeanが提供するBeanDefinitionReaderはいくつかのカスタム要素をspringのbean要素に変換するだけで、springの構成が読みやすくなります.
 
 
この文の参考:
spring読み取りxml設定ソースコード解析