随笔-124  评论-194  文章-0  trackbacks-0
 

以下文字摘自:JOINJOIN2, 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 我爱佳娃 阅读(33547) | 评论 (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 我爱佳娃 阅读(1663) | 评论 (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 我爱佳娃 阅读(2292) | 评论 (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 我爱佳娃 阅读(1005) | 评论 (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 我爱佳娃 阅读(2589) | 评论 (3)编辑 收藏

我使用MSN博客两年,几乎是在它推出,并刚为中国用户所知时,就使用了。两年来发表了很多游记、照片、感想、评论。

但是,最近它被无情删除了,多次发送邮件给微软技术支持,都石沉大海。所以,我要以最强烈和最负责任的态度在这里呼吁和告诫每一位博客使用者:

请象远离毒品一样,远离MSN博客!

 

(天下虽无免费的蛋糕,但就算施舍给乞丐的蛋糕也没有微软这种做法的。)

posted @ 2007-06-21 18:03 我爱佳娃 阅读(343) | 评论 (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 我爱佳娃 阅读(3247) | 评论 (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 我爱佳娃 阅读(653) | 评论 (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 我爱佳娃 阅读(1167) | 评论 (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 我爱佳娃 阅读(747) | 评论 (0)编辑 收藏
仅列出标题
共13页: First 上一页 5 6 7 8 9 10 11 12 13 下一页