以下文字摘自:JOIN, JOIN2, HQL, Fetch
Join用法:
主要有Inner Join 及 Outer Join:
最常用的(默認是Inner):
Select <要選擇的字段> From <主要資料表>
<Join 方式> <次要資料表> [On <Join 規則>]
Inner Join 的主要精神就是 exclusive , 叫它做排他性吧! 就是講 Join 規則不相符的資料就會被排除掉, 譬如講在 Product 中有一項產品的供貨商代碼 (SupplierId), 沒有出現在 Suppliers 資料表中, 那么這筆記錄便會被排除掉
Outer Join:
Select <要查詢的字段> From <Left 資料表>
<Left | Right> [Outer] Join <Right 資料表> On <Join 規則>
語法中的 Outer 是可以省略的, 例如你可以用 Left Join 或是 Right Join, 在本質上, Outer Join 是 inclusive, 叫它做包容性吧! 不同于 Inner Join 的排他性, 因此在 Left Outer Join 的查詢結果會包含所有 Left 資料表的資料, 顛倒過來講, Right Outer Join 的查詢就會包含所有 Right 資料表的資料
另外,還有全外聯:
FULL JOIN 或 FULL OUTER JOIN
完整外部聯接返回左表和右表中的所有行。當某行在另一個表中沒有匹配行時,則另一個表的選擇列表列包含空值。如果表之間有匹配行,則整個結果集行包含基表的數據值。
以及,
交叉聯接
交叉聯接返回左表中的所有行,左表中的每一行與右表中的所有行組合。交叉聯接也稱作笛卡爾積。
沒有 WHERE 子句的交叉聯接將產生聯接所涉及的表的笛卡爾積。第一個表的行數乘以第二個表的行數等于笛卡爾積結果集的大小。也就是說在沒有 WHERE 子句的情況下,若表 A 有 3 行記錄,表 B 有 6 行記錄 : :
SELECT A.*,B.* FROM 表A CROSS JOIN 表B
那以上語句會返回 18 行記錄。
Fetch:
在我們查詢Parent對象的時候,默認只有Parent的內容,并不包含childs的信息,如果在Parent.hbm.xml里設置lazy="false"的話才同時取出關聯的所有childs內容.
問題是我既想要hibernate默認的性能又想要臨時的靈活性該怎么辦? 這就是fetch的功能。我們可以把fetch與lazy="true"的關系類比為事務當中的編程式事務與聲明式事務,不太準確,但是大概是這個意思。
總值,fetch就是在代碼這一層給你一個主動抓取得機會.
Parent parent = (Parent)hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query q = session.createQuery(
"from Parent as parent "+
" left outer join fetch parent.childs " +
" where parent.id = :id"
);
q.setParameter("id",new Long(15));
return (Parent)q.uniqueResult();
}
});
Assert.assertTrue(parent.getChilds().size() > 0);
你可以在lazy="true"的情況下把fetch去掉,就會報異常. 當然,如果lazy="false"就不需要fetch了
HQL一些特色方法:
in and between may be used as follows:
from DomesticCat cat where cat.name between 'A' and 'B'
from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
and the negated forms may be written
from DomesticCat cat where cat.name not between 'A' and 'B'
from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
Likewise, is null and is not null may be used to test for null values.
Booleans may be easily used in expressions by declaring HQL query substitutions in Hibernate configuration:
<property name="hibernate.query.substitutions">true 1, false 0</property>
This will replace the keywords true and false with the literals 1 and 0 in the translated SQL from this HQL:
from Cat cat where cat.alive = true
You may test the size of a collection with the special property size, or the special size() function.
from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
For indexed collections, you may refer to the minimum and maximum indices using minindex and maxindex functions. Similarly, you may refer to the minimum and maximum elements of a collection of basic type using the minelement and maxelement functions.
from Calendar cal where maxelement(cal.holidays) > current_date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
The SQL functions any, some, all, exists, in are supported when passed the element or index set of a collection (elements and indices functions) or the result of a subquery (see below).
select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)
Note that these constructs - size, elements, indices, minindex, maxindex, minelement, maxelement - may only be used in the where clause in Hibernate3.
Elements of indexed collections (arrays, lists, maps) may be referred to by index (in a where clause only):
from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
The expression inside [] may even be an arithmetic expression.
select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
HQL also provides the built-in index() function, for elements of a one-to-many association or collection of values.
select item, index(item) from Order order
join order.items item
where index(item) < 5
Scalar SQL functions supported by the underlying database may be used
from DomesticCat cat where upper(cat.name) like 'FRI%'
posted @
2007-07-26 16:44 我愛佳娃 閱讀(33589) |
評論 (1) |
編輯 收藏
這幾個學習材料非常短小精悍,可清晰快捷的掌握以下幾個概念,方便更深入學習。建議象我一樣沒接觸過或者有疑惑的同學看一下。每個項目10至20分鐘就可以看完、看懂:
XML tutorial:
http://www.w3schools.com/xml/default.asp
SOAP tutorial:
http://www.w3schools.com/soap/default.asp
WSDL tutorial:
http://www.w3schools.com/wsdl/default.asp
WEB Service tutorial:
http://www.w3schools.com/webservices/default.asp
posted @
2007-07-10 10:25 我愛佳娃 閱讀(1688) |
評論 (1) |
編輯 收藏
DWR2.0的推技術:這里有介紹
comet的實現介紹:這里
其中的原理在于維護HTTP長連接,這里有介紹
摘錄一部分,說明其原理:
Pushlet基于HTTP流,這種技術常常用在多媒體視頻、通訊應用中,比如QuickTime。與裝載HTTP頁面之后馬上關閉HTTP連接的做法相反,Pushlet采用HTTP流方式將新數據源源不斷地推送到client,再此期間HTTP連接一直保持打開。有關如何在Java中實現這種Keep-alive的長連接請參看Sun提供的《HTTP Persistent Connection》和W3C的《HTTP1.1規范》。
示例1
我們利用HTTP流開發一個JSP頁面(因為它易于部署,而且它在web server中也是作為servlet對待的),此頁面在一個定時器循環中不斷地發送新的HTML內容給client:
<%
int i = 1;
try {
while (true) {
out.print("<h1>"+(i++)+"</h1>");
out.flush();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
out.print("<h1>"+e+"</h1>");
}
}
} catch (Exception e) {
out.print("<h1>"+e+"</h1>");
}
%>
在Pushlet源代碼中提供了此頁面(examples/basics/push-html-stream.jsp)。上面的頁面并不是十分有用,因為在我們刷新頁面時,新內容機械地、持續不斷地被添加到頁面中,而不是server端更新的內容。
示例2 現在讓我們步入Pushlet工作機理中一探究竟。通過運行Pushlet的示例源代碼(examples/basics/ push-js-stream.html),我們會看到這個每3秒刷新一次的頁面。那么它是如何實現的呢?
此示例中包含了三個文件:push-js-stream.html、push-js-stream-pusher.jsp、push-js-stream-display.html。
其中push-js-stream.html是主框架文件,它以HTML Frame的形式包含其它兩個頁面。
push-js-stream-pusher.jsp是一個JSP,它執行在server端,此文件內容如下:
7: <%
8: /** Start a line of JavaScript with a function call to parent frame. */
9: String jsFunPre = "<script language=JavaScript >parent.push('";
10:
11: /** End the line of JavaScript */
12: String jsFunPost = "')</script> ";
13:
14: int i = 1;
15: try {
16:
17: // Every three seconds a line of JavaScript is pushed to the client
18: while (true) {
19:
20: // Push a line of JavaScript to the client
21: out.print(jsFunPre+"Page "+(i++)+jsFunPost);
22: out.flush();
23:
24: // Sleep three secs
25: try {
26: Thread.sleep(3000);
27: } catch (InterruptedException e) {
28: // Let client display exception
29: out.print(jsFunPre+"InterruptedException: "+e+jsFunPost);
30: }
31: }
32: } catch (Exception e) {
33: // Let client display exception
34: out.print(jsFunPre+"Exception: "+e+jsFunPost);
35: }
36: %>
注意在示例1和示例2中使用JSP時都存在一個問題:一些servlet引擎在某個client離開時會“吃掉”IOException,以至于JSP頁面將永不拋出此異常。所以在這種情況下,頁面循環將會永遠執行下去。而這正是Pushlet實現采用servlet的原因之一:可以捕獲到IOException。 在上面代碼的第21行中可以看到在一個定時器循環(3秒/周期)中打印了一些HTML并將它們輸出到client瀏覽器。請注意,這里推送的并非HTML而是Javascript!這樣做的意義何在?
它把類似“<script language=JavaScript >parent.push('Page 4')</script>”的一行代碼推送到瀏覽器;而具有JavaScript引擎的瀏覽器可以直接執行收到的每一行代碼,并調用parent.push()函數。而代碼中的Parent便是瀏覽器頁面中所在Frame的Parent,也就是push-js-stream.html。讓我們看看都發生了什么?
<script LANGUAGE="JavaScript">
var pageStart="<HTML><HEAD></HEAD><BODY BGCOLOR=blue TEXT=white><H2>Server pushes: <para>";
var pageEnd="</H2></BODY></HTML>";
// Callback function with message from server.
// This function is called from within the hidden JSP pushlet frame
function push(content) {
// Refresh the display frame with the content received
window.frames['displayFrame'].document.writeln(pageStart+content+pageEnd);
window.frames['displayFrame'].document.close();
}
</script>
<!-- frame to display the content pushed by the pushlet -->
<!-- Hidden frame with the pushlet that pushes lines of JavaScript-->
</FRAMESET>
可以看到push-js-stream.html中的push()函數被名為pushletFrame的JSP Frame調用:把傳入的參數值寫入到displayFrame(此Frame為push-js-stream-display.html)。這是動態HTML的一個小技巧:使用document對象的writeln方法刷新某個Frame或者Window的內容。
于是displayFrame成為了用于顯示內容的、真正的視圖。displayFrame初始化為黑色背景并顯示“wait…”直到來自server的內容被推送過來:
<H1>WAIT...</H1>
這便是Pushlet的基本做法:我們從servlet(或者從示例中的JSP)把JavaScript代碼作為HTTP流推送到瀏覽器。這些代碼被瀏覽器的JavaScript引擎解釋并完成一些有趣的工作。于是便輕松地完成了從server端的Java到瀏覽器中的JavaScript的回調。
上面的示例展示了Pushlet原理,但這里存在一些等待解決的問題和需要增添的特性。于是我建立了一個小型的server端Pushlet框架(其類結構圖表將會展示在下面),添加了一些用在client中的JavaScript庫。由于client需要依賴更多的DHTML特性(比如Layers),我們將首先粗略地溫習一些
DHTML知識。示例代碼見examples/dhtml。
posted @
2007-07-03 18:10 我愛佳娃 閱讀(2326) |
評論 (0) |
編輯 收藏
什么叫北向或者南向接口:
A northbound interface is an interface that conceptualizes lower level details. It interfaces to higher level layers and is normally drawn at the top of an architectural overview.
A southbound interface decomposes concepts in the technical details, mostly specific to a single component of the architecture. Southbound interfaces are drawn at the bottom of an architectural overview.
Northbound interfaces normally talk to southbound interfaces of higher level components and vice versa.
These terms are generic in the sense that they are uniformly used over all layers of an application, i.e. independent of the fact that the system is about hardware, GUI, middle-ware etc.
A northbound interface is typically an output-only interface (as opposed to one that accepts user input) found in carrier-grade network and telecommunications network elements. The languages or protocols commonly used include SNMP and TL1. For example, a device that is capable of sending out syslog messages but is not configurable by the user is said to implement a northbound interface.
B-NT: Broadband-Network Termination. A specific type of Broadband CPE used in DSL networks.
CPE: Customer Premises Equipment; 用戶預置設備。it refers to any TR-069-compliant devices and therefore covers both Internet Gateway Devices and LAN-side end devices.
CWMP: CPE WAN Management Protocol (the subject of TR069 standard).
Managed Object (MO): A managed object is a software object that encapsulates the manageable characteristics and behaviors of a particular Network Resource.
posted @
2007-07-02 11:30 我愛佳娃 閱讀(1037) |
評論 (0) |
編輯 收藏
dojo提供了不錯的樹控件,但上下文菜單比較簡單,不能動態改變:比如我想根據不同節點顯示不同的上下文菜單就比較困難,根據多種實驗和查閱下面提供一種實現方式。在此過程中也學到不少東西。先把解決方案說一下,再把發現過程說一下:
DOJO提供了AOP的方式來“注入”代碼,我們就把修改menu的代碼注入到TreeContextMenuV3里就可以了:
在open事件前注入我們的代碼:
dojo.event.connect("before", dojo.widget.byId("contextMenu1"), "open", this, "onContextMenuOpen");
注意before是表示在menu打開之前調用,其它關鍵字還可以是after,around。強,贊一個!
在onContextMenuOpen函數中,做改變菜單的事情:

