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は主に二つのことを完成して、
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設定ソースコード解析