在JAXP中透明的快取XSL轉換器 (轉)

amyz發表於2007-08-14
在JAXP中透明的快取XSL轉換器 (轉)[@more@]

在JAXP中透明的快取轉換器

:namespace prefix = o ns = "urn:schemas--com::office" /> 

使用內建在Tranormer Factory中的快取功能在保持易用性的同時提高

 

摘要

當在具有眾多併發程式重複使用XSLT(Extensible Stylesheet Language Transformations)的環境中時,實現一個樣式表快取可以從根本上提高Web應用的效能。可是對於純粹的JAXP來說,在大多數JA( for XML Parsing) 程式中使用樣式表快取通常並不方便。本文章將介紹如何實現增加快取功能至transformer factory,並可以完全透明的使用快取。(年5月2日)

 

作者:Alexey Valikov

 

不要懷疑,XSLT的確是一個強大的技術,人們使用它構建了非常多的XML應用。具體來說,眾多的Web開發者們可以利用XSLT在呈現層(Presentation Layer)的優勢增加Web應用的易用性和伸縮性。然而這個優勢的代價是更多的和更高的負載,這個問題使開發者們在使用XSLT時更關注和快取技術。當在一個有眾多併發程式共享樣式表的Web環境中時快取尤為重要。

 

在這些情況中,恰當的transformation快取被證實對於提高效能有極大的幫助。最常見的建議是在使用JAXP時的將transformations載入入一個Templates,並且使用這個物件來建立Transformer物件,這比直接從工廠中例項化Transformer物件具有更好的效果。那樣一個Templates物件可以在稍後被重用於建立更多的transformers,這樣就節約了每一次用在樣式表解析和編譯的時間。在“Top Ten Java and XSLT ”中,Eric Burke在Tip1中列出了下列程式碼

 

xsltSource = new StreamSource(xsltFile);
TransformerFactory transFact = TransformerFactory.newInstance();
Templates cachedXSLT = transFact.newTemplates(xsltSource);
Transformer trans = cachedXSLT.newTransformer();

 

在這個示例中,xsltFile的transformation首先被載入cachedXSLT Templates物件。然後這個物件被用於建立新的transformer物件trans。這樣使用的優勢在於當我們需要另一個transformer物件時,解析和編譯過程可能會被跳過。

 

Transformer anotherTrans = cachedXSLT.newTransformer();

 

儘管這個技巧肯定會提高效能(特別是當重複使用相同的樣式表時,比如在Web應用中),坦白的講,它對於開發者來說並不方便。原因是除基於Templates的transformer例項外,你還必須注意樣式表的最後時間,重新載入過期(outdated)的transformations,提供的並且在多執行緒訪問樣式表時仍具有高的快取,還很多其他的細節。甚至將需要的功能封裝進一個單獨的transformer快取實現也無法幫助使用第三方模組的開發者。一個很好的例子就是 x:transform標記:當前在org..s.standard.tag.common.xml.TransformSupport和org.apache.taglibs.standard.tag.el.xml.TransformTag類中直接用TransformerFactory的newTransformer(…)方法。很明顯,x:transform無法藉助外部的快取實現來提高效能

 

不過還是有一個簡單的、不錯的方法來解決這個問題的。JAXP始終允許我們替代其Transformer Factory,那我們為什麼不寫一個具有caching能力的factory來解決這個問題呢?

 

這個主意並不難實現。我們可以擴充套件任何一個合適的TransformerFactory(我使用Michale Key的Saxon 7.3)並且過載父類的newTransformer(…)方法使從基於的StreamSources中載入的transmations可以被快取並從快取中讀取。當然,前提是transmations在載入後沒有被修改。下面是一個新版本的newTransformer(…)方法:

 

public Transformer newTransformer(final Source source)
 throws TransformerConfigurationException
{
 // Check that source in a StreamSource
 if (source instanceof StreamSource)
 try
 {
 // Create URI of the source
 final URI uri = new URI(source.getSystemId());
 // If URI points to a file, load transformer from the file
 // (or from the cache)
 if ("file".equalsIgnoreCase(uri.getScheme()))
 return newTransformer(new File(uri));
 }
 catch (URISyntaxException urise)
 {
 throw new TransformerConfigurationException(urise);
 }
 return super.newTransformer(source);
}

 