this.onContextMenuOpen = function (evt)
{
dojo.log.info ("before context open");
dojo.log.info (evt);
var m = dojo.widget.byId("contextMenu1");
dojo.log.info (m);
m.removeChild (dojo.widget.byId("treeContextMenuEdit"));
m.removeChild (dojo.widget.byId("treeContextMenuDown"));
m.removeChild (dojo.widget.byId("treeContextMenuCreate"));
m.removeChild (dojo.widget.byId("treeContextMenuCreate2"));


if (null == this.context_menu["MenuItem2"])
{

var id = dojo.widget.createWidget("MenuItem2",
{caption: "Page Info"});
dojo.log.info (id);
this.context_menu["MenuItem2"] = id;
m.addChild(id);
}
//m.destroyChildren ();
dojo.log.info (m);
//w.destory ();
};
在這里可以根據節點來增刪菜單項了,context_menu成員是用來記錄加入過的菜單項,以免重復加入。
另外,removeChild沒有destory掉菜單項,應該可以重復使用。所以,我設想的實現是,在程序開頭將所有可能的菜單項動態創建好,存在一個MAP中,然后,在這里來動態加刪它們。
下面是尋找解決方法的過程:
剛開始時,我想是改變整個樹的菜單,確實也找到了可以編程改變它的方法:
var ctxMenu = dojo.widget.byId("contextMenu");
var tree = dojo.widget.byId("phyTree");
dojo.log.info (ctxMenu);

