2006 年 03 月 05 日
關于blob在ibatis中的處理 
在 iBATIS 2.0.9 以前,處理 Oracle BLOB 類型相當麻煩,要自己實現TypeHandlerCallback 接口。iBATIS 2.0.9 提供了 BlobTypeHandlerCallback 實現類,寫入、取出程序和上面的 SQL Server 一樣。只是映射文件 resultMap 元素需要修改。
typeHandler="com.ibatis.sqlmap.engine.type.BlobTypeHandlerCallback"/>
因為ibatis2.0.9提供oracle的blob字段處理器。
Posted by@rimen at 2006 年 03 月 05 日 | Comments (0) | TrackBack
2006 年 02 月 26 日
關于ThreadLocal如何保證數據線程安全 
在多線程的java環境中,我們很多使用需要一個共享內存來保證線程之間的通訊,這個在java里
面很簡單,實例變量和靜態變量。這也大家是在不經意間的使用方法,想都不用想都可以實現這些方法來
達到多線程共享數據的目的,這些共享數據存在一個線程安全的問題。特別是對于具有串行意義的數據,
流入請求號、事務號等。
這里有兩個概念:
1.保證在一個線程中使用完這個數據之前不能讓別人去修改這個字段的意義.
2.在整個線程生命周期內,保證這個數據一致性,而且不影響其他線程讀取這個數據.
我們在考慮現存安全的時候,對第一種情況比較關注.例如大家都知道在對一個具有線程安全數據
訪問的塊加入一個synchronized.這里有些誤導,大家會考慮在對大部分的線程安全操作考慮加入這個關鍵
字來達到不出錯的目的,但是這個是一種很不明智的做法,我們很多時候在控制同步粒度的時候考慮不周,
導致大量的線程在同步阻塞,使得系統的性能大幅下降.所以我建議不到不得已少用慎用這個關鍵字.
對于第二種:大家或許或考慮用本地變量來保證這個特性.確實在一個方法中,我們的變量保證第
二種情況描述的特性,但是在我們復雜的環境中,往往有很多個線程在完成系統的任務,這樣我們的時候不
可能用一個方法來描述一個線程的任務.例如我們在j2ee環境中,客戶的請求號,我們在應用中需要用這個
號碼來跟蹤客戶的信息.還有我們的事務,我們很多時候在系統中需要獲得一個TXNID來貫穿事務.這樣我們
能做到的就是使用ThreadLocal變量.
大家先看一下jdk中描述的,ThreadLocal提供了現成本地變量副本,為每個存放在這個實例中的變
量創建一個本地副本,在線程第一次方法這個變量的時候創建這個副本.而且表示,一般這個實例定義成一
個私有靜態變量,供所有的線程來保存他們自己的全局資源副本.這樣我們很容易想到使用它來做一個序號
發生器,當一個客戶線程進來的時候給他分配一個序號,而在序號可以保存到他的請求結束:
public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};
public static int get() {
serialNum.set("")
return ((Integer) (serialNum.get())).intValue();
}
}
Posted by@rimen at 2006 年 02 月 26 日 | Comments (0) | TrackBack
2006 年 01 月 19 日
關于資源下載 
很多朋友說這里下載太慢了。由于現在沒有自己的服務器資源。
有一個朋友提供了一個公網的CVS:
CVS用戶配置
認證方式: pserver
路徑: /home/cvsroot
主機地址: 218.16.119.21
用戶名: soosooone
密碼: soosooone
項目名:soosoo
Posted by@rimen at 2006 年 01 月 19 日 | Comments (0) | TrackBack
2005 年 12 月 11 日
關于企業系統之間交互是app server的角色 
在整個企業應用程序架構中,應用系統充當業務引擎實現,網絡提供通訊宿主,而整正能給整個企業架構附注生命的還是app server。
想象一下在20年期,計算機技術還處于開始發展的時代,面向過程的計算機軟件技術開始盛行。有很多大牛可以在c上做出高效龐大的應用系統。
但是就沒有什么大牛能做出一個企業應用體系。因為那個時候沒有app server這個東西。
app server也許最早出現的是操作系統的概念,它是軟件運行的環境,我想很多人要問,為什么有了操作系統這個巨無霸的軟件,為什么還需要
app server呢。我想從下面幾點陳述:
1.面向的服務對象不應用
大家很多時候會在app server 上開發各種各樣的系統,也有人在操作系統上開發各種各樣的系統。但是只要你注意一下就知道,他們面向服務的對象
是不一樣的。在面向對象軟件技術出現之前,也有很多數據庫應用系統,但是他們在規模上和服務模式上多時一樣的。目前大家聽得最多的B/S模式和C/S 模式,
也這是這兩種模式可以來界定他們自己的差別.基于操作系統的應用程序,由于操作系統的差異性,大部分程序是基于C/S模式,提供各種操作系統的客戶斷安裝版本.有的甚至還需要和硬件關聯在一起,基于這種技術的程序,在客戶端的代碼異常龐大,維護困難.而在app server 上的程序,由于app server可以提供面向不同操作系統的統一服務,因此對應用程序的版本控制非常簡單.客戶也可以通過統一的方式獲得上面的應用服務.綜合來說,基于操作系統的程序適合比較底層,時實性要求比較高的應用.而基于app server 上的開發比較適合業務性很強的應用.
2.管理的對象不一樣
由于操作系統主要實現的是對硬件物理設備的管理,給予上面的開放比較容易或者這些資源的訪問控制方法.就目前軟件技術的水平上開,很少會直接在操作系統上做業務系統的開發,因為他們需要管理的資源主要是業務邏輯和數據模型.而對物理設備的管理比較少.在行業分布上開, app server 比較容易在金融和物流等領域中應用,因為他們主要是業務邏輯,而在計算機網絡通訊業,例如電信,移動等領域這種技術就沒有那么適合.
3.內核技術
操作系統的內核技術是對物理資源的管理,給應用開發者提供一套api,不同的操作系統差異性比較大,也就是說你在 windows上開發的應用系統基本上不能在linux上運行,而基于app server技術的應用,一般實現的是一個企業級的規范,內核主要實現對不同操作系統的封裝.從環境的分布上開,操作系統是app server的運行環境.因此很多app server提供的對操作系統功能的模擬,提供一個軟計算機/虛擬計算機的概念.而對應用的接口一般不是app server定的,而是app server服務的規范體系定的.
Posted by@rimen at 2005 年 12 月 11 日 | Comments (0) | TrackBack
2005 年 12 月 01 日
j2ee中web層性能優化 
你的J2EE應用是不是運行的很慢?它們能不能承受住不斷上升的訪問量?本文講述了開發高性能、高彈性的JSP頁面和Servlet的性能優化技術。其意思是建立盡可能快的并能適應數量增長的用戶及其請求。在本文中,我將帶領你學習已經實踐和得到證實的性能調整技術,它將大大地提高你的servlet和jsp頁面的性能,進而提升J2EE的性能。這些技術的部分用于開發階段,例如,設計和編碼階段。另一部分技術則與配置相關。
技術1:在HttpServlet init()方法中緩存數據
服務器會在創建servlet實例之后和servlet處理任何請求之前調用servlet的init()方法。該方法在servlet的生命周期中僅調用一次。為了提高性能,在init()中緩存靜態數據或完成要在初始化期間完成的代價昂貴的操作。例如,一個最佳實踐是使用實現了javax.sql.DataSource接口的JDBC連接池。DataSource從JNDI樹中獲得。每調用一次SQL就要使用JNDI查找DataSource是非常昂貴的工作,而且嚴重影響了應用的性能。Servlet的init()方法可以用于獲取DataSource并緩存它以便之后的重用:
public class ControllerServlet extends HttpServlet
{
private javax.sql.DataSource testDS = null;
public void init(ServletConfig config) throws ServletException
{
super.init(config);
Context ctx = null;
try
{
ctx = new InitialContext();
testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
}
catch(NamingException ne)
{
ne.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public javax.sql.DataSource getTestDS()
{
return testDS;
}
...
...
}
技術2:禁用servlet和Jsp的自動裝載功能
當每次修改了Servlet/JSP之后,你將不得不重新啟動服務器。由于自動裝載功能減少開發時間,該功能被認為在開發階段是非常有用的。但是,它在運行階段是非常昂貴的;servlet/JSP由于不必要的裝載,增加類裝載器的負擔而造成很差的性能。同樣,這會使你的應用由于已被某種類裝載器裝載的類不能和當前類裝載器裝載的類不能相互協作而出現奇怪的沖突現象。因此,在運行環境中為了得到更好的性能,關閉servlet/JSP的自動裝載功能。
技術3:控制HttpSession
許多應用需要一系列客戶端的請求,因此他們能互相相關聯。由于HTTP協議是無狀態的,所以基于Web的應用需要負責維護這樣一個叫做session的狀態。為了支持必須維護狀態的應用,Java servlet技術提供了管理session和允許多種機制實現session的API。HttpSession對象扮演了session,但是使用它需要成本。無論何時HttpSession被使用和重寫,它都由servlet讀取。你可以通過使用下面的技術來提高性能:
l 在JSP頁面中不要創建默認的HttpSession:默認情況下,JSP頁面創建HttpSession。如果你在JSP頁面中不用HttpSession,為了節省性能開銷,使用下邊的頁面指令可以避免自動創建HttpSession對象:
]]>
l 不要將大的對象圖存儲在HttpSession中:如果你將數據當作一個大的對象圖存儲在HttpSession中,應用服務器每次將不得不處理整個HttpSession對象。這將迫使Java序列化和增加計算開銷。由于序列化的開銷,隨著存儲在HttpSession對象中數據對象的增大,系統的吞吐量將會下降。
l 用完后釋放HttpSession:當不在使用HttpSession時,使用HttpSession.invalidate()方法使sesion失效。
l 設置超時值:一個servlet引擎有一個默認的超時值。如果你不刪除session或者一直把session用到它超時的時候,servlet引擎將把session從內存中刪除。由于在內存和垃圾收集上的開銷,session的超時值越大,它對系統彈性和性能的影響也越大。試著將session的超時值設置的盡可能低。
技術4:使用gzip壓縮
壓縮是刪除冗余信息的作法,用盡可能小的空間描述你的信息。使用gzip(GNU zip)壓縮文檔能有效地減少下載HTML文件的時間。你的信息量越小,它們被送出的速度越快。因此,如果你壓縮了由你web應用產生的內容,它到達用戶并顯示在用戶屏幕上的速度就越快。不是任何瀏覽器都支持gzip壓縮的,但檢查一個瀏覽器是否支持它并發送gzip壓縮內容到瀏覽器是很容易的事情。下邊的代碼段說明了如何發送壓縮的內容。
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
OutputStream out = null
// Check the Accepting-Encoding header from the HTTP request.
// If the header includes gzip, choose GZIP.
// If the header includes compress, choose ZIP.
// Otherwise choose no compression.
String encoding = request.getHeader("Accept-Encoding");
if (encoding != null && encoding.indexOf("gzip") != -1)
{
response.setHeader("Content-Encoding" , "gzip");
out = new GZIPOutputStream(response.getOutputStream());
}
else if (encoding != null && encoding.indexOf("compress") != -1)
{
response.setHeader("Content-Encoding" , "compress");
out = new ZIPOutputStream(response.getOutputStream());
}
else
{
out = response.getOutputStream();
}
...
...
}
技術5:不要使用SingleThreadModel
SingleThreadModel保證servlet一次僅處理一個請求。如果一個servlet實現了這個接口,servlet引擎將為每個新的請求創建一個單獨的servlet實例,這將引起大量的系統開銷。如果你需要解決線程安全問題,請使用其他的辦法替代這個接口。SingleThreadModel在Servlet 2.4中是不再提倡使用。
技術6:使用線程池
servlet引擎為每個請求創建一個單獨的線程,將該線程指派給service()方法,然后在service()方法執行完后刪除該線程。默認情況下,servlet引擎可能為每個請求創建一個新的線程。由于創建和刪除線程的開銷是很昂貴的,于是這種默認行為降低了系統的性能。我們可以使用線程池來提高性能。根據預期的并發用戶數量,配置一個線程池,設置好線程池里的線程數量的最小和最大值以及增長的最小和最大值。起初,servlet引擎創建一個線程數與配置中的最小線程數量相等的線程池。然后servlet引擎把池中的一個線程指派給一個請求而不是每次都創建新的線程,完成操作之后,servlet引擎把線程放回到線程池中。使用線程池,性能可以顯著地提高。如果需要,根據線程的最大數和增長數,可以創建更多的線程。
技術7:選擇正確的包括機制
在JSP頁面中,有兩中方式可以包括文件:包括指令(<%@ include file="test.jsp" %>)和包括動作()。包括指令在編譯階段包括一個指定文件的內容;例如,當一個頁面編譯成一個servlet時。包括動作是指在請求階段包括文件內容;例如,當一個用戶請求一個頁面時。包括指令要比包括動作快些。因此除非被包括的文件經常變動,否則使用包括指令將會獲得更好的性能。
技術8:在useBean動作中使用合適的范圍
使用JSP頁面最強大方式之一是和JavaBean組件協同工作。JavaBean使用標簽可以嵌入到JSP頁面中。語法如下:
"package.className" type="typeName">
scope屬性說明了bean的可見范圍。scope屬性的默認值是page。你應該根據你應用的需求選擇正確的范圍,否則它將影響應用的性能。
例如,如果你需要一個專用于某些請求的對象,但是你把范圍設置成了session,那么那個對象將在請求結束之后還保留在內存中。它將一直保留在內存中除非你明確地把它從內存中刪除、使session無效或session超時。如果你沒有選擇正確的范圍屬性,由于內存和垃圾收集的開銷將會影響性能。因此為對象設置合適的范圍并在用完它們之后立即刪除。
雜項技術
l 避免字符串連接:由于String對象是不可變對象,使用“+”操作符將會導致創建大量的零時對象。你使用的“+”越多,產出的零時對象就越多,這將影響性能。當你需要連接字符串時,使用StringBuffer替代“+”操作。
l 避免使用System.out.println:System.out.println同步處理磁盤輸入/輸出,這大大地降低了系統吞吐量。盡可能地避免使用System.out.println。盡管有很多成熟的調試工具可以用,但有時System.out.println為了跟蹤、或調試的情況下依然很有用。你應該配置System.out.println僅在錯誤和調試階段打開它。使用final Boolean型的變量,當配置成false時,在編譯階段完成優化檢查和執行跟蹤輸出。
l ServletOutputStream 與 PrintWriter比較:由于字符輸出流和把數據編碼成字節,使用PrintWriter引入了小的性能開銷。因此,PrintWriter應該用在所有的字符集都正確地轉換做完之后。另一方面,當你知道你的servlet僅返回二進制數據,使用ServletOutputStream,因為servlet容器不編碼二進制數據,這樣你就能消除字符集轉換開銷。
總結
本文的目的是展示給你一些實踐的和已經證實的用于提高servlet和JSP性能的性能優化技術,這些將提高你的J2EE應用的整體性能。下一步應該觀察其他相關技術的性能調整,如EJB、JMS和JDBC等。
Posted by@rimen at 2005 年 12 月 01 日 | Comments (1) | TrackBack
2005 年 11 月 28 日
最近工作有點忙 
經過網友的提醒,今天猛然發現自己的blog有10天沒有更新了.看了最近的工作確實有點忙.由于短信項目在整個的系統詞典里面還是一個新的東西,所有很多東西需要去學習,看了程序的悲哀就在這里,永遠被學不玩的東西困擾著,慢慢的變得沒有了自己的想法,沒有了自己的方向.
不管這么樣,還是很謝謝大家多我工作的支持,很多人在關注著soosoo的進展,說實話,現在個人的精力實在有限,加上在這個現實社會中,有的時候光靠自己的興趣還不一定行的通,很多現實的問題在困擾著想我一樣的每一個程序員,一直發至內心不甘,想通過自己的雙手和智慧來改變自己的命運.著也算是對自己年輕生命的責任吧.
也許工作忙只不過是一個冠冕堂皇的理由來給自己的懶惰做辯解.可是在我的生活中,確實有了很多因素給自己不同的壓力,才有了心態上的一種波動,我會經自己的努力去改變自己,讓自己適應這個環境,給自己的人生打好每一個逗號和句號.
Posted by@rimen at 2005 年 11 月 28 日 | Comments (0) | TrackBack
2005 年 11 月 17 日
關于weblogic對jndi的spi接口實現問題 
sun提供jndi規范,通過這個接口規范把名稱和對象聯系起來。但是怎么實現在于中間廠商對這個spi接口的實現。
先看下面的代碼:
public static void main(String args[]){
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL,"t3://server1:7001");
properties.put(Context.SECURITY_PRINCIPAL,"username");
properties.put(Context.SECURITY_CREDENTIALS,"password");
Properties properties1 = new Properties();
properties1.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
properties1.put(Context.PROVIDER_URL,"t3://server2:7001");
// properties1.put(Context.SECURITY_PRINCIPAL,null);
// properties1.put(Context.SECURITY_CREDENTIALS,null);
try {
System.out.println("新建context="+properties);
Context context2= new InitialContext(properties1);
Context context=new InitialContext(properties);
context.close();
System.out.println("新建context="+properties1);
context2.lookup("cn/com/soosoo/test");
context2.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
大家可能目前還不知道這段代碼有什么問題,運行應該也沒有什么問題,很不幸的告訴你,運行的結果會出現一個security異常。大概描述是說在context2.lookup 的時候,weblogic的spi實現會拿context中設置的用戶去連接context2中的服務器。很多人會想不通這一點,說實話我自己開始都不是很相信這事實。因為在context2之前把context還關了。為什么會出現這中情況。
目前weblogic對jndi接口實現的時候,把上下文環境的用戶存入一個堆棧。當利用上下文環境進行查找對象的時候,如果發現其沒有用戶名和密碼,weblogic會在當前內存棧里面pop出來進行連接。這樣當如果沒有conetxt這個上下文環境的初始化設置的時候,就不會出現任何問題。解決這情況的有兩個途徑。1,所有的context都設置用戶和密碼,2,在進行context操作之前完成所有context2的獲取工作。也就是
System.out.println("新建context="+properties);
Context context2= new InitialContext(properties1);
System.out.println("新建context="+properties1);
context2.lookup("cn/com/soosoo/test");
context2.close();
Context context=new InitialContext(properties);
context.close();
特別是在大家做j2ee應用集成時候需要注意這個問題,因為你可能會出現多個業務系統服務的調用。
Posted by@rimen at 2005 年 11 月 17 日 | Comments (0) | TrackBack
2005 年 11 月 13 日
jvm裝入原理 
操作系統裝入jvm是通過jdk中java.exe來完成,通過下面4步來完成jvm環境.
1.創建jvm裝載環境和配置
2.裝載jvm.dll
3.初始化jvm.dll并掛界到JNIENV(JNI調用接口)實例
4.調用JNIEnv實例裝載并處理class類。
在我們運行和調試java程序的時候,經常會提到一個jvm的概念.jvm是java程序運行的環境,但是他同時一個操作系統的一個應用程序一個進程,因此他也有他自己的運行的生命周期,也有自己的代碼和數據空間.
首先來說一下jdk這個東西,不管你是初學者還是高手,是j2ee程序員還是j2se程序員,jdk總是在幫我們做一些事情.我們在了解java之前首先大師們會給我們提供說jdk這個東西.它在java整個體系中充當著什么角色呢?我很驚嘆sun大師們設計天才,能把一個如此完整的體系結構化的如此完美.jdk在這個體系中充當一個生產加工中心,產生所有的數據輸出,是所有指令和戰略的執行中心.本身它提供了java的完整方案,可以開發目前java能支持的所有應用和系統程序.這里說一個問題,大家會問,那為什么還有j2me,j2ee這些東西,這兩個東西目的很簡單,分別用來簡化各自領域內的開發和構建過程.jdk除了jvm之外,還有一些核心的API,集成API,用戶工具,開發技術,開發工具和API等組成
好了,廢話說了那么多,來點于主題相關的東西吧.jvm在整個jdk中處于最底層,負責于操作系統的交互,用來屏蔽操作系統環境,提供一個完整的java運行環境,因此也就虛擬計算機. 操作系統裝入jvm是通過jdk中java.exe來完成,通過下面4步來完成jvm環境.
1.創建jvm裝載環境和配置
2.裝載jvm.dll
3.初始化jvm.dll并掛界到JNIENV(JNI調用接口)實例
4.調用JNIEnv實例裝載并處理class類。
一.jvm裝入環境,jvm提供的方式是操作系統的動態連接文件.既然是文件那就一個裝入路徑的問題,java是怎么找這個路徑的呢?當你在調用java test的時候,操作系統會在path下在你的java.exe程序,java.exe就通過下面一個過程來確定jvm的路徑和相關的參數配置了.下面基于windows的實現的分析.
首先查找jre路徑,java是通過GetApplicationHome api來獲得當前的java.exe絕對路徑,c:\j2sdk1.4.2_09\bin\java.exe,那么它會截取到絕對路徑c:\j2sdk1.4.2_09\,判斷c:\j2sdk1.4.2_09\bin\java.dll文件是否存在,如果存在就把c:\j2sdk1.4.2_09\作為jre路徑,如果不存在則判斷c:\j2sdk1.4.2_09\jre\bin\java.dll是否存在,如果存在這c:\j2sdk1.4.2_09\jre作為jre路徑.如果不存在調用GetPublicJREHome查HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“當前JRE版本號”\JavaHome的路徑為jre路徑。
然后裝載jvm.cfg文件JRE路徑+\lib+\ARCH(CPU構架)+\jvm.cfgARCH(CPU構架)的判斷是通過java_md.c中GetArch函數判斷的,該函數中windows平臺只有兩種情況:WIN64的‘ia64’,其他情況都為‘i386’。以我的為例:C:\j2sdk1.4.2_09\jre\lib\i386\jvm.cfg.主要的內容如下:
-client KNOWN
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
在我們的jdk目錄中jre\bin\server和jre\bin\client都有jvm.dll文件存在,而java正是通過jvm.cfg配置文件來管理這些不同版本的jvm.dll的.通過文件我們可以定義目前jdk中支持那些jvm,前面部分(client)是jvm名稱,后面是參數,KNOWN表示jvm存在,ALIASED_TO表示給別的jvm取一個別名,WARN表示不存在時找一個jvm替代,ERROR表示不存在拋出異常.在運行java XXX是,java.exe會通過CheckJvmType來檢查當前的jvm類型,java可以通過兩種參數的方式來指定具體的jvm類型,一種按照jvm.cfg文件中的jvm名稱指定,第二種方法是直接指定,它們執行的方法分別是“java -J”、“java -XXaltjvm=”或“java -J-XXaltjvm=”。如果是第一種參數傳遞方式,CheckJvmType函數會取參數‘-J’后面的jvm名稱,然后從已知的jvm配置參數中查找如果找到同名的則去掉該jvm名稱前的‘-’直接返回該值;而第二種方法,會直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm類型名稱;如果在運行java時未指定上面兩種方法中的任一一種參數,CheckJvmType會取配置文件中第一個配置中的jvm名稱,去掉名稱前面的‘-’返回該值。CheckJvmType函數的這個返回值會在下面的函數中匯同jre路徑組合成jvm.dll的絕對路徑。如果沒有指定這會使用jvm.cfg中第一個定義的jvm.可以通過set _JAVA_LAUNCHER_DEBUG=1在控制臺上測試.
最后獲得jvm.dll的路徑,JRE路徑+\bin+\jvm類型字符串+\jvm.dll就是jvm的文件路徑了,但是如果在調用java程序時用-XXaltjvm=參數指定的路徑path,就直接用path+\jvm.dll文件做為jvm.dll的文件路徑.
二:裝載jvm.dll
通過第一步已經找到了jvm的路徑,java通過LoadJavaVM來裝入jvm.dll文件.裝入工作很簡單就是調用windows API函數:
LoadLibrary裝載jvm.dll動態連接庫.然后把jvm.dll中的導出函數JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs掛接到InvocationFunctions變量的CreateJavaVM和GetDefaultJavaVMInitArgs函數指針變量上。jvm.dll的裝載工作宣告完成。
三:初始化jvm,獲得本地調用接口,這樣就可以在java中調用jvm的函數了.調用InvocationFunctions->CreateJavaVM也就是jvm中JNI_CreateJavaVM方法獲得JNIEnv結構的實例.
四:運行java程序.
java程序有兩種方式一種是jar包,一種是class. 運行jar,java -jar XXX.jar運行的時候,java.exe調用GetMainClassName函數,該函數先獲得JNIEnv實例然后調用java類java.util.jar.JarFileJNIEnv中方法getManifest()并從返回的Manifest對象中取getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主類名作為運行的主類。之后main函數會調用java.c中LoadClass方法裝載該主類(使用JNIEnv實例的FindClass)。main函數直接調用java.c中LoadClass方法裝載該類。如果是執行class方法。main函數直接調用java.c中LoadClass方法裝載該類。
然后main函數調用JNIEnv實例的GetStaticMethodID方法查找裝載的class主類中
“public static void main(String[] args)”方法,并判斷該方法是否為public方法,然后調用JNIEnv實例的
CallStaticVoidMethod方法調用該java類的main方法。
Posted by@rimen at 2005 年 11 月 13 日 | Comments (1) | TrackBack
2005 年 11 月 03 日
環境命名上下文environment naming context (ENC)來定位j2ee資源 
java:comp/env 是環境命名上下文environment naming context (ENC)
是在EJB規范1.1以后引入的,引入這個是為了解決原來JNDI查找所引起的沖突問題,也是為了提高EJB或者J2EE應用的移植性。ENC是一個引用,引用是用于定位企業應用程序的外部資源的邏輯名。引用是在應用程序部署描述符文件中定義的。在部署時,引用被綁定到目標可操作環境中資源的物理位置(JNDI名)。使用ENC是把對其它資源的JNDI查找的硬編碼解脫出來,通過配置這個引用可以在不修改代碼的情況下,將引用指向不同的EJB(JNDI)。 在J2EE中的引用常用的有:
---------JDBC 數據源引用在java:comp/env/jdbc 子上下文中聲明
---------JMS 連接工廠在java:comp/env/jms 子上下文中聲明
---------JavaMail 連接工廠在java:comp/env/mail 子上下文中聲明
---------URL 連接工廠在 java:comp/env/url子上下文中聲明
假如你寫了一個EJB,獲取datasource如:dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/DBPool");
那么在配置文件中進行資源映射時,在ejb-jar.xml中,
jdbc/DBPool
javax.sql.DataSource
Container
在weblogic-ejb-jar.xml中,
jdbc/DBPool
OraDataSource
實際服務器中的JNDI名字是OraDataSource,邏輯名jdbc/DBPool只是用來和它作映射的,這樣做的好處是為了提高可移植性,移植的時候只需要把配置文件改一下就可以,而應用程序可不用改動。
Posted by@rimen at 2005 年 11 月 03 日 | Comments (1) | TrackBack
2005 年 11 月 01 日
gc和finalize關系 
gc只能清除在堆上分配的內存(純java語言的所有對象都在堆上使用new分配內存),而不能清除棧上分配的內存(當使用JNI技術時,可能會在棧上分配內存,例如java調用c程序,而該c程序使用malloc分配內存時).因此,如果某些對象被分配了棧上的內存區域,那gc就管不著了,對這樣的對象進行內存回收就要靠finalize().
舉個例子來說,當java 調用非java方法時(這種方法可能是c或是c++的),在非java代碼內部也許調用了c的malloc()函數來分配內存,而且除非調用那個了free() 否則不會釋放內存(因為free()是c的函數),這個時候要進行釋放內存的工作,gc是不起作用的,因而需要在finalize()內部的一個固有方法調用它(free()).
finalize的工作原理應該是這樣的:一旦垃圾收集器準備好釋放對象占用的存儲空間,它首先調用finalize(),而且只有在下一次垃圾收集過程中,才會真正回收對象的內存.所以如果使用finalize(),就可以在垃圾收集期間進行一些重要的清除或清掃工作.
Posted by@rimen at 2005 年 11 月 01 日 | Comments (0) | TrackBack
2005 年 10 月 25 日
java中的字符編碼處理 
做java已經兩年,一直在規范和框架中掙扎,往往很少去考慮一些細節上的原理.以為很多在規范中屏蔽了.今天由于一個用戶的需求引發了一個中文處理的問題.問題是這樣的:用戶要在一個發布一篇即有簡體也有繁體的文章.由于當前系統采用的GBK的編碼,發現有些問題,開始馬上想到換成unicode編碼,還是亂碼.暈......,下定決心把這個東東搞清楚...........
上google,查出一大堆這類問題,有點描述太復雜,有的描述太過于個例化.最后在csdn上找到了AbnerChai 深入Java中文問題及最優解決方法的文章,非常詳細,但是語言上有點羅唆.
其實說白了就是 顯示--os--java處理(java程序,web容器,java組件)--jvm(jdk)--os--現實設備.對于核心的jvm來說,把載入的文件class一定是unicode的(當然我說的jvm應該是國際版).剩下的問題就是一個輸入輸出的問題了.還有就是在jvm前后進行編碼轉換時的編碼源和編碼目標怎么確定問題.
首先了解一下目前國際化編碼:
1.ISO-8859 對于ISO-8859系列的字符集都想象成一個:2^8 = 16 * 16 = 256個格子的棋盤,這樣所有的西文字符(英文)用這樣一個16×16的坐標系就基本可以覆蓋全了。而英文實際上只用其中小于128(\x80)的部分就夠了。利用大于128部分的空間的不同定義規則形成了真對其他歐洲語言的擴展字符集:ISO-8859-2 ISO-8859-4等……
ISO-8859-1
ISO-8859-7
其他語言
英文 其他西歐字符
ōē
2.GB2312 BIG5 SJIS等多字節字符集(MultiByte Charsets):
對于亞洲語言來說:漢字這么多,用這么一個256格的小棋盤肯定放不下,所以要區別成千上萬的漢字解決辦法就是用2個字節(坐標)來定位一個“字”在棋盤上的位置,將以上規則做一個擴展:
如果第1個字符是小于128(\x80)的仍和英文字符集編碼方式保持兼容;
如果第1個字符是大于128(\x80)的,就當成是漢字的第1個字節,這個自己和后面緊跟的1個字節組成一個漢字;
其結果相當于在位于128以上的小棋格里每個小棋格又劃分出了一個16×16的小棋盤。這樣一個棋盤中的格子數(可能容納的字符數)就變成了128 + 128 * 256。按照類似的方式有了簡體中文的GB2312標準,繁體中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大約有六仟多個常用簡體漢字。
………………………………
由此可以看出,所有這些從ASCII擴展式的編碼方式中:英文部分都是兼容的,但擴展部分的編碼方式是不兼容的,雖然很多字在3種體系中寫法一致(比如“中文”這2個字)但在相應字符集中的坐標不一致,所以GB2312編寫的頁面用BIG5看就變得面目全非了。而且有時候經常在瀏覽其他非英語國家的頁面時(比如包含有德語的人名時)經常出現奇怪的漢字,其實就是擴展位的編碼沖突造成的。
3.GBK和GB18030理解成一個小UNICODE:GBK字符集是GB2312的擴展(K),GBK里大約有貳萬玖仟多個字符,除了保持和GB2312兼容外,繁體中文字,甚至連日文的假名字符也能顯示。而GB18030-2000則是一個更復雜的字符集,采用變長字節的編碼方式,能夠支持更多的字符。關于漢字的編碼方式比較
詳細的定義規范可以參考:
http://www.unihan.com.cn/cjk/ana17.htm
ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴展字符集GBK GB18030這個發展過程基本上也反映了字符集標準的發展過程,但這么隨著時間的推移,尤其是互聯網讓跨語言的信息的交互變得越來越多的時候,太多多針對本地語言的編碼標準的出現導致一個應用程序的國際化變得成本非常高。尤其是你要編寫一個同時包含法文和簡體中文的文檔,這時候一般都會想到要是用一個通用的字符集能夠顯示所有語言的所有文字就好了,而且這樣做應用也能夠比較方便的國際化,為了達到這個目標,即使應用犧牲一些空間和程序效率也是非常值得的。UNICODE就是這樣一個通用的解決方案。
4.UNICODE雙字節字符集:
所以你可以把UNICODE想象成這樣:讓所有的字符(包括英文)都用2個字節(2個8位)表示,這樣就有了一個2^(8*2) = 256 * 256 = 65536個格子的大棋盤。在這個棋盤中,這樣中(簡繁)日韓(還包括越南)文字作為CJK字符集都放在一定的區位內,為了減少重復,各種語言中寫法一樣的字共享一個“棋格”。
接下來就是怎么看java處理編碼問題了.
我們知道jvm處理的字節碼一定是unicode,對于沒有產生jvm之外的輸入和輸出的話,任何處理都不會帶來亂碼問題.例如javaBean ,ejb等不會產生編碼問題.
常用的輸入:
用戶和java程序交互(java組件解析或轉換器編碼)
jsp/servlet 等客戶端的輸入(通過web服務器獲得傳輸的信息默認編碼)
數據庫 通過jdbc獲得默認的信息編碼
jsp/java等源文件到字節碼(unicode)的輸入(有jsp編譯器和javac來轉換源文件的編碼)
第一種情況:就是java程序運行在console上等待用的信息輸入,這是java類會默認用file.encoding編碼格式對用戶輸入的串進行編碼并轉化為unicode保存入內存(用戶可以設置輸入流的編碼格式,這可以理解java組件做轉換器)。
編碼來源:默認os編碼,或者用戶在類中自己設定.編碼目標:jvm處理編碼unicode.這里編碼源必須要能包含輸入的字符集.
第二種情況:就是jvm裝入servlet進行接受表單輸入的值和URL中傳入的值,此時如果程序中沒有設定接受參數時采用的編碼格式,則WEB容器會默認采用ISO-8859-1編碼格式來接受傳入的值并在JVM中轉化為UNICODE格式的保存在WEB容器的內存中。
編碼來源:web容器,或者用filter 進行request.setCharacterEncoding(),編碼目標:jvm處理編碼unicode.這里的編碼源必須能包含輸入的字符集.
第三種情況:數據庫的JDBC驅動程序,默認的在Java程序和數據庫之間傳遞數據都是以ISO-8859-1為默認編碼格式的,所以,我們的程序在向數據庫內讀取數據時會把數據轉化成unicode.
編碼來源:默認jdbc編碼 iso,jdbc設定的編碼格式.編碼目標:jvm處理的unicode.這里的源必須能包含數據存儲的字符集.
第四種情況:java通過平臺編寫,獲得記事本編寫,有個文件編碼,一般默認系統編碼,有的工具可以改.通過javac進行編譯生成class文件unicode編碼.jvm可以處理unicode的文件.jsp編譯器編譯jsp時也是一樣,可以用jsp的pageEncoding 設置jsp文件的源編碼.
編碼來源:有javac獲得系統默認或者-encoding參數來確定編碼源.編碼目標:jvm處理的class文件.jsp只是多了一個編譯成java的過程.原理一樣.這里的編碼源設置必須保證能覆蓋文件種的字符集.
綜合上面的輸入情況,實際細心的讀者會發現,這里有兩種情況,一種是文件,一種是字節流,文件的話編碼可以設置在文件頭,例如jsp,xml等,字節流必須在java處理之前指定能覆蓋其字符集的編碼.(注意一定是字節流,字符流是已經進行了編碼的).只要保證java處理的編碼源正確一定可以能讓java正確處理.
輸出的情況相對簡單的多.jvm出來的信息是unicode,你要怎么處理只要能讓os的編碼集認識,現實一定不會有問題.
例如.console上java會以file.encoding格式傳會給操作系統
jsp客戶設置charSet:默認按ISO-8859-1編碼
jdbc可以設置其輸入編碼:默認按ISO-8859-1編碼
Posted by@rimen at 2005 年 10 月 25 日 | Comments (4) | TrackBack
2005 年 10 月 23 日
關于Lucence中的索引優化 
車東在他的文章中提到,通過提供IndexWriter的merge因子屬性可以提供索引的速度.從原理入手,進行lucence代碼的分析,然后通過測試數據進行實際應用中的優化.
先看下面的代碼:
private final void maybeMergeSegments() throws IOException {
long targetMergeDocs = minMergeDocs;
while (targetMergeDocs <= maxMergeDocs) {
// find segments smaller than current target size
int minSegment = segmentInfos.size();
int mergeDocs = 0;
while (--minSegment >= 0) {
SegmentInfo si = segmentInfos.info(minSegment);
if (si.docCount >= targetMergeDocs)
break;
mergeDocs += si.docCount;
}
if (mergeDocs >= targetMergeDocs) // found a merge to do
mergeSegments(minSegment + 1);
else
break;
targetMergeDocs *= mergeFactor; // increase target size
}
}
一看就知道,當segmentInfos中的merge的文檔數小于targetMergeDocs是,系統不會進行merge,而targetMergeDocs由minMergeDocs和mergeFactor決定.SegmentInfo 如果是單個索引的話,si.docCount為1,如果此時minMergeDocs為1,也就是targetMergeDocs為1,系統會進行merge,這樣在效率上回大大降低.minMergeDocs>1的話,決定merge的大小還是minMergeDocs,targetMergeDocs *= mergeFactor無效,可以說mergeFactor只對批量索引有效,si.docCount>1的情況.
如果是批量索引,當si.docCount>minMergeDocs時,minMergeDocs和targetMergeDocs 決定了merge的數量.
在我的優化過程中,由于采用的是單一索引,所以我調整mergeFactor帶來的性能上的差異不大,可以看成是系統帶來的誤差.而我改變minMergeDocs獲得的性能可以從下面的數據看到:
以10000條記錄為測試案例
minMergeDocs 對于運行的時間ms
default (10) times=32707
20 times=21191
30 times=18476
40 times=16334
50 times=16744
100 times=13138
200 times=10125
400 times=10234
800 times=10015
1600 times=11256
3200 out of memory
從上面的數據來看,在100~200之間性能和存儲比較優.當minMergeDocs 大到一定的程度,由于在內存調度上的問題,系統可能有更大量的訪問IO.建議在定制lucence時,對與單一的索引可以把minMergeDocs 設成100左右以提供性能.而車東給出的說法并不完全.
Posted by@rimen at 2005 年 10 月 23 日 | Comments (0) | TrackBack
2005 年 10 月 13 日
三種ejb的生命周期 
在做企業業務繼承的時候,通常利用session進行提供同步的遠程過程服務.通過EAI等Q組件,實現企業級的數據總線,把應用之間的網狀結構關系進行星型化..利用mdb從jms(Q)服務器上獲取相應的消息服務.總之在企業信息化的過程中,基于j2ee的應用整合,ejb提供了一種良好的方法,當然你也可以選擇websevice,但是在網絡結構和實時處理效率上還是有很大的差別.下面詳細描述以下三種ejb的生命周期,在開發中和利用他的容器回調功能,來注入和回收外部的各種資源。
1 session Bean (有狀態)
有狀態會話Bean實例有三種狀態,不存在,就緒,和鈍化。

客戶端調用home接口的create 方法,ejb容器實例化一個Bean并調用setSesssionContext
ejbCreate方法,使得Bean處于就緒狀態。然后客戶就可以使用其商業方法了。ejb容器對Bean的生命周期
進行管理,一般會對最少使用的EJB進行鈍化,當客戶在使用這個EJB時,容器會進行激活,這個過程對客
戶來說是透明的。當用戶調用remove方法,容器調用ejbRemove方法,ejb生命周期結束.
無狀態會話Bean實例就兩種狀態,不存在和就緒.

客戶端調用home接口的create方法,如果不存在可用的實例.jb容器實例化一個Bean并調用
setSesssionContext ejbCreate方法.當客戶調用remove方法之后,ejb容器則調用ejbRemove的方法,
ejb生命周期結束.
2 實體bean
實體bean有三種狀態,不存在,在pool中,就緒

ejb容器創建實例時調用setEntityContext,把容器的上下文傳到bean組件中.實例化之后bean
會移到池中,此時ejb沒有和任何的實體對象進行關聯,所有的bean實例是一樣,容器會指派它和具體的
實體標示關聯,進入就緒狀態。有兩種方法使得一個實體bean從池化進入到就緒狀態,一是客戶端使用
create方法,使得ejb容器調用ejbCreate and ejbPostCreate 方法,二是容器調用ejbActivate 方法,
這對客戶來說是透明的,只有當實體bean處于就緒狀態時,才能調用其商業方法。同樣如果實體bean要從
就緒進行池化也有兩種方法,一是客戶端調用remove方法,容器調用ejbRemove;二是容器ejbPassivate方法。
bmp和cmp,在bean實例從池化到就緒時,對于bmp的實體bean,容器不會自動設置primary key.因此ejbCreate and ejbActivate 需要獲得這個primary key ,如果這個key非法,ejbLoad and ejbStore methods 不能同步實體變量到數據庫。ejbCreate 通過參數傳入,ejbActivate 通過id = (String)context.getPrimaryKey();在pool狀態,這些需要持久化的實體變量則不需要,在ejbPasssivate 中把它賦值null。unsetEntityContext,
bean生命周期結束的時候,調用
3 mdb 消息bean
消息bean就兩種狀態:不存在和就緒

就像sessionless session bean,容器在實例化bean的時候,調用setMessageDrivenContext,ebjCreate. 調用ejbRemove方法結束生命周期。當消息到達的時候Onmessage方法。因此可以mdb是一種jms客戶端企業級組件。
Posted by@rimen at 2005 年 10 月 13 日 | Comments (1) | TrackBack
2005 年 10 月 05 日
深入理解abstract class和interface 
abstract class和interface是Java語言中對于抽象類定義進行支持的兩種機制,正是由于這兩種機制的存在,才賦予了Java強大的面向對象能力。abstract class和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對于abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對于它們的選擇甚至反映出對于問題領域本質的理解、對于設計意圖的理解是否正確、合理。本文將對它們之間的區別進行一番剖析,試圖給開發者提供一個在二者之間進行選擇的依據。
理解抽象類
abstract class和interface在Java語言中都是用來進行抽象類(本文中的抽象類并非從abstract class翻譯而來,它表示的是一個抽象體,而abstract class為Java語言中用于定義抽象類的一種方法,請讀者注意區分)定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢?
在面向對象的概念中,我們知道所有的對象都是通過類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發,就會發現問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠實例化的。
在面向對象領域,抽象類主要用來進行類型隱藏。我們可以構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。模塊可以操作一個抽象體。由于模塊依賴于一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠實現面向對象設計的一個最核心的原則OCP(Open-Closed Principle),抽象類是其中的關鍵所在。
從語法定義層面看abstract class和interface
在語法層面,Java語言對于abstract class和interface給出了不同的定義方式,下面以定義一個名為Demo的抽象類為例來說明這種不同。
使用abstract class的方式定義Demo抽象類的方式如下:
abstract class Demo {
abstract void method1();
abstract void method2();
…
}
使用interface的方式定義Demo抽象類的方式如下:
interface Demo {
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的數據成員,也可以有非abstarct的成員方法,而在interface方式的實現中,Demo只能夠有靜態的不能被修改的數據成員(也就是必須是static final的,不過在interface中一般不定義數據成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract class。
從編程的角度來看,abstract class和interface都可以用來實現"design by contract"的思想。但是在具體的使用上面還是有一些區別的。
首先,abstract class在Java語言中表示的是一種繼承關系,一個類只能使用一次繼承關系。但是,一個類卻可以實現多個interface。也許,這是Java語言的設計者在考慮Java對于多重繼承的支持方面的一種折中考慮吧。
其次,在abstract class的定義中,我們可以賦予方法的默認行為。但是在interface的定義中,方法卻不能擁有默認行為,為了繞過這個限制,必須使用委托,但是這會 增加一些復雜性,有時會造成很大的麻煩。
在抽象類中不能定義默認行為還存在另一個比較嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面(一般通過abstract class或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數)時,就會非常的麻煩,可能要花費很多的時間(對于派生類很多的情況,尤為如此)。但是如果界面是通過abstract class來實現的,那么可能就只需要修改定義在abstract class中的默認行為就可以了。
同樣,如果不能在抽象類中定義默認行為,就會導致同樣的方法實現出現在該抽象類的每一個派生類中,違反了"one rule,one place"原則,造成代碼重復,同樣不利于以后的維護。因此,在abstract class和interface間進行選擇時要非常的小心。
從設計理念層面看abstract class和interface
上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。本小節將從另一個層面:abstract class和interface所反映出的設計理念,來分析一下二者的區別。作者認為,從這個層面進行分析才能理解二者概念的本質所在。
前面已經提到過,abstarct class在Java語言中體現了一種繼承關系,要想使得繼承關系合理,父類和派生類之間必須存在"is a"關系,即父類和派生類在概念本質上應該是相同的(參考文獻〔3〕中有關于"is a"關系的大篇幅深入的論述,有興趣的讀者可以參考)。對于interface 來說則不然,并不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。為了使論述便于理解,下面將通過一個簡單的實例進行說明。
考慮這樣一個例子,假設在我們的問題領域中有一個關于Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:
使用abstract class方式定義Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定義Door:
interface Door {
void open();
void close();
}
其他具體的Door類型可以extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。
如果現在要求Door還要具有報警的功能。我們該如何設計針對該例子的類結構呢(在本例中,主要是為了展示abstract class和interface反映在設計理念上的區別,其他方面無關的問題都做了簡化或者忽略)?下面將羅列出可能的解決方案,并從設計理念層面對這些不同的方案進行分析。
解決方案一:
簡單的在Door的定義中增加一個alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有報警功能的AlarmDoor的定義方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
這種方法違反了面向對象設計中的一個核心原則ISP(Interface Segregation Priciple),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴于Door這個概念的模塊會因為"報警器"這個概念的改變(比如:修改alarm方法的參數)而改變,反之依然。
解決方案二:
既然open、close和alarm屬于兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstract class方式定義;兩個概念都使用interface方式定義;一個概念使用abstract class方式定義,另一個概念使用interface方式定義。
顯然,由于Java語言不支持多重繼承,所以兩個概念都使用abstract class方式定義是不可行的。后面兩種方式都是可行的,但是對于它們的選擇卻反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理。我們一一來分析、說明。
如果兩個概念都使用interface方式來定義,那么就反映出兩個問題:1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?2、如果我們對于問題領域的理解沒有問題,比如:我們通過對于問題領域的分析發現AlarmDoor在概念本質上和Door是一致的,那么我們在實現時就沒有能夠正確的揭示我們的設計意圖,因為在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。
如果我們對于問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有報警的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過,abstract class在Java語言中表示一種繼承關系,而繼承關系在本質上是"is a"關系。所以對于Door這個概念,我們應該使用abstarct class方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
這種實現方式基本上能夠明確的反映出我們對于問題領域的理解,正確的揭示我們的設計意圖。其實abstract class表示的是"is a"關系,interface表示的是"like a"關系,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那么上述的定義方式就要反過來了。
結論
abstract class和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的相似性。但是對于它們的選擇卻又往往反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理,因為它們表現了概念間的不同的關系(雖然都能夠實現需求的功能)。這其實也是語言的一種的慣用法,希望讀者朋友能夠細細體會。
Posted by@rimen at 2005 年 10 月 05 日 | Comments (0) | TrackBack
2005 年 10 月 04 日
avalon ioc容器的一個實現 
大家只要用過spring 的人或多或少對對ioc這個概念有的理解.他是一個設計模式,有始終不同的實現方式.spring同時javaBean set方法注入Bean組件.
Apache的Avalon是一個包括核心框架、工具、組件和容器的面向組件編程(COP)的完整開發平臺。通過使用關鍵設計模式,如反向控制模式(IoC)和分離考慮模(SoC),Avalon實現了傳統OOP框架的一些優點: 1.沒有執行鎖 2.組件之間低耦合 3.管理組件生命周期 4.配置管理和易用的API 5.組件元數據框架和工具 6.服務相關的管理獨立的、J2EE或Web環境的嵌入式容器 在COP方面,可重用的組件能夠被組合到容器中,以提供應用程序模塊。模塊可以依次使用來創建你所需要的,從客戶桌面應用程序,到FTP服務器,到Web服務,等等。Avalon提供各種基本組件和缺省的應用程序模塊,幫助你快速的建立你自己的應用程序解決方案。
Posted by@rimen at 2005 年 10 月 04 日 | Comments (0) | TrackBack
2005 年 09 月 29 日
Java XML API 漫談 
轉載自forum.hibernate.org.cn, author:robbin
在IBM的developerWorks上有幾篇非常優秀的關于Java XML API的評測文章,它們是:
http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml
http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml
http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part2/index.shtml
http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part1/index.shtml
對這幾篇文章我想說的就是“吐血推薦”
Java的XML API這幾篇文章該講的都講到了,我只想補充幾點:
一、Crimson和Xerces恩仇錄
Crimson來自于Sun捐贈給Apache的ProjectX項目,Xerces來自IBM捐贈給Apache的XML4J項目,結果Xerces勝出,成了Apache XML小組全力開發的XML API,而Crimon已經早就不做了,如今Xerces名滿天下,到處都是在用Xerces DOM和SAX解析器,只有Sun不服氣,非要在JDK1.4里面使用過時的Crimson,讓人感覺像是在賭氣一樣,真是讓人可憐又可氣!不過IBM發行JDK用的XML 解析器自然是Xerces。
由于JDK的Class Loader的優先級關系,當你采用JAXP編寫XML程序的時候,即使把Xerces包引入CLASSPATH,JDK還是會頑固的使用Crimson,這一點通過打開JVM的verbose參數可以觀察到。不過JDK也允許你采用其它的解析器,因此我們可以通過在JRE\lib\目錄下建一個jaxp.properties的文件,來替換解析器,jaxp.properties內容如下:
引用:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
javax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
這樣就可以使用Xerces,當然你必須還是要把Xerces包放到CLASSPATH下。
二、JAXP的姍姍來遲
Sun在XML領域總是后知后覺,等到Sun重視XML的時候,XML的API早就滿天 飛了,尤其是IBM具有非常大的領先優勢。不過Sun是規范的制訂者,于是參考W3C的標準制訂了JAXP規范。JAXP不像Xerces和Crimon那樣,它只是一個spec,本身是不做任何事情的,它的作用就是提出一個統一的接口,讓其它的XML API都來遵循JAXP編程,那么用JAXP寫出來的程序,底層的API可以任意切換。
具體來說JAXP包括了幾個工廠類,這就是JDK1.4里面的javax.xml.parsers 包,用來尋找符合DOM標準的XML API實現類的位置;此外JAXP還包括一整套interface,這就是JDK1.4里面的org.w3c.dom那幾個包。工廠類負責加載DOM的實現類。那么加載的規則是什么呢?
我是通過閱讀JAXP的源代碼知道的,工廠類首先會根據java命令行傳入的參數進行尋找,然后在根據JRE\lib\jaxp.properties中定義的實現類尋找,最后什么都找不到的話,就用Crimson。注意Crimons是由Bootstrap Class Loader來load的,如果你不通過上面兩個方法來改變工廠的尋找順序,那么鐵定用Crimson了
三、 DOM解析器和DOM API
當你嚴格采用JAXP編程的時候,是遵循W3C的DOm標準的,那么在JAXP底層你實際上可以任意切換不同的DOM實現,例如Xerces,或者Crimon,再或者其它,切換方法就是配置jaxp.properties。因此JAXP就是一些標準接口而已。
而Xerces和Crimon也不單單是一個DOM實現那么簡單,他們本身實際上也包含SAX解析器和DOM解析器。所以一個JAXP程序下面有如下層次:
引用:
JAXP應用程序 -> JAXP接口 -> Xerces DOM實現 -> Xerces DOM/SAX 解析器
只要你用JAXP編程,那么你就可以切換到Crimson上來
引用:
JAXP應用程序 -> JAXP接口 -> Crimson DOM實現 -> Crimson DOM/SAX 解析器
另外你也可以這樣來做:
引用:
JAXP應用程序 -> JAXP接口 -> Crimson DOM實現 -> Xerces DOM/SAX 解析器
不過如果你的程序不安裝JAXP來寫,那么就沒有辦法切換不同的DOM實現了。
四、不是標準的dom4j和jdom
W3C的DOM標準API難用的讓人想撞墻,于是有一幫人開發Java專用的XML API目的是為了便于使用,這就是jdom的由來,開發到一半的時候,另一部分人又分了出來,他們有自己的想法,于是他們就去開發dom4j,形成了今天這樣兩個API,至于他們之間的性能,功能之比較看看上面我推薦的文章就知道了,jdom全面慘敗。
jdom 相當于上面的 JAXP接口 + Xerces DOM實現部分,它本身沒有解析器,它可以使用Xerces或者Crimson的解析器,就是這樣:
引用:
jdom應用程序 -> jdom API -> Xerces/Crimson解析器
dom4j 和jdom類似,不過他自己綁定了一個叫做Alfred2的解析器,功能不是很全,但是速度很快,當沒有其它的解析器的時候,dom4j將使用Alfred2解析器,如下:
引用:
dom4j應用程序 -> dom4j API -> Xerces/Crimson解析器
或者
引用:
dom4j應用程序 -> dom4j API -> Alfred2解析器
你在SF上下載的dom4j.jar是不含 Alfred2解析器的,而dom4j-full.jar包含了 Alfred2解析器,在這種情況下,實際上你什么也不需要,光是一個dom4j-full.jar就全部都包括了。
因此可以看出采用dom4j/jdom編寫的應用程序,已經不具備可移植性了。
五、小插曲
Sun是JAXP標準的制訂者,甚至很執著的在JDK1.4里面綁定Crimson DOM實現和解析器,然后可笑的是,Sun自己的JAXM RI竟然不是用JAXP寫出來的,而是dom4j,制訂標準讓大家遵守,自己卻監守自盜,這未免太說不過去了吧!
BTW: Hibernate也用的是dom4j來讀取XML配置文件,如今已經越來越多的程序紛紛采用dom4j,如果你不是那么在乎可移植性,我強烈建議你采用dom4j。
--------------------------------------------------------------------------------
Reference Java XML API 漫談
http://www.dannyzhu.com:8000/Java/JavaXMLAPIAI
Posted by@rimen at 2005 年 09 月 29 日 | Comments (0) | TrackBack
2005 年 09 月 27 日
通過私有構造函數強化不可實例化的能力 
Item 3: Enforce noninstantiability with a private constructor
如果一個類缺少顯式的構造函數,編譯器會自動提供一個公有的、無參數的默認構造函數(default constructor)。
我們只要讓這個類包含單個顯式的私有構造函數,則它就不可被實例化了,而企圖通過將一個類做成抽象類來強制該類不可被實例化是行不通的。
A class can be made noninstantiable by including a single explicit private constructor:Attempting to enforce noninstantiability by making a class abstract does not work.
例如://noninstantiable utility class
public class UtilityClass{
//suppress default constructor for noninstantiability
private UtilityClass(){
//this constructor will never be invoked
}
...
}
說明:工具類(UtilityCLass)指只包含靜態方法和靜態域的類,它不希望被實例化因為對它進行實例化沒有任何意義。
這種做法的一個副作用,是它使得這個類不能被子類化,因為子類將找不到一個可訪問的超類的構造函數。
As a side effect, this idiom also prevents the class from being subclassed. All constructors must invoke an accessible superclass constructor, explicitly or implicitly, and a subclass would have no accessible constructor to invoke.
Posted by@rimen at 2005 年 09 月 27 日 | Comments (0) | TrackBack
2005 年 09 月 11 日
maven初驗 
一直以來,沉浸的ant的成就中,我也沒有對項目資源的管理提出更高的要求,也許本身也是項目接觸的原因。罱捎詮駒謐蚴褂胮ortal技術,而且是用開源的jetspeed.我當時聽了一大跳,說那么大的企業級應用怎么能這么草率的選擇開源的東西(雖然我對開源這東西很喜歡)。沒有辦法了,領導命令下來,做兵的只能埋頭苦干了。一開始碰到的是項目的編譯問題。說jetspeed是基于maven描述的
。
?? 暈~!maven是什么東西,看來真的是孤陋寡聞了。以前一直是eclipse的平臺下開發,加上ant工具,對項目的創建,開發,發布,文檔生成都很方便。第一次聽到這東西很納悶,心里罵到,這群人是不是吃了沒事干啊。可是罵歸罵,事情還得做。老方法,第一步google,第二步找到官方站點。我才奇怪,apache每天都上怎么就沒有注意的這東東呢。咳,看來rimen我真的很懶了,懶得連舉手之勞的事情都不做了~。言歸正傳,從實際的項目出發。我的目的很簡單,就是能夠建立jetspeed項目,能夠讓他在eclipse進行開發。在我心里就有了如下的問題:
?1.maven是什么東西
?2.maven和ant由什么區別,maven和現有的IDE工具有什么關系
?3.maven的主要過程和原理
?4.怎么利用maven進行項目的開發和管理
?經過一天的努力,查詢了一些相關的資料,發現有價值的中文資料不多,最全的還是apache上的doc文檔。不過都是老外的文字,要知道我最痛恨的就是那些僅有線條沒有寓意的文字了。不過為了工作也為了自己,咳,沒有辦法了,重洋眉外一下了,大家不要罵我^_^。但是這里還是推薦一篇對初學者比較有價值的文章,matrix上的:http://www.matrix.org.cn/resource/article/43/43661_Maven.html 也是從英文翻譯過來的,咳,國人啊,加油啊~!
?開始解開那幾個問題了.
?第一個問題很簡單,它就是一個工具,僅僅是一個工具,至于什么工具下面問題就可以體現了。它的原始想法就是為了簡化jakarta螺旋式項目的構建,解決項目之間jar依賴的共享。往往偉大的發明都是來自于解決某些人的懶惰,看當初java就是,服了那些老外了,什么都想的出,我也很懶,我也發現在eclipse開發多個項目的時候管理jar之間的版本很麻煩,可是我就想不出這個東西,任命了。
?我相信大家只要開發過java程序的人,或多或少都會接觸到ant的東東,怎么說呢,ant其實只能說一個腳本解析器,它對任務的處理還得依賴于外部的工具。這就不得不想到makefile這東西了,在以前開發c程序的時候,為了管理c項目的各種資源包括原文件和類庫,對編譯工具提供了一個make工具。我個人覺得ant就是make的java版本,錯了莫怪。至于maven,那可不是一言兩語可以概括的。不過我們可以重程序和源碼中抽身出來,想象自己是個pm或者一個architecture來觀察一個項目或一個軟件產品。它提供很多的基礎構件,特別是對j2ee系統,每個組件都可以看成一個宏觀上的資源。我們從軟件工廠的角度出發,它里面保護了機器設備(external jars,tools,plugins ),生產原料(jar,src,config file,databse),產品(ear,war,jar,doc,config file 等)。下面我們要管理這些東西,叫一個人(老總)取管理,我看他在高的工資也不會干,因為他知道錢有命賺沒命花,同時也為了考慮可發展道路,他不會這么干的。所以在maven中引入了plugin的概念。這個plugin管理的東西是上面的說的構件,這樣我們必須就有一東西來記錄這些東西,不然它怎么知道這是不是它的管轄范圍,這就是通過project.xml文件進行定義,它是基于pom元數據結合xml技術進行描述。當然有了被管理的對象,工廠必須有其業務規則,不然它也運作不起來,這就是通過plugin.jelly進行定義,利用jelly腳步語言進行構建每一個管理的目標。這樣老總一句話(maven命令),加上目標名,他就會找到相應的plugin進行根據它定義的規則進行處理。這僅僅是從宏觀上的描述,真的的原理還必須參考相關的文檔進行。這里面提出幾個概念大家可以注意一下,maven plugin,jelly腳本,pom元數據,軟件工廠等。還有一點就是和目前流行的IDE工具例如eclipse,instillj,jbuider,netbeans IDE 等工具的關系。以eclipse為例,嚴格來說maven和eclipse應該是風馬牛不相及的東西,如果硬要把這個兩個東西來過來說的話,那也只是人為強加的因素。但是可以肯定的作為常用的軟件,如果說maven創建的項目,如果不能在eclipse中開發,這可能就失去了這些工具的原來意義,這也許就是他們之間的唯一聯系。首先maven和eclipse對象項目資源描述元數據都是不同的,當然現在有mavenide(maven workshop)等工具來保證這些元數據之間的轉換和同步。其次eclispe和maven之間的功能和職責不同,maven基本上不用來進行源碼的開發,這樣用eclipse和maven結合來開發確實可以減少很多工作量。
?3.maven的主要原理我想沒有比它doc上將的清楚。我想說的就一點,一開始我總用ant的觀點去理解它,總覺得少了很多東西。其實maven最大的成功在于簡化了面向過程的任務管理。你知道定義資源,maven用基于插件的目標方式來完成構建。當然你也可以想ant一樣定義自己特殊的目標,你也可以把自己項目的目標制作成通用的插件。
?4.maven的項目開發,通過默認的模板新建一個maven項目.它會生成項目的各種資源和目錄,例如src,conf等.然后你可以通過maven的IDE插件生成相應的IDE項目,例如maven eclipse.
??????? 其實我也只是用它來編譯jetspeed.目前越來越多的項目基于maven的方式發布,我們可以通過maven的插件把它轉換成自己適應的IDE項目進行開發.基于它對項目管理強大功能,對資源和項目版本管理的統一性,我相信會有越來越多人會選擇它來做項目管理工具,特別是團隊開發.
Posted by@rimen at 2005 年 09 月 11 日 | Comments (1) | TrackBack