像你所看到的,如果transformer的source不是一個StreamSource或者未被指向一個檔案,父類的newTransformer(…)會返回transformer。如果source是一個基於檔案的StreamSource,這樣我們就可以使用快取來實現更智慧的transformation載入過程。

 

基於檔案的樣式表快取演算法非常簡單:根據一個給定的檔案,我們首先檢查快取中已經有絕對檔名相同的Templates物件。如果不存在,我們為這個檔案建立並快取一個新的Templates物件。如果已經存在於快取,我們檢查在Template載入後檔案是否曾修改過,比較檔案的最後修改時間和快取中的時間。如果檔案被更新過,Templates必須被再次載入,否則就從快取中取出。最後,用Template物件(根據情況而定,可能從快取或中載入)生成一個新的transformer。下面有一個根據這個演算法實現的方法:

 

protected Transformer newTransformer(final File file)
 throws TransformerConfigurationException
 {
 // Search the cache for the templates entry
 TemplatesCacheEntry templatesCacheEntry = read(file.getAbsolutePath());

 // If entry is found
 if (templatesCacheEntry != null)
 {
 // Check timestamp of modification
 if (templatesCacheEntry.lastModified
 < templatesCacheEntry.templatesFile.lastModified())
 // Clear entry, if it is obsolete
 templatesCacheEntry = null;
 }
 // If no templatesEntry is found or this entry was obsolete
 if (templatesCacheEntry == null)
 {
 logger.de("Loading transformation [" + file.getAbsolutePath() + "].");
 // If this file does not exists, throw the exception
 if (!file.exists())
 {
 throw new TransformerConfigurationException(
 "Requested transformation ["
 + file.getAbsolutePath()
 + "] does not exist.");
 }

 // Create new cache entry
 templatesCacheEntry =
 new TemplatesCacheEntry(newTemplates(new StreamSource(file)), file);

 // Save this entry to the cache
 write(file.getAbsolutePath(), templatesCacheEntry);
 }
 else
 {
 logger.debug("Using cached transformation [" + file.getAbsolutePath() + "].");
 }
 return templatesCacheEntry.templates.newTransformer();
 }

 

無論如何,我們仍然必須要考慮另外一個問題:執行緒安全。快取始終被很多併發程式所共享,我們必須有一個可靠的方法來使讀(從快取中獲得所需內容)和寫(儲存一個新載入的樣式表到快取)操作安全。

 

雖然Java提供了先進的同步功能,但這裡的問題並不是同步,而是如何平衡同步和效能。最簡單的方法是全部同步(full synchronization):我們將整個newTransformer(…)方法宣告為synchronized,但這種方法效率十分低下。通常我們只有有限的幾個樣式表,並且並不經常被修改,快取讀的次數要比寫的次數要多的多。全部同步(full synchronization)會阻擋其他併發的讀取者。首先,這並不是任何時候都需要。第二,這會導致瓶頸問題。

 

另一方面,使用未同步的容器(比如HashMap)來快取內容非常危險。如果我們沒有采取任何措施,那麼同時發生的讀取和寫操作(必然會出現的問題)會導致不穩定。

 

我們主要會遇到一個典型的讀/寫問題:一個給定的資源,可能有一個寫操作和若干個讀操作在某一時間同時發生。這個典型的問題也有一個典型的解決方法,來自於Doug Lea的Concurrent Programming in Java。這個方法是,跟蹤狀態(根據計算活動中和等待中的讀/寫執行緒的數量確定),只允許在沒有任何活動中的寫操作執行緒時才允許讀操作。同樣,只有在沒有活動中的讀操作執行緒時才允許寫操作。

 

為實現以上內容,我們將訪問快取的內容寫在兩個方法中,read()和write():

 

兩對berfore/after,read/write方法執行需要執行緒同步的步驟,確保安全,同時保證了訪問快取的效率。

 

protected synchronized void beforeRead()
{
 while (activeWriters > 0)
 try
 {
 wait();
 }
 catch (InterruptedException iex)
 {
 }
 ++activeReaders;
}

protected synchronized void afterRead()
{
 --activeReaders;
 notifyAll();
}

protected synchronized void beforeWrite()
{
 while (activeReaders > 0 || activeWriters > 0)
 try
 {
 wait();
 }
 catch (InterruptedException iex)
 {
 }
 ++activeWriters;
}

protected synchronized void afterWrite()
{
 --activeWriters;
 notifyAll();
}

 