ctxMenu.listenTree(tree);
ctxMenu.bindDomNode(tree.domNode);

關鍵是一句:bindDomNode。
這樣雖然可以動態“加載”菜單了,可是沒有“時機”來加載新菜單,無法達到根據節點變化來做改變。
隨后,我又想到重載,去創造這個“時機”:
dojo.require ("dojo.widget.TreeContextMenuV3");

dojo.provide("mywidgets.MyTreeContextMenu");

dojo.widget.defineWidget(
// widget name and class
"mywidgets.MyTreeContextMenu",
// superclass
[dojo.widget.TreeContextMenuV3],


function()
{
dojo.log.info ("my context menu create1");
},
// properties and methods

{

open: function()
{
var result = dojo.widget.PopupMenu2.prototype.open.apply(this, arguments);
dojo.log.info ("my context menu create");

for(var i=0; i< this.children.length; i++)
{

/**//* notify children */

if (this.children[i].menuOpen)
{
this.children[i].menuOpen(this.getTreeNode());
}
}
return result;
}
}
);

這也是我第一次重載DOJO,發現其實很簡單,關鍵要注意它的namespace:
dojo.require()語句的尋找方法是:
dojo.xxx => dojo/src/xxx.js
dojo.xxx.yyy => dojo/src/xxx/yyy.js
dojo.xxx.yyy.zzz => dojo/src/xxx/yyy/zzz.js
如果遇到不是dojo開頭時,它的尋找方法是:
example.xxx => dojo/../example/xxx.js
example.xxx.yyy => dojo/../example/xxx/yyy.js
example.xxx.yyy.zzz => dojo/../example/xxx/yyy/zzz.js
所以要把自己的代碼放到跟dojo同級就可以了。
這個辦法我沒往下試,因為重載的是contextmenu,在它里面把它“整個自己”換成別的menu,我覺得是不可行的。而看半天代碼也沒找著在哪重載右鍵點擊這個事件。不過啟發我可以更換子item來解決。
于是有了開頭的解決方案。
posted @
2007-07-01 11:15 我愛佳娃 閱讀(2613) |
評論 (3) |
編輯 收藏
我使用MSN博客兩年,幾乎是在它推出,并剛為中國用戶所知時,就使用了。兩年來發表了很多游記、照片、感想、評論。
但是,最近它被無情刪除了,多次發送郵件給微軟技術支持,都石沉大海。所以,我要以最強烈和最負責任的態度在這里呼吁和告誡每一位博客使用者:
請象遠離毒品一樣,遠離MSN博客!
(天下雖無免費的蛋糕,但就算施舍給乞丐的蛋糕也沒有微軟這種做法的。)
posted @
2007-06-21 18:03 我愛佳娃 閱讀(354) |
評論 (0) |
編輯 收藏
spring包裝了rmi后,使我們得到幾點便利:
不用調用rmic編譯stub和skeleton
不用直接實現remote接口
不需要啟動命名服務rmiregistry
但,卻不支持原來rmi的回調功能。查閱許多網頁也不得其解。
今天,想到一招,共享出來,如果大家有好辦法歡迎回貼共享。
正常做法是:
回調一般是用在一群client端需要server來通知的情況,一般server就用Vector來保存client對象。
server端需要提供一個方法,client把對象傳過來后,保存到Vector中,以后就可以通知client們了:
register (ClientObject obj);
在spring里,基本做法是一樣的,唯一不同是,在這個方法里,client不能傳對象,我們就傳一個client提供出來的rmi對象的url:
register(String url);
在client端,就如正常使用先獲得server對象,再調用這個方法,注意組成url的代碼:
NodeService service = (NodeService) factory.getAPIObject("nodeServiceProxy");
String name = null;

try
{
name = "rmi://" + InetAddress.getLocalHost().getHostName() + "/NodeNotifyService";

} catch (Exception ue)
{}
int result = service.registerFlower (name);
logger.info ("result="+result);

在server端registerFlower處理里,根據url動態創建這個對象,代碼如下:

public class NodeServiceImpl implements NodeService
{
public static final Logger logger = LoggerFactory.getLogger(NodeServiceImpl.class);
public NodeNotifyService service = null;

public int registerFlower (String url)
{
logger.info (url);
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceInterface(NodeNotifyService.class);
logger.info ("begin set url");
rmiProxyFactoryBean.setServiceUrl(url);

try
{
logger.info ("begin set");
rmiProxyFactoryBean.afterPropertiesSet();

} catch (Exception ex)
{
logger.info ("exception");
}


if (rmiProxyFactoryBean.getObject() instanceof NodeNotifyService)
{
service = (NodeNotifyService) rmiProxyFactoryBean.getObject();
}

return 0;
}
}


