#
ESB
1.下載
http://www.mulesoft.org/documentation/display/MULE2INTRO/Home
下載Mule ESB 2.2.1 Full Dist.
2.zip 文件
3.安裝所需要的環境
http://www.mulesoft.org/documentation/display/MULE2INTRO/Installing+Mule
java1.5+
Mule Build Tool有三種方式
Ant/Maven/Mule IDE
下面以Maven 為例進行操作
4.設置環境變量
比如
Linux/UNIX
export JAVA_HOME=/opt/java/jdk
export MAVEN_HOME=/opt/apache/maven-2.0.9
export MAVEN_OPTS='-Xmx512m -XX:MaxPermSize=256m'
export MULE_HOME=/opt/mule
export PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin:$MULE_HOME/bin
Windows
set JAVA_HOME=C:\Program Files\Java\jdk
set MAVEN_HOME=C:\Apache\maven-2.0.9
set MAVEN_OPTS=-Xmx512m -XX:MaxPermSize=256m
set MULE_HOME=C:\Mule
set PATH=%PATH%;%JAVA_HOME%\bin;%MAVEN_HOME%\bin;%MULE_HOME%\bin
Note: If you will run Mule as a Windows service, you must create system environment variables instead of user environment variables.
創建一個Maven repository 目錄,比如c:\.m2\repository 在windows系統上,如果windows 不讓創建.m2文件夾,請用mkdir目錄創建
打開Maven conf 目錄,比如c:\apache-maven-2.0.9\conf;打開setting.xml 文件,查找localRepository
將注釋去除或者添加一段
<localRepository>c:/.m2/repository</localRepository>
5.run Hello Word
進入windows cmd 命令行,分別查看ant/maven是否正常
在%MULE_HOME%\lib\user 中查看是否存在mule-example-hello.jar
如果不存在 那么我們需要ant 或者 mvn 來build jar
我對mvn不是很熟悉,就用ant,在MULE_HOME%\examples下可以看到hello的例子
下面有build.xml 和pom.xml
查看build.xml 會發現,執行ant setup就能build hello project
在下來就要運行mule,hello文件夾下面的readme有描述
------------------
Linux / Unix
------------
mule -config file:conf/hello-config.xml
mule -config file:conf/hello-http-config.xml
or
export MULE_LIB=./conf
mule -config hello-config.xml
mule -config hello-http-config.xml
Windows
-------
mule.bat -config file:conf/hello-config.xml
mule.bat -config file:conf/hello-http-config.xml
or
SET MULE_LIB=.\conf
mule.bat -config hello-config.xml
mule.bat -config hello-http-config.xml
如果運行 http config那么通過
http://localhost:8888/?name=Ross 可訪問
--------------------------------------------
更方便的是例子下面提供了hello.bat 直接運行該bat 并輸入1或者2為參數就能啟動hello mule
(需要提醒的是,第一次啟動mule會有一大堆許可文件要你看,你輸入任何鍵回車后多次過后你就能發現mule啟動成功了)
在conf下有hello-config.xml/hello-http-config.xml
hello-config.xml他們在console端,通過system.in輸入message,然后在system.out輸出
hello-http-config.xml 表示在瀏覽器中輸入http://localhost:8888/?name=Ross 那么web頁面會現在動態的name
6最簡單構建Mule project
你可以通過安裝Eclipse Mule IDE
http://www.mulesoft.org/documentation/display/MULE2INTRO/Quick+Start
或者Setting Up Eclipse for Use with Maven
http://www.mulesoft.org/documentation/display/MULE2INTRO/Setting+Up+Eclipse+for+Use+with+Maven
并發 Collections 提供了線程安全、經過良好調優的數據結構,簡化了并發編程。然而,在一些情形下,開發人員需要更進一步,思考如何調節和/或限制線程執行。由于 java.util.concurrent
的總體目標是簡化多線程編程,您可能希望該包包含同步實用程序,而它確實包含。
本文是 第 1 部分 的延續,將介紹幾個比核心語言原語(監視器)更高級的同步結構,但它們還未包含在 Collection 類中。一旦您了解了這些鎖和門的用途,使用它們將非常直觀。
1. Semaphore
在一些企業系統中,開發人員經常需要限制未處理的特定資源請求(線程/操作)數量,事實上,限制有時候能夠提高系統的吞吐量,因為它們減少了對特定資源的爭用。盡管完全可以手動編寫限制代碼,但使用 Semaphore 類可以更輕松地完成此任務,它將幫您執行限制,如清單 1 所示:
清單 1. 使用 Semaphore 執行限制
import java.util.*;import java.util.concurrent.*;
public class SemApp
{
public static void main(String[] args)
{
Runnable limitedCall = new Runnable() {
final Random rand = new Random();
final Semaphore available = new Semaphore(3);
int count = 0;
public void run()
{
int time = rand.nextInt(15);
int num = count++;
try
{
available.acquire();
System.out.println("Executing " +
"long-running action for " +
time + " seconds... #" + num);
Thread.sleep(time * 1000);
System.out.println("Done with #" +
num + "!");
available.release();
}
catch (InterruptedException intEx)
{
intEx.printStackTrace();
}
}
};
for (int i=0; i<10; i++)
new Thread(limitedCall).start();
}
}
|
即使本例中的 10 個線程都在運行(您可以對運行 SemApp
的 Java 進程執行 jstack
來驗證),但只有 3 個線程是活躍的。在一個信號計數器釋放之前,其他 7 個線程都處于空閑狀態。(實際上,Semaphore
類支持一次獲取和釋放多個 permit,但這不適用于本場景。)
回頁首
2. CountDownLatch
如果 Semaphore
是允許一次進入一個(這可能會勾起一些流行夜總會的保安的記憶)線程的并發性類,那么 CountDownLatch
就像是賽馬場的起跑門柵。此類持有所有空閑線程,直到滿足特定條件,這時它將會一次釋放所有這些線程。
清單 2. CountDownLatch:讓我們去賽馬吧!
import java.util.*;
import java.util.concurrent.*;
class Race
{
private Random rand = new Random();
private int distance = rand.nextInt(250);
private CountDownLatch start;
private CountDownLatch finish;
private List<String> horses = new ArrayList<String>();
public Race(String... names)
{
this.horses.addAll(Arrays.asList(names));
}
public void run()
throws InterruptedException
{
System.out.println("And the horses are stepping up to the gate...");
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch finish = new CountDownLatch(horses.size());
final List<String> places =
Collections.synchronizedList(new ArrayList<String>());
for (final String h : horses)
{
new Thread(new Runnable() {
public void run() {
try
{
System.out.println(h +
" stepping up to the gate...");
start.await();
int traveled = 0;
while (traveled < distance)
{
// In a 0-2 second period of time....
Thread.sleep(rand.nextInt(3) * 1000);
// ... a horse travels 0-14 lengths
traveled += rand.nextInt(15);
System.out.println(h +
" advanced to " + traveled + "!");
}
finish.countDown();
System.out.println(h +
" crossed the finish!");
places.add(h);
}
catch (InterruptedException intEx)
{
System.out.println("ABORTING RACE!!!");
intEx.printStackTrace();
}
}
}).start();
}
System.out.println("And... they're off!");
start.countDown();
finish.await();
System.out.println("And we have our winners!");
System.out.println(places.get(0) + " took the gold...");
System.out.println(places.get(1) + " got the silver...");
System.out.println("and " + places.get(2) + " took home the bronze.");
}
}
public class CDLApp
{
public static void main(String[] args)
throws InterruptedException, java.io.IOException
{
System.out.println("Prepping...");
Race r = new Race(
"Beverly Takes a Bath",
"RockerHorse",
"Phineas",
"Ferb",
"Tin Cup",
"I'm Faster Than a Monkey",
"Glue Factory Reject"
);
System.out.println("It's a race of " + r.getDistance() + " lengths");
System.out.println("Press Enter to run the race....");
System.in.read();
r.run();
}
}
|
注意,在 清單 2 中,CountDownLatch
有兩個用途:首先,它同時釋放所有線程,模擬馬賽的起點,但隨后會設置一個門閂模擬馬賽的終點。這樣,“主” 線程就可以輸出結果。 為了讓馬賽有更多的輸出注釋,可以在賽場的 “轉彎處” 和 “半程” 點,比如賽馬跨過跑道的四分之一、二分之一和四分之三線時,添加 CountDownLatch
。
回頁首
3. Executor
清單 1 和 清單 2 中的示例都存在一個重要的缺陷,它們要求您直接創建 Thread
對象。這可以解決一些問題,因為在一些 JVM 中,創建 Thread
是一項重量型的操作,重用現有 Thread
比創建新線程要容易得多。而在另一些 JVM 中,情況正好相反:Thread
是輕量型的,可以在需要時很容易地新建一個線程。當然,如果 Murphy 擁有自己的解決辦法(他通常都會擁有),那么您無論使用哪種方法對于您最終將部署的平臺都是不對的。
JSR-166 專家組(參見 參考資料)在一定程度上預測到了這一情形。Java 開發人員無需直接創建 Thread
,他們引入了 Executor
接口,這是對創建新線程的一種抽象。如清單 3 所示,Executor
使您不必親自對 Thread
對象執行 new
就能夠創建新線程:
清單 3. Executor
Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() { ... });
|
使用 Executor
的主要缺陷與我們在所有工廠中遇到的一樣:工廠必須來自某個位置。不幸的是,與 CLR 不同,JVM 沒有附帶一個標準的 VM 級線程池。
Executor
類實際上 充當著一個提供 Executor
實現實例的共同位置,但它只有 new
方法(例如用于創建新線程池);它沒有預先創建實例。所以您可以自行決定是否希望在代碼中創建和使用 Executor
實例。(或者在某些情況下,您將能夠使用所選的容器/平臺提供的實例。)
ExecutorService 隨時可以使用
盡管不必擔心 Thread
來自何處,但 Executor
接口缺乏 Java 開發人員可能期望的某種功能,比如結束一個用于生成結果的線程并以非阻塞方式等待結果可用。(這是桌面應用程序的一個常見需求,用戶將執行需要訪問數據庫的 UI 操作,然后如果該操作花費了很長時間,可能希望在它完成之前取消它。)
對于此問題,JSR-166 專家創建了一個更加有用的抽象(ExecutorService
接口),它將線程啟動工廠建模為一個可集中控制的服務。例如,無需每執行一項任務就調用一次 execute()
,ExecutorService
可以接受一組任務并返回一個表示每項任務的未來結果的未來列表。
回頁首
4. ScheduledExecutorServices
盡管 ExecutorService
接口非常有用,但某些任務仍需要以計劃方式執行,比如以確定的時間間隔或在特定時間執行給定的任務。這就是 ScheduledExecutorService
的應用范圍,它擴展了 ExecutorService
。
如果您的目標是創建一個每隔 5 秒跳一次的 “心跳” 命令,使用 ScheduledExecutorService
可以輕松實現,如清單 4 所示:
清單 4. ScheduledExecutorService 模擬心跳
import java.util.concurrent.*;
public class Ping
{
public static void main(String[] args)
{
ScheduledExecutorService ses =
Executors.newScheduledThreadPool(1);
Runnable pinger = new Runnable() {
public void run() {
System.out.println("PING!");
}
};
ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
}
}
|
這項功能怎么樣?不用過于擔心線程,不用過于擔心用戶希望取消心跳時會發生什么,也不用明確地將線程標記為前臺或后臺;只需將所有的計劃細節留給 ScheduledExecutorService
。
順便說一下,如果用戶希望取消心跳,scheduleAtFixedRate
調用將返回一個 ScheduledFuture
實例,它不僅封裝了結果(如果有),還擁有一個 cancel
方法來關閉計劃的操作。
回頁首
5. Timeout 方法
為阻塞操作設置一個具體的超時值(以避免死鎖)的能力是 java.util.concurrent
庫相比起早期并發特性的一大進步,比如監控鎖定。
這些方法幾乎總是包含一個 int
/TimeUnit
對,指示這些方法應該等待多長時間才釋放控制權并將其返回給程序。它需要開發人員執行更多工作 — 如果沒有獲取鎖,您將如何重新獲??? — 但結果幾乎總是正確的:更少的死鎖和更加適合生產的代碼。(關于編寫生產就緒代碼的更多信息,請參見 參考資料 中 Michael Nygard 編寫的 Release It!。)
Concurrent Collections 是 Java™ 5 的巨大附加產品,但是在關于注釋和泛型的爭執中很多 Java 開發人員忽視了它們。此外(或者更老實地說),許多開發人員避免使用這個數據包,因為他們認為它一定很復雜,就像它所要解決的問題一樣。
事實上,java.util.concurrent
包含許多類,能夠有效解決普通的并發問題,無需復雜工序。閱讀本文,了解 java.util.concurrent
類,比如 CopyOnWriteArrayList
和 BlockingQueue
如何幫助您解決多線程編程的棘手問題。
1. TimeUnit
盡管本質上 不是 Collections 類,但 java.util.concurrent.TimeUnit
枚舉讓代碼更易讀懂。使用 TimeUnit
將使用您的方法或 API 的開發人員從毫秒的 “暴政” 中解放出來。
TimeUnit
包括所有時間單位,從 MILLISECONDS
和 MICROSECONDS
到 DAYS
和 HOURS
,這就意味著它能夠處理一個開發人員所需的幾乎所有的時間范圍類型。同時,因為在列舉上聲明了轉換方法,在時間加快時,將 HOURS
轉換回 MILLISECONDS
甚至變得更容易。
回頁首
2. CopyOnWriteArrayList
創建數組的全新副本是過于昂貴的操作,無論是從時間上,還是從內存開銷上,因此在通常使用中很少考慮;開發人員往往求助于使用同步的 ArrayList
。然而,這也是一個成本較高的選擇,因為每當您跨集合內容進行迭代時,您就不得不同步所有操作,包括讀和寫,以此保證一致性。
這又讓成本結構回到這樣一個場景:需多讀者都在讀取 ArrayList
,但是幾乎沒人會去修改它。
CopyOnWriteArrayList
是個巧妙的小寶貝,能解決這一問題。它的 Javadoc 將 CopyOnWriteArrayList
定義為一個 “ArrayList
的線程安全變體,在這個變體中所有易變操作(添加,設置等)可以通過復制全新的數組來實現”。
集合從內部將它的內容復制到一個沒有修改的新數組,這樣讀者訪問數組內容時就不會產生同步成本(因為他們從來不是在易變數據上操作)。
本質上講,CopyOnWriteArrayList
很適合處理 ArrayList
經常讓我們失敗的這種場景:讀取頻繁,但很少有寫操作的集合,例如 JavaBean 事件的 Listener
s。
回頁首
3. BlockingQueue
BlockingQueue
接口表示它是一個 Queue
,意思是它的項以先入先出(FIFO)順序存儲。在特定順序插入的項以相同的順序檢索 — 但是需要附加保證,從空隊列檢索一個項的任何嘗試都會阻塞調用線程,直到這個項準備好被檢索。同理,想要將一個項插入到滿隊列的嘗試也會導致阻塞調用線程,直到隊列的存儲空間可用。
BlockingQueue
干凈利落地解決了如何將一個線程收集的項“傳遞”給另一線程用于處理的問題,無需考慮同步問題。Java Tutorial 的 Guarded Blocks 試用版就是一個很好的例子。它構建一個單插槽綁定的緩存,當新的項可用,而且插槽也準備好接受新的項時,使用手動同步和 wait()
/notifyAll()
在線程之間發信。(詳見 Guarded Blocks 實現。)
盡管 Guarded Blocks 教程中的代碼有效,但是它耗時久,混亂,而且也并非完全直觀。退回到 Java 平臺較早的時候,沒錯,Java 開發人員不得不糾纏于這種代碼;但現在是 2010 年 — 情況難道沒有改善?
清單 1 顯示了 Guarded Blocks 代碼的重寫版,其中我使用了一個 ArrayBlockingQueue
,而不是手寫的 Drop
。
清單 1. BlockingQueue
import java.util.*;
import java.util.concurrent.*;
class Producer
implements Runnable
{
private BlockingQueue<String> drop;
List<String> messages = Arrays.asList(
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"Wouldn't you eat ivy too?");
public Producer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
for (String s : messages)
drop.put(s);
drop.put("DONE");
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
class Consumer
implements Runnable
{
private BlockingQueue<String> drop;
public Consumer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(msg);
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
public class ABQApp
{
public static void main(String[] args)
{
BlockingQueue<String> drop = new ArrayBlockingQueue(1, true);
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
|
ArrayBlockingQueue
還體現了“公平” — 意思是它為讀取器和編寫器提供線程先入先出訪問。這種替代方法是一個更有效,但又冒窮盡部分線程風險的政策。(即,允許一些讀取器在其他讀取器鎖定時運行效率更高,但是您可能會有讀取器線程的流持續不斷的風險,導致編寫器無法進行工作。)
BlockingQueue
還支持接收時間參數的方法,時間參數表明線程在返回信號故障以插入或者檢索有關項之前需要阻塞的時間。這么做會避免非綁定的等待,這對一個生產系統是致命的,因為一個非綁定的等待會很容易導致需要重啟的系統掛起。
回頁首
4. ConcurrentMap
Map
有一個微妙的并發 bug,這個 bug 將許多不知情的 Java 開發人員引入歧途。ConcurrentMap
是最容易的解決方案。
當一個 Map
被從多個線程訪問時,通常使用 containsKey()
或者 get()
來查看給定鍵是否在存儲鍵/值對之前出現。但是即使有一個同步的 Map
,線程還是可以在這個過程中潛入,然后奪取對 Map
的控制權。問題是,在對 put()
的調用中,鎖在 get()
開始時獲取,然后在可以再次獲取鎖之前釋放。它的結果是個競爭條件:這是兩個線程之間的競爭,結果也會因誰先運行而不同。
如果兩個線程幾乎同時調用一個方法,兩者都會進行測試,調用 put,在處理中丟失第一線程的值。幸運的是,ConcurrentMap
接口支持許多附加方法,它們設計用于在一個鎖下進行兩個任務:putIfAbsent()
,例如,首先進行測試,然后僅當鍵沒有存儲在 Map
中時進行 put。
回頁首
5. SynchronousQueues
根據 Javadoc,SynchronousQueue
是個有趣的東西:
這是一個阻塞隊列,其中,每個插入操作必須等待另一個線程的對應移除操作,反之亦然。一個同步隊列不具有任何內部容量,甚至不具有 1 的容量。
本質上講,SynchronousQueue
是之前提過的 BlockingQueue
的又一實現。它給我們提供了在線程之間交換單一元素的極輕量級方法,使用 ArrayBlockingQueue
使用的阻塞語義。在清單 2 中,我重寫了 清單 1 的代碼,使用 SynchronousQueue
替代ArrayBlockingQueue
:
清單 2. SynchronousQueue
import java.util.*;
import java.util.concurrent.*;
class Producer
implements Runnable
{
private BlockingQueue<String> drop;
List<String> messages = Arrays.asList(
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"Wouldn't you eat ivy too?");
public Producer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
for (String s : messages)
drop.put(s);
drop.put("DONE");
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
class Consumer
implements Runnable
{
private BlockingQueue<String> drop;
public Consumer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(msg);
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
public class SynQApp
{
public static void main(String[] args)
{
BlockingQueue<String> drop = new SynchronousQueue<String>();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
|
實現代碼看起來幾乎相同,但是應用程序有額外獲益:SynchronousQueue
允許在隊列進行一個插入,只要有一個線程等著使用它。
在實踐中,SynchronousQueue
類似于 Ada 和 CSP 等語言中可用的 “會合通道”。這些通道有時在其他環境中也稱為 “連接”,這樣的環境包括 .NET (見 參考資料)。
回頁首
結束語
當 Java 運行時知識庫提供便利、預置的并發性時,為什么還要苦苦掙扎,試圖將并發性導入到您的 Collections 類?本系列的下一篇文章將會進一步探討 java.util.concurrent
名稱空間的內容。
Java中finalize()
java這個finalize內置方法,估計很多人不會去用途,如果理解這個方法的用法和含義就能做一些可能我們一起認為不能做的事情。
在JAVA中有一種垃圾收集器的機制,當它運行時(通常在系統內存低到一定限度時自動運行),會回收不再使用的對象所占用的內存,所以,在JAVA程序中,我們通常只考慮創建對象,而從不關心對象的清除。Finalize()是JAVA為類提供的一種特殊方法。垃圾收集器的工作過程大致是這樣的:一旦垃圾收集器準備好釋放無用對象占用的存儲空間,它首先調用那些對象的finalize()方法,然后才真正回收對象的內存。通過使用finalize(),就可以在垃圾收集器運行期間進行一些特殊的工作。
你們也就是說,當gc事件啟動時候,他是調用對象的finalize(),來實現真正的回收,那么首先這些對象是沒有用的,最簡單的使用,我可以在finalize()中添加
system.out.print 來跟蹤系統的回收了那些對象,可以深層次的了解系統對象的使用情況,比如那些對象回收最頻繁等等。
另外我在網上也看到有人這么用finalize(). 主要是統計在線人。這個網上也有很多,本人自己也做過,但是在logout的時候,有三種情況
1.點擊程序的logout,這個我們可以監聽到。2.去別的網站 3.關閉瀏覽器。(當然現在第二,第三有些網站也能通過script捕獲到,這里我們不談)
我們假定傳統的,session在服務端還是存在的,一般是經過服務器端timeout,自動將這個session的對象失效,那么我們在這些對象調用finalize()做一些
統計就能知道那些人已經離線。
具體這個例子
http://www.qqread.com/java/w712250600.html
這只是一個例子
我想說的是,我們如果理解了finalize的含義和用途,就能在很多地方用好它,未嘗不是一種新方式。
其實如果是直接通過jdbc去連接數據庫,那么下面的鏈接的
http://www.dankomannhaupt.de/projects/index.html
的jdbcappender.zip 已經能很方便的實現這個功能,
但是在現實情況,特別是大型應用,基本都是通過datasource來獲取
connection,而這個zip中已經明確說了不支持 DataSource,那么我們怎么辦呢?
我是這樣解決的,對于j2ee的應用本身不管你用spring+hibernate還是c3p0 來獲取
Datasource,最終他還是得到jdbc中的connection來進行數據庫操作,而jdbcappender
也是通過connection來操作數據庫,那么思路就是如果通過在你框架中獲取jdbc的connection,
然后將他set到jdbcapplender中就可以了。
確定這種思路后,我就需要了解jdbcappender.zip中的代碼,看如果將connection 放入jdbcappender中
首先 我們看一下如果正常通過jdbc配置,這個jdbcAppender是怎么集成到log4j中的
下面是jdbcAppender的用法
public class Log4JTest {
// Create a category instance for this class
static Logger logger = Logger.getLogger(Log4JTest.class);
public static void main(String[] args) {
JDBCAppender ja = new JDBCAppender();
// Set options with method setOption()
ja.setConnector("org.apache.log4j.jdbcplus.examples.OracleConnectionHandler");
ja.setUrl("jdbc:oracle:thin:@..");
ja.setUsername("mex_pr_dev65");
ja.setPassword("mex_pr_dev65");
ja.setSqlhandler("org.apache.log4j.jdbcplus.examples.SqlHandler");
// Add the appender to a category
logger.addAppender(ja);
logger.debug("debug");
}
}
}
上面的類基本分為這么幾個部分
1.和一般log4j用法一樣, 獲取logger instance
2.new 一個JDBCAppender
3.為jdbc設置兩部分參數,一個是jdbc 建立連接的參數,如url等 另一部分是具體insert sql的handler(你可以用handler來體現insert 語句也可以將sql寫入log4j的配置文件,我這里就用handler處理insert語句)
4.將logger instance中添加剛才設置的JDBCAppender
5.完成
從上面的例子來看好像不能set connection,那么后來查看JDBCAppender的源碼發現
JDBCAppender有setConnectionHandler(interface JDBCConnectionHandler())方法;
可以通過這個方法能將connection放入jdbcappender中,但必須實現接口DBCConnectionHandler
接口JDBCConnectionHandler 如下
public interface JDBCConnectionHandler {
/**
* Get a connection
*
* @return The Connection value
* @exception Exception
* Description of Exception
*/
Connection getConnection() throws Exception;
/**
* Get a defined connection
*
* @param _url
* Description of Parameter
* @param _username
* Description of Parameter
* @param _password
* Description of Parameter
* @return The Connection value
* @exception Exception
* Description of Exception
*/
Connection getConnection(String _url, String _username, String _password) throws Exception;
}
這個時候你發現 我們找了半天Connection的地方原來在這里
那么我只要實現這個接口的getConnection() 方法就能將connection放入JDBCAppender中,
現在我們構建一個Handler,我這里用的框架只是Hibernate3
我需要從hibernate3中獲取connecion就可以了
public class Cas2HibernateConnectionHandler implements JDBCConnectionHandler {
//hibernate 的session Factory
SessionFactory sf = HibernateConnection.getInstance();
public Connection getConnection(){
Connection con =null;
try {
con = sf.openSession().connection();
} catch (Exception e) {
e.printStackTrace();
}
return con;
}
public Connection getConnection(String arg0, String arg1, String arg2)
throws Exception {
// TODO Auto-generated method stub
return null;
}
}
這就是我的handler,為了讓代碼更清楚,我把我的HibernateConnection也貼出來,這是常見的Sington模式獲取hibernate的SessionFactory
public class HibernateConnection {
private static SessionFactory sessionFactoryInstance = null;
private HibernateConnection() {
}
//單元測試用的
synchronized public static SessionFactory getInstance() {
if (sessionFactoryInstance == null) {
Configuration config;
try {
config = new Configuration().configure(new File("E:\\cas2\\config\\hibernate\\hibernate.cfg.xml"));
sessionFactoryInstance = config.buildSessionFactory();
} catch (HibernateException e) {
e.printStackTrace();
}
}
return sessionFactoryInstance;
}
}
說到這里我順便說一下,我這邊框架用的是hibernate3,還有很多數據庫相關的框架比如hibernate+spring或者c3p0等等,這些都無所謂
只要找到相應如果獲得jdbc connection的方法,同時構建自己的ConnectionHandler并實現里面的getConnection()就可以了。
到這個時候數據庫連接已經能獲取了,接下來就需要寫具體的insert語句,這里有兩種方法,一種就是直接在log4j的配置文件中寫
這個配置文件可以xml,也可以properties,這個網站也都有具體描述怎么做,這么不說了;
另外一種就是ja.setSqlhandler("org.apache.log4j.jdbcplus.examples.SqlHandler");
自己實現一個sqlHandler 后set到JDBCAppender中就可以了
新構建的sqlHander需要實現接口
public interface JDBCSqlHandler {
/**
* Get a sql-statement.
* Return null or empty string if nothing should be logged.
*
* @return The SQL statement. Null if there is nothing to log.
* @exception Exception
* Any Exception
*/
String getStatement(LoggingEvent event) throws Exception;
}
他里面只有一個方法getStatement,里面寫入具體insert 語句就可以了
public class CasLoginHandler implements JDBCSqlHandler {
private final int MAX_LENGTH_MESSAGE = 3000;
public String getStatement(LoggingEvent event) throws Exception {
// try { throw new Throwable(); } catch (Throwable th) {
// th.printStackTrace(); }
LocationInfo locinfo = event.getLocationInformation();
ThrowableInformation throwableinfo = event.getThrowableInformation();
StringBuffer throwableStringBuffer = new StringBuffer();
String locinfoString = "'', '', '', ''";
if (locinfo != null) {
locinfoString = "'" + locinfo.getClassName() + "', '" + locinfo.getMethodName()
+ "', '" + locinfo.getFileName() + "', '" + locinfo.getLineNumber() + "'";
}
if (throwableinfo != null) {
String[] lines = throwableinfo.getThrowableStrRep();
for (int index = 0; index < lines.length; index++) {
throwableStringBuffer = (StringBuffer) throwableStringBuffer.append(lines[index]
+ "\r\n");
}
}
StringBuffer sb = new StringBuffer();
sb.append("Insert into UM_SYS_LOG (ID, LOG_DATE, LOG_LEVEL, LOGGER, OPERATOR, APPLICATION_NAME, MESSAGE_KEY, LOG_MESSAGE)Values(");
sb.append("SEQ_UM_SYS_LOG.nextval,");
sb.append("TO_DATE('");
sb.append(DateFormatUtil.formDateToyyyyMMdd24(new Date(event.timeStamp)));
sb.append("','YYYY-DD-MM HH24:mi:SS')");
sb.append(",'");
sb.append(event.getLevel().toString());
sb.append("','");
sb.append(event.getLoggerName());
sb.append("','bgao','CAS2','login sucess','user bgao loginSuccess')");
return sb.toString();
}
}
這是我構建的CasLoginHandler。
然后將該Handler 設置到JDBCAppender中就行了
ja.setSqlhandler("com.bgao.log.CasLogHandler");
這樣整個將log日志寫入數據庫就完成了。當然下面還有問題可以供思考,一般log.debug("msg");這種寫法所有的日志信息都是以一條msg輸出
,比如"張三在127.0.0.1機器查詢表×××" ;如何簡單得將一句話insert到不同字段,這個有很多方式,可以思考一下哪種更方便,更簡潔。
我最近研究這個memcache 發現這個東東,版本以及名稱很多,有點混亂,這兩天研究下來我是這么梳理的,不知道對不對,和大家一起分享
Memcached 是分布式cache,他有服務端和client端,核心版本是在Linux上運行
官方網站為 http://memcached.org/ 對應的wiki在google上
http://code.google.com/p/memcached/ 其實現在memcached的相關文檔和代碼都在google.code上了
對應Linux 上版本的維護的挺好,但是對于windows版本的就很糟糕了,可能因為大型應用大多是Unix或其變種
我在google上搜索了半天找到下面基本版本
windows的就有
http://jehiah.cz/projects/memcached-win32/
http://www.splinedancer.com/memcached-win32/ 基于上面的win32
win-1.2.6版本可以在
http://code.jellycan.com/memcached/ 找到
雖然這三個地方都是memcached for windows 而且還是不同人寫的,但是他們感覺好像都有版本約定,比較有序,
比如http://jehiah.cz/projects/memcached-win32/ 好像是1.1--1.2.1
http://www.splinedancer.com/memcached-win32/ 本身網站上就寫了
This is a port of memcached to the win32 architecture by Kenneth Dalgleish, based on Kronuz's 1.2.1 port
是基于上面的1.2.1寫的 他有的版本是1.2.4
而http://code.jellycan.com/memcached/ 版本是1.2.5 1.2.6
這種有序是我猜想的,也有可能是他們都是根據核心 memcached 進行編譯為win32版本,而win32的版本根據核心memcached版本來定義的。
這些只是猜想,反正結論是windows 的memcached server版本比較分散沒有主要維護,但是他們也有各種的版本歷史。如有知道這個歷史內幕的
請反饋,謝謝。
我剛剛在google wiki上發現了windows 一起其他os的memcached鏈接
http://code.google.com/p/memcached/wiki/Start 【Server ports to other platforms--->windows】
現在是1.4版本了,其實也是鏈接到其他website
http://labs.northscale.com/memcached-packages/ (又多了一個出處)
好了,到現在我們不再猜測他的歷史問題,雖然windows 版本很多但是他們的安裝步驟都是一樣的
我現在從http://labs.northscale.com/memcached-packages/ 下載的是最新的版本memcached-win32-1.4.4-54-g136cb6e.zip
一,安裝memcached for windows
1.解壓memcached-win32-1.4.4-54-g136cb6e.zip
2.將里面的文件放入 E:\memcached\memcached_win32
3.直接到目錄E:\memcached\memcached_win32 下執行命令 memcached.exe -d install 安裝服務
4.這個時候去控制面板--》管理工具--》服務 中就能看到一個memcached 的服務
5.如果卸載服務 那就memcached.exe -d uninstall,除此 還有 start restart命令,具體你可以通過memcached.exe -h 查看幫助
目前只是可以看到是否安裝了服務,接下來我們應該去測試這個memcached server是否成功
測試這個memcached 有很多方式,
本身memcahed 有很多client端http://code.google.com/p/memcached/wiki/Clients
有C++,java,.net,php 等等
他們的任何一個client都可以用來測試,我們這里就用php
首先我們要搭建php 環境
二,安裝apache和php(這些網上都有介紹)
1.下載apache2 http://httpd.apache.org/download.cgi
2.安裝 apache2
3.下載php5
http://windows.php.net/download/ 注意要下VC6,VC6支持apache
而VC9不支持apache 是支持IIS
4.我們下載zip的這個包(比較綠色)
5.解壓zip包 主目錄為E:\php5,將“php.ini-recommended”文件備份并更名為“php.ini”。
6.查看php.ini
查找“extension_dir”字段,賦值為php解壓路徑中的ext目錄下,如"E:\php5\ext"
查找 cgi.force_redirect 字串.默認值為1.將其修改為0.并取消前面的;號
7.分別查找擴展,將其之前的;去掉。
extension=php_mbstring.dll(寬字符,用于支持PhpMyAdmin,避免出現字符顯示問題)
extension=php_mcrypt.dll(用于支持PhpMyAdmin)
這些dll在E:\php5\ext可以找到,這里我們為了連接memcached server 我們需要
加入一行 ‘extension=php_memcache.dll’一般ext沒有該dll
請在http://downloads.php.net/pierre/ 中下載相應的memcache.dll 放入到ext文件夾中
我用的是 php_memcache-5.2-Win32-vc6-x86-20090408.zip
反正就這幾個memcache dll你都試試(php的這種方式真是不好,應該還有其他方式,這里沒有研究)
8.配置Apache以支持php5:
打開apache安裝目錄下的“conf”文件夾,apache的配置主要依靠httpd.conf,用編譯工具打開該文件,修改其中的某些字段:
(1)Listen 字段, 其后默認值為80,你可以修改該端口值以改變apache服務的端口(不至于和tomcat等工具的端口發生沖突)
(2)DocumentRoot 這是你自己網頁文件的放置目錄,默認為apache安裝目錄下的“htdoc”文件夾,也可以改為本機上的其他目錄,采用絕對路徑。
我使用的是:DocumentRoot "D:/phpwork/"(新建的工作目錄)
9.設置起始頁:
這個地方可以照抄我的配置,也可以自己增加需要的起始頁文件名。注意文件名之間用空格隔開,而不是用逗號
<IfModule dir_module>
DirectoryIndex index.php index.html default.php default.html index.htm
</IfModule>
9.配置php模塊:在#LoadModule(有一排的代碼) 后加上兩句話(此處為我的安裝目錄,可根據自己的安裝情況進行適當的**)
PHPIniDir "E:/php5/"
LoadModule php5_module "E:/php5/php5apache2_2.dll" [這個php5apache2_2.dll 有下載的php5.3版本中就沒有,后來下載了5.2,第一次配php就遇到這種事情,這種模式真的很不好]
10. 保存httpd.conf文件,重啟Apache 如果成功啟動,在phpwork下新建一個HelloWorld.php文件
<?php
echo "HelloWorld!<br>";
phpinfo();
?>
在瀏覽器中輸入http://localhost/:你自己設置的端口號/HelloWorld.php.哈哈~~~至此將輸出HelloWorld以及php配置環境變量信息,這就成功了。
11.測試memcached,在phpwork下新建一個memcacheTest.php文件
<?php
$mem = new Memcache;
$mem->connect("127.0.0.1", 11211);
$mem->set("key", 'This is a test!', 0, 60);
$val = $mem->get('key');
echo $val;
?>
在瀏覽器中輸入http://localhost/:你自己設置的端口號/memcacheTest.php 如果看見This is a test!,那就表示成功了。
演化架構和緊急設計: 演化架構
敏捷架構的考慮和技術
Neal Ford 是一家全球性 IT 咨詢公司
ThoughtWorks 的軟件架構師和 Meme
Wrangler。他的工作還包括設計和開發應用程序、教材、雜志文章、課件和視頻/DVD 演示,而且他是各種技術書籍的作者或編輯,包括最近的新書
The Productive
Programmer 。他主要的工作重心是設計和構建大型企業應用程序。他還是全球開發人員會議上的國際知名演說家。請訪問他的
Web 站點。
簡介: 這一期的 演化架構和緊急設計
將會解決演化架構相關的各種主題,包括設計和架構之間的重要區別(以及如何區分兩者),您在創建企業級架構時遇到的某些問題,以及面向服務的架構中靜態類型和動態類型的區別。
查看本系列更多內容
發布日期: 2010 年 3 月 01 日
級別: 中級
其他語言版本: 英文
在 本系列的第一期
中,我推薦了軟件世界中的一些架構定義。無論如何,如果您已經閱讀過本系列,您會注意到我花費了大部分時間在設計上。我之所以這么做是基于以下幾個原因:首先,在當前緊急設計尚未被廣泛關注時,軟件世界里存在很多架構定義(良莠不齊);其次,在設計方面很多問題都有具體的、不受環境限制的解決方案。架構往往還涉及到很多組織內的物理和邏輯基礎設施,使其難以獨立談起。
這一期填補了敏捷構架材料缺失的空白。在此我討論的是如何分辨架構和設計,涵蓋了一些廣泛的架構考慮,然后通過討論版本控制端點,淺談敏捷的面向服務架構(SOA)。
分辨架構和設計
Martin Fowler 對架構的定義(來自和他的對話中)是我認為最好的:
架構就是完成之后很難更改的東西。所以這種東西應該盡可能越少越好。
您可以想象一下架構和設計之間的交互,如圖 1 中所示的關系:
圖 1.
架構和設計的關系
一個軟件系統的架構形成是所有其他部分存在的基礎,如 圖 1
中的灰盒所示。設計部分存在于架構之上,如紅盒所示。處于更基礎的地位,架構部分難以移動和替換是因為您不得不移動所有以架構為基礎的部分來適應改變。這一定義使識別設計和架構更為簡單。例如,您所使用的
Web 框架就是一個架構,因為它難以替換。盡管,在那個 Web
框架中您使用不同的設計模式來表述特定的目標,這就表示大部分的正式設計模式是設計,而不是架構的一部分。
Fowler 所定義的架構的推論是:您應該靈活地構造架構部分,以便能夠更輕松地替換它們(如果真的需要的話)。但是如何才能確保這點呢?這里有個例子。
許多框架都會試圖誘導您使用其自身的類,而不是 JDK 或者一個開放標準機構(例如 OASIS)提供的更普遍的類。這就是耦合的
“毒販模式”:如果您服從這些誘導,您就只能永遠受制于框架。這些框架采取的普遍方法就是,如果您使用了它們的類,某方面就會變得異常簡單。這方面的完美例子就來自于
Apache Struts Web 框架(見 參考資料)。
在您的應用程序中包含業務規則和其他非基礎設施代碼的類是域類:它們包含著您的問題領域相關的有趣信息。Sturts 中的一個好助手類就是
ActionForm
類。如果您從 ActionForm
繼承了您的域對象,
您的應用程序就會變得更方便。您可以從參數完成自動表格填充、自動驗證(Web 和服務器層),以及其他便利。您所要做的就只是把 Struts
ActionForm
類作為子集,如圖 2 所示:
圖 2. 使用 Struts
ActionForm
類
在 圖 2 中,標簽為 Model 的盒子包含了您的域對象。它擴展了 Struts 的
ActionForm
,使得這一結構此后難以改變。如果以后您決定 ScheduleItem
也需要在一個
Swing 應用程序中運行,那就很難辦了。您只剩下兩個難以接受的解決方案:將所有的 Struts 拖拽到 Swing 應用程序中(且不使用它)或者擺脫對
Struts 的依賴。
較好的替代方案就是采用組合而不是繼承,如圖 3 所示:
圖 3.
通過組合來對您的域類解耦合
在此版本中,域類(黃色部分)包含了一個定義日程項目語義的界面。原始的 ScheduleItem
將實現這個界面,它還可以由
ScheduleItemForm
來實現,使得這兩個類的語義總是保持一致。反過來,ScheduleItemForm
擁有
ScheduleItem
域對象的一個實例,ScheduleItemForm
的所有讀值器和寫值器傳遞到封裝的 ScheduleItem
的底層讀值器和寫值器。這就允許您利用 Struts
的良好特性,同時擺脫該框架的束縛。
經驗法則是:可以使框架對您有所了解,而您不可以對框架有所了解。只要您可以維持那種關系,您就能避免把自己的代碼耦合到基礎設施中去,這使您能夠更輕易地改變架構和設計。有時可能要多花點功夫來完成這個任務,但是您可以擁有更好的靈活性。Struts
并不是唯一向您提供這種誘惑的框架。幾乎所有的框架都包含將您限制在框架中的幫助工具。如果您在域類中導入來自某個框架或者廠商的數據包,那您以后就有得頭疼了。
回頁首
關于架構的考慮
除了架構的定義,典型的企業設置中還出現了各種廣泛的問題。我將在這里介紹針對其中一些問題的敏捷架構解決方法。
架構的政治
當您被提升到架構師職位時,公司政治將是您所要遇到的眾多難題之一。因為架構師 基本上是公司中最高的技術職位,您會成為 IT
部門內發生的所有決策的發言人(和辯護人),無論好壞。事實上,您還常常要因為失敗受到責備,卻不會因為成功而贏得信任。一些新上任的架構師試圖對這些置之不理(當您在技術職位時這也許非常有效),但是在您的新職位這明顯行不通。
請您記住在許多軟件項目中,溝通比技術更為重要。如果您曾經在某個軟件項目上失敗過,那么請您思考一下失敗的原因:是出于某個技術
原因,還是某些溝通
問題?大部分時間,失敗是因為溝通而不是技術。技術問題有其解決方案。(有時它們很難解決,但總歸有解決方案。)但社會問題就更加復雜和棘手了。Peopleware(見
參考資料)這本書中有這樣一句名言:
總是存在人的問題。
即使是您認為應該按部就班,直截了當的技術決策,也會有政治參雜其中,特別是您處于決定是否批準購買某企業工具的職位。(從樂觀的角度看,您可能有機會由某個工具廠家掏腰包打次異國情調的高爾夫。)請記得,作為一名架構師,您不僅需要做出重要的決策,您還必須為這些決策辯護。有時和您交談的人有他們自己的議事日程,這些內容或許在邏輯上行不通,但是在企業政治的考驗面前卻行得通。不要氣餒,您要記清楚最初之所以作出這個決策的原因。
構建與購買
大公司中常出現的普遍問題之一就是決定是構建還是購買:針對現在的需求,我們是應該購買 COTS(Commercial Off-the-Shelf
Software)還是自己構建?要做出此決策的動機是可以理解的 —
如果公司可以找到一些完全符合自身需要的現成軟件,這樣就節約了時間和金錢。不幸的是,許多軟件廠商理解這一需求,所以編寫可以定制的打包軟件,如果軟件不能完全符合客戶的需要的話。他們意在盡力構建最通用的軟件,因為這樣能適用更多的生態系統。但是越是通用,就越需要定制。所以有時即使很多顧問在,也需要花費很多年才能完成所有的定制代碼。
是否應該購買 COTS 的問題實際上歸結為另一個問題:業務流程是由軟件在戰略上 還是經費上
支持?如果業務流程僅僅是經費問題,購買 COTS 就合情合理。這類軟件例子包括人力資源、財務、以及其他普通的業務流程。戰略
軟件在您的業務領域給您競爭優勢,這個競爭優勢不能輕易放棄。
圖 4 所示的流程圖用于幫助您決定是構建還是購買:
圖 4.
決策是構建還是購買的流程圖
在這個流程圖中,您要做出的第一個決策就是戰略和經費的重要區別。如果需求是戰略性的,您往往需要自己構建解決方案。如果不這么做,您就會將自己置于一個和對手公平競爭的環境中,而不是構建完全符合您現在和將來需求的軟件。打包軟件吹噓其可定制性,但還是有對定制程度的限制。如果您自己編寫,會花費較長的時間,但是您有了一個平臺,在這個平臺上您可以構建將您和對手區分開的軟件。
流程圖中的第二個決策就是詢問數據包軟件是否能立刻起作用。在購買數據包軟件時常見的一個陷阱就是錯誤估計其適應您的業務流程所需的準確時間;大部分公司都把這個時間錯估了一個數量級。您所需的定制越多,所耗費的時間就越長。更糟糕的是,一些公司還允許改變他們的業務流程來適應軟件。這是一個錯誤,因為無論好壞,您的業務流程都應和對手的有所區別。
這個決策樹中的第三步就是詢問數據包是否可擴展,這和定制性
剛好相反。可擴展的系統由經過良好定義的方法來擴展功能,而無需一切事先就緒。這些擴展點包括經過良好定義的 APIs、SOAP 調用等等。定制意味著您要通過
“欺騙” 來讓數據包完成您的工作。例如,如果您試圖打開一個 WAR 文件,那么您可以用一個不同的圖像(必須用 index.gif 來命名)來替換用
index.gif
命名的文件,您是進行定制而不是擴展。最終檢驗標準是您的更改是否能夠通過升級。如果是,您就擴展了數據包;如果不是,您就定制了數據包。定制不鼓勵您不斷升級數據包,因為您會意識到對新版本做出相同的改變需要付出多少努力。那么,趨勢就是不進行更新,落后于最新版四、五個版本,這將使您面臨失去對現在正在使用的老版本的支持的危險。
是經費問題還是戰略問題因公司而異。例如,我曾為一家財務服務公司做過顧問,它的招聘過程被認為是其關鍵戰略優勢之一。他們雇傭最好、最聰明的人,花費大量的時間和精力來尋找適合的人。他們曾就購買
COTS 人力資源系統咨詢過我的意見,我建議他們不要那樣做:為什么要讓自己置身于一個和對手公平競爭的環境呢?最后,他們采納了我的建議,編寫自己的 HR
系統。編寫花費了較長的時間,但一旦完成,他們就有了一個平臺,能夠完成對其對手來說更勞動密集型的任務。招聘對許多組織來說是簡單的經費問題,但對這家公司來說卻是戰略問題。
回頁首
架構中的類型控制
SOA
計劃中經常出現的一個更技術化(更不面向流程)的主題往往和分布式系統中的類型控制和版本控制有關。這就是這類項目中常見的陷阱之一。它之所以常見,不僅因為人們很容易遵循工具廠商鋪好的路,還因為問題需要一段時間才能凸顯出來
— 最嚴重的問題產生于您不了解在項目早期應該知道的東西。
關于能否用動態類型語言構建 “企業”
系統的爭論已經有了定論,這個結論現在也不能給予什么啟示。然而,這一爭論意味著就端點的類型控制而言,對分布式系統有了重要的考慮。所謂端點,指的是兩個完全不同的系統之間的通信門戶。兩個相互競爭的類型控制樣式是
SOAP 和 Representational State Transfer (REST),前者通常采用諸如 Web Services Description
Language (WSDL)這樣的標準來創建一個強類型,而后者適用于類型更寬松的、以文檔為中心的方法(見 參考資料)。SOAP 與 REST
的詳細優缺點不在本文的討論范圍之內;在此我主要想說的是端點層面上寬松類型的好處,這些好處可以使用任一樣式實現。
更動態的類型控制在端點處是很重要的,因為這些端點會在以不同速度演變的系統之間形成一個已發布的集成
API。您想在那些系統之間避免嚴格耦合的特定簽名(類型和參數名),因為那樣會使通信的雙方都很脆弱、容易崩潰,削弱了您分別對兩個應用程序進行版本升級的能力。
這里有個例子。在傳統的 SOAP 式集成中,使用的協議類型是 Remote Procedure Call (RPC),并用 WSDL
來定義兩個應用程序間通話的詳細信息,如圖 5 所示:
圖 5. 在應用程序間使用 RPC
式調用
RPC 式集成使用 WSDL 來進行一個 “常規” 方法調用,并將其抽象出來發送到 SOAP。這樣,每個類都映射到 WSDL
中的一個類型,包括其所有參數的類型。這種方法將通信雙方強烈耦合到一起,因為它們都依賴 WSDL
來定義發送的內容和預期接收的內容。問題源于這種嚴格的定義。如果您需要修改其中一個應用程序來采用不同的參數或者改變現有的類型,且不能同時更改這兩個應用程序,那又該怎么辦呢?該如何對端點進行版本控制?有幾個方法是可行的,但所有這些方法都有嚴重的妥協之處。例如,您可以用新的
WSDL 定義創建另外一個端點。如果原始端點命名為 addOrder
,那么您可以創建另一個端點,命名為
addOrder2
。您會看到這種方法前景不妙。不久,您就會有數十個稍有不同、到處包含重復代碼的端點來處理一次性情況,因為一旦發布,就很難預測人們會怎么應用這些集成點。您也可以使用
Universal Description, Discovery, and Integration
(UDDI)(或者僅僅是哈希圖)這樣的工具來欺騙端點,但那并不會有很好的效果。根本問題就是端點間的嚴格耦合,那會阻止其按自然、獨立的速度發展演變。
一種替代方法就是把集成端點當做寬松類型對待,如圖 6 所示:
圖 6.
在集成端點采用寬松類型控制
通過將有趣的端點信息傳送到一個文檔內,您可以在通信雙方任意一方主要升級和次要升級過程中保持端點定義不變。您可以靈活選擇,而不是依賴 WSDL
來嚴格定義預期的內容。現在,端點總是接收一個封裝該端點所需類型的文檔。
要解決端點的版本控制問題,端點要做的第一步就是把文檔解開,確定已經傳輸的內容,并用預期的內容與之協調。我通常聯合使用 Factory 和 Strategy
設計模式(見 參考資料)來確定是否正在獲得預期的內容,如圖 7 所示:
圖 7. 在端點內解開內容來確定類型
端點的首要工作就是查看文檔的清單,確定它包含的內容。然后,它用一個庫來實例化適當的策略,將那些信息從文檔中抽出。一旦所有部分都通過驗證(必要時可用
WSDL),反序列化的對象就被傳遞,用于業務處理。
這種方法有幾個好處。首先,擁有一個帶有兩個正交作業的機制是個壞主意,然而那正是傳統 RPC 所假設的:端點既要負責提供已發布的集成
API,又要負責驗證類型。因為其有兩個行為,所以您可能會弄混代碼,使其更難理解和維護。其次,現在這個端點可以有多個用戶,每個用戶使用稍有差異的版本。只要您有一個策略,您就能夠用相同的端點支持任何版本(包括那些更新緩慢的應用程序的老版本)。這允許您根據需要進行改變,不用擔心這會迫使企業內應用程序的其他部分和您的改變保持一致
—— 它們可以根據自己的進度改變并使用新的文檔版本。
目前沒有任何工具或者框架允許您輕松地實現這種方法,但是一些額外的前期工作提供了之前提到過的好處。您可以使用 SOAP 或 REST 來實現這個樣式(不過在
REST
中會更容易,因為它本身就是以文檔為中心的)。通過創建一個寬松類型的生態系統,您可以使不同的開發小組按自己的節奏開展工作,從而使整個企業的應用程序使用以最小的摩擦前進。這就是演化架構的精髓:奠定一個基礎來支持盡快實施無摩擦的、不損害功能的變革。
回頁首
結束語
架構是個龐大且復雜的軟件主題;在這部分中,我試圖涉及許多不同的方面,從政治到 SOA
中的端點版本控制的實現細節。在以后的部分中,我將不斷充實這些關于一般架構和新架構方法的想法,幫助您構建一個可發展的
SOA,以免向軟件商支付數百萬美元的高額費用。
參考資料
學習
討論
關于作者
Neal Ford 是一家全球性 IT 咨詢公司 ThoughtWorks
的軟件架構師和 Meme Wrangler。他的工作還包括設計和開發應用程序、教材、雜志文章、課件和視頻/DVD
演示,而且他是各種技術書籍的作者或編輯,包括最近的新書 The Productive
Programmer 。他主要的工作重心是設計和構建大型企業應用程序。他還是全球開發人員會議上的國際知名演說家。請訪問他的 Web 站點。
OO四劍客
現在面向對象的開發已經基本成為程序員認定的真理,圍繞這個思想很多人做了將原來不是面向對象的東西轉換
為面向對象的工作,比如面向對象數據庫
(參考 『http://linugb118.blog.ccidnet.com/blog-htm-do-showone-uid-39808-type-blog-itemid-102097.html』db4o 參考我以前寫的)
,還比如or-mapping的出現,等等。
本人以前也對面向對象的工具以及一些面向對象的產品進行過源碼的研究,后來在看了《Jakarta Commons Cookbook》
后,我覺得具體可以這么歸納面向對象開發的四個要素:Predicate(斷言),Closure(終結者),Comparator(比較器),Transformer(轉換),
我們先不解釋這四個要素的含義和用意,我們先來看看非面向對象語言的描述,如果在學校學習過面向過程的
編程語言如PASCAL的話,大家其實知道,如果需要實現某個功能,那么在一個函數中從頭寫到尾,其實對于計算機
本身而言,他也是從頭到尾執行的,他本身不會跳轉,只有你給if或者else 這樣的詞語告訴它,那么機器才會根據你的
意思知道是跳轉到別的地方還是繼續執行。目前跳轉的常用模式就是if(A){do}else{} 以及循環while(A) do{},
這里開始引入第一個要素Predicate(斷言),什么是Predicate,其實上面的A就是Predicate。在面向過程語言中。
往往是等于,AND,OR,不等于等一些判斷,而等式鏈接的直接是變量,鏈接符號那么是預先定義的符號,比如==,&&,||, ! 等等
那么面向對象的話,一切都是對象,首先等式鏈接的兩邊是對象,雖然對象語言中仍然有預先定義的符號,但是我們
覺得這個不符合面向對象的原則【一切皆是對象】,于是我們要把等式符號也要轉換為對象,我們就引入接口Predicate
在接口Predicate中
public interface Predicate
{
public boolean evaluate(Object object);
}
只有一個方法evaluate,他返回true或者false;
在common collections中 與等式符號對于的有
EqualPredicate ==
NotPredicate !=
TruePredicate true
FalsePredicate false
OrPredicate ||
AndPredicate &&
等等
而本身因為鏈接的是對象,因此和對象相關的特定Predicate有
IdentityPredicate
InstanceOfPredicate
NullPredicateNullIsTruePredicate
NotNullPredicateNullIsFalsePredicate
UniquePredicate
除了上面已經實現的Predicate,而Predicate本身因為是接口,所有用戶也可以自己去實現自己的Predicate
比如
public class LaunchPredicate implements Predicate {
public LaunchPredicate( ) {}
public boolean evaluate(Object object) {
if(...)
{return true}
return false;
}}
另外我們說過Predicate本身是對象,而等式鏈接的對象當然也可以是Predicate對象本身,因此就有了無窮無盡的
組合,也就是Composite Predicates
上面的AndPredicate和OrPredicate就是能鏈接Predicate對象的Predicate,除此之外還有
AllPredicate, OnePredicate, AnyPredicate, NonePredicate 當然從原則上說其他Predicate的實現也能
鏈接Predicate對象,但是含義不夠通用。
前面對照面向過程語言我講的是if/while后面的斷言部分,那么我們現在看看if/while后面do的部分
在面向過程語言中,do就是一段代碼。那么如果利用面向對象的思想來思考,那么do應該是一個對象,那這個對象
就是Closure(終結者)
public interface Closure
{
public void execute(java.lang.Object input);
}
在接口中,我們可以看出里面只有一個execute接口,用戶可以實現自己的Closure來實現代碼片段想做的事情。
接下來,有個問題,多個代碼片段可能合成一個片段,那么怎么處理,Closure 同樣可以,只要引入ChainedClosure
就可以了,ChainedClosure的用法
Closure[] cArray = new Closure[] { repairShielding, fuel };
Closure preLaunch = new ChainedClosure( cArray );
我們剛才看的只是片段里面沒有if/while 斷言,如果片段里面有if/while 斷言怎么辦,那么這個時候我們
根據上面講的斷言引入三個特殊的Closure:IfClosure 和WhileClosure/ForClosure
他們的用法如下
Predicate isWinning = new Predicate( ){...}
Closure sell = new Closure( ){...}
Closure buy = new Closure( ){...}
Closure stockAction = new IfClosure( isWinning, buy, sell );
這是一個買賣股票的例子,如果isWinning為true 那么執行買入buy,否則執行賣出;
Closure drive = new Closure( ) {...}
Predicate hasFuel = new Predicate( ) {...}
Closure useAllFuel = new WhileFuel( hasFuel, drive );
執行循環數字那么就用ForClosure
Closure driveSome = new ForClosure( 5, drive );
到這里,我們需要回過來看看對象的斷言,對象怎么能夠相互比較的,其實很簡單,有兩種方法,一種
方法:繼承Comparable,實現CompareTo方法 另外一種是:用外部的Comparator來比較。
參考【我寫過一篇文件
http://linugb118.blog.ccidnet.com/blog-htm-do-showone-uid-39808-type-blog-itemid-102094.html】
我們這里講的是Comparator,Comparator比較靈活,獨立于對象。
public interface Comparator
{
public int compare(Object o1, Object o2);
public boolean equals(Object obj);
}
Comparator接口中主要需要實現的是compare方法。你可以自己寫自己的Comparator,當然collections中
已經有了些常用的Comparator
如ReverseComparator 反向比較
如果要給book反向排序,那么就這樣使用Collections.sort( books, reverseComparator );
多個比較器同時使用就用ComparatorChain;
如下面例子:
ComparatorChain comparatorChain = new ComparatorChain( );
comparatorChain.addComparator( new BeanComparator( "lastName" ) );
comparatorChain.addComparator( new BeanComparator( "firstName" ) );
comparatorChain.addComparator( new BeanComparator( "age" ), true );
除此之外 還有特定的比較器NullComparator和FixedOrderComparator
他們的用法:
Comparator nullComparator = new NullComparator( BookComparator );
和
String[] medalOrder = {"tin", "bronze", "silver", "gold", "platinum"};
Comparator medalComparator = new FixedOrderComparator( medalOrder );----按medalOrder指定的順序排
到現在為止,面向對象的四劍客我們已經講過三個,剩下最后一個是Transformer(轉換)
public interface Transformer
{
public java.lang.Object transform(java.lang.Object input)
}
其實從程序的角度,我覺得Transformer和Closure都是將input 的東西進行處理然后輸出,好像一樣的,
沒錯,從程序角度是一樣的,但是如果從對象角度來看,他們還是有一些區別,Transformer是將一個對象
轉換為其他對象或者一個新的對象,一般不太會改變對象只是做一些轉換,而Closure就是處理對象,會在
上面做很多改變。
Transformer同樣可以用ChainedTransformer來將多個Transformer鏈起來使用
Transformer[] chainElements = new Transformer[] { multiply, increment };
Transformer chain = new ChainedTransformer( chainElements );
另外Transformer可以和Predicate一起形成條件開關轉換器SwitchTransform
Predicate[] pArray = new Predicate[] { isOdd, isEven };
Transformer[] tArray = new Transformer[] { oddTransform, evenTransform };
Transform predicateTransform =
new SwitchTransform( pArray, tArray, new NOPTransformer( ) );
這里如果isOdd就執行oddTransform,如果isEven就執行evenTransform,默認執行new NOPTransformer()
讀完上面的四劍客,估計你應該對面向對象一切皆為對象的理念有了更深刻的理解的。^_^
上次有幸參加了高煥堂老師的軟件架構的講座:
總結了下面幾點:
1.做框架的思想很簡單,就是所謂的雕刻之道,軟件就如一塊大理石,把多余的部分去掉,那就可以了
再比如如何做汽車的框架,為了滿足汽車能在沙灘上,地面上,山坡上跑,我們只要把輪胎去掉,那么
剩下的就是框架,做軟件完整的API不要寫,留給空位就行了。
2.麥肯錫的思路(反向思維):當需要想完成某個目標的時候,往往一般人會想我現在應該先去做什么,然后
再做什么,這樣的思路往往出來的step by step 只有一種,如果反向思維,從現在目標開始反向推理出前一
階段的幾種可能性,然后從分別對這幾種可能性再向前推,以此類推可以形成一個樹狀,然后根據先有情況
去除不能滿足的鏈路,這樣同樣的問題 你的思路和方法以及可選擇的路線就多很多,往往不是一條。這就是
反向思維
3.如果把軟件生產比作工廠,請問軟件工廠的原料是什么? 是需求? 如果回答是需求,那就錯了。
需求和架構沒有關系。需求是桌面,架構是桌腳,桌腳的要幾個,什么形狀和桌面沒有關系,只要桌腳能支持
桌面就行了。
4.架構就像萬里長城,他是保護自己人的,是自己人能安居樂業,外面的多變都被萬里長城擋在外面,
框架下面的可以多變,沒錢就改版,改版就有錢。
5.寫框架的是強龍,寫AP的是地頭蛇。買主出現才有地頭蛇。也就是需求出現的時候才有地頭蛇。
6.軟件哲學,如何讓先寫的call后寫的?引入接口和基類就能完成這個問題
7.子接口因為都是基礎基類,那么他們之間怎么new,如果他們要new 也就向框架要,這樣才能不違背框架的用意。
8.框架先不要考慮太多效率的問題,效率的問題在后面慢慢修改,這樣可以減少考慮的因素,更容易理清。
9.強龍要有主控權,那么框架所做的事情就是能讓強龍能包容改變
10.如果不想子系統繼承那么就用final 關鍵字
11.如果兩個類 不要相互繼承,但是要他們相互call,那么就在他們里面分別定義一個方法,相互call
12.基類告訴子類,讓子類call她,那么子類才能call基類,沒有call子類,那么子類不能先去call她。
13一般進程process 是不共享的,他們在不同的位置區間,如果要跨進程的call,那么用IPC。而Process
一般分Main thread;Message Quene;Main Looper。其中Main thread是主線程,他通過Looper 一直查看他的
MQ,MQ記錄要求做的事情,如果MQ里面有什么事情,那么Main thread 就拿到他把他做掉。
14.Main thread 主要是處理UI相關的用戶事件,而且一般有時間設置比如每個function不能超過5s。
15Andriod中是通過IBinder 來實現跨進程的通信。
16.主線程一定有一個MQ 一個Looper。而小線程沒有,所有小線程從一開始到執行完就結束了,但是小線程
不能touch UI,只有Main thread 可以touch UI相關的用戶事件。
17 架構師是在暗室里面抓黑貓,在沒有路的情況下找出一條可行之路,所有沒有步驟可言。
18 做框架 盡量把人家會抓住你的地方分開,如果實在分不開,可以當壁虎,把壁虎的尾巴給人家抓。
19 框架的東西盡量要用c++寫,因為c++比起java 安全,快,無反編譯。
20 做一個系統一定要只要你的控制中心和整合中心,而且他們只有一個并且只有一個連接。控制中心好比大腦
而整合中心好比骨骼。在控制中心可以增加狀態機來增加控制力和安全性。