瞭解了以上的程式碼後,我們最終獲得了一個可以透明的實現對基於檔案的樣式表的快取功能的transformer factory(你可以在資源處全部)。這裡只展示了使factory完全適用於標準JAXP程式的部分。

 

有一些方法可以使TransformerFactory.newInstance()方法返回一個定製的transformer factory實現的例項。其中最簡單易懂的方法就是在system property中的javax.xml.transform.TransformerFactory中指定factory的類名。

 

這個辦法的優點就是具有最高的優先順序,缺點就是必須手工操作。

 

另一個方法是是使用一個(Java Runtime Environment)級的檔案${JRE_HOME}/lib/jaxp.properties來指定你自己的類名

...
# Specifies transformer factory implementation
javax.xml.transform.TransformerFactory=de.fzi.s.transform.CachingTransformerFactory
...

 

最後一個方法是透過Services API在原資訊(meta-information)中提供transformer factory名。只需要在META-INF/services目錄中建立一個叫做javax.xml.transform.TransformerFactory的檔案。這個檔案的內容應該是一個指定了定製的transformer factory類名的單行字串。這種方法有一個問題:另一個JAR可能也嘗試透過Services API設定factory類。例如,如果你把你的JAR和Saxon’s JAR放入你的Web應用的WEB-INF/lib目錄,實際上JAXP使用的factory將會根據JAR檔案的載入順序確定。避免這種問題的方法是簡單的將factory設定在WEB-INF/classes/META-INF/services/javax.xml.transform.TransformerFactory檔案中。在本文情況中,該檔案需要包含一個單行字串de.fzi.dbs.transform.CachingTransformerFactory。

 

使快取可以透明的使用

 

現在,當一切都完成後。你沒那麼頭痛了。你不再需要擔心載入、快取和重新載入樣式表。你可以確保使用標準JAXP的第三方庫可以很好的使用快取功能。你可以肯定不會再有併發衝突,快取也不會是瓶頸。

 

可是,使用這個實現仍然有一些缺點。首先,這個factory只快取基於檔案的樣式表。原因是我們可以很容易的檢視檔案的最後修改的日期,但其他資源並不都能實現這個功能。另一個問題是樣式表import和include其他樣式表。修改了被import和include的樣式表並不會使主樣式表重新載入。最後,擴充套件一個現有的factory實現將你綁在了一個特定的XSLT Processor(除非你為每個一個你可能使用的factory都寫一個快取擴充套件)。不過,值得高興的一點是,大部分情況下,這些問題比起我們從基於factory的快取中獲得的:使用透明、方便、高效能來說無關緊要。

 

關於作者

 

Alexey Valikov是一個具有廣泛背景的科學家,特別是Java和XML技術。他現在在FZI(電腦科學研究中心,Karlsruhe/Germany)研究“Web應用中的效率問題”。他工作於FZI的XML Competence Center,他同時擔任XML技術顧問,並且參與了European Commission research projects。Alexey是一本很受歡迎的XSLT應用指南“The Technology of XSLT”的作者,該書出版於俄羅斯。

 

資源

  • 下載本文的原始碼:
  • "Top Ten Java and XSLT Tips," Eric M. Burke (java.oreilly.com, August 2001):
  • Concurrent Programming in Java, Second Edtion: Design Principles and Patterns, Doug Lea (Addison-Wesley Pub Co., 1999; ISBN: 0201310090):
  • Concurrent Programming in Java online supplement:
  • Saxon, an XSLT processor written by Michael Kay:
    /">
  • JAXP documentation:
  • Jar file specification, service provr documentation:
    /1.4/docs/guide/jar/jar.html#Service%20Provider">%20Provider
  • log4J, Java logging package (used in proposed factory implementation):
  • Apache Ant, Java-based build tool (you may use it to build the proposed factory implementation):

    More JavaWorld stories on XSLT:
    • "," Victor Okunev (February 2002)
    • "xslt.html">Boost Struts with XSLT and XML," Julien Mercay and Gilbert Bouzeid (February 2002)
    • "," Taylor Cowan (December 2001)
  • Browse the Java and XML section of JavaWorld's Topical Index:
  • Talk more about XSLT in our XML & Java discussion:
    http://forums.devworld.com/webx?50@@.ee6b78f
  • Sign up for JavaWorld's free weekly e Enterprise Java newsletter:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956500/,如需轉載,請註明出處,否則將追究法律責任。

相關文章