這樣就從url轉換成client對象了,當然了,還是需要在client和server各自配置文件里配置RmiServiceExporter,這一步很簡單,和正常的spring的rmi是一樣的了。
做到這步后,我還想把所有接口文件放到一個JAR里,client和server的實現,以及各自邏輯放到各自的JAR中,這樣各自改實現就不需要兩邊更新包。改接口的話,就更新接口所在JAR。不知道這樣安排是否合理?
posted @
2007-06-21 15:46 我愛佳娃 閱讀(3279) |
評論 (4) |
編輯 收藏
1、誰創建線程?
即使您從未顯式地創建一個新線程,您仍可能會發現自己在使用線程。線程被從各種來源中引入到我們的程序中。
有許多工具可以為您創建線程,如果要使用這些工具,應該了解線程如何交互,以及如何防止線程互相干擾。
2、AWT 和 Swing
任何使用 AWT 或 Swing 的程序都必須處理線程。AWT 工具箱創建單個線程,用于處理 UI 事件,任何由 AWT 事件調用的事件偵聽器都在 AWT 事件線程中執行。
您不僅必須關心同步對事件偵聽器和其它線程之間共享的數據項的訪問,而且還必須找到一種方法,讓由事件偵聽器觸發的長時間運行任務(如在大文檔中檢查拼寫或在文件系統中搜索一個文件) 在后臺線程中運行,這樣當該任務運行時,UI 就不會停滯了(這可能還會阻止用戶取消操作)。這樣做的一個好的框架示例是 SwingWorker 類
AWT 事件線程并不是守護程序線程;這就是通常使用 System.exit() 結束 AWT 和 Swing 應用程序的原因。
3、使用 TimerTask
JDK 1.3 中,TimerTask 工具被引入到 Java 語言。這個便利的工具讓您可以稍后在某個時間執行任務(例如,即從現在起十秒后運行一次任務),或者定期執行任務(即,每隔十秒運行任務)。
實現 Timer 類非常簡單:它創建一個計時器線程,并且構建一個按執行時間排序的等待事件隊列。
TimerTask 線程被標記成守護程序線程,這樣它就不會阻止程序退出。
因為計時器事件是在計時器線程中執行,所以必須確保正確同步了針對計時器任務中使用的任何數據項的訪問。
在 CalculatePrimes 示例中,并沒有讓主線程休眠,我們可以使用 TimerTask,方法如下:
public static void main(String[] args) {
Timer timer = new Timer();
final CalculatePrimes calculator = new CalculatePrimes();
calculator.start();
timer.schedule(
new TimerTask() {
public void run()
{
calculator.finished = true;
}
}, TEN_SECONDS);
}
4、servlet 和 JavaServer Pages 技術
servlet 容器創建多個線程,在這些線程中執行 servlet 請求。作為 servlet 編寫者,您不知道(也不應該知道)您的請求會在什么線程中執行;如果同時有多個對相同 URL 的請求入站,那么同一個 servlet 可能會同時在多個線程中是活動的。
當編寫 servlet 或 JavaServer Pages (JSP) 文件時,必須始終假設可以在多個線程中并發地執行同一個 servlet 或 JSP 文件。必須適當同步 servlet 或 JSP 文件訪問的任何共享數據;這包括 servlet 對象本身的字段。
5、實現 RMI 對象
RMI 工具可以讓您調用對在其它 JVM 中運行的對象進行的操作。當調用遠程方法時,RMI 編譯器創建的 RMI 存根會打包方法參數,并通過網絡將它們發送到遠程系統,然后遠程系統會將它們解包并調用遠程方法。
假設您創建了一個 RMI 對象,并將它注冊到 RMI 注冊表或者 Java 命名和目錄接口(Java Naming and Directory Interface (JNDI))名稱空間。當遠程客戶機調用其中的一個方法時,該方法會在什么線程中執行呢?
實現 RMI 對象的常用方法是繼承 UnicastRemoteObject。在構造 UnicastRemoteObject 時,會初始化用于分派遠程方法調用的基礎結構。這包括用于接收遠程調用請求的套接字偵聽器,和一個或多個執行遠程請求的線程。
所以,當接收到執行 RMI 方法的請求時,這些方法將在 RMI 管理的線程中執行。
6、小結
線程通過幾種機制進入 Java 程序。除了用 Thread 構造器中顯式創建線程之外,還可以用許多其它機制創建線程:
AWT 和 Swing
RMI
java.util.TimerTask 工具
servlet 和 JSP 技術
共享變量
1、 共享變量
要使多個線程在一個程序中有用,它們必須有某種方法可以互相通信或共享它們的結果。
讓線程共享其結果的最簡單方法是使用共享變量。它們還應該使用同步來確保值從一個線程正確傳播到另一個線程,以及防止當一個線程正在更新一些相關數據項時,另一個線程看到不一致的中間結果。
線程基礎中計算素數的示例使用了一個共享布爾變量,用于表示指定的時間段已經過去了。這說明了在線程間共享數據最簡單的形式是:輪詢共享變量以查看另一個線程是否已經完成執行某項任務。
2、存在于同一個內存空間中的所有線程
正如前面討論過的,線程與進程有許多共同點,不同的是線程與同一進程中的其它線程共享相同的進程上下文,包括內存。這非常便利,但也有重大責任。只要訪問共享變量(靜態或實例字段),線程就可以方便地互相交換數據,但線程還必須確保它們以受控的方式訪問共享變量,以免它們互相干擾對方的更改。
任何線程可以訪問所有其作用域內的變量,就象主線程可以訪問該變量一樣。素數示例使用了一個公用實例字段,叫做 finished,用于表示已經過了指定的時間。當計時器過期時,一個線程會寫這個字段;另一個線程會定期讀取這個字段,以檢查它是否應該停止。注:這個字段被聲明成 volatile,這對于這個程序的正確運行非常重要。在本章的后面,我們將看到原因。
3、受控訪問的同步
為了確保可以在線程之間以受控方式共享數據,Java 語言提供了兩個關鍵字:synchronized 和 volatile。
Synchronized 有兩個重要含義:它確保了一次只有一個線程可以執行代碼的受保護部分(互斥,mutual exclusion 或者說 mutex),而且它確保了一個線程更改的數據對于其它線程是可見的(更改的可見性)。
如果沒有同步,數據很容易就處于不一致狀態。例如,如果一個線程正在更新兩個相關值(比如,粒子的位置和速率),而另一個線程正在讀取這兩個值,有可能在第一個線程只寫了一個值,還沒有寫另一個值的時候,調度第二個線程運行,這樣它就會看到一個舊值和一個新值。同步讓我們可以定義必須原子地運行的代碼塊,這樣對于其他線程而言,它們要么都執行,要么都不執行。
同步的原子執行或互斥方面類似于其它操作環境中的臨界段的概念。
4、確保共享數據更改的可見性
同步可以讓我們確保線程看到一致的內存視圖。
處理器可以使用高速緩存加速對內存的訪問(或者編譯器可以將值存儲到寄存器中以便進行更快的訪問)。在一些多處理器體系結構上,如果在一個處理器的高速緩存中修改了內存位置,沒有必要讓其它處理器看到這一修改,直到刷新了寫入器的高速緩存并且使讀取器的高速緩存無效。
這表示在這樣的系統上,對于同一變量,在兩個不同處理器上執行的兩個線程可能會看到兩個不同的值!這聽起來很嚇人,但它卻很常見。它只是表示在訪問其它線程使用或修改的數據時,必須遵循某些規則。
Volatile 比同步更簡單,只適合于控制對基本變量(整數、布爾變量等)的單個實例的訪問。當一個變量被聲明成 volatile,任何對該變量的寫操作都會繞過高速緩存,直接寫入主內存,而任何對該變量的讀取也都繞過高速緩存,直接取自主內存。這表示所有線程在任何時候看到的 volatile 變量值都相同。
如果沒有正確的同步,線程可能會看到舊的變量值,或者引起其它形式的數據損壞。
5、用鎖保護的原子代碼塊
Volatile 對于確保每個線程看到最新的變量值非常有用,但有時我們需要保護比較大的代碼片段,如涉及更新多個變量的片段。
同步使用監控器(monitor)或鎖的概念,以協調對特定代碼塊的訪問。
每個 Java 對象都有一個相關的鎖。同一時間只能有一個線程持有 Java 鎖。當線程進入 synchronized 代碼塊時,線程會阻塞并等待,直到鎖可用,當它可用時,就會獲得這個鎖,然后執行代碼塊。當控制退出受保護的代碼塊時,即到達了代碼塊末尾或者拋出了沒有在 synchronized 塊中捕獲的異常時,它就會釋放該鎖。
這樣,每次只有一個線程可以執行受給定監控器保護的代碼塊。從其它線程的角度看,該代碼塊可以看作是原子的,它要么全部執行,要么根本不執行。
6、簡單的同步示例
使用 synchronized 塊可以讓您將一組相關更新作為一個集合來執行,而不必擔心其它線程中斷或看到計算的中間結果。以下示例代碼將打印“1 0”或“0 1”。如果沒有同步,它還會打印“1 1”(或“0 0”,隨便您信不信)。
public class SyncExample {
private static lockObject = new Object();
private static class Thread1 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 0;
System.out.println(x);
}
}
}
private static class Thread2 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 1;
System.out.println(y);
}
}
}
public static void main(String[] args) {
new Thread1().run();
new Thread2().run();
}
}
在這兩個線程中都必須使用同步,以便使這個程序正確工作。
7、Java 鎖定
Java 鎖定合并了一種互斥形式。每次只有一個線程可以持有鎖。鎖用于保護代碼塊或整個方法,必須記住是鎖的身份保護了代碼塊,而不是代碼塊本身,這一點很重要。一個鎖可以保護許多代碼塊或方法。
反之,僅僅因為代碼塊由鎖保護并不表示兩個線程不能同時執行該代碼塊。它只表示如果兩個線程正在等待相同的鎖,則它們不能同時執行該代碼。
在以下示例中,兩個線程可以同時不受限制地執行 setLastAccess() 中的 synchronized 塊,因為每個線程有一個不同的 thingie 值。因此,synchronized 代碼塊受到兩個正在執行的線程中不同鎖的保護。
public class SyncExample {
public static class Thingie {
private Date lastAccess;
public synchronized void setLastAccess(Date date) {
this.lastAccess = date;
}
}
public static class MyThread extends Thread {
private Thingie thingie;
public MyThread(Thingie thingie) {
this.thingie = thingie;
}
public void run() {
thingie.setLastAccess(new Date());
}
}
public static void main() {
Thingie thingie1 = new Thingie(),
thingie2 = new Thingie();
new MyThread(thingie1).start();
new MyThread(thingie2).start();
}
}
8、同步的方法
創建 synchronized 塊的最簡單方法是將方法聲明成 synchronized。這表示在進入方法主體之前,調用者必須獲得鎖:
public class Point {
public synchronized void setXY(int x, int y) {
this.x = x;
this.y = y;
}
}
對于普通的 synchronized方法,這個鎖是一個對象,將針對它調用方法。對于靜態 synchronized 方法,這個鎖是與 Class 對象相關的監控器,在該對象中聲明了方法。
僅僅因為 setXY() 被聲明成 synchronized 并不表示兩個不同的線程不能同時執行 setXY(),只要它們調用不同的 Point 實例的 setXY() 就可同時執行。對于一個 Point 實例,一次只能有一個線程執行 setXY(),或 Point 的任何其它 synchronized 方法。
9、同步的塊
synchronized 塊的語法比 synchronized 方法稍微復雜一點,因為還需要顯式地指定鎖要保護哪個塊。Point 的以下版本等價于前一頁中顯示的版本:
public class Point {
public void setXY(int x, int y) {
synchronized (this) {
this.x = x;
this.y = y;
}
}
}
使用 this 引用作為鎖很常見,但這并不是必需的。這表示該代碼塊將與這個類中的 synchronized 方法使用同一個鎖。
由于同步防止了多個線程同時執行一個代碼塊,因此性能上就有問題,即使是在單處理器系統上。最好在盡可能最小的需要保護的代碼塊上使用同步。
訪問局部(基于堆棧的)變量從來不需要受到保護,因為它們只能被自己所屬的線程訪問。
10、大多數類并沒有同步
因為同步會帶來小小的性能損失,大多數通用類,如 java.util 中的 Collection 類,不在內部使用同步。這表示在沒有附加同步的情況下,不能在多個線程中使用諸如 HashMap 這樣的類。
posted @
2007-06-17 12:26 我愛佳娃 閱讀(682) |
評論 (0) |
編輯 收藏
典型的三層結構
三層結構估計大家都很熟悉了。就是表示(presentation)層, 領域(domain)層, 以及基礎架構(infrastructure)層。
表示層邏輯主要處理用戶和軟件的交互。現在最流行的莫過于視窗圖形界面(wimp)和基于html的界面了。表示層的主要職責就是為用戶提供信息,以及把用戶的指令翻譯。傳送給業務層和基礎架構層。 基礎架構層邏輯包括處理和其他系統的通信,代表系統執行任務。例如數據庫系統交互,和其他應用系統的交互等。大多數的信息系統,這個層的最大的邏輯就是存儲持久數據。
還有一個就是領域層邏輯,有時也被叫做業務邏輯。它包括輸入和存儲數據的計算。驗證表示層來的數據,根據表示層的指令指派一個基礎架構層邏輯。
領域邏輯中,人們總是搞不清楚什么事領域邏輯,什么是其它邏輯。例如,一個銷售系統中有這樣一個邏輯:如果本月銷售量比上個月增長10%,就要用紅色標記。要實現這個功能,你可能會把邏輯放在表示層中,比較兩個月的數字,如果超出10%,就標記為紅色。
這樣做,你就把領域邏輯放到了表示層中了。要分離這兩個層,你應該現在領域層中提供一個方法,用來比較銷售數字的增長。這個方法比較兩個月的數字,并返回boolean類型。表示層則簡單的調用該方法,如果返回true,則標記為紅色。
————————————————
| 客戶端層 | 用戶交互,UI實現
| Browser,WirelessDevice,WebService | Http, Soap 協議(SOP體系)
————————————————
————————————————
| 表現層 | 集中登錄,會話管理
| Struts,Jsf,Webwork,Tapstry, Velocity | 內容創建,格式,傳送
————————————————
————————————————
| 業務服務層 | 業務邏輯,事務,數據,服務
| SessionEJB,Spring,Jdoframework) | SessionEjb,POJO Service
————————————————
————————————————
| 集中層 | 資源適配器,遺留/外部系統
|Jms,Jdbc,Connnector,External Service | 規則引擎,工作流
————————————————
(持久化EntityBean,Hibernate,iBatis,Jdo,Dao,TopLink etc.)
————————————————
| 資源層 | 資源,數據庫,外部服務
| DataBase,Resource,External Service | (大型主機,B2B集中系統)
————————————————
原文摘錄如下:
Part 1 層
層(layer)這個概念在計算機領域是非常了不得的一個概念。計算機本身就體現了一種層的概念:系統調用層、設備驅動層、操作系統層、CPU指令集。每個層都負責自己的職責。網絡同樣也是層的概念,最著名的OSI的七層協議。
層到了軟件領域也一樣好用。為什么呢?我們看看使用層技術有什么好處:
● 你使用層,但是不需要去了解層的實現細節。
● 可以使用另一種技術來改變基礎的層,而不會影響上面的層的應用。
● 可以減少不同層之間的依賴。
● 容易制定出層標準。
● 底下的層可以用來建立頂上的層的多項服務。 當然,層也有弱點:
● 層不可能封裝所有的功能,一旦有功能變動,勢必要波及所有的層。
● 效率降低。
當然,層最難的一個問題還是各個層都有些什么,以及要承擔何種責任。
典型的三層結構
三層結構估計大家都很熟悉了。就是表示(presentation)層, 領域(domain)層, 以及基礎架構(infrastructure)層。
表示層邏輯主要處理用戶和軟件的交互。現在最流行的莫過于視窗圖形界面(wimp)和基于html的界面了。表示層的主要職責就是為用戶提供信息,以及把用戶的指令翻譯。傳送給業務層和基礎架構層。 基礎架構層邏輯包括處理和其他系統的通信,代表系統執行任務。例如數據庫系統交互,和其他應用系統的交互等。大多數的信息系統,這個層的最大的邏輯就是存儲持久數據。
還有一個就是領域層邏輯,有時也被叫做業務邏輯。它包括輸入和存儲數據的計算。驗證表示層來的數據,根據表示層的指令指派一個基礎架構層邏輯。
領域邏輯中,人們總是搞不清楚什么事領域邏輯,什么是其它邏輯。例如,一個銷售系統中有這樣一個邏輯:如果本月銷售量比上個月增長10%,就要用紅色標記。要實現這個功能,你可能會把邏輯放在表示層中,比較兩個月的數字,如果超出10%,就標記為紅色。
這樣做,你就把領域邏輯放到了表示層中了。要分離這兩個層,你應該現在領域層中提供一個方法,用來比較銷售數字的增長。這個方法比較兩個月的數字,并返回boolean類型。表示層則簡單的調用該方法,如果返回true,則標記為紅色。
例子
層技術不存在說永恒的技巧。如何使用都要看具體的情況才能夠決定,下面我就列出了三個例子:
例子1:一個電子商務系統。要求能夠同時處理大量用戶的請求,用戶的范圍遍及全球,而且數字還在不斷增長。但是領域邏輯很簡單,無非是訂單的處理,以 及和庫存系統的連接部分。這就要求我們1、表示層要友好,能夠適應最廣泛的用戶,因此采用html技術;2、支持分布式的處理,以勝任同時幾千的訪問; 3、考慮未來的升級。
例子2:一個租借系統。系統的用戶少的多,但是領域邏輯很復雜。這就要求我們制作一個領域邏輯非常復雜的系統,另外,還要給他們的用戶提供一個方便的輸入界面。這樣,wimp是一個不錯的選擇。
例子3:簡單的系統。非常簡單,用戶少、邏輯少。但是也不是沒有問題,簡單意味著要快速交付,并且還要充分考慮日后的升級。因為需求在不斷的增加之中。
何時分層
這樣的三個例子,就要求我們不能夠一概而論的解決問題,而是應該針對問題的具體情況制定具體的解決方法。這三個例子比較典型。
第二個例子中,可能需要嚴格的分成三個層次,而且可能還要加上另外的中介(mediating)層。例3則不需要,如果你要做的僅是查看數據,那僅需要幾個server頁面來放置所有的邏輯就可以了。
我一般會把表示層和領域層/基礎架構層分開。除非領域層/基礎架構層非常的簡單,而我又可以使用工具來輕易的綁定這些層。這種兩層架構的最好的例子就 是在VB、PB的環境中,很容易就可以構建出一個基于SQL數據庫的windows界面的系統。這樣的表示層和基礎架構層非常的一致,但是一旦驗證和計算 變得復雜起來,這種方式就存在先天缺陷了。
很多時候,領域層和基礎架構層看起來非常類似,這時候,其實是可以把它們放在一起的。可是,當領域層的業務邏輯和基礎架構層的組織方式開始不同的時候,你就需要分開二者。
更多的層模式
三層的架構是最為通用的,尤其是對IS系統。其它的架構也有,但是并不適用于任何情況。
第一種是Brown model [Brown et al]。它有五個層:表示層(Presentation),控制/中介層(Controller/Mediator),領域層(Domain), 數據映射層(Data Mapping), 和數據源層(Data Source)。它其實就是在三層架構種增加了兩個中間層。控制/中介層位于表示層和領域層之間,數據映射層位于領域層和基礎架構層之間。
表示層和領域層的中介層,我們通常稱之為表示-領域中介層,是一個常用的分層方法,通常針對一些非可視的控件。例如為特定的表示層組織信息格式,在不 同的窗口間導航,處理交易邊界,提供Server的facade接口(具體實現原理見設計模式)。最大的危險就是,一些領域邏輯被放到這個層里,影響到其 它的表示層。
我常常發現把行為分配給表示層是有好處的。這可以簡化問題。但表示層模型會比較復雜,所以,把這些行為放到非可視化的對象中,并提取出一個表示-領域中介層還是值得的。
Brown ISA
表示層 表示層
控制/中介層 表示-領域中介層
領域層 領域層
數據映射層 數據庫交互模式中的Database Mapper
數據源層 基礎架構層
領域層和基礎架構層之間的中介層屬于本書中提到的Database Mapper模式,是三種領域層到數據連接的辦法之一。和表示-領域中介層一眼,有時候有用,但不是所有時候都有用。
還有一個好的分層架構是J2EE的架構,這方面的討論可以見『J2EE核心模式』一書。他的分層是客戶層(Client),表示層(Presentation),業務層(Business ),整合層(Integration),資源層(Resource)。差別如下圖:
J2EE核心 ISA
客戶層 運行在客戶機上的表示層
表示層 運行在服務器上的表示層
業務層 領域層
整合層 基礎架構層
資源層 基礎架構層通信的外部數據
微軟的DNA架構定義了三個層:表示層(presentation),業務層(business),和數據存儲層(data access),這和我的架構相似,但是在數據的傳遞方式上還有很大的不同。在微軟的DNA中,各層的操作都基于數據存儲層傳出的SQL查詢結果集。這樣的話,實際上是增加了表示層和業務層同數據存儲層之間的耦合度。 DNA的記錄集在層之間的動作類似于Data Transfer Object。
Part 2 組織領域邏輯
要組織基于層的系統,首要的是如何組織領域邏輯。領域邏輯的組織有好幾種模式。但其中最重要的莫過于兩種方法:Transation Script和Domain Model。選定了其中的一種,其它的都容易決定。不過,這兩者之間并沒有一條明顯的分界線。所以如何選取也是門大學問。一般來說,我們認為領域邏輯比較復雜的系統可以采用Domain Model。
Transation Script就是對表示層用戶輸入的處理程序。包括驗證和計算,存儲,調用其它系統的操作,把數據回傳給表示層。用戶的一個動作表示一個程序,這個程序可 以是script,也可以是transation,也可以是幾個子程序。在例子1中,檢驗,在購物車中增加一本書,顯示遞送狀態,都可以是一個 Transation Script。
Domain Model是要建立對應領域名詞的模型,例如例1中的書、購物車等。檢驗、計算等處理都放到領域模型中。
Transation Script屬于結構性思維,Domain Model屬于OO思維。Domain Model比較難使用,一旦習慣,你能夠組織更復雜的邏輯,你的思想會更OO。到時候,即使是小的系統,你也會自然的使用Domain Model了。
但如何抉擇呢?如果邏輯復雜,那肯定用Domain Model:如果只需要存取數據庫,那Transation Script會好一些。但是需求是在不斷進化的,你很難保證以后的需求還會如此簡單。如果你的團隊不善于使用Domain Model,那你需要權衡一下投入產出比。另外,即使是Transation Script,也可以做到把邏輯和基礎架構分開,你可以使用Gateway。
對例2,毫無疑問要使用Domain Model。對例1就需要權衡了。而對于例3,你很難說它將來會不會像例2那樣,你現在可以使用Transation Script,但未來你可能要使用Domain Model。所以說,架構的決策是至關緊要的。
除了這兩種模式,還有其它中庸的模式。Use Case Controller就是處于兩者之間。只有和單個的用例相關的業務邏輯才放到對象中。所以大致上他們還是在使用Transation Script,而Domain Model只是Database Gateway的一組集合而已。我不太用這種模式。
Table Module是另一個中庸模式。很多的GUI環境依托于SQL查詢的返回結果。你可以建立內存中的對象,來把GUI和數據庫分開來。為每個表寫一個模塊,因此每一行都需要關鍵字變量來識別每一個實例。
Table Module適用于很多的組件構建于一個通用關系型數據庫之上,而且領域邏輯不太復雜的情況。Microsoft COM 環境,以及它的帶ADO.NET的.NET環境都適合使用這種模式。而對于Java,就不太適用了。
領域邏輯的一個問題是領域對象非常的臃腫。因為對象的行為太多了,類也就太大了。它必須是一個超集。這就要考慮哪些行為是通用的,哪些不是,可以由其它的類來處理,可能是Use Case Controller,也可能是表示層。
還有一個問題,復制。他會導致復雜和不一致。這比臃腫的危害更大。所以,寧可臃腫,也不要復制。等到臃腫為害時再處理它吧。
選擇一個地方運行領域邏輯
我們的精力集中在邏輯層上。領域邏輯要么運行在Client上,要么運行在Server上。
比較簡單的做法是全部集中在Server上。這樣你需要使用html的前端以及web server。這樣做的好處是升級和維護都非常的簡單,你也不用考慮桌面平臺和Server的同步問題,也不用考慮桌面平臺的其它軟件的兼容問題。
運行在Client適合于要求快速反應和沒有聯網的情況。在Server端的邏輯,用戶的一個再小的請求,也需要信息從Client到Server繞一圈。反應的速度必然慢。再說,網絡的覆蓋程度也不是說達到了100%。
對于各個層來說,又是怎么樣的呢?
基礎架構層:一般都是在Server啦,不過有時候也會把數據復制到合適的高性能桌面機,但這是就要考慮同步的問題了。
表示層在何處運行取決于用戶界面的設計。一個Windows界面只能在Client運行。而一個Web界面就是在Server運行。也有特別的例子,在桌面機上運行web server的,例如X Server。但這種情況少的多。
在例1中,沒有更多的選擇了,只能選在Server端。因此你的每一個bit都會繞一個大圈子。為了提高效率,盡量使用一些純html腳本。
人們選用Windows界面的原因主要就是需要執行一些非常復雜的任務,需要一個合適的應用程序,而web GUI則無法勝任。這就是例2的做法。不過,人們應該會漸漸適應web GUI,而web GUI的功能也會越來越強大。
剩下的是領域邏輯。你可以全部放在Server,也可以全部放在Client,或是兩邊都放。
如果是在Client端,你可以考慮全部邏輯都放在Client端,這樣至少保證所有的邏輯都在一個地方。而把web server移至Client,是可以解決沒有聯網的問題,但對反應時間不會有多大的幫助。你還是可以把邏輯和表示層分離開來。當然,你需要額外的升級和維護的工作。
在Client和Server端都具有邏輯并不是一個好的處理辦法。但是對于那些僅有一些領域邏輯的情況是適用的。有一個小竅門,把那些和系統的其它部分沒有聯系的邏輯封裝起來。 領域邏輯的接口
你的Server上有一些領域邏輯,要和Client通信,你應該有什么樣的接口呢?要么是一個http接口,要么是一個OO接口。
http接口適用于web browser,就是說你要選擇一個html的表示層。最近的新技術就是web service,通過基于http、特別是XML進行通信。XML有幾個好處:通信量大,結構好,僅需一次的回路。這樣遠程調用的的開銷就小了。同時,XML還是一個標準,支持平臺異構。XML又是基于文本的,能夠通過防火墻。
雖然XML有那么多的好處,不過一個OO的接口還是有它的價值的。hhtp的接口不明顯,不容易看清楚數據是如何處理的。而OO的接口的方法帶有變量和名字,容易看出處理的過程。當然,它無法通過防火墻,但可以提供安全和事務之類的控制。
最好的還是取二者所長。OO接口在下,http接口在上。但這樣做就會使得實現機制非常的復雜。
Part 3 組織web Server
很多使用html方式的人,并不能真正理解這種方式的優點。我們有各種各樣好用的工具,但是卻搞到讓程序難以維護。
在web server上組織程序的方式大致可以分為兩種:腳本和server page。
腳本方式就是一個程序,用函數和方法來處理http調用。例如CGI腳本和java servlet。它和普通的程序并沒有什么兩樣。它從web頁面上獲得html string形態的數據,有時候還要做一些表達式匹配,這正是perl能夠成為CGI腳本的常用語言的原因。而java servelet則是把這種分析留給程序員,但它允許程序員通過關鍵字接口來訪問信息,這樣就會少一些表達式的判斷。這種格式的web server輸出是另一種html string,稱為response,可以通過流數據來操作。
糟糕的是流數據是非常麻煩的,因此就導致了server page的產生,例如PHP,ASP,JSP。
server page的方式適合回應(response)的處理比較簡單的情況。例如“顯示歌曲的明細”,但是你的決策取決于輸入的時候,就會比較雜亂。例如“通俗和搖滾的顯示格式不同”。
腳步擅長于處理用戶交互,server page擅長于處理格式化回應信息。所以很自然的就會采用腳本處理請求的交互,使用server page處理回應的格式化。這其實就是著名的MVC(Model View Controller)模式中的view/controller的處理。
應用Model View Controller模式首要的一點就是模型要和web服務完全分離開來。使用Transaction Script或Domain Model模式來封裝處理流程。
接下來,我們就把剩余的模式歸入兩類模式中:屬于Controller的模式,以及屬于View的模式。
View模式
View這邊有三種模式:Transform View,Template View和Two Step View。Transform View和Template View的處理只有一步,將領域數據轉換為html。Two Step View要經過兩步的處理,第一步把領域數據轉換為邏輯表示形式,第二步把邏輯表示轉換為html。
兩步處理的好處是可以將邏輯集中于一處,如果只有一步,變化發生時,你就需要修改每一個屏幕。但這需要你有一個很好的邏輯屏幕結構。如果一個web應用有很多的前端用戶時,兩步處理就特別的好用。例如航空訂票系統。使用不同的第二步處理,就可以獲得不同的邏輯屏幕。
使用單步方法有兩個可選的模式:Template View,Transform View。Template View其時就是把代碼嵌入到html頁面中,就像現在的server page技術,如ASP,PHP,JSP。這種模式靈活,強大,但顯得雜亂無章。如果你能夠把邏輯程序邏輯在頁面結構之外進行很好的組織,這種模式還是有它的優點的。
Transform View使用翻譯方式。例如XSLT。如果你的領域數據是用XML處理的,那這種模式就特別的好用。
Controller模式
Controller有兩種模式。一般我們會根據動作來決定一項控制。動作可能是一個按鈕或鏈接。所這種模式就是Action Controller模式。
Front Controller更進一步,它把http請求的處理和處理邏輯分離開來。一般是只有一個web handle來處理所有的請求。你的所有的http請求的處理都由一個對象來負責。你改變動作結構的影響就會降到最小。
posted @
2007-06-08 19:32 我愛佳娃 閱讀(1193) |
評論 (1) |
編輯 收藏
文章在這里
可參考:
http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml
http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml
結論是:DOM4J是這場測試的獲勝者,目前許多開源項目中大量采用 DOM4J
其實我認為歸根結底就是兩種技術:DOM和SAX
DOM是一次性把XML讀進內存,構造Document對象,然后可以以任何規則來解析文件,也可以做響應修改,處理比較靈活。但是DOM比較消耗內存,不能處理太大的文件。主流的DOM有JDOM,DOM4J;
SAX是一個事件驅動的解析器,是逐句讀文件,觸發事件,執行操作。但是它只能解析,不能對XML文件修改。
posted @
2007-06-08 18:55 我愛佳娃 閱讀(774) |
評論 (0) |
編輯 收藏