alartin's profileWindows Live 共享空间PhotosBlogListsMore Tools Help

Blog


    October 26

    IkeWiki: 协同知识管理的语义Wiki (1)

    作者:Sebastian Schaffert

    1 Introduction
    A serious obstacle for the development of SemanticWeb
    applications is the lack of formal ontologies and knowledge
    represented in other formal languages.

    语义网应用的困难在于缺少正式的本体论以及如何让各自领域的专家正式化自己的知识。

    Arguably, one of the
    main reasons for this is the rather high technical barrier for
    using Semantic Web technologies that deters many domain
    experts from formalising “their” knowledge. Therefore, formalised
    knowledge is currently limited to computer science
    and a small number of areas with a very high demand of
    interoperability (e.g. genetic engineering).

    语义网往往限制在计算机科学以及少数异质性需求的领域,比如遗传工程等。


    At the same time, wiki systems are becoming more and
    more popular as tools for content and knowledge management.
    Much knowledge is nowadays available in systems
    like Wikipedia. Unfortunately, this vast knowledge is not
    accessible for machines. If a small amount of this knowledge
    would be formalised, wiki systems could provide improved
    interfaces and advanced searching and navigation
    facilities.

    关于知识管理,Wiki系统是个很流行的工具。但是可惜的是机器无法理解这些知识。如果能够将知识正式化的话,有助于高级搜索以及知识向导,用户体验就会很好。


    “Semantic Wiki” systems aim to combine “traditional”
    wiki systems with Semantic Web technology. This combination
    bears much potential in many application areas. In
    this article, I present our prototype system IkeWiki (ike =
    knowledge, wiki = fast), which is a feature-rich semantic
    wiki system we developed at Salzburg Research. IkeWiki
    serves as a testbed for possible applications of semantic

    语义Wiki目标就是将传统的Wiki和语义网结合起来,IkeWiki就是这种系统的原型。

    wiki technology. I briefly outline three of these applications
    (collaborative ontology engineering, collaborative knowledge
    management, and educational environments).

    协同本体工程,协同知识管理,教育环境


    This article is structured as follows: Section 2 summarises
    important features of (traditional) wiki systems;
    Section 3 introduces SemanticWikis and describes features
    common to all or many of them; Section 4 briefly describes
    the design and architecture of IkeWiki; Section 5 presents
    the three application scenarios; Section 6 gives an overview
    over related work; finally, Section 7 concludes with some
    perspectives.

    本文第二部分介绍传统Wiki的重要特性,第三部分介绍语义Wiki的通用特性,第四部分介绍IkeWiki的设计和架构,第五部分展示上述三个应用场景,第六部分综述相关工作,第七部分展望未来。

    2 Traditional Wiki Systems
    “Wiki” is the short form for “WikiWikiWeb” and is derived
    from the Hawaiian expression “wiki wiki” meaning
    “fast” or “quick”. A wiki is essentially a collection of Web
    sites connected via hyperlinks. While there is a wide range
    of wiki systems available (e.g. MediaWiki1, MoinMoin2,
    TWiki3) with different purposes and audiences, all of them
    share the following common properties:

    通过浏览器编辑内容
    Editing via Browser. Content is usually edited via a simple
    browser interface that can be used without installing
    any additional (expensive) software. This makes editing
    simple and allows to modify pages from everywhere in the
    world with only minimal technical requirements. As a consequence,
    content creators can access and update the wiki
    from wherever they are, e.g. at work, at home, at conferences,
    nowadays even while travelling.

    简单的Wiki语法
    Simplified Wiki Syntax. Content is usually expressed in
    a simplified hypertext format (‘wiki syntax”) that is much
    easier to use for non-technical users than e.g. HTML. Formatting
    thus does not require knowledge of HTML.

    回滚机制(版本机制)

    Rollback Mechanism. Changes to the content of a wiki
    are versioned each time they are stored, i.e. previous versions
    of pages are kept. This allows to revert back to earlier
    versions of a page e.g. in case important parts have been
    accidentally deleted or undesirable modifications have been
    made by someone else. Also, most wiki systems allow to
    compare two versions of a page, making it possible to identify
    changes between edits quickly.

    非受限访问

    Unrestricted Access. In most wiki systems, access is
    completely unrestricted – i.e. anyone can correct, modify,
    complete, or even delete anything. While this might seem
    strange, and even dangerous, from a traditional perspective,
    practice shows that the system works: on the one hand, illmeaning
    users are rather rare; on the other hand, all changes
    can easily be undone using the rollback mechanism.
    Note that some wikis still allow to apply further access
    restrictions using users and groups as found in traditional
    content management systems.

    协同编辑

    Collaborative Editing. The above-mentioned properties
    combined make wikis an ideal tool for collaborative editing.
    As soon as someone creates content, others can contribute
    to it, extend it, correct it, etc. Many wiki systems provide
    further support for collaborative editing, e.g. by means of
    discussion forums, summaries of changes, and list of last
    updates.

    强连接
    Strong Linking. Pages in a wiki are usually strongly
    linked with each other using hyperlinks. The reason for this
    is that the simplified wiki syntax makes it very easy to define
    a link to another page in the wiki. For example, in many
    wikis a link is defined by enclosing a word in square brackets,
    or by using a so-called “CamelCase” where a word contains
    several upper-case letters. Links to non-existing pages
    are usually rendered in a different colour. If a user clicks on
    such a link, the system redirects him to a view where he can
    create the non-existing page. In many wikis, this is even the
    only way to create a page.
    Links in a wiki are the most important tool for navigation.
    Therefore, many systems allow not only to follow
    links in the direction they are defined but also in reverse
    direction (“back-links”).

    搜索功能
    Search Function. As a further tool for navigation, almost
    all wiki systems allow a full-text search over the content of
    all pages.

    上传其他形式的内容(图片,文件,多媒体等)
    Uploading of Other Content. In addition to creating text
    content, some of the more sophisticated wikis allow uploading
    of arbitrary (multimedia) content, like documents, images,
    program code, etc.

    Unlike other groupware or content/knowledge management
    tools, a wiki system gives users almost complete freedom
    over the content development process without rigid workflow,
    access restrictions, or predefined structures. Users
    need not adapt their practice to the ”dictate of the system”,
    but can allow their own practice to define the structure. This
    is important, because different domains often have – or even
    require – different kinds of workflow.

    Wiki的应用领域
    As a recent survey on the popular technology site Slashdot
    showed4, wiki systems are currently used for a wide
    variety of purposes, including:

    百科全书
    • encyclopaedia systems: collect knowledge in a certain
    area (e.g. Wikitravel5) or unrestricted (e.g.
    Wikipedia6) in a community effort with contributions
    from a wide range of users

    软件开发
    • software development: collaboratively create documentation,
    collect ideas, track bugs; most of today’s
    high-profile Open Source projects (e.g. Apache,
    Mozilla, OpenOffice) use wikis for coordination

    项目知识管理
    • project knowledge management: project tracking,
    brainstorming and exchange of ideas, coordination of
    activities, agenda tool for collecting topics of meetings,
    project notes repository, knowledge base, staff
    directory

    个人知识管理
    • personal knowledge management: sketchpad to collect
    and elaborate personal ideas, addresses, dates, tasks,
    bookmarks, etc. [6]

    协同写作
    • collaborative writing: authors work collectively on a
    writing (short story, novel, etc.) which is immediately
    accessible by readers for their enjoyment

    内容管理系统/知识库
    • CMS/knowledge base: collect content, connect content,
    simple publication tool

    October 16

    Netbeans Platform: Lookup! Lookup!

    Netbeans Platform的Lookup类给我们带来了什么呢?注入依赖和解耦!Lookup的基本用法常常是:

    Foo foo = someLookup.lookup(Foo.class)

    使用Lookup来查找一个Foo类的实例。你可以将Lookup想成是一个Map,键是类,值是实例。

    Netbeans中Lookup用途有:

    1. 表现一个对象的能力。IS-A关系转变为HAS-A关系。

    想想传统的一种解耦方式:将其他模块创建的对象cast到特定的类型。Netbeans,不这么做,Netbeans做法是让其他模块创建的对象实现Lookup.Provider接口(这个接口只有一个方法:public Lookup getLookup()),然后通过这个Lookup获得你需要协同工作的对象。然后你的代码询问这个对象是否拥有特定接口的实例:

    Your code  <--   interact with --> Some object -- implements --> Lookup.Provider

                                                             |__ has a Lookup Object -- maintain

                                                                                                            |__ Object 1

                                                                                                            |__ Object 2

                                                                                                            |__ Object 3 ...

    这样,通过Lookup, Some object 将 IS-A 关系(Some object实现Object1接口,Object2接口,Object3接口)转化为HAS-A关系(Some object的lookup对象拥有Object1, Object2, Object3).

    这种转化具有重要的意义!方便!快捷!动态(运行时无法改变实现的接口和类型)!弹性(运行时一个对象的能力是可能变化的,但是类型是不能变化的)!例如如果你想保存一个对象,你不必问其是否实现Saveable接口,你只需要问其是否拥有一个SaveCookie实例(SaveCookie有一个save方法)。

    2. 依赖注射和解耦

    一个模块能够定义多个接口让其他模块实现这些接口,这个模块可以使用Lookup.getDefault()方法获得全局Lookup,然后根据全局Lookup查找其接口的所有实现。

    3. 动态服务发现

    模块能够非常简单的将代表全局服务或伪单例的对象通过默认的Lookup进行注射。

    单例模式的的目的是让一个对象只拥有一个实例。实现这种模式又很多方法,例如最简单的是创建一个工厂方法,然后将构造器定义为私有的,然后维护一个实例,对所有的调用(工厂方法)都返回这个实例。单例模式在Netbeans中也有广泛的应用。通常全局服务没有必要有多个实例,例如在整个应用程序的主窗口中我们只需要一个状态显式栏实例,来显式状态。例如Netbeans的Windows系统API,你不用直接使用它,这个模块org.netbeans.core.windows将StatusDisplayer实现注射进来,放在默认的Lookup中,你仅仅使用StatusDisplayer.getDefault().setStatusText("something").就可以设置状态信息了。

    Lookup.Result 和 Lookup.Template

     

    什么样的对象需要拥有自己的Lookup呢?

    在Netbeans中的一些基本API类中又很多都有getLookup()方法,他们都实现了Lookup.Provider接口。Netbeans中有三个著名的例子:

    1. Project. Project API . Project实际就是一个将一个目录和一个Lookup结合在一起,再加点东西的一个对象。Project API 定义了能在一个Project实例的Lookup中出现的(可选的)类。不同的模块实现了Project,提供自己的实现。其他API定义一些其他的类。例如Java Project API定义了一个ClassPathProvider的接口。这个接口能在Java源代码Project的Lookup中被找到,而其他普通的Project没必要有这个接口。整个Project类定义如下:

    public interface Project extends Lookup.Provider {

    // 维护一个文件对象,代表项目目录
    FileObject getProjectDirectory();

    // 维护一个Lookup对象
    Lookup getLookup();
    }

    可以从Project的Lookup中请求多个接口。例如ProjectInformation对象能够提供Project名称等基本信息。

    2. TopComponent . 顶层组件是Netbeans的窗口系统管理的一个GUI面板。继承于TopComponent的类可以通过TopComponent.getLookup()方法来操作面板中的选择等事务。

    3. Node. 节点就是通常说得树-节点类型的对象,代表底层的数据模型。例如项目和文件窗口中的文件树就是节点树。org.openide.nodes.Node就有getLookup()方法。

    说了Netbeans中著名的三种具有Lookup对象的类,到底什么样的对象需要Lookup对象呢??

    答案是,需要暴露某些能力的对象。这些对象可以通过Lookup对象向外界表明其具备某些能力,从而让其他代码能够使用这些对象的能力。举个例子,如果你想保存一个文件对象,那么这个文件对象肯定有保存的能力。传统方式怎么做呢:

    public void actionPerformed (ActionEvent e) {
    Object o = something.getSelection();

    // 你会检查其是否实现Saveable接口
    if (o instanceof Saveable && ((Saveable) o).canSave()) {
    ((Saveable) o).save();
    }
    }

    可惜的是这种方法太不强大了。Java对象无法轻易更改他们的类型。因为情况总是在变化的。Java文件包括源文件和编译文件,有些事情你无法在只有源文件没有编译文件的情况下做,例如执行。并不是所有Java源文件都有对应的编译文件的。因此Lookup的出现能够很好的解决这种问题。Java文件的Lookup内容是可以随时变化。当Java源文件被编译后,可以向Java文件的Lookup内容添加一个编译文件对象,如果Java文件被编译了,那么Lookup内容中就保存一个编译文件对象,如果编译文件被删除了,这个编译对象也从Lookup内容中删除了。

    public void actionPerformed (ActionEvent e) {
    Lookup lkp = Utilities.actionsGlobalContext();
    SaveCookie save = lkp.lookup (SaveCookie.class);
    if (save != null) {
    save.save();
    }
    }

    事实上,Netbeans中一个正在被编辑的文件的保存过程,就是代表这个文件的节点Node的Lookup中出现了一个SaveCookie对象。这个代码并不需要知道SaveCookie.save()方法的具体细节,只需要知道SaveCookie对象在Lookup中,就可以保存。

    Lookup是一种通讯机制

    上面说得Project就是一个例子,Netbeans中有一个概念叫服务提供者接口(Service Provider Interface, SPI ). 不同的模块通过在默认的Lookup中安装ProjectFactory实例,来插入不同的项目类型project type.你可以在Netbeans的项目向导中发现你可以创建不同类型的项目。Netbeans包含多个项目接口的实现。

    Lookup和代理

    ProxyLookup允许将两个Lookup融合。

    Lookup和选择

    那我们如何在程序中使用Lookup呢?

    基本上说,我们无需自己编写Lookup(当然如果需要的话,你可以编写)。

    NetBeans 的 Lookup 可以說是无处不在,它也是 NetBeans 中最基本的模块。在执行的時候,NetBeans 将Lookup分为系统服务池的Lookup和具有焦点窗口的对象池的Lookup。我们可以从以下地方获得Lookup:TopComponentNodeUtilities.actionsGlobalContext()、和 Lookups四个地方。

    我们通过Utilities.actionsGlobalContext()来获得对象池的Lookup, 这里Netbeans已经帮我们整合过Lookup对象了.Utilities.actionsGlobalContext()将返回一个Lookup对象,这个对象是活动的(具有焦点)的顶层组件的Lookup的代理Lookup,如果顶层组件是Explorer视图的话,将代理被选择了的节点的Lookup对象们。

    我们通过Lookup.getDefault()方式获得系统服务池的Lookup.

    Netbeans平台为我们准备好了几种使用方式:

    1. 通过Lookups. 如果你需要包含固定数目对象的Lookup,你可以使用Lookups.fixed(Object... objectsToLookup) (注意:... 代表可变参数数量),也可以使用Lookups.singleton(Object objectToLookup)返回一个只包含一个对象的Lookup.

    2. AbstractLookup和InstanceContent. 如果你希望你的Lookup可以动态的包含对象(数目可变),那么你可以使用AbstractLookup和InstanceContent相结合的方式。注意AbstractLookup并不是抽象类。具体的用法这里有个很好的例子,Fox的Lookup讲解

    在这个例子中,有一个业务对象FoxObject, 有两个顶层组件:Consumer顶层组件和Producer顶层组件。Producer顶层组件中包含两个状态按钮(ToggleButton):FoxButton和DonButton。Consumer顶层组件有一个List表单。

    效果是:如果你按下Producer顶层组件窗口中的其中一个Button,就会创建一个FoxObject对象,其名字根据按钮的名字命名,例如按下DonButton将创建一个名字为Don的FoxObject. 如果程序的焦点在Producer顶层组件上(蓝色背景意味焦点在其上,灰色背景意味焦点不在其上)的话,Consumer顶层组件的List表单将出现这个名字为Don的FoxObject, 当你再次按下DonButton时,按钮的状态改变,名字为Don的FoxObject被删除,List表单中相应的对象也消失了。如果你将程序焦点换到Consumer顶层组件的时候,List表单中的所有对象都消失了,重新将焦点换到Producer顶层组件上时,List表单中的对象重新出现了。

    这是怎么实现的呢?我们看一下:

    1. 我们在Producer顶层组件中维护一个InstanceContent对象,两个FoxObject对象。在Producer顶层组件构造的时候,我们初始化Lookup:

    private void initLookup(){

            // 创建InstanceContent,然后构建一个AbstractLookup,然后将这个Lookup和Producer顶层组件关联
            m_InstancePool=new InstanceContent(); 
            this.associateLookup(new AbstractLookup(m_InstancePool)); 
        }

    在ToggleButton状态变化方法中,我们根据Button的状态,决定InstanceContent的内容(名为Don的FoxObject对象的添加和删除)

    private void m_PutDonItemStateChanged(java.awt.event.ItemEvent evt) {                                         
            if(evt.getStateChange()==ItemEvent.SELECTED){
                if(m_Don==null){
                    m_Don=FoxObject.createInstance("don", "Don, Chen");
                }
                m_InstancePool.add(m_Don);
            }else if(evt.getStateChange()==ItemEvent.DESELECTED){
                if(m_Don!=null){
                    m_InstancePool.remove(m_Don);
                }
            }
        }          

    2. 我们让Consumer顶层组件实现LookupLisenter, 因为他要根据FoxObject对象的状态来显式List表单。他维护一个JList表单以及一个Lookup.Result结果。

    final class ConsumerTopComponent extends TopComponent implements LookupListener {...

    当Consumer顶层组件被打开和关闭时:

    public void componentOpened() {
            clearList();

            // 初始化LookupQuery
            initLookupQuery();
            FoxService oService=(FoxService) Lookup.getDefault().lookup(FoxService.class);
            if(oService !=null ){
                oService.saySomething();
            }
        }
        public void componentClosed() {

            // 关闭LookupQuery
            uninitLookupQuery();
            clearList();
        }

    我们看一下,在Consumer顶层组件窗口被打开时,我们根据FoxObject类制作一个模版,然后通过Utilities.actionsGlobalContext()查找到所有FoxObject的实例,以Lookup.Result方式展现。找到这些FoxObject实例后,对代表他们的Lookup.Result添加监听器,就是Consumer顶层组件自己。然后更新结果列表。

    private void initLookupQuery(){
            Lookup.Template oFoxTemplate=new Lookup.Template(FoxObject.class);
            m_LookupResult=Utilities.actionsGlobalContext().lookup(oFoxTemplate);
            m_LookupResult.addLookupListener(this);
            refreshResultList();
        }
        private void uninitLookupQuery(){
            if(m_LookupResult!=null){
                m_LookupResult.removeLookupListener(this);
                m_LookupResult=null;
            }
        }

    实现Lookup监听器的方法:

    public void resultChanged(LookupEvent ev){
            refreshResultList();
        }

    一旦Lookup变化了,resultChanged方法就被调用,执行refreshResultList方法。这个方法获得Lookup.Result中的所有实例,如果有实例的话,重新根据这些实例绘制Consumer顶层组件的List表单。

    private void refreshResultList(){
            Collection<FoxObject> cList=m_LookupResult.allInstances();
            if(!cList.isEmpty()){
                this.m_FoxObjectList.setListData(new Vector<FoxObject>(cList));
                this.m_FoxObjectList.revalidate();
                this.m_FoxObjectList.repaint();
            }else{
                clearList();
            }
        }

    整个演示就完成了。这里我们特别要注意几点:

    1. FoxObject是业务对象,它仅仅关注自己的业务和信息,例如名字,ID等,对其他的事情一概不知。

    2. Producer顶层组件需要和一个Lookup关联,这个Lookup是AbstractLookup和InstanceContent共同完成的,以便Lookup内的对象数目可以动态改变(通过ToggleButton的状态):

    ProducerTopComponent --> AbstractLookup --> InstanceContent     --> FoxObject Class

                                                         |                       |__ Add/Remove --> Fox FoxObject <--> Button1

                                                         |                       |__ Add/Remove --> Don FoxObject <--> Button2

                                                         |

                                                         |    关键:Netbeans通过Utilities.actionsGlobalContext将

                                                         |    Producer顶层组件和Consumer顶层组件解耦,相互通过Lookup沟通

    ConsumerTopComponent --> Utilities.actionsGlobalContext().lookup(FoxObject.class)

                           |______  监听 -->      |__ Lookup.Result

                                             |______  更新 -->     JList (根据Lookup.Result的变化)

    3. 结构图

                   <--------------                                 FoxObject                       --------------->

                   |                                                              |                                                   |

                   |        <-----   Lookup (Utilitis, AbstractLookup, InstanceConent)  ------>       |

                   |         |                                                                                                |       |

         ConsumerTopComponent                                XXX                  ProducerTopComponent

    October 12

    Netbeans Platform: 注册与发现的机制

    Netbeans Platform是模块化的设计。我们看一下最基本的开发思路:

    例如,我们想要写一个XML验证器模块。我们会做一下几个事情:

    1. 设计Validator接口, 定义一些验证所需的方法。方便以后提供Manifest验证器,html验证器等。

    2. 设计实现Validation类,这个类维护一个Validator的Map, 允许添加和删除Validatior,如果需要验证的话,这个类会遍历Validator找出响应的Validator实现。

    3. 实现XMLValidator类,实现Validator接口

    4. 实现XMLModuleInstall类,继承ModuleInstall类。这个类维护一个静态的Validation类,完成XMLValidator的注册和去注册任务

    这里有个问题,就是上述办法包含太多的样板代码。例如Validation中要维护注册许多验证器。每个模块需要提供Validator的实现类的同时,还要编写另一个类继承ModuleInstall类来完成注册和去注册的任务, 例如XMLValidator还要XMLModuelInstall, XMLValidator负责验证任务,XMLModuelInstall负责注册和去注册XMLValidator. 通过ModuleInstall程序在启动时都要加载验证器,不管你到底用不用它。

    所以,Netbeans Platform需要一个好的注册模式。

    我们先看看其他已经存在的注册模式:

    java.awt.Toolkit使用一种基于属性的解决方案,property-based.

    public Toolkit getDefaultToolkit() {
    java.awt.Toolkit t = null;
    String classname =
    System.getProperty("java.awt.Toolkit"); // 从系统中获得java.awt.Toolkit属性:类名称
    if (classname != null) {
    try {
    Class c = Class.forName(classname); // 根据类名称获得响应的类
    t = (java.awt.Toolkit) c.newInstance(); // 使用类创建类实例
    } catch (Exception ex) {
    System.out.println
    ("Cannot initialize toolkit: " + classname);
    ex.printStackTrace();
    }
    }
    // Fallback
    if (t == null) {
    t = new GenericAWTToolkit(); // 如果都不成功,返回一个通用的默认的实例
    }
    }

    这种解决方案比以前有进步,没有烦人的setter方法和注册方法,不过它需要你在Java虚拟机运行时正确的设置属性。你不得不在程序启动时初始化。

    我们看看Netbeans Platform如何做这件事情。首先,设计理念是,基于现有的标准,需要的时候作些增强,必须很好的和Netbeans运行时容器提供的动态模块环境相匹配。这种解决方案必须对任何接口和类都适合才行。并且它要支持监听器,允许任何人监听已经注册了的对象的任何变化。例如,监听一个模块的激活和失活。

    Netbeans Platform如何做到这一点呢?让我们看看:Lookup!

    MetaInf Service 元信息服务

    JDK1.3的时候,Java提出了一个概念:服务提供者,Service Provider.这个概念介绍了一种完全是声明式的注册机制。实际上这种注册机制完全基于Java虚拟机的当前类路径,并没有什么其他的东西。这种注册机制使用Java类路径定义注册了的对象,非常容易使用。改变注册提供者,仅仅需要将提供者的jar包放在应用程序的类路径上就可以了。这样提供者能够迅速被查找到并且可以被其他代码访问。

    背后的基本思想是每个希望提供某些接口实现的jar文件(Netbeans的术语就是模块),例如javax.xml.parser.DocumentBuilderFactory的实现,能够创建自己的接口实现,比如org.saxon.MyFactory. 然后通过在自己jar包中创建一个META-INF/services/javax.parser.DocumentBuilderFactory文件的方式暴露给系统,这被称为系统中的服务。这个文件中每一行包含一个实现类的名字。在这个例子中,这个文件中存在:org.saxon.MyFactory一行,文件名字就是接口,里面的内容的一行就是一个实现。

    我们看一下如何运行的呢?当你使用DocumentBuilderFactory.newInstance()来创建一个实现实例的时候,系统会扫描所有的META-INF/services/javax.parser.DocumentBuilderFactory文件。如何扫描呢?通过ClassLoader.getResources("META-INF/services/javax.parser.DocumentBuilderFactory"). 系统将会读取文件的内容,通过默认构造器创建实例。究竟那一个实现会被创建呢?答案是第一个。

    这种方式很好,也很流行了,在JDK1.6中,更是增加了一个辅助类java.util.ServiceLoader. 使用这个类,能更加容易的查找到所有注册的服务,不用手工编写“查找,读取,实例化”的代码。因此在JDK1.6中上述的任务简化为:

    return ServiceLoader.load(DocumentBuilderFactory.class, someClassLoader).iterator().next();

    OK, 让我们回到Netbeans上,Netbeans的解决方案基于上述技术。它被称为Lookup!

    全局Lookup

    org.openide.util.Lookup, 它和java.util.ServiceLoader一样是service provider模式。你可能要问,为什么你要单独搞出这么个东西?原因如下:

    1. 如果你使用JDK1.6以前的版本(虽然不太可能),你可以使用Lookup作为替代

    2. Lookup能够立刻在Netbeans运行时容器中使用。它知道如何发现系统中的模块,并且读取他们定义的服务。

    3. Lookup支持监听器。客户代码可以设置监听器,监听Lookup内容的变化。这在动态环境中非常重要,模块可以在运行时激活或者失活,从而影响一系列注册服务的提供者。

    4. Lookup可以扩展和替代。JDK1.6的java.util.ServiceLoader是最终类,并且是硬编码的,而Lookup是可扩展的类,允许各种实现,这在单元测试中尤其重要。甚至你可以编写一个增强版的Lookup, 让它不仅仅从META-INF/services中读取信息。

    5. Lookup是通用的抽象层。每个classloader可以有一个JDK的ServiceLoader实例,而在Netbeans中可以有成千上万个相互独立的Lookup,每个可以代表一个单一的位置去发现和查询服务和接口。事实上,这正是Netbeans使用Lookup的方式:他代表每个对话,窗口元素,树中节点等的上下文。

    我们重新看一下Validation的例子,在先前的例子中,Validation依靠维护一个Validator的Map进行注册和去注册。那么使用Lookup后,Validation根本不需要这个Map,代码如下:

    package org.netbeans.examples.validate.api;
    import org.openide.util.Lookup;
    public final class Validations {
    private Validations() {}

    // 将Validator.class定制为一个Lookup模版 Lookup.Template,

    // 通过Lookup获得Validator.class的模版查找到Lookup.Result结果

    // 这个结果包含所有找到的Validator实例,遍历这些实例,找到相应的Validator实例来验证


    private static Lookup.Result<Validator> validators =
    Lookup.getDefault().lookup(
    new Lookup.Template<Validator>(Validator.class)
    );
    // Or in 6.0 version just:
    // Lookup.Result<Validator> validators =
    // Lookup.getDefault().lookupResult(Validator.class);


    public static void validate(String mimeType, InputStream is)
    throws IOException {
    for (Validator v : validators.allInstances()) {
    if (v.supportsMimeType(mimeType)) {
    v.validate(is);
    return;
    }
    }
    throw new IOException("No validator found for " + mimeType);
    }
    }

    使用Lookup不仅省却了Validation的Map,而且不需要registerValidator等方法。全部依赖于Lookup.getDefault的实现和其注册方式。这样Validation类只需关注validate方法就可以了。

    看看吧,现在如果我们想实现另一个验证器Manifest Validator. 我们不再需要知道ModuleInstall或者在模块初始化和结束化调用任何方法。我们仅仅需要声明ManifestValidator实现了Validator接口就可以了:

    package org.netbeans.examples.manifestvalidator;
    public final class ManifestValidator implements Validator {
    public boolean supportsMimeType(String mimeType) {
    return "text/x-manifest".equals(mimeType);
    }
    public void validate(InputStream is) throws IOException {
    // Just try to read the file as manifest:
    Manifest mf = new Manifest(is);
    }
    }

    接下来怎么将ManifestValidator注册呢?很简单,在META-INF目录下创建META-INF/services/org.netbeans.examples.validate文件,告诉人们存在实现这个接口的实现,然后在这个文件中增加一行:org.netbeans.examples.manifestvalidator.ManifestValidator。告诉人们ManifestValidator实现了这个接口。万事OK了!记住啊,我们有个org.netbeans.examples.manifestvalidator包,这个包包括一个META-INF目录,这个目录下有一个以接口名字命名的文件,这个文件中有一行实现类的类名,注册完成了!

    October 11

    Netbeans Platform: 模块版本

    Netbeans Platform中模块版本问题写作API版本 API Versionning. 我认为不是很恰当,实际上在Netbeans中只有模块项目才能设置API Versioning. 打开一个模块项目的属性表,可以看到:

    Category-->

             Sources

             Libraries

             Display

             API Versioning        <-- 负责模块的版本工作

             Build-->

                     Compiling

                     Packaging

    本质上,其他模块,或者程序是和模块本身打交道的,所以在外界看来应该是模块的版本。所以我们这里使用模块的版本来讲述API Versioning.

    Netbeans Platform: 模块间的通信

    1. 模块间交互的方式

    在Netbeans中,一个模块可以由多个package组成。需要注意的是,尽管你将package中的类声明为public,并不意味着其他模块可以使用这个类。Netbeans的模块化设计在模块之间设置了一个屏蔽用来降低模块间的耦合,从而达到弹性设计。因此在一个模块中,我们需要设置模块的public package属性, 注意只有模块Module,而不是模块组Module Suite才有这样的设置。public packages属性允许你设置模块中的那些package是可以被其他模块使用/访问的。例如,你有一个biz模块,其中包含com.you.biz 和com.you.util两个包,从字面上看util是个工具包,用来辅助biz包,只有biz包才代表真正的业务,因此将com.you.biz设为public供其他模块使用/访问,而将util包屏蔽。这样的好处不用解释。使用/访问这个包的其他模块自然要依赖于模块biz, 因此他们也需要设置模块依赖关系。例如,在他们的manifest文件中要有如下:

    OpenIDE-Module-Module-Dependencies: com.you.biz > 1.5

    这意味着这个模块依赖于你的biz模块并且版本要高于等于1.5,> 1.5是可选的,你也可以不指定这个。

    但是真的只有将模块中的package指定为public, 其他模块才能访问这个package麽?答案是否定的!Netbeans允许你绕过这条路来直接访问package, 但是要你风险自负!这个方法就是实现依赖 Implementation dependency, 这个术语有点不好理解,我们会解释的。在解释之前,我们先想一下,为什么我们要绕过public package属性,去干这件事?一个可能的情况是:你发现一个模块的作者忘记将一个package设为public了,但是你又发现,这个package里的public类你完全了解,并且知道如何使用。很可惜的是,作者忘记或者没有将这个package设为public, 原因天晓得!不过你就是希望使用这个package里的public类, 怎么办呢?那就使用实现依赖,不过这种方式有局限,就是你必须明确指定,注意是明确指定,你要使用的模块的版本。原因很简单,因为这个模块的作者根本没有将这个package设为public,所以他将来也许会改变这个packege的结构和内容,也许他认为虽然这个package要公开,但目前不成数,然而你却绕过他的设置,直接访问了!所以真正的问题在于兼容。未来的版本只保证公开的不变,其他都有变化的可能。风险在于你的模块可能因此被破坏了。所以,你必须指定确切的版本,来让Netbeans核查。这就是术语实现依赖的来源,你依赖的是模块的特定的实现。假定模块B实现依赖于模块A,除非Netbean系统核查系统中模块A的版本和模块B所依赖的模块A的版本一致,才装载模块B。在模块B中,manifest文件中如下配置:

    OpenIDE-Module-Module-Dependencies: com.you.moduleA = 3

    在模块A中,manifest文件配置如下:

    OpenIDE-Module-Implementation-Version: 3

    你可以在Netbeans图形界面中的项目属性中查看实现依赖,要求其他模块的版本号不要是负整数。你可以使用自动添加实现版本的功能。

    特别强调: 不到万不得已,不要使用实现依赖!!实际这只是一种折衷的办法。甚至在上述使用实现依赖的例子中,如果不是需要访问这个包中所有的公开类,我宁肯将我需要的类拷贝出来,违背通常的设计模式,也不要使用实现依赖。

    朋友依赖 Friend dependencies , 朋友依赖和上述有点不同,想象一下这种情况,一个模块的作者认为他开发的包还不够成熟,他只想将其开放给一些朋友模块,例如一个预定义的朋友模块列表。只有他们才能使用这些包。建议你宁可使用朋友依赖也不要使用实现依赖。

    September 26

    Hibernate映射:一对一

    对象世界的一对一关系映射到数据库中有几个选择,我们使用用户和邮件地址这个例子。如果每个用户只能有一个邮件地址的话,那么这个关系就是一对一。映射到数据库中我们有多种方式来表现这种关系:

    1. 只使用一张表。用户和邮件地址在同一张表中,例如APP_USER表

    id username email
    1 alartin alartin@mail.com
    2 alarnan alarnan@mail.com
    3    

    2. 使用两张表,不使用外键。通过共享主键的方式来表示一对一关系。例如APP_USER和EMAIL两个表:

    id username
    1 alartin
    2 alarnan
    3  

     

    id email
    1 alartin@mail.com
    2 alarnan@mail.com
    3  

     

     

    注意:这两张表的主键是共享的。APP_USER表的id和EMAIL表的id是一致的。这种一致性可以由Hibernate负责。

    3. 使用两张表,使用外键。如果共享主键不合适的话,我们可以引入外键。不过要注意:只能其中一张表包含外键,负责的话会出现循环依赖。

    APP_USER表和上面的一样,但是EMAIL表变为(user_id为外键):

    id email user_id(unique)
    1 alarnan@mail.com 2
    2 alartin@mail.com 1
    3    

     

    在这种情况下,要保证一对一关系,需要在user_id列上添加一个唯一约束,否则多个email可以对应一个user_id不符合一对一关系。不过这种设计也有好处,如果想将一对一关系变为多对一关系(从Email角度出发)的话,只需将唯一约束删除就可以了。

    September 13

    Hibernate的陷阱:身份问题和业务键

    几年前看过一篇报道,说香港闹金融危机,结果深圳著名的二奶公寓就跑出来很多姑娘落草为鸡,原因是香港款爷跑路了,而内地的二奶没有身份,自然也就树倒猢狲散。出乱子拉。在这里讲这么个故事好像很言不达意,不过告诉我们一个问题,那就是身份的重要性和如何识别身份。Hibernate也有这方面的问题,我在BEA的dev2dev上看到一篇专门讲述这个问题的文章,姑且转载这里。

    别让Hibernate偷走了您的身份

    时间:2006-11-03
    作者:James Brundege
    浏览次数: 7030
    本文关键字:J2EEJDO/JDBC/SQLJJavaHibernateJDBCORMObject-relational mappingUUIDUniversally Unique Identifierequalshashcodeidentityid对象关系映射

    文章工具
    推荐给朋友 推荐给朋友
    打印文章 打印文章

       企业级Java应用程序常常把数据在Java对象和相关数据库之间来回移动。从手工编写SQL代码到诸如Hibernate这样成熟的对象关系映射(ORM)解决方案,有很多种方法可以实现这个过程。无论采用什么样的技术,一旦开始将Java对象持久存储到数据库中,身份将成为一个复杂且难以管理的课题。可能出现的情况是:您实例化了两个不同的对象,而它们却代表数据库中的同一行。为了解决这个问题,您可能采取的措施是在持久性对象中实现equals()和hashCode(),可是要恰当地实现这两个方法比乍看之下要有技巧一些。让问题更糟糕的是,那些传统的思路(包括 Hibernate官方文档所提倡的)对于新的项目并不一定能提出最实用的解决方案。

      对象身份在虚拟机(VM)中和在数据库中的差异是问题滋生的温床。在虚拟机中,您并不会得到对象的ID,您只是简单地持有对象的直接引用。而在幕后,虚拟机确实给每个对象指派了一个8字节大小的ID,这个ID才是对象的真实引用。当您将对象持久存储到数据库中的时候,问题开始产生了。假定您创建了一个 Person对象并将它存入数据库(我们可以叫它person1)。而您的其他某段代码从数据库中读取了这个Person对象的数据,并将它实例化为另一个新的Person对象(我们可以叫它Person2)。现在您的内存中有了两个映射到数据库中同一行的对象。一个对象引用只能指向它们的其中一个,可是我们需要一种方法来表示这两个对象实际上表示着同一个实体。这就是(在虚拟机中)引入对象身份的原因。

      在Java语言中,对象身份是由每个对象都持有的equals()方法(以及相关的hashCode()方法)来定义的。无论两个对象是否为同一个实例, equals()方法都应该能够判别出它们是否表示同一个实体。hashCode()方法和equals()方法有关联是因为所有相等的对象都应该返回相同的hashCode。默认情况下,equals()方法仅仅比较对象引用。一个对象和它自身是相等的,而和其他任何实例都不相等。对于持久性对象来说,重写这两个方法,让代表着数据库中同一行的两个对象被视为相等是很重要的。而这对于Java中Collection(Set、Map和List)的正确工作更是尤为重要。

       为了阐明实现equal()和hashCode()的不同途径,让我们考虑一个准备持久存储到数据库中的简单对象Person。

    public class Person {
        private Long id;
        private Integer version;
    
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public Integer getVersion() {
            return version;
        }
        public void setVersion(Integer version) {
            this.version = version;
        }
    
        // person-specific properties and behavior
    
    }
    

      在这个例子中,我们遵循了同时持有id字段和version字段的最佳实践。Id字段保存了在数据库中作为主键使用的值,而version字段则是一个从0开始增长的增量,随着对象的每次更新而变化(这帮助我们避免并发更新的问题)。为了更清楚一些,让我们看看允许Hibernate把这个对象持久存储到数据库的Hibernate映射文件:

    <?xml version="1.0"?>
    <!DOCTYPE hibernate-mapping SYSTEM
        "http://hibernate.sourceforge.net/
        hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="my.package">
    
      <class name="Person" table="PERSON">
    
        <id name="id" column="ID"
            unsaved-value="null">
          <generator class="sequence">
            <param name="sequence">PERSON_SEQ</param>
          </generator>
        </id>
    
        <version name="version" column="VERSION" />
    
        <!-- Map Person-specific properties here. -->
    
      </class>
    
    </hibernate-mapping>
    
    

      Hibernate映射文件指明了Person的id字段代表数据库中的ID列(也就是说,它是PERSON表的主键)。包含在id标签中的 unsaved-value="null"属性告诉Hibernate使用id字段来判断一个Person对象之前是否被保存过。ORM框架必须依靠这个来判断保存一个对象的时候应该使用SQL的INSERT子句还是UPDATE子句。在这个例子中,Hibernate假定一个新对象的id字段一开始为 null值,当它第一次被保存时id才被赋予一个值。generator标签告诉Hibernate当对象第一次保存时,应该从哪里获得指派的id。在这个例子中,Hibernate使用数据库序列作为唯一ID的来源。最后,version标签告诉Hibernate使用Person对象的version 字段进行并发控制。Hibernate将会执行乐观锁定方案,根据这个方案,Hibernate在保存对象之前会根据数据库版本号检查对象的版本号。

      我们的Person对象还缺少的是equals()方法和hashCode()方法的实现。既然这是一个持久性对象,我们并不想依赖于这两个方法的默认实现,因为默认实现并不能分辨代表数据库中同一行的两个不同实例。一种简单而又显然的实现方法是利用id字段来进行equal()方法的比较以及生成 hashCode()方法的结果。

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof Person))
            return false;
    
        Person other = (Person)o;
    
        if (id == other.getId()) return true;
        if (id == null) return false;
    
        // equivalence by id
        return id.equals(other.getId());
    }
    
    public int hashCode() {
        if (id != null) {
            return id.hashCode();
        } else {
            return super.hashCode();
        }
    }
    

      不幸的是,这个实现存在着问题。当我们首次创建Person对象时id的值为null,这意味着任何两个Person对象只要尚未保存,就将被认为是相等的。如果我们想创建一个Person对象并把它放到一个Set中,再创建一个完全不同的Person对象也把它放到同一个Set里面,事实上第二个Person对象并不能被加入。这是因为Set会断定所有未保存的对象都是相同的。

       您可能会试图去实现一个使用id(只在已设置id的情况下)的equals()方法。毕竟,如果两个对象都没有被保存过,我们可以假定它们是不同的对象。这是因为在它们被保存到数据库的时候,它们会被赋予不同的主键。

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof Person))
            return false;
    
        Person other = (Person)o;
    
        // unsaved objects are never equal
        if (id == null || other.getId() == null)
            return false;
    
        return id.equals(other.getId());
    }
    

      这里有个隐含的问题。Java Collection框架在Collection的生命周期中需要基于不变字段的equals()和hashCode()方法。换句话来说,当一个对象处在Collection中的时候,不可以改变equals()和hashCode()的值。举个例子,下面这段程序:

    Person p = new Person();
    Set set = new HashSet();
    set.add(p);
    System.out.println(set.contains(p));
    p.setId(new Long(5));
    System.out.println(set.contains(p));
    

      输出结果:true false

       对set.contains(p)的第2次调用返回false,这是因为Set再也找不到p了。用专业术语来讲,就是Set丢失了这个对象!这是因为当对象在集合中时,我们改变了hashCode()的值。

      当您想要创建一个将其他域对象保存在Set、Map或是List中的域对象时,这是一个问题。为了解决这个问题,您必须为所有对象提供一种equals ()和hashCode()的实现,这种实现能够保证在它们在对象保存前后正确工作并且当对象在内存中时(返回值)不可变。Hibernate Reference Documentation (v. 3)提供了以下的建议:

       “不要使用数据库标识符来实现相等性判断,而应该使用业务键(business key),这是一个唯一的、通常不改变的属性的组合体。当一个瞬态对象(transient object)被持久化的时候,数据库标识符会发生改变。当一个瞬态实例(常常与detached实例一起使用)保存在一个Set中时,哈希码的改变会破坏Set的约定。业务键的属性并不要求和数据库主键一样稳定,只要保证当对象在同一个Set中时它们的稳定性。”(Hibernate Reference Documentation v. 3.1.1)。

       “我们推荐通过判断业务键相等性来实现equals()和hashCode()。业务键相等性意味着equals()方法只比较能够区分现实世界中实例的业务键(普通候选键)的属性。”(Hibernate Reference Documentation v. 3.1.1)。

      换句话说,普通键用于equals()和hashCode(),而Hibernate生成的代理项键用于对象的id。这要求对于每个对象有一个相关的不可变的业务键。可是,并不是每个对象类型都有这样的一种键,这时候您可能会尝试使用会改变但不经常改变的字段。这和业务键不必与数据库主键一样稳定的思想相吻合。如果这种键在对象所在集合的生存期中不改变,那这就“足够好”了。这是一种危险的观点,因为这意味着您的应用程序可能不会崩溃,但是前提是没有人在特定的情况下更新了特定的字段。所以,应当有一种更好的解决方案,这种解决方案确实也存在。

       不要让Hibernate管理您的id。

      试图创建和维护对象及数据库行的各自身份定义是目前为止所有讨论问题的根源。如果我们统一所有身份形式,这些问题都将不复存在。也就是说,作为以数据库为中心和以对象为中心的ID的替代品,我们应该创建一种通用的、特定于实体的ID来代表数据实体,这种ID应该在数据第一次输入的时候创建。无论这个唯一数据实体是保存在数据库中,是作为对象驻留在内存中,还是存储在其他格式的介质中,这个通用ID都应该可以识别它。通过使用数据实体第一次创建时指派的实体 ID,我们可以安全地回到equals()和hashCode()的原始定义,它们只需使用这个id:

    public class Person {
        // assign an id as soon as possible
        private String id = IdGenerator.createId();
        private Integer version;
    
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
    
        public Integer getVersion() {
            return version;
        }
        public void setVersion(Integer version) {
            this.version = version;
        }
    
        // Person-specific fields and behavior here
    
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || !(o instanceof Person))
                return false;
    
            Person other = (Person)o;
    
            if (id == null) return false;
            return id.equals(other.getId());
        }
    
        public int hashCode() {
            if (id != null) {
                return id.hashCode();
            } else {
                return super.hashCode();
            }
        }
    }
    

      这个例子使用对象id作为equals()方法判断相等的标准,以及hashCode()返回哈希码的来源。这就简单了许多。但是,要让它正常工作,我们需要两样东西。首先,我们需要保证每个对象在被保存之前都有一个id值。在这个例子里,当id变量被声明的时候,它就被指派了一个值。其次,我们需要一种判断这个对象是新生成的还是之前保存过的的手段。在我们最早的例子中,Hibernate通过检查id字段是否为null来判断对象是否为新的。既然对象id永不为null,很显然这种方法不再有效。通过配置Hibernate,让它检查version字段,而不是id字段是否为null, 我们可以很容易地解决这个问题。version字段是一个更恰当的用来判断对象是否被保存过的指示符。

       下面是我们改进过的Person类的Hibernate映射文件。

    <?xml version="1.0"?>
    <!DOCTYPE hibernate-mapping SYSTEM
    "http://hibernate.sourceforge.net/
    hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="my.package">
    
      <class name="Person" table="PERSON">
    
        <id name="id" column="ID">
          <generator class="assigned" />
        </id>
    
        <version name="version" column="VERSION"
            unsaved-value="null" />
    
        <!-- Map Person-specific properties here. -->
    
      </class>
    
    </hibernate-mapping>
    

      注意,id下面的generator标签包含了属性class="assigned"。这个属性告诉Hibernate我们不是从数据库指派 id值,而是在代码中指派id值。Hibernate会简单地认为即使是新的未保存的对象也有id值。我们也给version标签新增了一个属性: unsaved-value="null"。这个属性告诉Hibernate应该把version值而不是id值为null作为对象是新创建而成的指示器。我们也可以简单地告诉Hibernate把负值作为对象未保存的指示符,如果您喜欢把version字段的类型设置为int而不是Integer,这将是很有用的。

      我们已经从转移到纯对象id中获取了不少好处。我们对equals()和hashCode()方法的实现更加简单而且更易阅读。这些方法再也不易出错而且无论在保存对象之前还是之后,它们都能与Collection一起正常工作。Hibernate也变得更快一些,这是因为在保存新的对象之前它再也不需要从数据库读取一个序列值。此外,新定义的equals()和hashCode()对于所有包含id对象的对象来说是通用的。这意味着我们可以把这些方法移至一个抽象父类。我们不再需要为每个域对象重新实现equals()和hashCode(),而且我们也不再需要考虑对于每个类来说哪些字段组合是唯一且不变的。我们只要简单地扩展这个抽象父类。当然,我们没必要强迫域对象从父类中扩展出来,所以我们定义了一个接口来保证设计的灵活性。

    public interface PersistentObject {
        public String getId();
        public void setId(String id);
    
        public Integer getVersion();
        public void setVersion(Integer version);
    }
    
    public abstract class AbstractPersistentObject
            implements PersistentObject {
    
        private String id = IdGenerator.createId();
        private Integer version;
    
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
    
        public Integer getVersion() {
            return version;
        }
        public void setVersion(Integer version) {
            this.version = version;
        }
    
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null ||
                !(o instanceof PersistentObject)) {
    
                return false;
            }
    
            PersistentObject other
                = (PersistentObject)o;
    
            // if the id is missing, return false
            if (id == null) return false;
    
            // equivalence by id
            return id.equals(other.getId());
        }
    
        public int hashCode() {
            if (id != null) {
                return id.hashCode();
            } else {
                return super.hashCode();
            }
        }
    
        public String toString() {
            return this.getClass().getName()
                + "[id=" + id + "]";
        }
    }
    

      现在我们有了一个简单而高效的方法来创建域对象。它们扩展了AbstractPersistentObject,该父类能在它们第一次创建时自动赋予一个id,并且恰当地实现了equals()和hashCode()。域对象也得到了一个toString()方法的合理默认实现,这个方法可以有选择地被重写。如果这是一个查询例子的测试对象或者示例对象,id可以被修改或者被设为null。否则它是不应当被改变的。如果因为某些原因我们需要创建一个扩展其他类的域对象,这个对象就应当实现PersistentObject接口而不是扩展抽象类。

       Person类现在就简单多了:

    public class Person
        extends AbstractPersistentObject {
    
        // Person-specific fields and behavior here
    
    }
    

      从上一个例子开始Hibernate映射文件就不会再改变了。我们不想麻烦Hibernate去了解抽象父类,我们只要保证每个 PersistentObject映射文件包含一个id项(和一个“被指派的”生成器)和一个带有unsaved-value="null"属性的 version标签。机敏的读者可能已经注意到,每当一个持久性对象被实例化的时候,它的id得到了指派。这意味着当Hibernate在内存中创建一个已保存对象的实例时,虽然这个对象是已经存在并从数据库中读取的,它也会得到一个新的id。这说好了。然后Hibernate会接着调用对象的setId ()方法,用保存的id来替换新分配的id。额外的id生成并不是什么问题,因为id生成算法是廉价的(也就是说,它并不牵扯到数据库)。

       到现在为止一切都很好,但是我们遗漏了一个重要的细节:如何实现IdGenerator.createId()。我们可以为理想中的键生成(key-generation)算法定义一些标准:

    • 键可以不牵扯到数据库而很廉价地生成。
    • 即使跨越不同的虚拟机和不同机器,键也要保证唯一性。
    • 如果可能,键可以由其他程序、编程语言和数据库生成,但是至少要能与它们兼容。

      我们所需的是通用唯一标识符(universally unique identifier,UUID)。UUID由16个字节(128位)的数字组成,遵守标准格式。UUID的String版本看起来类似如下:

       2cdb8cee-9134-453f-9d7a-14c0ae8184c6

      里面的字符是简单的字节16进制表示,横线把数字的不同部分分隔开来。这种格式简单而且易于处理,只是36个字符有点长了。因为横线总是被安置在相同的位置,所以可以把它们去掉,从而把字符的数目减少到32个。为了更为简洁地表示,可以创建一个byte[16]的数组或是两个8字节大小的long来保存这些数字。如果您使用的是Java 1.5或更高版本,可以直接使用UUID类,虽然这不是它在内存中最简洁的格式。有关更多信息,请参阅Wikipedia UUID条目JavaDoc UUID类条目

       UUID生成算法有多种实现。既然最终UUID是一种标准格式,我们在IdGenerator类中采用哪一种实现都没有关系。既然无论采用什么算法每个 id都会被保证唯一,我们甚至可以在任何时候改变算法的实现或是混合匹配不同的实现。如果您使用的是Java 1.5或更高版本,最方便的实现是java.util.UUID类:

    public class IdGenerator {
        public static String createId() {
            UUID uuid = java.util.UUID.randomUUID();
            return uuid.toString();
        }
    }
    

      对不使用Java 1.5或更高版本的人来说,至少有两种扩展库实现了UUID并且与1.5之前的Java版本兼容:Apache Commons ID项目Java UUID Generator (JUG)项目。它们在Apache License之下都是可用的(在LGPL之下JUG也是可用的)。

       这是使用JUG库实现IdGenerator的例子:

    import org.safehaus.uuid.UUIDGenerator;
    public class IdGenerator {
    
        public static final UUIDGenerator uuidGen
            = UUIDGenerator.getInstance();
    
        public static String createId() {
            UUID uuid
                = uuidGen.generateRandomBasedUUID();
            return uuid.toString();
        }
    }
    

      Hibernate中内置的UUID生成器算法又如何呢?这是获得对象身份的UUID的适当途径吗?如果您想让对象身份独立于对象持久性,这就不是一个好方法。虽然Hibernate确实提供了生成UUID的选项,但这样的话我们又回到了最早的那个问题上:对象ID的获得并不在它们被创建的时候,而是在它们被保存的时候。

      使用UUID作为数据库主键的最大障碍是它们在数据库中(而不是在内存中)的大小,在数据库中索引和外键的复合会促使主键大小的增加。您必须在不同情况下使用不同的表示方法。使用String表示,数据库的主键大小将会是32或36字节。数字也可以直接以字节存储,这样大小就减少一半,但是如果直接查询数据库,标识符将变得难以理解。这些方法对您的项目是否可行取决于您的需求。

      如果数据库不接受UUID作为主键,您可以考虑使用数据库序列。但总是应该在新对象创建的时候被指派一个ID而不是让Hibernate管理ID。在这种情况下,创建新域对象的业务对象可以调用一个使用数据访问对象(DAO)从数据库序列中检索id的服务。如果使用一个Long数据类型来表示对象id,一个单独的数据库序列(以及服务方法)对您的域对象来说就已经足够了。

    结束语

      当对象持久存储到数据库中时,对象身份总是很难被恰当地实现。尽管如此,问题其实完全在于,对象在保存之前允许对象没有id就存在。我们可以通过从诸如Hibernate这样的对象关系映射框架中获得指派对象ID的职责来解决这个问题。一旦对象被实例化,它就应该被指派一个ID。这使对象身份变得简单而不易出错,也减少了域模型中需要的代码量。

    作者简介

    James Brundege 目前是一位独立承包商,并在其拥有的公司Synaptocode Software LLC.中担当顾问。

    August 30

    理解HTTP session原理及应用

    理解HTTP session原理及应用

    理解session机制
    session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
    当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为  session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个  session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个  session id将被在本次响应中返回给客户端保存。
    保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID=  ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是  JSESSIONID。
    由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id 传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
    为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
    另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
    <form name="testform" action="/xxx">
    <input type="text">
    </form>
    在被传递给客户端之前将被改写成
    <form name="testform" action="/xxx">
    <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
    <input type="text">
    </form>
    这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
    实际上这种技术可以简单的用对action应用URL重写来代替。
    在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个  session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。
    恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。
    五、理解javax.servlet.http.HttpSession
    HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。
    首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,  cookie的生存时间等。
    一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session 也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,  Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。
    复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。
    cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。
    cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。
    关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
    六、HttpSession常见问题
    (在本小节中session的含义为⑤和⑥的混合)
    1、session在何时被创建
    一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用  HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <%  @page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句  HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的  session对象的来历。
    由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。
    2、session何时被删除
    综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)
    3、如何做到在浏览器关闭时删除session
    严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。
    4、有个HttpSessionListener是怎么回事
    你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有  HttpSessionBindingListener,HttpSessionActivationListener和  HttpSessionAttributeListener。
    5、存放在session中的对象必须是可序列化的吗
    不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在  Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果  session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。
    6、如何才能正确的应付客户端禁止cookie的可能性
    对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
    http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
    7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
    参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。
    8、如何防止用户打开两个浏览器窗口操作导致的session混乱
    这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。
    9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
    做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。
    10、为什么session不见了
    排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
    出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。
    七、跨应用程序的session共享
    常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。
    然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。
    首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从  Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。
    根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。
    笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。
    iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
    <session-info>
    <path>/NASApp</path>
    </session-info>
    需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得  setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。
    在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享 session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端 cookie,URL参数或者隐藏字段等手段。
    我们再看一下Weblogic Server是如何处理session的。
    从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下
    对于这样一种结构,在  session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端  cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,
    应用程序A
    context.setAttribute("appA", session); 
    应用程序B
    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 
    值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。
    那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为 /appA 访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。
    八、总结
    session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。
    摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

    August 29

    JBoss Seam: 具有上下文的组件模型

    Chapter 3. The contextual component model
    Prev     Next

    Chapter 3. The contextual component model 具有上下文的组件模型

    The two core concepts in Seam are the notion of a context and the notion of a component. Components are stateful objects, usually EJBs, and an instance of a component is associated with a context, and given a name in that context. Bijection provides a mechanism for aliasing internal component names (instance variables) to contextual names, allowing component trees to be dynamically assembled, and reassembled by Seam.

    Seam中两个核心概念是上下文概念和组件概念。组件是有状态的对象,通常是EJB,一个组件的实例和一个上下文相关联,并且在这个上下文中被命名。双向注入提供了一种别名机制,给内部组件名字(实例变量)创建了上下文别名,允许组件树能够被动态的组装,以及被Seam重新组装。

    Let's start by describing the contexts built in to Seam.

    让我们开始介绍Seam的内置上下文。

    3.1. Seam contexts Seam上下文

    Seam contexts are created and destroyed by the framework. The application does not control context demarcation via explicit Java API calls. Context are usually implicit. In some cases, however, contexts are demarcated via annotations.

    Seam上下文由Seam框架创建和销毁。应用并不显式调用Java API来控制上下文的划分。上下文通常都是隐含的。在某些情况下,上下文通过注释来划分。

    The basic Seam contexts are:

    基本的Seam上下文包括:

    • Stateless context 无状态上下文

    • Event (or request) context 事件(或请求)上下文

    • Page context 页面上下文

    • Conversation context 对话上下文

    • Session context 会话上下文

    • Business process context 业务流程上下文

    • Application context 应用上下文

    You will recognize some of these contexts from servlet and related specifications. However, two of them might be new to you: conversation context, and business process context. One reason state management in web applications is so fragile and error-prone is that the three built-in contexts (request, session and application) are not especially meaningful from the point of view of the business logic.

    你可能很快就能认出一些Servlet和相关规范中的上下文。但是有两个上下文你可能头一次遇到:对话上下文和业务流程上下文。Web应用的状态管理非常脆弱和容易出错的一个原因就是Web内置的上下文(请求,会话和应用)从业务流程的角度上来看并没有特别的意义。

    A user login session, for example, is a fairly arbitrary construct in terms of the actual application work flow. Therefore, most Seam components are scoped to the conversation or business process contexts, since they are the contexts which are most meaningful in terms of the application.

    例如,一个用户登录的会话,按照实际的应用工作流来说,是一个相当武断的构造。因此,大部分Seam组件都被指定在对话或者业务流程的上下文范围内,因为这些上下文更加具有应用意义。

    Let's look at each context in turn.

    让我们来依次看看每个上下文。

    3.1.1. Stateless context 无状态上下文

    Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (this is really a non-context). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they are important and often useful.

    完全没有状态的组件(主要是无状态会话Bean)总是存在于无状态上下文中(这是完全没有上下文)。无状态组件并不有趣,并且和面向对象的思想相违背。但是,它们非常重要并且非常有用。

    3.1.2. Event context 事件上下文

    The event context is the "narrowest" stateful context, and is a generalization of the notion of the web request context to cover other kinds of events. Nevertheless, the event context associated with the lifecycle of a JSF request is the most important example of an event context, and the one you will work with most often. Components associated with the event context are destroyed at the end of the request, but their state is available and well-defined for at least the lifecycle of the request.

    事件上下文是“最窄”的有状态的上下文,是负责其他类型事件的Web请求上下文的泛化。不过,和一个JSF请求的生命周期相关联的事件上下文是事件上下文的重要例子。这个事件上下文你会经常用到。和事件上下文相关联的组件在请求结束后被销毁,但是至少在请求的生命周期中组件的状态是可以获得的,并且经过了良好的定义。

    When you invoke a Seam component via RMI, or Seam Remoting, the event context is created and destroyed just for the invocation.

    当你通过RMI或者Seam Remoting调用Seam组件时,事件上下文只是为这个调用而被创建和销毁。

    3.1.3. Page context 页面上下文

    The page context allows you to associate state with a particular instance of a rendered page. You can initialize state in your event listener, or while actually rendering the page, and then have access to it from any event that originates from that page. This is especially useful for functionality like clickable lists, where the list is backed by changing data on the server side. The state is actually serialized to the client, so this construct is extremely robust with respect to multi-window operation and the back button.

    页面上下文能够让你将状态和一个已被绘制的页面中特定的实例相关联。你可以在事件监听器中初始化状态,或者实际绘制这个页面的时候初始化这个状态。这对于由在服务器端变化的数据构成的可点击列表这样的功能非常有用。这个状态实际上被序列化发送给客户端,所以这样的构建非常强壮,可以解决多窗口操作和支持回退按钮。

    3.1.4. Conversation context 对话上下文

    The conversation context is a truly central concept in Seam. A conversation is a unit of work from the point of view of the user. It might span several interactions with the user, several requests, and several database transactions. But to the user, a conversation solves a single problem.

    对话上下文是Seam中真正的核心概念。从用户角度出发,一个对话是一个工作单元。它可能跨越多个用户交互,多个请求,多个数据库的事务处理。但是对于用户来说,一个对话只解决一个单一的问题。

    For example, "book hotel", "approve contract", "create order" are all conversations. You might like to think of a conversation implementing a single "use case" or "user story", but the relationship is not necessarily quite exact.

    例如,“旅馆预定”,“合同批准”,“创建订单”都是对话。你肯能认为对话实现了单一的”用例“或者”用户故事“,但是这个关系并不非常确切。

    A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.

    一个对话持有关注”在这个窗口中,用户正在干什麽“的状态。一个单一用户可能在任何时间点上使用多个窗口进行多个对话。对话上下文能够让我们确保不同的对话之间不会相互冲突,产生Bug.

    It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!

    也许你会费些时间来熟悉面向对话的应用。但是一旦一明白并熟悉了这个概念,我想你会喜欢它的,并且,你将不必考虑对话这个事情了。

    Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.

    一些对话仅仅为了一个单一的请求而存在。跨越多个请求的对话必须通过Seam提供的注释来划分。

    Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.

    一些对话其实也是任务。任务是一种长时间运行的业务流程的对话,当业务流程成功的完成后,任务能够触发业务流程状态的改变。Seam提供了任务划分的特定一组注释。

    Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.

    对话能够套嵌对话,一个对话可能被一个更”宽“的对话包含。这是一个高级的特性。

    Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.

    通常,对话的状态在请求之间Servlet会话中由Seam实际持有。Seam实现了一个可配置的对话超时,能够自动的销毁失活的对话,从而保证了一个单一用户登录会话的状态不会由于用户放弃对话而无限制的增长。

    Seam serializes processing of concurrent requests that take place in the same long-running conversation context, in the same process.

    Seam将位于同一个流程中,同一个长时间运行的对话上下文中的并发请求序列化。

    Alternatively, Seam may be configured to keep conversational state in the client browser.

    另外,Seam也可以配置为在客户端浏览器中保留对话状态。

    3.1.5. Session context 会话上下文

    A session context holds state associated with the user login session. While there are some cases where it is useful to share state between several conversations, we generally frown on the use of session context for holding components other than global information about the logged in user.

    会话上下文持有用户登录会话的状态。虽然有些时候我们需要在不同的对话中共享状态,但通常我们不会同意使用会话上下文来持有组件而不是登录用户的全局信息。

    In a JSR-168 portal environment, the session context represents the portlet session.

    在JSR-168 portal环境中,会话上下文代表portlet会话。

    3.1.6. Business process context 业务流程上下文

    The business process context holds state associated with the long running business process. This state is managed and made persistent by the BPM engine (JBoss jBPM). The business process spans multiple interactions with multiple users, so this state is shared between multiple users, but in a well-defined manner. The current task determines the current business process instance, and the lifecycle of the business process is defined externally using a process definition language, so there are no special annotations for business process demarcation.

    业务流程上下文持有与长时间运行的业务流程相关的状态。这个状态由BPM引擎(JBoss jBPM)来管理和持久化。业务流程跨越多个用户的多个交互,所以这个状态以一种很好的方式在多用户间共享。当前任务决定当前业务流程实例,业务流程的生命周期通过外部的业务定义语言来定义,所以无需为业务流程的划分指定特别的注释。

    3.1.7. Application context 应用上下文

    The application context is the familiar servlet context from the servlet spec. Application context is mainly useful for holding static information such as configuration data, reference data or metamodels. For example, Seam stores its own configuration and metamodel in the application context.

    应用上下文和Servlet规范中的Servlet上下文非常相像。应用上下文主要持有一些静态信息,例如配置数据,引用数据或者元模型。例如,Seam将自己的配置信息和元模型存储在应用上下文中。

    3.1.8. Context variables 上下文变量

    A context defines a namespace, a set of context variables. These work much the same as session or request attributes in the servlet spec. You may bind any value you like to a context variable, but usually we bind Seam component instances to context variables.

    一个上下文定义一个名域,一系列上下文变量。这和Servlet规范中的会话和请求属性是一样的。你可以为一个上下文变量绑定任何值,但通常我们将Seam组件的实例绑定给上下文变量。

    So, within a context, a component instance is identified by the context variable name (this is usually, but not always, the same as the component name). You may programatically access a named component instance in a particular scope via the Contexts class, which provides access to several thread-bound instances of the Context interface:

    所以,在上下文中,一个组件实例通过上下文变量的名字来识别(上下文变量的名字通常和组件名字一样,但并不总是这样)。你可以编程通过Context类来访问某个特定范围的已经命名了的组件实例。Context类提供了很多线程绑定的实例的访问方法。

    User user = (User) Contexts.getSessionContext().get("user");

    You may also set or change the value associated with a name:

    你也可以设置或改变一个名字关联的值:

    Contexts.getSessionContext().set("user", user);

    Usually, however, we obtain components from a context via injection, and put component instances into a context via outjection.

    通常,我们通过注入方式从上下文中获得组件,通过注出方式将组件实例放入上下文。

    3.1.9. Context search priority 上下文搜索优先权

    Sometimes, as above, component instances are obtained from a particular known scope. Other times, all stateful scopes are searched, in priority order. The order is as follows:

    某些时候,例如上面所说的,组件从某个特定的已经知道的范围中获得。但是有些时候,所有有状态的范围都要通过优先队列搜索,优先队列如下:

     

    • Event context  事件上下文

    • Page context  页面上下文

    • Conversation context  对话上下文

    • Session context  会话上下文

    • Business process context  业务流程上下文

    • Application context  应用上下文

    You can perform a priority search by calling Contexts.lookupInStatefulContexts(). Whenever you access a component by name from a JSF page, a priority search occurs.

    你可以调用Contexts.lookupInStatefuleContext()方法进行一个优先搜索。无论你何时在一个JSF页面中通过名字来访问组件,一个优先搜索都会发生。

    3.1.10. Concurrency model 并发模型

    Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing threadsafeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.

    无论是Servlet还是EJB规范都没有定义任何工具来负责管理从同一个客户端发出的并发请求。Servlet容器简单的让所有线程都并发运行,将线程安全性的工作仍给了应用代码。EJB容器允许并发访问无状态的组件,如果多个线程访问一个有状态的组件的话抛出异常。

    This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.

    这样的行为在旧式Web应用下还可以,旧式Web应用基于细粒度(疑为笔误,应该是粗粒度)和同步请求。但是对于现代应用来说,大量的细粒度和异步请求(AJAX),并发请求是很常见的,需要被编程模型支持。Seam将并发管理层组织进它的上下文模型中。

    The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.

    The Seam会话和应用上下文是多线程的。Seam允许在一个上下文中的并发请求被并发处理。事件和页面上下文天生就是是单线程的。业务流程上下文严格的讲是多线程的,但是实际应用中,并发的情况非常少,大部分情况下都可以忽略。最后,Seam通过将在同一个长时间运行的对话上下文中的并发请求序列化来强迫实现一个流程一个对话一个单线程的模型。

    Since the session context is multithreaded, and often contains volatile state, session scope components are always protected by Seam from concurrent access. Seam serializes requests to session scope session beans and JavaBeans by default (and detects and breaks any deadlocks that occur). This is not the default behaviour for application scoped components however, since application scoped components do not usually hold volatile state and because synchronization at the global level is extremely expensive. However, you can force a serialized threading model on any session bean or JavaBean component by adding the @Synchronized annotation.

    因为会话上下文是多线程的,经常包含可变的状态,所以Seam总是保护并发访问会话范围的组件。默认情况下,Seam将请求序列话至会话范围的会话Beans和JavaBeans(同时监测和破坏任何死锁)。这个并不是应用范围的组件默认的行为,因为应用范围的组件通常很少持有可变的状态,要知道全局水平上的同步代价非常高昂。虽然如此,你仍然可以在任何会话Bean和JavaBean组件前加上@Synchronized注释,强迫组件使用序列化线程模型。

    This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.

    这个并发模型意味着AJAX客户端可以安全的使用可变的会话和对话状态,不用增加开发者的任何工作。

    3.2. Seam components  Seam组件

    Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.

    Seam组件是POJOs(普通的Java对象)。特别的,它们也可以是JavaBeans或者EJB3.0企业Bean。虽然Seam不要求组件是EJB甚至根本不需要EJB3.0兼容的容器,但是Seam是按照EJB3.0设计的,并且非常深的整合了EJB3.0。Seam支持下面的组件类型:

     

    • EJB 3.0 stateless session beans  EJB3.0无状态会话beans

    • EJB 3.0 stateful session beans  EJB3.0有状态会话beans

    • EJB 3.0 entity beans EJB3.0 实体beans

    • JavaBeans JavaBean

    • EJB 3.0 message-driven beans EJB3.0消息beans 

    3.2.1. Stateless session beans 无状态会话beans

    Stateless session bean components are not able to hold state across multiple invocations. Therefore, they usually work by operating upon the state of other components in the various Seam contexts. They may be used as JSF action listeners, but cannot provide properties to JSF components for display.

    Stateless session beans always live in the stateless context.

    Stateless session beans are the least interesting kind of Seam component.

    3.2.2. Stateful session beans

    Stateful session bean components are able to hold state not only across multiple invocations of the bean, but also across multiple requests. Application state that does not belong in the database should usually be held by stateful session beans. This is a major difference between Seam and many other web application frameworks. Instead of sticking information about the current conversation directly in the HttpSession, you should keep it in instance variables of a stateful session bean that is bound to the conversation context. This allows Seam to manage the lifecycle of this state for you, and ensure that there are no collisions between state relating to different concurrent conversations.

    Stateful session beans are often used as JSF action listener, and as backing beans that provide properties to JSF components for display or form submission.

    By default, stateful session beans are bound to the conversation context. They may never be bound to the page or stateless contexts.

    Concurrent requests to session-scoped stateful session beans are always serialized by Seam.

    3.2.3. Entity beans

    Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.

    Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.

    Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.

    By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.

    Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.

    3.2.4. JavaBeans

    Javabeans may be used just like a stateless or stateful session bean. However, they do not provide the functionality of a session bean (declarative transaction demarcation, declarative security, efficient clustered state replication, EJB 3.0 persistence, timeout methods, etc).

    In a later chapter, we show you how to use Seam and Hibernate without an EJB container. In this use case, components are JavaBeans instead of session beans. Note, however, that in many application servers it is somewhat less efficient to cluster conversation or session scoped Seam JavaBean components than it is to cluster stateful session bean components.

    By default, JavaBeans are bound to the event context.

    Concurrent requests to session-scoped JavaBeans are always serialized by Seam.

    3.2.5. Message-driven beans

    Message-driven beans may function as a seam component. However, message-driven beans are called quite differently to other Seam components - instead of invoking them via the context variable, they listen for messages sent to a JMS queue or topic.

    Message-driven beans may not be bound to a Seam context. Nor do they have access to the session or conversation state of their "caller". However, they do support bijection and some other Seam functionality.

    3.2.6. Interception 拦截

    In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed. For entity beans, interception is not required since bijection and context demarcation are not defined. For session beans, we must register an EJB interceptor for the session bean component. We could use an annotation, as follows:

    为了完成一些Seam的魔力(双向注射,上下文划分,验证等等),Seam必须拦截组件调用。对于JavaBeans,Seam完全控制了组件的实例化,不需要任何特定的配置。对于实体beans,没有必要进行拦截,因为没有定义双向注射和上下文划分。对于会话beans,我们必须为会话bean组件注册EJB拦截器。我们使用注释来实现这一点:

    @Stateless
    @Interceptors(SeamInterceptor.class)
    public class LoginAction implements Login {
    ...
    }

    But a much better way is to define the interceptor in ejb-jar.xml.

    然而一个更好的方式是在ejb-jar.xml中定义拦截器。

    <interceptors>
    <interceptor>
    <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
    </interceptor>
    </interceptors>

    <assembly-descriptor>
    <interceptor-binding>
    <ejb-name>*</ejb-name>
    <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
    </interceptor-binding>
    </assembly-descriptor>

    3.2.7. Component names 组件的名称

    All seam components need a name. We can assign a name to a component using the @Name annotation:

    所有Seam组件都要有名称,我们通过@Name注释给组件命名:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    ...
    }

    This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.

    这个名字是seam组件的名字和其他EJB规范定义的名字没有任何关系。但是,seam组件名字就像JSF管理bean的名字一样,这两个概念是一样的。

    @Name is not the only way to define a component name, but we always need to specify the name somewhere. If we don't, then none of the other Seam annotations will function.

    @Name不是唯一定义组件名字的方法,但是我们总要在某个地方指定名字。如果我们不这么做的话,其他seam注释也不会工作

    Just like in JSF, a seam component instance is usually bound to a context variable with the same name as the component name. So, for example, we would access the LoginAction using Contexts.getStatelessContext().get("loginAction"). In particular, whenever Seam itself instantiates a component, it binds the new instance to a variable with the component name. However, again like JSF, it is possible for the application to bind a component to some other context variable by programmatic API call. This is only useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable.

    和JSF一样,一个seam组件的实例通常和一个同名的上下文变量绑定。例如我们使用Context.getStatelessContext().get("loginAction")方法来访问LoginAction.特别的,无论何时Seam本身初始化一个组件,都将这个新组件与同名的变量绑定。然而又和JSF一样,你可以通过程序API调用将组件和其他上下文变量绑定。这只有在一个组件有多个角色的系统中才有用处。例如当前登录的User可以绑定在currentUser会话上下文变量中,但是一个User也可以绑定到user对话上下文变量,如果这个User负责一些管理功能。

    For very large applications, and for built-in seam components, qualified names are often used.

    对于大型应用和内置的seam组件,通常使用符合标准的名字。

    @Name("com.jboss.myapp.loginAction")
    @Stateless
    public class LoginAction implements Login {
    ...
    }

    We may use the qualified component name both in Java code and in JSF's expression language:

    我们可以在Java代码和JSF表达语言中都是用符合标准的组件名字:

    <h:commandButton type="submit" value="Login"
    action="#{com.jboss.myapp.loginAction.login}"/>

    Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml file:

    这个有点讨厌,Seam也提供了别名机制让你可以给符合标准的名字赋别名。例如在component.xml文件中添加一行:

    <factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

    All of the built-in Seam components have qualified names, but most of them are aliased to a simple name by the components.xml file included in the Seam jar.

    所有内置的Seam组件都有符合标准的名字,但是大部分都拥有别名,这些配置在Seam jar文件中的component.xml文件中。

    3.2.8. Defining the component scope 定义组件范围

    We can override the default scope (context) of a component using the @Scope annotation. This lets us define what context a component instance is bound to, when it is instantiated by Seam.

    我们可以使用@Scope注释来覆盖组件的默认范围(上下文)。这允许我们在Seam实例化组件实例的时候设置组件实例的上下文。

    @Name("user")
    @Entity
    @Scope(SESSION)
    public class User {
    ...
    }

    org.jboss.seam.ScopeType defines an enumeration of possible scopes.

    3.2.9. Components with multiple roles 拥有多个角色的组件

    Some Seam component classes can fulfill more than one role in the system. For example, we often have a User class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role annotation lets us define an additional named role for a component, with a different scope—it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)

    一些Seam组件类在系统中可以充当多个角色。例如,我们通常会有一个User类,这个类通常在会话范围内代表当前用户,但是在用户管理界面中也用到这个类作为对话范围组件。@Role注释让我们能够为组件定义一个附加的角色,可以有不同的范围,这样一个组件类可以绑定到不同的上下文变量中。(任何Seam组件实例可以和多个上下文变量绑定,但是这个注释让我们在类的级别上实现,充分利用了自动实例化的好处)。

    @Name("user")
    @Entity
    @Scope(CONVERSATION)
    @Role(name="currentUser", scope=SESSION)
    public class User {
    ...
    }

    The @Roles annotation lets us specify as many additional roles as we like.

    @Roles注释能让我们指定多个角色

    @Name("user")
    @Entity
    @Scope(CONVERSATION)
    @Roles({@Role(name="currentUser", scope=SESSION),
    @Role(name="tempUser", scope=EVENT)})
    public class User {
    ...
    }

    3.2.10. Built-in components 内置的组件

    Like many good frameworks, Seam eats its own dogfood and is implemented mostly as a set of built-in Seam interceptors (see later) and Seam components. This makes it easy for applications to interact with built-in components at runtime or even customize the basic functionality of Seam by replacing the built-in components with custom implementations. The built-in components are defined in the Seam namespace org.jboss.seam.core and the Java package of the same name.

    和其他不错的框架一样,Seam干了他自己该干的事情,实现了一套内置的Seam拦截器(参见后面)和Seam组件。这让应用能够非常容易的在运行时和内置组件交互,甚至通过使用定制的组件替换内置的组件来定制基本功能。内置的组件在Seam名域org.jboss.seam.core中定义,Java包有相同的名字。

    The built-in components may be injected, just like any Seam components, but they also provide convenient static instance() methods:

    内置的组件可以被注入,就像任何Seam组件一样,但是他们也提供了方便的instance()静态方法。

    FacesMessages.instance().add("Welcome back, #{user.name}!");

    3.3. Bijection 双向注射

    Dependency injection or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable.

    现在每个Java开发者都非常熟悉依赖注入或反转控制这些概念。依赖注入允许一个组件通过容器”注入“的方式获得另一个组件的引用,容器将另外的的组件注入setter或者实例变量中。

    In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable.

    在我们看到的所有依赖注入的实现中,注入发生在组件被构造时,在组件实例的生命周期内引用不能改变。对于无状态的组件来说,这是有道理的。从客户角度出发,一个特定的无状态的组件的所有实例都可以互换。

    On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:

    另一方面,Seam强调使用有状态的组件。所以传统的依赖注入不再是非常有用的构造了。Seam引入了注入的泛化概念双向注射。对比注入,双向注射是:

     

    • contextual - bijection is used to assemble stateful components from various different contexts (a component from a "wider" context may even have a reference to a component from a "narrower" context)

    • 上下文相关的-双向注射用来从各种不同的上下文中(一个来自”更宽“的上下文的组件可以拥有一个来自比它“更窄”的上下文中的组件的引用)组装有状态的组件 。

    • bidirectional - values are injected from context variables into attributes of the component being invoked, and also outjected from the component attributes back out to the context, allowing the component being invoked to manipulate the values of contextual variables simply by setting its own instance variables

    • 双向-值从上下文变量中注入到被调用的组件的属性中,并且可以从组件的属性中注出回上下文,允许组件被调用,通过简单的设置自己的实例变量来操作上下文变量的值

    • dynamic - since the value of contextual variables changes over time, and since Seam components are stateful, bijection takes place every time a component is invoked

    • 动态的-因为上下文变量的值随着时间而变化,还因为Seam组件是有状态的,每当一个组件被调用式都会发生双向注射

    In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.

    本质上说,双向注射能够让你通过指定实例变量的值是注入,还是注出或者两者都是,从而将一个上下文变量别名为一个组件的实例变量。当然,我们使用注释来完成双向注射。

    The @In annotation specifies that a value should be injected, either into an instance variable:

    @In注释指定一个值应该被注入到一个实例变量

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In User user;
    ...
    }

    or into a setter method:

    或者注入到一个setter方法中:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @In
    public void setUser(User user) {
    this.user=user;
    }

    ...
    }

    By default, Seam will do a priority search of all contexts, using the name of the property or instance variable that is being injected. You may wish to specify the context variable name explicitly, using, for example, @In("currentUser").

    在默认情况下,Seam将使用注入的实例或者属性的名字对所有的上下文进行优先搜索。你可以显式的指定上下文变量的名字。例如@In("currentUser").

    If you want Seam to create an instance of the component when there is no existing component instance bound to the named context variable, you should specify @In(create=true). If the value is optional (it can be null), specify @In(required=false).

    For some components, it can be repetitive to have to specify @In(create=true) everywhere they are used. In such cases, you can annotate the component @AutoCreate, and then it will always be created, whenever needed, even without the explicit use of create=true.

    You can even inject the value of an expression:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In("#{user.username}") String username;
    ...
    }

    (There is much more information about component lifecycle and injection in the next chapter.)

    The @Out annotation specifies that an attribute should be outjected, either from an instance variable:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @Out User user;
    ...
    }

    or from a getter method:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @Out
    public User getUser() {
    return user;
    }

    ...
    }

    An attribute may be both injected and outjected:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    @In @Out User user;
    ...
    }

    or:

    @Name("loginAction")
    @Stateless
    public class LoginAction implements Login {
    User user;

    @In
    public void setUser(User user) {
    this.user=user;
    }

    @Out
    public User getUser() {
    return user;
    }

    ...
    }

    3.4. Lifecycle methods

    Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback (@PostConstruct, @PreDestroy, etc). But Seam also supports the use of any of these callbacks with JavaBean components. However, since these annotations are not available in a J2EE environment, Seam defines two additional component lifecycle callbacks, equivalent to @PostConstruct and @PreDestroy.

    The @Create method is called after Seam instantiates a component. Components may define only one @Create method.

    The @Destroy method is called when the context that the Seam component is bound to ends. Components may define only one @Destroy method.

    In addition, stateful session bean components must define a method with no parameters annotated @Remove. This method is called by Seam when the context ends.

    Finally, a related annotation is the @Startup annotation, which may be applied to any application or session scoped component. The @Startup annotation tells Seam to instantiate the component immediately, when the context begins, instead of waiting until it is first referenced by a client. It is possible to control the order of instantiation of startup components by specifying @Startup(depends={....}).

    3.5. Conditional installation

    The @Install annotation lets you control conditional installation of components that are required in some deployment scenarios and not in others. This is useful if:

    • You want to mock out some infrastructural component in tests.

    • You want change the implementation of a component in certain deployment scenarios.

    • You want to install some components only if their dependencies are available (useful for framework authors).

    @Install works by letting you specify precedence and dependencies.

    The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precendence. There are some predefined precedence values (in ascending order):

    1. BUILT_IN — the lowest precedece components are the components built in to Seam.

    2. FRAMEWORK — components defined by third-party frameworks may override built-in components, but are overridden by application components.

    3. APPLICATION — the default precedence. This is appropriate for most application components.

    4. DEPLOYMENT — for application components which are deployment-specific.

    5. MOCK — for mock objects used in testing.

    Suppose we have a component named messageSender that talks to a JMS queue.

    @Name("messageSender") 
    public class MessageSender {
    public void sendMessage() {
    //do something with JMS
    }
    }

    In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:

    @Name("messageSender") 
    @Install(precedence=MOCK)
    public class MockMessageSender extends MessageSender {
    public void sendMessage() {
    //do nothing!
    }
    }

    The precedence helps Seam decide which version to use when it finds both components in the classpath.

    This is nice if we are able to control exactly which classes are in the classpath. But if I'm writing a reusable framework with many dependecies, I don't want to have to break that framework across many jars. I want to be able to decide which components to install depending upon what other components are installed, and upon what classes are available in the classpath. The @Install annotation also controls this functionality. Seam uses this mechanism internally to enable conditional installation of many of the built-in components. However, you probably won't need to use it in your application.

    3.6. Logging

    Who is not totally fed up with seeing noisy code like this?

    private static final Log log = LogFactory.getLog(CreateOrderAction.class);

    public Order createOrder(User user, Product product, int quantity) {
    if ( log.isDebugEnabled() ) {
    log.debug("Creating new order for user: " + user.username() +
    " product: " + product.name()
    + " quantity: " + quantity);
    }
    return new Order(user, product, quantity);
    }

    It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.

    Seam provides a logging API that simplifies this code significantly:

    @Logger private Log log;

    public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
    return new Order(user, product, quantity);
    }

    It doesn't matter if you declare the log variable static or not—it will work either way, except for entity bean components which require the log variable to be static.

    Note that we don't need the noisy if ( log.isDebugEnabled() ) guard, since string concatenation happens inside the debug() method. Note also that we don't usually need to specify the log category explicitly, since Seam knows what component it is injecting the Log into.

    If User and Product are Seam components available in the current contexts, it gets even better:

    @Logger private Log log;

    public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
    return new Order(user, product, quantity);
    }

    Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.

    3.7. The Mutable interface and @ReadOnly

    Many application servers feature an amazingly broken implementation of HttpSession clustering, where changes to the state of mutable objects bound to the session are only replicated when the application calls setAttribute() explicitly. This is a source of bugs that can not effectively be tested for at development time, since they will only manifest when failover occurs. Furthermore, the actual replication message contains the entire serialized object graph bound to the session attribute, which is inefficient.

    Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.

    For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by calling setAttribute() once in every request that the component was invoked by the application. Of course, this strategy is inefficient for read-mostly components. You can control this behavior by implementing the org.jboss.seam.core.Mutable interface, or by extending org.jboss.seam.core.AbstractMutable, and writing your own dirty-checking logic inside the component. For example,

    @Name("account")
    public class Account extends AbstractMutable
    {
    private BigDecimal balance;

    public void setBalance(BigDecimal balance)
    {
    setDirty(this.balance, balance);
    this.balance = balance;
    }

    public BigDecimal getBalance()
    {
    return balance;
    }

    ...

    }

    Or, you can use the @ReadOnly annotation to achieve a similar effect:

    @Name("account")
    public class Account
    {
    private BigDecimal balance;

    public void setBalance(BigDecimal balance)
    {
    this.balance = balance;
    }

    @ReadOnly
    public BigDecimal getBalance()
    {
    return balance;
    }

    ...

    }

    For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute() once in every request, unless the (conversation-scoped) entity is currently associated with a Seam-managed persistence context, in which case no replication is needed. This strategy is not necessarily efficient, so session or conversation scope entity beans should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the entity bean instance. For example,

    @Stateful
    @Name("account")
    public class AccountManager extends AbstractMutable
    {
    private Account account; // an entity bean

    @Unwrap
    public void getAccount()
    {
    return account;
    }

    ...

    }

    Note that the EntityHome class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component.

    3.8. Factory and manager components

    We often need to work with objects that are not Seam components. But we still want to be able to inject them into our components using @In and use them in value and method binding expressions, etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy, for example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple of nice features that make it easier to work with non-component objects bound to contexts.

    The factory component pattern lets a Seam component act as the instantiator for a non-component object. A factory method will be called when a context variable is referenced but has no value bound to it. We define factory methods using the @Factory annotation. The factory method binds a value to the context variable, and determines the scope of the bound value. There are two styles of factory method. The first style returns a value, which is bound to the context by Seam:

    @Factory(scope=CONVERSATION)
    public List<Customer> getCustomerList() {
    return ... ;
    }

    The second style is a method of type void which binds the value to the context variable itself:

    @DataModel List<Customer> customerList;

    @Factory("customerList")
    public void initCustomerList() {
    customerList = ... ;
    }

    In both cases, the factory method is called when we reference the customerList context variable and its value is null, and then has no further part to play in the lifecycle of the value. An even more powerful pattern is the manager component pattern. In this case, we have a Seam component that is bound to a context variable, that manages the value of the context variable, while remaining invisible to clients.

    A manager component is any component with an @Unwrap method. This method returns the value that will be visable to clients, and is called every time a context variable is referenced.

    @Name("customerList")
    @Scope(CONVERSATION)
    public class CustomerListManager
    {
    ...

    @Unwrap
    public List<Customer> getCustomerList() {
    return ... ;
    }
    }

    The manager component pattern is especially useful if we have an object where you need more control over the lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when the context ends you could @Unwrap the object, and perform cleanup in the @Destroy method of the manager component.

    @Name("hens")
    @Scope(APPLICATION)
    public class HenHouse {

    private Set<Hen> hens;

    @Unwrap
    public List<Hen> getHens() {
    if (hens == null) {
    // Setup our hens
    }
    return hens;
    }

    @Observer({"chickBorn", "chickenBoughtAtMarket"})
    public addHen() {
    hens.add(hen);
    }

    @Observer("chickenSoldAtMarket")
    public removeHen() {
    hens.remove(hen);
    }

    @Observer("foxGetsIn")
    public addHen() {
    hens.clear();
    }
    ...
    }

    Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.


    Prev  Up  Next
    Chapter 2. Getting started with Seam, using seam-gen  Home  Chapter 4. Configuring Seam components
    August 28

    JBoss Seam: Introduction

    Introduction to JBoss Seam
    Prev   Next

    Introduction to JBoss Seam   JBoss Seam介绍

    Seam is an application framework for Enterprise Java. It is inspired by the following principles:

    Seam是一种企业级Java的应用框架。它的灵感来源于以下原理或技术:

    One kind of "stuff"  只有一种工具

    Seam defines a uniform component model for all business logic in your application. A Seam component may be stateful, with the state associated with any one of several well-defined contexts, including the long-running, persistent, business process context and the conversation context, which is preserved across multiple web requests in a user interaction.

    Seam为你的应用程序中所有的商业逻辑定义了一种统一的组件模型。一个Seam组件可能是有状态的,这种状态可以和任何定义良好的上下文相关联,这些上下文包括长时间运行上下文,持久化上下文,商业过程上下文和在用户交互中能够跨越多个Web请求的会话上下文。

    There is no distinction between presentation tier components and business logic components in Seam. You can layer your application according to whatever architecture you devise, rather than being forced to shoehorn your application logic into an unnatural layering scheme forced upon you by whatever combination of stovepipe frameworks you're using today.

    Seam中的组件并不区分表现层组件和业务逻辑组件。你可以根据任何你设计的体系来将你的应用程序分层,而非必须削足适履的将你的程序逻辑被迫按照某种分层机制才能和你目前使用的框架相结合。

    Unlike plain Java EE or J2EE components, Seam components may simultaneously access state associated with the web request and state held in transactional resources (without the need to propagate web request state manually via method parameters). You might object that the application layering imposed upon you by the old J2EE platform was a Good Thing. Well, nothing stops you creating an equivalent layered architecture using Seam—the difference is that you get to architect your own application and decide what the layers are and how they work together.

              和普通的Java EE或者J2EE组件不同,Seam组件可以同时访问Web请求相关的状态和事务性资源持有的状态(不必手工的通过方法参数来传递Web请求的状态).你可能不认为在旧的J2EE平台上加入一个应用层是一种好办法。然而,你仍可以使用Seam来实现一个相同的应用层体系-区别在于,你需要架构你自己的应用并且决定需要哪些层以及它们如何协同。

    Integrate JSF with EJB 3.0 将JSF和EJB3.0整合

    JSF and EJB 3.0 are two of the best new features of Java EE 5. EJB3 is a brand new component model for server side business and persistence logic. Meanwhile, JSF is a great component model for the presentation tier. Unfortunately, neither component model is able to solve all problems in computing by itself. Indeed, JSF and EJB3 work best used together. But the Java EE 5 specification provides no standard way to integrate the two component models. Fortunately, the creators of both models foresaw this situation and provided standard extension points to allow extension and integration with other frameworks.

    JSF和EJB3.0是Java EE 5中最好的两个特性。EJB3是一个新的服务器端的业务和持久化组件模型。同时JSF是一种很棒的表现层组件。可惜的是,这两个组件模型都没有办法单独解决所有问题。实际上,JSF和EJB3可以完美结合。但是JavaEE5规范并没有提供这两种组件模型如何整合的标准。幸运的是,这两种模型的创建者预见了这种情况并且提供了标准的扩展点,从而我们能够通过扩展来与其他框架整合。

    Seam unifies the component models of JSF and EJB3, eliminating glue code, and letting the developer think about the business problem.

    Seam统一了JSF和EJB3的组件模型,减少了整合代码(意思是为了让这两种模型相互沟通而必须编写的额外代码),从而能够让开发者专心于业务问题

    It is possible to write Seam applications where "everything" is an EJB. This may come as a surprise if you're used to thinking of EJBs as coarse-grained, so-called "heavyweight" objects. However, version 3.0 has completely changed the nature of EJB from the point of view of the developer. An EJB is a fine-grained object—nothing more complex than an annotated JavaBean. Seam even encourages you to use session beans as JSF action listeners!

    你可以写出一个完全是EJB组成的Seam应用。如果你以前认为EJB是粗粒度,重量级的家伙,你肯定会感到惊讶。实际上,EJB的3.0版本完全的改变了原始设计,从开发折角度来说,完全颠覆了旧的EJB体系。现在的EJB是一种细粒度的轻量级的对象-并不比使用注释的JavaBean复杂多少。Seam甚至鼓励你将会话Bean作为JSF动作的监听器。

    On the other hand, if you prefer not to adopt EJB 3.0 at this time, you don't have to. Virtually any Java class may be a Seam component, and Seam provides all the functionality that you expect from a "lightweight" container, and more, for any component, EJB or otherwise.

              另一方面,如果你不想采用EJB3.0, 完全可以。 实际上任何Java类都可以成为Seam的组件,Seam提供了你          期望的一个轻量级容器能够提供的所有功能,并且提供了任何组件,包括EJB或其他的组件的所有功能。

    Integrated AJAX 和AJAX的整合

    Seam supports the best open source JSF-based AJAX solutions: JBoss RichFaces and ICEfaces. These solutions let you add AJAX capability to your user interface without the need to write any JavaScript code.

               Seam支持最棒的基于JSF的开源AJAX解决方案:JBoss RichFaces和ICEfaces. 这些解决方案能够让你根           本不用编写任何JavaScript代码的情况下,让你的用户体会到最棒的AJAX体验。

    Alternatively, Seam provides a built-in JavaScript remoting layer that lets you call components asynchronously from client-side JavaScript without the need for an intermediate action layer. You can ever subscribe to server-side JMS topics and receive messages via AJAX push.

    另外,Seam提供了内置的JavaScript远端层让你能够从客户端异步的调用组件,不需要通过动作层做中介。你可以随时订阅服务器端的JMS主题和通过AJAX push来接收消息。

    Neither of these approaches would work well, were it not for Seam's built-in concurrency and state management, which ensures that many concurrent fine-grained, asynchronous AJAX requests are handled safely and efficiently on the server side.

              这些方法能够很好的工作完全取决于Seam内置的并发和状态管理机制,它们保证了并发的细粒度,异步                AJAX请求能够安全并且有效的在服务器端被处理。

    Business process as a first class construct 一流的业务过程构建

    Optionally, Seam provides transparent business process management via jBPM. You won't believe how easy it is to implement complex workflows, collaboration and and task management using jBPM and Seam.

    Seam通过jBPM提供了透明的业务过程管理。你甚至不会相信通过jBPM和Seam能够这么简单的实现复杂的工作流以及协同和任务管理功能。

    Seam even allows you to define presentation tier pageflow using the same language (jPDL) that jBPM uses for business process definition.

    Seam甚至能够让你通过jBPM使用的业务过程定义语言(jPDL)来定义表现层的页面流。

    JSF provides an incredibly rich event model for the presentation tier. Seam enhances this model by exposing jBPM's business process related events via exactly the same event handling mechanism, providing a uniform event model for Seam's uniform component model.

              JSF提供了不可思议的表现层富事件模型。通过以通用事件处理机制一样的方式暴露jBPM的业务过程相关的          事件,Seam增强了这一富事件模型,为Seam统一的组件模型提供了一种统一的事件模型。

    Declarative state management 声明式的状态管理

    We're all used to the concept of declarative transaction management and declarative security from the early days of EJB. EJB 3.0 even introduces declarative persistence context management.

               我们在早期的EJB中非常熟悉声明式的事务管理和安全管理的概念。EJB3.0甚至引入了声明式的持久性上           下文管理。

    These are three examples of a broader problem of managing state that is associated with a particular context, while ensuring that all needed cleanup occurs when the context ends. Seam takes the concept of declarative state management much further and applies it to application state. Traditionally, J2EE applications implement state management manually, by getting and setting servlet session and request attributes.

              。。。。。。。Seam延伸了声明式状态管理的概念,提出了应用状态。传统的J2EE应用通过getting和           setting Servlet会话和请求属性来手工实现状态管理。          理

     This approach to state management is the source of many bugs and memory leaks when applications fail to clean up session attributes, or when session data associated with different workflows collides in a multi-window application. Seam has the potential to almost entirely eliminate this class of bugs.

    但是通过这种方式来进行状态管理非常容易造成很多Bug和内存泄漏问题,特别是当应用程序清除会话属性失败或者和在多窗口应用中不同工作流相关联的会话数据冲突时。Seam能够完全避免这种类型的Bug.

    Declarative application state management is made possible by the richness of the context model defined by Seam. Seam extends the context model defined by the servlet spec—request, session, application—with two new contexts—conversation and business process—that are more meaningful from the point of view of the business logic.

    通过Seam定义的丰富的上下文模型,Seam能够进行声明式的应用状态管理。Seam扩展了Servlet规范-请求,会话,应用-加入了两个新的上下文-会话和业务过程,从业务逻辑的角度来说,这样更具意义。

    You'll be amazed at how many things become easier once you start using conversations. Have you ever suffered pain dealing with lazy association fetching in an ORM solution like Hibernate or JPA? Seam's conversation-scoped persistence contexts mean you'll rarely have to see a LazyInitializationException. Have you ever had problems with the refresh button? The back button? With duplicate form submission? With propagating messages across a post-then-redirect? Seam's conversation management solves these problems without you even needing to really think about them. They're all symptoms of the broken state management architecture has been prevalent since the earliest days of the web.

    Bijection 双向注射

    The notion of Inversion of Control or dependency injection exists in both JSF and EJB3, as well as in numerous so-called "lightweight containers". Most of these containers emphasize injection of components that implement stateless services.

             JSF和EJB3中都存在反转控制或依赖注入的概念,其他的一些所谓“轻量级容器“也都有这些概念。大部分这         些容器都强调实现了无状态服务的组件的注入。

    Even when injection of stateful components is supported (such as in JSF), it is virtually useless for handling application state because the scope of the stateful component cannot be defined with sufficient flexibility, and because components belonging to wider scopes may not be injected into components belonging to narrower scopes.

    虽然有状态的组件注入被支持(例如JSF),其实它在应用状态的处理中毫无用处,因为我们无法足够灵活的定义有状态的组件的作用范围,并且作用范围大的组件不能注入到作用范围小的组件中去。

    Bijection differs from IoC in that it is dynamic, contextual, and bidirectional. You can think of it as a mechanism for aliasing contextual variables (names in the various contexts bound to the current thread) to attributes of the component. Bijection allows auto-assembly of stateful components by the container. It even allows a component to safely and easily manipulate the value of a context variable, just by assigning to an attribute of the component.

              双向注射和反转控制不同,它是动态的,具有上下文的,并且是双向的。你可以将它想象成一种组件属性的         上下文变量的别名机制(不同上下文中的名字进入到当前线程中)。双向注射能够通过容器自动组装有状态         的组件。它甚至允许一个组件仅仅通过属性赋值的方式安全并且简单的操作一个上下文变量的值。

    Workspace management and multi-window browsing 工作域管理和多窗口浏览

    Seam applications let the user freely switch between multiple browser tabs, each associated with a different, safely isolated, conversation. Applications may even take advantage of workspace management, allowing the user to switch between conversations (workspaces) in a single browser tab. Seam provides not only correct multi-window operation, but also multi-window-like operation in a single window!

              Seam应用能够让用户在多个浏览器窗口间切换,每个窗口和一个不同的安全隔离的对话相关联。应用甚至          利用工作域管理机制,允许用户在单一的浏览器窗口中的不同的对话(工作域)间切换。Seam不仅仅提供          正确的多窗口操作,并且在单一窗口中也能够正确处理多窗口形式的操作。

    Prefer annotations to XML  使用注释,而不是XML

    Traditionally, the Java community has been in a state of deep confusion about precisely what kinds of meta-information counts as configuration. J2EE and popular "lightweight" containers have provided XML-based deployment descriptors both for things which are truly configurable between different deployments of the system, and for any other kinds or declaration which can not easily be expressed in Java. Java 5 annotations changed all this.

    EJB 3.0 embraces annotations and "configuration by exception" as the easiest way to provide information to the container in a declarative form. Unfortunately, JSF is still heavily dependent on verbose XML configuration files. Seam extends the annotations provided by EJB 3.0 with a set of annotations for declarative state management and declarative context demarcation. This lets you eliminate the noisy JSF managed bean declarations and reduce the required XML to just that information which truly belongs in XML (the JSF navigation rules).

    Integration testing is easy 整合测试变简单了

    Seam components, being plain Java classes, are by nature unit testable. But for complex applications, unit testing alone is insufficient. Integration testing has traditionally been a messy and difficult task for Java web applications. Therefore, Seam provides for testability of Seam applications as a core feature of the framework.

              Seam组件本身是普通的Java类,自然非常适合单元测试。但是对于复杂的应用来说,单元测试是不够的。
    整合测试曾经是Java Web应用中非常痛苦和困难的任务。因此,Seam将整合测试作为整个应用框架中重要的核心特征。

    You can easily write JUnit or TestNG tests that reproduce a whole interaction with a user, exercising all components of the system apart from the view (the JSP or Facelets page). You can run these tests directly inside your IDE, where Seam will automatically deploy EJB components using JBoss Embedded.

              你可以简单的编写JUnit或TestNG测试,模拟和用户的整个交互,剥离视图(JSP或Facelets页面)测试系统          所有的组件。你可以直接在你的IDE中运行测试,Seam使用JBoss Embedded自动将EJB组件部署。

    The specs ain't perfect 规范总是不够完美

    We think the latest incarnation of Java EE is great. But we know it's never going to be perfect. Where there are holes in the specifications (for example, limitations in the JSF lifecycle for GET requests), Seam fixes them. And the authors of Seam are working with the JCP expert groups to make sure those fixes make their way back into the next revision of the standards.

             我们觉得最近的Java EE规范不错。但是它远远称不上完美。规范中到处都有漏洞(例如,JSF生命周期对于         GET请求具有很大的限制),Seam矫正了这些漏洞。Seam的创建者和JCP的专家组一起工作,将在下一个         标准修订版本中矫正它们。

    There's more to a web application than serving HTML pages Web应用不仅仅是HTML页面

    Today's web frameworks think too small. They let you get user input off a form and into your Java objects. And then they leave you hanging. A truly complete web application framework should address problems like persistence, concurrency, asynchronicity, state management, security, email, messaging, PDF and chart generation, workflow, wikitext rendering, webservices, caching and more. Once you scratch the surface of Seam, you'll be amazed at how many problems become simpler...

    当前的Web框架的视野太小。这些框架只是让你获得用户的输入,将它们传入你的Java对象。然后它们消失了。一个真正的Web应用框架应该负责解决诸如持久性,并发性,异步性,状态管理,安全,电子邮件,消息服务,PDF,图表绘制,工作流,Wiki文本绘制,Web服务,缓存和更多的问题。一旦你尝试了Seam,你会惊奇的发现这些问题非常简单...

    Seam integrates JPA and Hibernate3 for persistence, the EJB Timer Service and Quartz for lightweight asychronicity, jBPM for workflow, JBoss Rules for business rules, Meldware Mail for email, Hibernate Search and Lucene for full text search, JMS for messaging and JBoss Cache for page fragment caching. Seam layers an innovative rule-based security framework over JAAS and JBoss Rules.

              Seam为持久性整合了JPA,Hibernate3,为轻量级的异步性整合了EJB Timer服务和Quartz, 为工作流整合了          jBPM, 为业务规则整合了JBoss Rules, 为电子邮件整合了 Meldware Mail, 为全文搜索整和Hibernate Search
    和Lucene, 为消息服务整合了JMS,为页面片段缓存整合了JBoss Cache. Seam甚至在JAAS和JBoss Rules上增加了创新的基于规则的安全性框架。

    There's even JSF tag libraries for rendering PDF, outgoing email, charts and wikitext. Seam components may be called synchronously as a Web Service, asynchronously from client-side JavaScript or Google Web Toolkit or, of course, directly from JSF.

              甚至,Seam提供了JSF标签库来绘制PDF, 电子邮件,图表和Wiki文本。Seam组件能够被Web Service同步          调用,被客户端JavaScript或者Google Web Toolkit异步调用,当然也能被JSF直接调用。

    Get started now! 现在就开始!

    Seam works in any Java EE application server, and even works in Tomcat. If your environment supports EJB 3.0, great! If it doesn't, no problem, you can use Seam's built-in transaction management with JPA or Hibernate3 for persistence. Or, you can deploy JBoss Embedded in Tomcat, and get full support for EJB 3.0.

              Seam可以在任何Java EE应用服务器中运行,甚至可以在Tomcat中运行。如果你的环境支持EJB3.0,那很          好!不过如果不支持,那也没关系。你可以使用Seam内置的事务管理,使用JPA或Hibernate3实现持久性。          或者,你可以将JBoss Embedded部署到Tomcat中,从而获得对EJB3.0的完全支持。

    It turns out that the combination of Seam, JSF and EJB3 is the simplest way to write a complex web application in Java. You won't believe how little code is required!

    现实表明,将Seam,JSF, EJB3结合以来使用是开发复杂的Java Web应用的最简单的方式!你根本不能相信它需要的代码量少到什么地步!



    Prev Up Next
    Seam - Contextual Components Home Chapter 1. Seam Tutorial
    August 27

    Netbeans Platform: 工具包

    Overview

    Utility Classes 工具类

    Not all of the classes in this package are of interest for all module writers, but some of them may or even are as they are used through out our sources.
    并不是所有的模块开发者都对这个工具包中的所有类感兴趣,但是,它们却是在我们源代码中被广泛使用。

    Package org.openide.util

    • Lookup and its associated support package as that is the adaptable interface that objects can provide if they wish to offer dynamic capabilities.
    • 查找类和它的相关支持包,支持包是适配器接口,对象可以通过它提供动态的能力
    • NbBundle as our specialized support for localization and replacement to ResourceBundle.
    • NbBundle是Netbeans特定的本地化支持类,是ResourceBundle的替代。
    • Task and especially RequestProcessor which is our way to manage pools of thread workers and execute asynchronous computations.
    • 任务和请求处理器,请求处理器是Netbeans Platform用来管理线程池和进行异步计算的工具
    • HelpCtx to specify help ids for various UI components
    • HelpCtx用来指定不同用户界面组件的帮助ID
    • Utilities which contain a lot of methods of possible interest. For example actionsGlobalContext, loadImage, mergeImage, topologicalSort, activeReferenceQueue, translate.
    • 工具类包含多个有意思的方法。例如...
    • Enumerations provide enhacened support for manipulation with Enumerations and especially their on-demand generation.
    • 枚举提供了枚举的增强操作

    Services Registration and Lookup API 服务注册和查找API

    For lookup, this centers around Lookup and helper implementations in org.openide.util.lookup.

    对于lookup,这部分围绕位于org.openide.util.lookup的Lookup类和其帮助类

    Contents 内容

    Lookup 查找

    The whole NetBeans platform is moving toward installation of services via XML layer or META-INF/services. Layer-based installation is more flexible in many ways.

    整个Netbeans平台向着以XML层或者META-INF/services方式来安装服务的方向发展。基于层的安装在很多情况下更加具有弹性。

    The need for having a standard interface to access such registrations gave rise to the lookup system first introduced in NetBeans 3.2 and expanded upon for NetBeans 3.3. The center of this API from the client perspective is very simple - you can look up a class, and get an instance of that class (or a collection of them). The service provider side of it is more complex but useful lookup implementations are already provided in the core system; for common cases you can register an object into lookup just by adding one simple file to an XML layer or META-INF/services/classname in your module JAR file.

    This section of the Services API will first discuss what instances are and how to create them from files, as this is the core concept for service providers. It will discuss how you can manually retrieve sets of instances as a client, which is not used very frequently in new code but helps to understand what lookup is doing behind the scenes. Then lookup itself is discussed, and how the standard instance lookup works and how it relates to JDK's standard for service provider registration. Lookup templates, which separate the provision of instances from the provision of categories, will be explained.

    Working with Instances

    Central to the management of services and many other aspects of NetBeans' configuration is the notion of instances. An instance is just any Java object, generally of a particular type appropriate to its use, which is provided from some object (generally, a data object) using InstanceCookie. As an example, menu items may be added by inserting data objects that provide this cookie into the proper folder, and having the instance be a system action (or other things). Or an XML DTD may be registered by placing an object with an instance of org.xml.sax.EntityResolver in the proper folder.

    Where do these instances come from? Technically, it is up to you to decide how to provide InstanceCookie; you could if really necessary create your own data loader that provides it according to some unusual scheme, and add files recognized by that loader to the right folder. Practically, the APIs provide implementations of this cookie sufficient for normal purposes.

    The most common way to provide an instance is using InstanceDataObject. This is a type of data object whose sole purpose is to provide the instance cookie. It typically does so based on a class name you supply. There are several styles of instance file; all are empty (i.e. the file contents are of zero length, and just the file name and attributes matter). There are then other ways of providing instances which rely on the file contents. Here are the methods of providing instances defined in the APIs:

    Default instance - InstanceDataObject

    If there is a file with the extension *.instance, then its name (minus extension) will be converted to a class name by replacing dashes with dots; and a fresh instance of that class will be created and used as the instance. For example, com-mycom-mymodule-MyAction.instance produces an instance of the class com.mycom.mymodule.MyAction.

    Since reflection is used to create the new instance, just as in the realm of JavaBeans, the class must be loadable from your module or otherwise from within NetBeans (technically, via the classloader found by querying Lookup for ClassLoader); public; and have a public no-argument constructor. (Or be a SharedClassObject.) This form is often used for singleton classes such as system actions: there is no need to specify any parameters to the constructor, any instance will suffice.

    Default instance with separate class - InstanceDataObject

    Rather than shoving the class name into the file name, you can name the file more normally and specify the class with a file attribute. Then the class name is specified as a string-valued attribute on the instance named instanceClass. For example, a keyboard shortcut could be registered as follows in an XML layer:

    <file name="C-F6.instance">
    <attr name="instanceClass" stringvalue="com.mycom.mymodule.MyAction"/>
    </file>

    In addition to instanceClass you may specify an additional attribute instanceOf giving the name of a superclass (or implemented interface) of the instance class. In fact it may be a comma-separated list of superclasses and interfaces. While its purpose is explained more fully below, essentially it lets you give the system a hint as to what this instance is for before your instance class is even loaded into the VM. For example:

    <file name="com-me-some-service.instance">
    <attr name="instanceClass" stringvalue="com.me.FactoryForEverything"/>
    <attr name="instanceOf"
    stringvalue="org.xml.sax.EntityResolver,org.openide.cookies.ExecCookie"/>
    </file>
    Non-default instance - InstanceDataObject

    A powerful way of providing instances is to use the expressiveness of the XML layer syntax to handle the instance creation. In this case the file attribute instanceCreate can be defined and the attribute value becomes the instance. Typically the attribute value would be specified using the methodvalue syntax of layers. For example:

    <file name="com-me-some-service.instance">
    <attr name="instanceClass" stringvalue="com.me.FactoryForEverything"/>
    <attr name="instanceCreate" methodvalue="com.me.FactoryForEverything.configure"/>
    <attr name="myParam" urlvalue="nbres:/com/me/config-1.properties"/>
    <attr name="instanceOf"
    stringvalue="org.xml.sax.EntityResolver,org.openide.cookies.ExecCookie"/>
    </file>

    According to the general system for XMLFileSystem, you now need a method configure in FactoryForEverything which must be static; the method need not be public (if you do not want other Java code to see it). It may take a file object as argument if you wish - this will be the instance file; typically you use this to pass extra configuration from the layer, useful if you want to create multiple instances with the same creation method. So for example you might implement such a method like this:

    public class FactoryForEverything extends SomeBaseFactory
    implements EntityResolver, ExecCookie {
    public FactoryForEverything(Map props) {
    // ...
    }
    // ...
    // Called directly from XML layer. Pass URL to
    // properties file from attr 'myParam'.
    private static Object configure(FileObject inst) throws IOException {
    URL u = (URL)inst.getAttribute("myParam");
    Properties p = new Properties();
    p.load(u.openStream());
    return new FactoryForEverything(p);
    }
    }
    Serialized beans

    A simple way to provide an instance is to serialize it as a JavaBean, into a file with the extension *.ser. This is not very useful from a layer, because you should avoid putting binary data into a layer, but may be useful in some circumstances.

    XML-based instancesEntityCatalog

    Again, modules may also have additional ways of providing instances from files. For example, currently the utilities module enables any URL file (*.url) to be used directly in a menu, as the URL file provides an instance of Presenter.Menu.

    As an interactive demonstration of these things, first go into Filesystem Settings and make the system filesystem (first in the list) visible; then explore its contents in Filesystems, going into some subdirectory of Menu. Note the various actions and menu separators; these are all by default instance data objects. You may find some of these on disk in your installation directory under system/ if you have customized them, but by default they live in memory only. You may copy-and-paste these instances from one place to another; create new ones on disk and watch them be recognized and inserted into the menus after a few seconds; and you may also choose Customize Bean (which really customizes the provided instance) on (say) a toolbar separator (under Toolbars) to change the separator size, and serialize the result to a new *.ser file which should then create a toolbar separator.

    Folders of Instances

    InstanceCookie.Of and lazy class loading

    Now it is time to mention the purpose of InstanceCookie.Of. Suppose that there are two generic interfaces under consideration: e.g. javax.swing.Action and org.xml.sax.EntityResolver. For each interface, there is code to find registered instances, all under the Services/ folder. Furthermore, using either of these interfaces is relatively rare, and might not happen at all during a NetBeans session; and the implementations of the instances are complicated and involve a lot of code, so it is undesirable (for performance reasons) to load these implementations unless and until they are really needed. If you write the layers simply like this:
    <filesystem>
    <folder name="Services">
    <folder name="Hidden">
    <file name="com-me-MyAction.instance"/>
    <file name="com-me-MyResolver.instance"/>
    </folder>
    </folder>
    </filesystem>
    everything will work, but this is inefficient. Consider some piece of code asking for all actions. The search through the services folder for actions would ask each of these files if it provides an instance cookie assignable to javax.swing.Action. For com-me-MyAction.instance, this will load the class com.me.MyAction, determine that it implements Action, and thus create a MyAction instance and return it; so far so good. But when com-me-MyResolver.instance is encountered, it will again load com.me.MyResolver, only to find that this does not implement Action and skip the instance. The behavior is correct, but now the MyResolver class has been loaded into the VM even though no one will ever use it (unless a resolver search is made). This will degrade startup time and memory usage (and thus performance).

    So the better solution is to mark each file in advance, saying what interfaces it is intended to provide in its instance:

    <filesystem>
    <folder name="Services">
    <folder name="Hidden">
    <file name="com-me-MyAction.instance">
    <attr name="instanceOf" stringvalue="javax.swing.Action"/>
    </file>
    <file name="com-me-MyResolver.instance">
    <attr name="instanceOf" stringvalue="org.xml.sax.EntityResolver"/>
    </file>
    </folder>
    </folder>
    </filesystem>

    Now the folder instance processor for Action will pass over com-me-MyResolver.instance without needing to load com.me.MyResolver, since it sees that its interfaces are declared, and Action is not among them. Of course, the interface classes - Action and EntityResolver - need to be loaded right away, but they were probably already loaded anyway, so this is acceptable.

    Caution: if you do supply an instanceOf attribute, but it does not list all of the implemented interfaces and superclasses of the actual implementation class (including that implementation class itself, which is not implied), a lookup query on one of the missing superclasses may or may not succeed. So you should include in instanceOf any superclasses and interfaces that you think someone might use in a lookup query, possibly including the actual implementation class.

    Lookup and Service Installation

    The client side of the lookup system centers around one class, Lookup. In the simplest usage, all that is needed is to get some single instance of a given class (or subclass). For example, if some kind of service has been defined as an interface or abstract class, and you wish to find the implementation of it, you may simply use:
    MyService impl = (MyService)Lookup.getDefault().lookup(MyService.class);
    if (impl == null) /* nothing registered */ ...
    impl.useIt();
    Such implementation has to be registered by some module to the system. Either via layer as described above or as a JDK's service provider. If some module wants to register for example org.me.MyService it shall provide file name META-INF/services/org.me.MyService in its own JAR with single line containing name of the implementation class (for example org.you.MyServiceImpl). The lookup infrastructure will then load the implementation class and call its default constructor to answer the query in the above example.

    The Lookup supports two small extensions to the JDK's standard. It allows a module to remove class registered by another one. That is why it is possible to write a module that disables the org.you.MyServiceImpl implementation and provides its own. This the expected content of its META-INF/services/org.me.MyService file:
      # remove the other implementation (by prefixing the line with #-)
    #-org.you.MyServiceImpl

    # provide my own
    org.alien.MyServiceAlienImpl
    The reason why the removal line starts with #- is to keep compatibility with JDK's implementation. The # means comment and thus JDK will not interpret the line and will not get confused by the - before class name.

    Second extension allows ordering of items. The class implementing the interface can be followed by advisory position attribute. If multiple implementations are defined in one file then each can be followed by its own positioning attribute. When querying on an interface, items with a smaller position are guaranteed to be returned before items with a larger position. Items with no defined position are returned last. Example of content of META-INF/services/org.me.MyService file could be:

      org.you.MyServiceImpl
    #position=20
    org.you.MyMoreImportantServiceImpl
    #position=10
    The MyMoreImportantServiceImpl will be returned in lookup before the MyServiceImpl. It is recommended to pick up larger numbers so that there is gap for other modules if they need to get in front of your item. And, again, to keep compatibility the position attribute must starts with comment delimiter.

    If more than one implementation has been registered, the "first" will be returned. For example, if the implementations were present in the Services folder as *.instance files, then folder order would control this.

    As mentioned above, the NetBeans default lookup searches in the Services folder and its subfolders for instances whose class matches the requested class. Technically, it looks for data objects with InstanceCookie.Of claiming to match the requested superclass, or plain InstanceCookie whose instanceClass is assignable to it.

    Note that you may use this method to find singleton instances of subclasses of SharedClassObject that have been registered in lookup (as is normally the case for system options). However for this purpose it is simpler to use the static finder method SharedClassObject.findObject(Class, true) which is guaranteed to find the singleton whether it was registered in lookup or not (if necessary it will first initialize the object according to saved state).

    In many situations it is normal for there to be more than one registered implementation of a service. In such a case you use a more general method:

    Lookup.Template templ = new Lookup.Template(MyService.class);
    final Lookup.Result result = Lookup.getDefault().lookup(templ);
    Collection impls = result.allInstances(); // Collection<MyService>
    // use Java Collections API to get iterator, ...
    // Pay attention to subsequent changes in the result.
    result.addLookupListener(new LookupListener() {
    public void resultChanged(LookupEvent ev) {
    // Now it is different.
    Collection impls2 = result.allInstances();
    // use the new list of instances...
    }
    });
    Here you receive a collection of all instances matching your query, again in the order found if this matters. You can also listen to changes in the list (additions, deletions, and reorderings). It is fine to keep a Lookup.Result for a long period of time as it may implement its own caching scheme and only really compute the instances when allInstances is called; in fact it may be more efficient to keep a result, and listen for changes in it, than to repeatedly call lookup and create fresh result objects.

    When a lookup query is finished - for example when Lookup.Result.allInstances() returns some Collection of instances - it is guaranteed that all objects registered in the same thread prior to the call to lookup() or prior to some change notification on the Lookup.Result, will be returned. Specifically, lookup instances registered via module layer will be available by the time ModuleInstall.restored() (or .installed()) is called. There are two situations in which lookup results may be incomplete: when you are currently inside the dynamic scope of some method providing a lookup instance itself; and when you are dynamically inside a DataLoader method involved in recognizing data objects.

    Persisting Lookup Information

    In some circumstances it is necessary to not only find registered objects, but to select some of them and make this selection persistent. For example, some setting may have as its value a choice among available services matching some interface; the value needs to be persisted, but it is the identity of the choice, rather than the full state of the instance itself, which must be stored.

    In such cases it is possible to use code like this:

    Lookup.Template templ = new Lookup.Template(MyService.class);
    Lookup.Result result = Lookup.getDefault().lookup(templ);
    Iterator it = result.allItems().iterator();
    while (it.hasNext()) {
    Lookup.Item item = (Lookup.Item)it.next();
    String displayName = item.getDisplayName();
    if (/* user accepts displayName as the right one */) {
    MyService instance = (MyService)item.getInstance();
    // use instance for now, and ...
    String id = item.getId();
    someSettings.setChosenService(id);
    break;
    }
    }
    // later...
    String storedID = someSettings.getChosenService();
    Lookup.Template templ = new Lookup.Template(MyService.class, storedID, null);
    Iterator it = Lookup.getDefault().lookup(templ).allInstances().iterator();
    if (! it.hasNext()) /* failed to find it... */
    MyService instance = (MyService)it.next();
    // use instance again
    The ID permits you to track which instance from all those available in the lookup result was last selected by the user, and find the "same" instance later, perhaps after a restart of NetBeans. The exact form of the ID is the private knowledge of the implementor of the lookup, but typically if the instance has been provided via layer the ID will mention the name of the file from which it was derived.

    Creating Lookups

    There are a number of reasons to create your own lookup implementation. For one thing, the lookup system which scans the Services/ folder will recognize instances of subclasses of Lookup specially by proxying to them. This can be very powerful because you may register just one layer file which points to your custom lookup, which in turn may provide an unlimited number of actual instances/items for queries (and compute them in a manner other than registration by files). Another reason is to associate a context with a data object using Environment.Provider; for example the data object might be an XML file and this provider might be registered with the file by means of the public ID of the doctype (informally, the DTD). Here the provider has an associated lookup which can serve requests for cookies and such things. See more information about associating lookups with XML files.

    The simplest way to create a fresh lookup is to base it on other existing ones. ProxyLookup accepts a list of other lookup implementations (in the constructor and also changeable later). The results it provides are constructed by merging together the results of the delegate lookups.

    If you want to use the common mechanism of finding instances in folders (or subfolders) and serving these as the results, Lookups.forPath(String) makes this possible: you need only provide a name of a folder to look in, and use Lookups.forPath(theFolderName) to retrieve a lookup implementation which will scan this folder and its subfolders for data objects with InstanceCookie matching the lookup template. Furthermore, any instance cookies whose instance class is assignable to Lookup will be treated specially: they will be proxied to, so these sub-lookups may provide additional instances if they match the lookup template. In order to get the full functionality associated with such a lookup it is wise to request presence of org.netbeans.modules.settings > 1.13 as that is the module that does most of the work behind Lookups.forPath. To register javax.xml.parsers.DocumentBuilderFactory into Lookups.forName("my/xml/app/data") add the above described dependency and put following code in your layer file:

    <folder name="my">
    <folder name="xml">
    <folder name="app">
    <folder name="data">
    <file name="ThisIsMyRegistration.instance>
    <attr name="instanceCreate" newvalue="pkg.ClassNameOfYourImpl"/>
    </file>
    </folder>
    </folder>
    </folder>
    </folder>

    In fact the Lookups.forPath can be used in completely standalone mode. This is not very recommended in the NetBeans IDE, but can be found pretty useful when using this library in standalone applications: Lookups.forPath(path) scans all instances registered in the META-INF/services style just it uses META-INF/namedservices/path prefix instead. As a result to perform a successfull search for all javax.xml.parsers.DocumentBuilderFactory inside Lookups.forName("my/xml/app/data") one can register the implementation into META-INF/namedservices/my/xml/app/data/javax.xml.parsers.DocumentBuilderFactory.

    The most powerful way to provide a lookup is to directly define what instances and items it should provide, by subclassing. For this, AbstractLookup is recommended as it is easiest to use.

    The simplest way to use AbstractLookup is to use its public constructor (in which case you need not subclass it). Here you provide an AbstractLookup.Content object which you have created and hold on to privately, and which keeps track of instances and permits them to be registered and deregistered. Often InstanceContent is used as the content implementation. To add something to the lookup, simply use add(Object) (and remove(Object) for the reverse). These may be called at any time and will update the set of registered instances (firing result changes as needed).

    In case it is expensive to actually compute the object in the lookup, but there is some cheap "key" which can easily generate it, you may instead register the key by passing in an InstanceContent.Convertor. This convertor translates the key to the real instance that the lookup client sees, if and when needed. For example, if you have a long list of class names and wish to register default instances of each class, you might actually register the class name as the key, and supply a convertor which really loads the class and instantiates it. This makes it easy to set up the lookup, but nothing is really loaded until someone asks for it.

    Settings

    Settings require special support in the lookup system: these are objects (perhaps singletons but not necessarily) which should be made available to lookup, yet whose content can be changed and stored to disk (typically as a result of user interaction with the GUI). *.instance files and similar constructions are fine for registering fixed objects from layer - "fixed" in the sense that while the user might copy, delete, move, or reorder them, the actual object they provide is statically determined and does not generally have a means of being modified. In contrast, settings have nontrivial content. A typical setting is a system options, simply a singleton bean with a set of properties and a structured GUI presentation driven by BeanInfo.

    In order to save such settings, an XML file is normally used, and the APIs provide a convenient DTD for settings which can be represented as a single bean. In typical usage, the module layer declares an initial settings file which instructs lookup to create a default instance of the settings class. This might look like the following:

    <?xml version="1.0"?>
    <!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN" "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd">
    <settings version="1.0">
    <module name="com.foo/1" spec="1.0"/>
    <instanceof class="org.openide.options.SystemOption"/>
    <instanceof class="com.foo.MyOption"/>
    <instance class="com.foo.MyOption"/>
    </settings>
    Such a file might be placed in Services/com-foo-my-settings.xml (the exact name inside the Services folder is not important but ought to be globally unique to avoid conflicts with other modules). It provides an InstanceCookie with the settings object as instance.

    The interesting parts of this file are:

    • The <instance/> element which declares that the actual object will be a default instance of the class com.foo.MyOption, i.e. created via default public constructor.
    • Various <instanceof/> elements which specify what lookup templates will find this instance. Giving these elements permits the system to defer creating the setting instance until it is actually requested (that is, using InstanceCookie.Of). It is only necessary to indicate superclasses and interfaces that you actually expect someone to look up when searching for this setting, but be careful that you know what these might be. The actual class of the instance itself should be listed as well.
    • An optional but recommended <module/> element that declares which module provides this setting. You give the full code name (including a slash followed by the major release version, if applicable) of the module and its specification version. The purpose of this element becomes apparent if the user ever customizes the setting file, thus writing the changes to disk (for example in the system/Services/ folder), and then uninstalls the module: keeping the name of the declaring module in the file ensures that with the module uninstalled, the system will quietly ignore the stale setting rather than trying to blithely load the settings class and failing with a ClassNotFoundException. If the module is subsequently reinstalled, the setting will automatically become active again (regain its InstanceCookie). Similarly, settings will not be loaded if they were written by a newer version of the module than the one currently installed - module-supplied settings should be readable by newer versions of the module, but generally not older ones.

    There are actually three ways that the instance may be declared:

    1. Using <instance/> as above to generate a default instance. This is most common.

    2. You may pass an additional attribute method indicating a static method to call to produce the instance, rather than using a default constructor. The method may either be a simple name, in which case it is assumed to be a method in the class given by class, or you may give a full class name followed by a dot and method name to invoke a static method from some other class. The method may optionally take a FileObject argument which will be the settings file itself. This is analogous to the mechanism used for creating complex *.instance files. For example:

      <file name="some-difficult-to-make-instance.settings">
      <![CDATA[<?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE settings PUBLIC
      "-//NetBeans//DTD Session settings 1.0//EN"
      "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd">
      <settings version="1.0">
      <module name="my.module/1" spec="1.0"/>
      <instanceof class="javax.swing.Action"/>
      <instanceof class="my.module.MyAction"/>
      <instance class="my.module.MyAction" method="createAction"/>
      </settings>
      ]]>
      <attr name="param" stringvalue="someval"/>
      </file>
      package my.module;
      public class MyAction extends javax.swing.AbstractAction {
      public MyAction(String name) {/* ... */}
      // ...
      public static MyAction createAction(FileObject fo) {
      return new MyAction((String)fo.getAttribute("param"));
      }
      }

      This will result in an instance of MyAction being created with name someval.

    3. You may use the <serialdata> element. Its textual contents are a hexadecimal dump (whitespace ignored) of the raw serialized bytes of an object to serve as the instance. Naturally this is the least preferred mechanism as it is not human-readable.

    In the future it is planned for a fourth mechanism to be supported, taking advantage of the JDK 1.4 XML persistence ("archiver") system.

    A client can find the current setting in a couple of ways:

    • In the common case of SystemOption, you may simply call SharedClassObject.findObject(Class, true) which will either provide a previously initialized singleton, or find the setting in lookup if possible and read any customized state before returning it. You may then use property change listeners as needed to listen for changes.
    • Just ask the system lookup for the settings class (or a relevant superclass). This can be used to retrieve non-singleton settings; use the lookup result to track changes in the list of setting instances, and some ad-hoc method to track runtime changes in individual instances.

    How does the customization of setting instances work? After finding a setting instance via this DTD, the system will automatically look for a beans-style event set of type PropertyChangeListener, and add its own listener. If the bean changes state (either programmatically or as a result of user manipulation), the property change will cause the new state to be written out to the original XML file, keeping the same name. (Normally this would mean the XML would be written to disk in the user directory.)

    (Currently the state is simply serialized into the XML file using <serialdata>, meaning the class must be Serializable, but in the future if an alternate persistence form is available, such as the JDK 1.4 archiver, this will be used instead.)

    Conversely, changes to the setting file on disk should trigger a reload of the state and modification of the in-memory bean (or creation of a new instance cookie with a new bean).

    UI for Services

    There are several things you can do to not only have services and lookup function programmatically but also look good and behave nicely within the user's view of configuration options and settings.

    Service Templates

    For many kinds of services, especially ServiceTypes but also others, it is necessary to permit the user to create new instances of the service. Generally two criteria should be met for such services:

    1. The service is not a singleton, so it is meaningful to have more than one instance.
    2. The service has some user-configurable properties, so it is useful to have more than one instance.

    Creation of new service instances may be supported simply by producing a template residing beneath Templates/Services/. If the service normally resides in a subfolder of services, for example Services/Executor/, then the template should correspondingly be placed in Templates/Services/Executor/.

    The template should work like regular source code templates do: a file giving the initial structure of the service (typically a *.settings file), with the file attribute template set to true, and optionally a templateWizardDescription and templateWizardURL. For example:

    <folder name="Templates">
    <folder name="Services">
    <folder name="Executor">
    <file name="my-module-executor.settings" url="executor.settings">
    <attr name="template" boolvalue="true"/>
    <!-- SystemFileSystem.localizedName and SystemFileSystem.icon as usual -->
    </file>
    </folder>
    </folder>
    </folder>

    If the user selects New From Template on the corresponding options folder, the template will be available.

    Services display area and mirroring

    In addition to providing services, it is desirable to display them to the user as well. This is done, as is customary in other aspects of NetBeans configuration, by displaying customized variants of the data nodes coming from the system filesystem. The root folder for displaying options is called UI/Services/. Its subfolders govern the display of the options available in the system.

    As a rule, it is undesirable to place any actual settings in this folder (nor would they be recognized by the default lookup anyway). That is because the organization of this folder is driven by UI needs, without regards to API maintenance or compatibility of persisted user settings. So this folder solely mirrors configuration available elsewhere. You may freely reorganize the mirror according to current UI needs: existing modules plugging into services areas will continue to work unmodified, and existing user customizations stored to disk will continue to apply, since both of these act on the original files (which should not be moved frivolously).

    While technically you could place anything you wish in the UI folder, in practice a few types of things are used:

    • Symbolic links to settings files displayed elsewhere. In NetBeans' old-style options, these will appear as the real setting. For example:

      <folder name="UI">
      <folder name="Services">
      <folder name="Editing">
      <file name="my-module-config.shadow">
      <attr name="originalFile" stringvalue="Services/my-module-Config.settings"/>
      <attr name="originalFileSystem" stringvalue="SystemFileSystem"/>
      </file>
      </folder>
      </folder>
      </folder>

      The attribute "originalFileSystem" can be omitted. In this case the search for the linked file will be done on the Filesystem on which the link resides, which is wanted for the usages on SystemFileSystem. The link file should have zero length.
      Note that any localized display name and icon should be set on the original settings file; they will be picked up automatically by the shadow. The older style of linking using the CDATA section is still supported:

      <folder name="UI">
      <folder name="Services">
      <folder name="Editing">
      <file name="my-module-config.shadow">
      <![CDATA[Services/my-module-Config.settings
      SystemFileSystem
      ]]>
      </file>
      </folder>
      </folder>
      </folder>

      Here the shadow file consists of two lines, the first being the path to the real settings, the second always being SystemFileSystem.

    • Links to other kinds of files, such as folders, whether part of the services lookup area or not. For example:

      <folder name="UI">
      <folder name="Services">
      <folder name="IDEConfiguration">
      <folder name="LookAndFeel">
      <file name="my-module-stuff.shadow">
      <attr name="originalFile" stringvalue="Stuff"/>
      </file>
      </folder>
      </folder>
      </folder>
      </folder>
      <folder name="Stuff">
      <attr name="SystemFileSystem.localizingBundle" stringvalue="my.module.Bundle"/>
      <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/my/module/stuff.gif"/>
      <!-- perhaps some files to display here, perhaps not -->
      </folder>

      This defines a folder Stuff in the system filesystem which may be used for some kind of special configuration, and displays it in options.

    • Specialized nodes. In some cases you do not wish to display a particular folder but want to have complete control over the literal display of the option. In such a case you need only include a *.instance file with an instance of the node (or its handle, if you prefer), as the node delegate of this object will be (a clone of) the node you provide. For example:

      <folder name="UI">
      <folder name="Services">
      <folder name="Building">
      <!-- Some subclass of org.openide.nodes.Node: -->
      <file name="my-module-ConfigurationNode.instance"/>
      </folder>
      </folder>
      </folder>

    No particular substructure of UI/Services/ is defined by the APIs. For optimal UI integration you may wish to examine the categories used by other modules and try to reuse an existing category appropriate to your needs.

    In some cases it is necessary to hide a service file, or a whole folder of services. While you can place files into Services/ and simply not make any corresponding mirror in UI/Services/, you may wish to create services or subfolders inside existing displayable folders (for purposes of lookup, generally) yet not have them be visible in the UI. In this case you should mark the file or subfolder with the file attribute hidden (set to boolean true).

    Property editor for services

    If you wish to permit the user to select a service as part of the Property Sheet (from a node property, or as a PropertyPanel, providing general GUI embeddability), this is supported. You should use the property editor assigned to java.lang.Object (not your desired service interface). Various hints defined in the Explorer API permit you to control the result.

    As an example, here is a node property permitting you to ask the user to select a value of type my.module.Thing, being some interface or abstract superclass, where some instances are registered to lookup, conventionally in the Services/Things/ folder which the module has provided:

    public abstract class ThingProperty extends PropertySupport.ReadWrite {
    protected ThingProperty(String name, String displayName, String shortDescription) throws IOException {
    super(name, Object.class, displayName, shortDescription);
    setValue("superClass", Thing.class); // NOI18N
    setValue("nullValue", NbBundle.getMessage(ThingProperty.class, "LBL_no_thing")); // NOI18N
    DataFolder thingsFolder = DataFolder.create(
    DataFolder.findFolder(Repository.getDefault().getDefaultFileSystem().getRoot()),
    "Services/Things" // NOI18N
    );
    setValue("node", thingsFolder.getNodeDelegate()); // NOI18N
    }
    public final Object getValue() {
    return getThing();
    }
    public final void setValue(Object o) {
    if (o != null) {
    Lookup.Template templ = new Lookup.Template(Thing.class, o, null);
    Iterator it = Lookup.getDefault().lookup(templ).allItems().iterator();
    if (it.hasNext()) {
    setThingID(((Lookup.Item)it.next()).getId());
    } else {
    // Thing was registered but is not persistable.
    setThingID(null);
    }
    } else {
    setThingID(null);
    }
    }
    public final boolean supportsDefaultValue() {
    return true;
    }
    public final void restoreDefaultValue() {
    setValue(null);
    }
    // May be used by code wishing to get the actual Thing (or null):
    public final Thing getThing() {
    String id = getThingID();
    if (id != null) {
    Lookup.Template templ = new Lookup.Template(Thing.class, null, id);
    Iterator it = Lookup.getDefault().lookup(templ).allInstances().iterator();
    if (it.hasNext()) {
    return (Thing)it.next();
    } else {
    // Invalid ID.
    return null;
    }
    } else {
    return null;
    }
    }
    // Subclasses implement to actually read/write Thing persistent IDs (or null):
    protected abstract String getThingID();
    protected abstract void setThingID(String id);
    }

    A property extending this class would in the current UI display a pull-down list of all Thing implementations available in lookup; the custom property editor dialog would display the Things folder with anything contained inside it for the user to select from, provided it in fact had an instance assignable to Thing. The special null value is explicitly permitted here and would be displayed with the label given in the bundle.


    Built on August 19 2007.  |  Portions Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.

    Netbeans Platform: 文件系统

    Overview

    Javadoc

    Take a look at the Javadoc, especially for FileObject.

    Contents 内容

    Filesystems API 文件系统API

    The Filesystems API permits module authors to access files in a uniform manner: e.g. you may be unaware of whether a "file object" you are using is a plain disk file, or a JAR entry.

    文件系统API能够让模块的开发者以统一的方式访问文件:例如,你不必意识到你使用的"文件对象"是一个简单的磁盘文件还是一个JAR文件条目。

    A FileSystem is a namespace for FileObjects - a FileSystem has a root folder, which is a FileObject, which may have child files and folders - just like a file system on disk. A FileSystem may or may not represent actual files on disk; this API can be used to represent any hierarchical storage of file-like data.

    一个文件系统是文件对象的一个名域-一个文件系统拥有一个根文件夹,这个根文件夹就是一个文件对象,它可以拥有子文件或者子文件夹-就像磁盘上的文件系统一样。一个文件系统可以代表磁盘上的真实文件,也可能不是。这个API可以代表任何象文件一样可以层次存储的数据。

    What This API is Not For 这个API不是什么

    This API pertains only to manipulating files on disk (or whatever storage mechanism a filesystem may use), and makes no reference to their contents nor to how they are being used elsewhere in NetBeans (beyond whether or not they are locked). If you are looking for information on how to create custom content types, use the editor, create Explorer nodes corresponding to files, etc., this API is not the primary document you should be reading.
    这个API只适用于操作磁盘上的文件(或者是文件系统使用的任何存储机制),它并不拥有文件内容的引用,也不拥有Netbeans其他地方使用的文件的引用。如果你正在查找如何创建自定义的内容类型,使用Editor,创建文件相对应的查看器节点。这个API并不是你应该阅读的首要文档。

    From the perspective of the Filesystems API, all files consist of byte streams (albeit with MIME types). Usually, the functionality of this API will actually be used indirectly, via the Loaders API, which abstracts away most of the details of files and presents data in terms of DataObjects, which typically represent the parsed content of a file and provide objects to programmatically access that content via DataObject.getCookie().

    从文件系统API的角度来看,所有的文件都是由Byte流(虽然拥有MIME类型)构成的。通常,这个API的功能都是通过Loaders API间接的被使用。Loaders API抽象了文件的大部分具体底层信息,将数据表现为数据对象。数据对象代表了已经被解析的文件对象的内容。开发者可以通过DataObject.getCookie()来访问文件对象的内容。

    Common Tasks 通用任务

    Basic operations using the Filesystems API should be familiar, as they are similar to any file-access system. FileObjects for a user's data on disk are typically a thin wrapper around java.io.File; elements of NetBeans' internal configuration are also FileObjects, these typically representing data actually stored in an XML file inside a module's jar.
    使用文件系统API的基本操作和其他任何文件访问系统相似。用户磁盘上的数据的文件对象通常是一个简单的Java文件包装器;Netbeans内部配置信息的元素也是文件对象,它们通常代表一个模块JAR文件中一个XML文件中的数据。

    There are a few differences from traditional file-access APIs, such as monitoring files/folders for changes. This section gives examples of the more common tasks associated with using filesystems.

    和传统的文件访问API还是有不同之处的,例如对文件/文件夹变化的监控。这部分展示了文件系统使用中的通用任务的例子。

    Finding files and folders 查找文件和文件夹

    Normally, you will be looking for a file or folder by name, and will want to get the FileObject which represents it.
    通常你会按照文件或者文件夹的名字查找,然后得到代表它的文件对象。

    If you need to get a file object corresponding to a file on disk, use:

    如果你想要的到磁盘上某个文件的文件对象,使用:

    fileObject = FileUtil.toFileObject(new File("/some/path/to/file.txt"));

    FileUtil also has methods for working with archive (ZIP/JAR) entries. File objects corresponding to archive entries are read-only but otherwise behave much like disk files.

    FileUtil也有一些方法用来处理压缩文件(ZIP/JAR)条目。压缩文件中条目对应的文件对象是只读的,但是其他方面和磁盘文件很象。

    To find all the folders and files directly contained in this folder, you may use:

    查找文件夹中所有直接的子文件夹和文件,你可以使用:

    FileObject children[]=folder.getChildren();
    Occasionally you may need to present a given file object as a URL; for example, to display it in a web browser. This is straightforward:
    你也许想要得到某个文件对象的URL;例如在WEB浏览器上查看它。方法很直接:
    URL url = file.getURL();
    HtmlBrowser.URLDisplayer.getDefault().showURL(url);

    Creating, deleting, and renaming files and folders 创建,删除,重命名文件和文件夹

    This example creates a subfolder and then a new file within that subfolder:
    下面的例子创建了一个文件夹的子文件夹,然后在子文件夹中创建一个文件:
    FileObject subfolder=folder.createFolder("sub");
    FileObject newfile=subfolder.createData("NewSource", "java");

    You can delete a file easily:

    删除一个文件更简单:

    newfile.delete();
    If you want to rename a file, you must first take out a lock on the file, to make sure that no one else is actively using the file at the same time. Then you may rename it:
    如果你想重命名一个文件,你首先要获得这个文件的锁,确定没有其他人在同时使用它,然后你可以重命名:
    FileLock lock = null;
    try {
    lock=newfile.lock();
    } catch (FileAlreadyLockedException e) {
    // Try again later; perhaps display a warning dialog.
    return;
    }
    try {
    newfile.rename(lock, "NewSrc", "java");
    } finally {
    // Always put this in a finally block!
    lock.releaseLock();
    }
    If you want to move a file into a different directory (or even file system), you cannot use rename(...); the easiest way is to use a NetBeans helper method:
    如果你想将文件移动到另外一个目录(甚至其他文件系统),你不能用rename()方法,最简单的方法是使用帮助方法:
    FileObject someFile;
    FileObject whereTo;
    FileUtil.moveFile(someFile, whereTo, "YourSource");
    Note that in the current API set, it is neither possible nor necessary to lock folders (e.g. when creating new children), as normally locks are used to protect data files from conflicts between the Editor, the Explorer, and so on. If in the future there are thread-related problems associated with improper simultaneous access to the same folder, support for folder locking could be added to the Filesystems API.
    注意:在当前的API中,不可能也没必要锁住文件夹(例如创建子文件夹时)

    Similarly, there is no support currently for nonexclusive read locks - if you require exclusion of writers during a read, you must take out a regular write lock for the duration of the read. This is not normally necessary, since typically only the Editor will be reading and writing the contents of the file, and other file operations do not involve information which could be partially corrupted between threads. If necessary, the API includes facilities for read-many/write-one locks.

    Reading and writing files 读取和写文件

    Reading and writing the contents of a data file is straightforward:
    读取和写文件的内容非常直观:
    BufferedReader from=new BufferedReader(new InputStreamReader(someFile.getInputStream()));
    try {
    String line;
    while ((line=from.readLine()) != null) {
    // do something with line
    } finally {
    from.close();
    }

    FileLock lock;
    try {
    lock=someFile.lock();
    } catch (FileAlreadyLockedException e) {
    return;
    }
    try {
    PrintWriter to=new PrintWriter(someFile.getOutputStream(lock));
    try {
    to.println("testing...");
    to.println("1..2..3..");
    } finally {
    to.close();
    }
    } finally {
    lock.releaseLock();
    }

    Listening on file events 监听文件事件

    If you need to keep track of what is being done to a file by other components, you can monitor it using normal Java events:
    如果你需要跟踪其他组件对一个文件做了什么处理的话,你可以使用普通的Java事件来监听它:
    someFile.addFileChangeListener(new FileChangeAdapter() {
    public void fileChanged(FileEvent ev) {
    System.out.println("Contents changed.");
    }
    public void fileAttributeChanged(FileAttributeEvent ev) {
    System.out.println(ev.getName() + ": " + ev.getOldValue() + " -> " + ev.getNewValue());
    }
    });
    All events affecting existing files are actually fired twice, once from the file itself and once from its containing folder, so you may just want to listen on the parent folder. Also, file creation events are fired on the folder only, of course:
    所有影响已经存在的文件的事件实际上会被激发两次,一次是文件本身,另一次是包含它的文件夹,所以你也许只是想监听父文件夹。同时,文件创建事件只能是文件夹激发的:
    FileObject someFolder=someFile.getParent();
    someFolder.addFileChangeListener(new FileChangeAdapter() {
    public void fileChanged(FileEvent ev) {
    System.out.println("Contents of " + ev.getFile() + " changed.");
    }
    public void fileDataCreated(FileEvent ev) {
    System.out.println("File " + ev.getFile() + " created.");
    }
    });

    Determining MIME Content Type 决定MIME内容类型

    FileObject.getMIMEType() reports a basic MIME type for a file, which can used to classify it for editing and other purposes.

    FileObject.getMIMEType()方法报告一个文件的基本MIME类型,它可以用来区别编辑或者其他目的。

    If you need to influence the MIME type resolution process, you can register a MIMEResolver. To simplify this process you can register a MIMEResolver declaratively. It is not only easier (no coding needed) but can be more efficient by sharing results among multiple declared resolvers. See the declarative MIME resolvers how-to for more information about this.

    Special Filesystems

    Each file object resides on a FileSystem which handles a large tree of files. Normally you need not be aware of this, since the "Master Filesystem" module automatically handles creation of whatever FileSystems are needed to represent any files on disk, as well as any ZIP/JAR entries for archives on disk (using JarFileSystem). It is possible to implement your own filesystem for specialized purposes (normally by subclassing AbstractFileSystem and perhaps registering a corresponding URLMapper).

    The Filesystems API also includes a couple of special implementations which are used by the NetBeans core to assemble the system filesystem used for application configuration, and could also be used by module writers in some circumstances: MultiFileSystem and XMLFileSystem. See their Javadoc for more information.


    Built on August 19 2007.  |  Portions Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.

    Netbeans Platform: 节点

    Overview

    Javadoc

    The Javadoc for this API resides in org.openide.nodes. In particular, the class Node is the logical starting point.

    Contents内容

    Nodes API 节点API

    The Nodes API controls the usage and creation of nodes, which are a variant of JavaBeans that may have adjustable property sets; provide cookies and actions; be visually displayed in the Explorer with full hierarchy support; and other features.

    节点API用来使用和创建节点。Netbeans Platform中的节点其实就是一种JavaBean的变体,能够拥有可调整的属性集,提供Cookie和动作,同时能够在查看器中以层次形式显示,还有其他一些特征。

    What is a Node? 什么是节点?

    A node provides the visual representation and apparent behavior of most objects in NetBeans. It may be used to represent a data object from the Datasystems API (and provide a more UI-friendly wrapper around that data object), or it may be created separately for a special purpose. For example, every icon displayed in the Explorer is a node, and this is the primary UI interface to the entire hierarchy of nodes. (Many dialog boxes, the Component Palette, and so on, also operate based on nodes, via the Explorer API.)

    节点为Netbeans中的大部分对象提供可视化显示以及透明的行为。它可以显示数据系统API中的一个数据对象,为其提供一个界面更加友好的表现层包装器,或者也可以因为其他目的单独创建一个节点。例如,在查看器中每个图标都是一个节点,它是整个节点层次的首要用户界面,很多对话框,以及调色板组件等等,也都是基于节点,通过查看器API来操作的。

    Nodes themselves ought not be used to hold actual data; that should be stored in a data object, or in some other appropriate storage mechanism. Rather, they provide a presentation device for existing data.

    节点本身不应该持有任何实际的数据,实际的数据应该存储在数据对象中,或者其他合适的存储机制。节点的功能更应该是提供一个已经存在的数据的显示层或者显示设备。

    JavaBeans and nodes

    A node is a sort of extension to the JavaBeans concept, adding some features that were necessary for the full functioning of explorer views. Some key components that were missing from the JavaBeans specification:
    本质上说,节点是JavaBean概念的一种延伸,同时添加了很多支持查看器浏览的特征。而这些特征在JavaBean的规范中并不存在:
    • Full hierarchy support. The Java 2 Bean Context API provides basic support for hierarchies of Beans in a tree structure, but not enough to handle the requirements of NetBeans, such as special support for various kinds of child containment policies.完全层次支持。
    • Java2 Bean Context API提供了JavaBean在树状结构中的基本支持,例如各种子节点包含策略等。
    • Cookies, actions, and other NetBeans-specific interfaces are better placed as direct Java-level API requirements, rather than always needing to use casts or introspection to determine their availability.
    • 更好的处理了Cookies,动作和其他Netbeans特有的接口,开发者不需要通过转化或者自省来使用它们
    • Certain basic operations on nodes, such creating a serializable form of the node, or cut-and-paste support, are better implemented as an abstract base class.
    • 支持对节点的特定的基本操作。例如创建节点的序列化形式,或者剪切粘贴支持,都通过一个抽象的基类来实现。
    • Most importantly, regular use of JavaBeans requires that introspection be used whenever property lists are required for an object. This is a very frequent operation which was found to have unacceptable overhead. Also, introspection, due to the nature of Java and the Bean architecture, prevents dynamic modification of the set of properties supported by an object, which the Nodes API permits.
    • 最重要的是,一般JavaBean的使用需要对每个对象的属性列表都使用自省机制,而这种操作太普遍了,并且会形成不可容忍的性能瓶颈。同时,自省机制由于Java和Bean体系本身的原因,不能对一个对象的属性集进行动态修饰。然而节点API却支持这一功能。
    That said, the JavaBeans architecture was by no means ignored while designing the Nodes system; it was used as a conceptual foundation. To blur the distinction, special node implementations are available which wrap around standard JavaBeans and present them as nodes, including handling any Bean Context available, introspected properties, and so on.

    Nodes in the Explorer

    Using the Explorer API, you can create a variety of different presentations for any tree or subtree of the Nodes hierarchy.

    The Explorer interactions with nodes include actions that the node provides (generally available in a right-click context menu); cookie-based action enabling (so that the node selection affects the availability of system actions, like Compile); cut, copy, and paste support, as well as reordering of children, deletion, and creation of new children; displayable names and icons, which may be sensitive to the state of the node; and so on.

    Importantly, nodes are not dead data - they are live components. So, actions taken in one part of the system will frequently cause open Explorer views to refresh to display the new node structure (for example, after pasting a component onto a form); and conversely, actions that seem natural to do in the Explorer will usually be accessible there through its interface and update the rest of the system accordingly (for example, deleting a toolbar button in the Environment subtree has immediate effect). These capabilities owe to the rich event notification supported by the Nodes API.

    Common node types

    This is a partial list of common types of nodes, some more apparent than others, to give an idea of what is possible with the API:
    • Data nodes, based on data objects. A simple example would be the node created for an HTML file on disk: you can edit it, open it in the browser, move it about, and so on.

      A more complex example is a Java class representing a form - this node actually has one child subtree representing the Java source hierarchy (classes, methods, and fields); and one subtree representing the AWT/Swing component hierarchy (frames, panels, buttons, etc.). Each type of subnode has its own behavior - for example, the component nodes can display Layout and Events property sheets, and if containers, can permit pasting in of components onto the form.

    • Data folder nodes, possibly representing Java packages. These allow creation of new objects from template, compilation across the package, and so on.
    • A user-developed JavaBean on disk. The user is able to customize it, serialize it, and so on.
    • A node derived from a JavaBean, not representing one - in this case, the node's actual behaviors and properties would literally be the same as those of the JavaBean. The Bean would not generally be stored in a user filesystem, and its node may be present anywhere.
    • Settings in the (old) Advanced Options. These are used for their property sheets, which dynamically modify aspects of NetBeans' behavior and persist across sessions.
    • The Component Palette - the list of tabs, and the components on each tab, are actually nodes, and in fact the same nodes as appear in the Explorer under the Component Palette folder. The same is true of workspaces, actions on the toolbars, and so on.
    • A breakpoint in the debugger is a node, as well as the folder containing all breakpoints. The special Debugger window just displays these in a specific way.
    • A project desktop node contains various files associated only with a specific project, as well as settings controlling aspects of that project's operation, such as a build procedure.
    The point is that most aspects of UI integration can be handled well with appropriate use of nodes; modules which use them to good effect will blend into the rest of NetBeans naturally from a user's perspective, and probably be easier to write as well.

    Creating Custom Nodes 创建自定义节点

    This section details the steps you must take to create various kinds of customized nodes. In simple cases, you need do very little, as there exist prebuilt default implementations. For more sophisticated nodes, for example containing a structured list of children of a particular type (as is the case with e.g. the Active Processes list), some more work is necessary, but again much of it will involve subclassing existing supports.
    这部分内容详叙如何创建各种类型的自定义节点。最简单的情况,你不需要做太多事情,Netbeans Platform已经有很多预创建的默认实现,你可以直接使用它们。对于更复杂的节点,例如含有特殊类型的具备特殊结构的子节点列表(例如,活动进程列表),需要更多的代码工作,虽然如此,它仍继承和使用了很多已经存在的对节点支持。

    General Aspects 通览

    All nodes must subclass the general Node abstract class. However, in practice it is most common to actually subclass a convenient base class, AbstractNode, or one of its subclasses. Your node class needs to specify a list of child nodes in the constructor (the contents of which may be changed later, but not the identity of the Children object itself); for the case of a leaf node, just pass in Children.LEAF.
    所有的节点必须继承通用的节点抽象类。然而,实际使用时,大部分都是直接继承一个很方便的基类:AbstractNode,或者这个基类的一个子类。这样,你的类必须在构造器中指定一个子节点列表(其内容可以事后更改,但是子节点对象本身不能改变)。如果你的这个节点是叶节点的话,直接在构造器中传入Children.LEAF指明。

    As this class is not abstract, there are no strict requirements on what needs to be overridden. However, the following general methods you are likely to want to override:

    • AbstractNode.canCopy() and related methods (for cutting, renaming, and destroying) are all true by default, so that the node may be moved around somewhat arbitrarily (though to paste requires the permission of the new parent, of course). If it is inappropriate for your node to be relocated as data in this way, you should turn these abilities off. See the section on edit operations for details.
    • The node has two names - a system name and a display name. The display name is the human-presentable name actually displayed next to the icon in the Explorer, and so forth, and its use is quite free. The system name should be set to something simpler, and ought not be tampered with gratuitously (unless the node is explicitly renamed) - this is because it is used for internal purposes such as reconstituting a node after serialization.

      You may set these explicitly with Node.setName(...) and Node.setDisplayName(...), or you may want to take advantage of AbstractNode's ability to have the display name be calculated implicitly from the system name by means of a format string. There is also a short description which is intended for things such as tool tips on the node.

    • Almost all nodes should have a properly set icon. You may actually specify a set of icons according to whether small or large icons are to be used, whether the node's hierarchy (if applicable) is expanded or collapsed, etc. The best approach is to use AbstractNode.setIconBaseWithExtension(...) to set the base name for the icon image resources.

    Properties, sets, and sheets 属性,属性集和属性表单

    There are three levels of organization for node properties:
    节点的属性共有三层组织结构:
    • Properties, which represent individual settable properties of the node object, with a definition class and so on, very similar to JavaBean properties (and in fact extending Bean features).
    • 属性,代表节点对象的单独可设置的属性,拥有一个定义类,非常类似JavaBean的属性(其实就是Bean特征的扩展)
    • Property sets, which are just collections of properties that typically would be displayed together - for example, all the event handlers for a form component.
    • 属性集,相关的一起显示的一组属性。例如表单组件的所有事件处理器
    • Sheets, which group together several named property sets - for example, the same form component may also have a normal Bean property set (foreground, etc.), and an expert set (with rarely-used properties).
    • 属性表单, 一组由多个具有名字的属性集组成的表单。
    Nodes are only required to cluster properties into sets, by implementing Node.getPropertySets(); but sheets are convenient to handle, and they may be used from an AbstractNode, for example by overriding AbstractNode.createSheet() to provide the basic list of property sets desired for the node. (You should get the sheet set you need from it, checking whether it really exists yet, add properties to the sheet set, and then replace it into the sheet to be sure your changes take effect.)

    Each property has a few interesting aspects to it:

    There are a number of support classes from Node.Property which provide useful refinements:
    • PropertySupport.Name creates a property specifically binding the node's name (system name, not display name; if you are using AbstractNode, handling the system name only should suffice). This support ought to be used by any node for which it makes sense for the user to modify the name in the property sheet. If modifying the name should be permitted but would need to trigger other changes, the support probably would not be helpful (or it could be subclassed).
    • IndexedPropertySupport helps create indexed properties.
    • PropertySupport.ReadOnly and similar classes restrict the directionality of the property.
    • PropertySupport.Reflection helps create a property based on JavaBean introspection.
    You may group whatever properties are needed for your node into different sets as desired (or put them all into one set, if there is no useful distinction); note that the property sets are also feature descriptors, and so may have internal and display names, etc.

    Naturally, common node implementation classes such as DataNode may automatically create a sheet with some useful properties on it; then this sheet should generally be appended to by overriding AbstractNode.createSheet() and calling the superclass method first.

    For complex nodes, such as a system option controlling the appearance of an entire editor, it may be cumbersome for the user to edit individual properties, especially without getting a holistic preview. If this is the case, a customizing GUI component may be returned from Node.getCustomizer(), and Node.hasCustomizer() turned on. The exact way in which the customizer will be displayed is determined by NetBeans, but typically it will be popped up in a dialog or non-modal window; it should be tied to the node's properties however appropriate.

    If a full customizer is not required, individual properties may still have a custom editing style associated with them; Node.Property.getPropertyEditor() is used to look for a property editor, defaulting to the standard JavaBeans property editor for the appropriate type.

    Note that the Nodes API, unlike JavaBeans, permits a specific instance of PropertyEditor to be associated with the node, not just its class - so if you override getPropertyEditor(), it is possible to select an editor based on the current state of the node (for example, a table may have a completely different editor when it is bound to a SQL rowset), or to keep an initialized editor associated with a node that may have some UI state not kept in the node itself.

    Hierarchy nodes and their children 层级结构的节点和其子节点

    Creating a leaf node - a node with no children - is fairly straightforward, since the child list may simply be specified as Node.EMPTY. To create a hierarchy node that will act as a parent for other nodes, you must consider both what type of children it will have, and how those children should be managed.
    创建一个叶节点-一个没有子节点的节点-是相当直观的,因为子节点可以简单的用Node.EMPTY指明。创建一个拥有其他节点的具有层次结构的父节点,你需要考虑:子节点的类型,如何管理这些子节点。

    The basic data structure for managing a child list is Children, which is not likely to be subclasses directly but rather used in the form of one of the support classes available for it. Note that the node must keep the same children object throughout its lifetime, and the children object is responsible for managing the addition, removal, and structure of children under it.

    管理子节点列表的基本的数据类型是Children类,这个类你不大可能直接继承使用,但是通常你会使用它里面的其中一个支持类。注意:一个节点必须在它的整个生命周期中维护相同的子节点对象,子节点对象负责管理子节点的添加,删除和它下面的子节点的结构。

    A simple child list may be created with Children.Array. You need only create it with the default constructor, and add child nodes to it (at any time, or remove them later for that matter) using Children.add(...).

    一个最简单的子节点列表可以通过Children.Array来创建。你仅仅需要使用默认的构造器创建它,使用Children.add()向它添加子节点(在任何时候,或者事后删除它)

    If it is desirable that the children be sorted when displayed, you can use e.g. Children.SortedArray to do this. In this case, the comparator (i.e. sort criteria) can be changed at any time.

    如果你希望子节点在显示的时候需要排序,你可以使用Children.SortedArray来实现。

    If the children need to be accessed based on keys, as in a hashtable, this is possible with Children.Map (and also Children.SortedMap). Along similar lines, Children.Keys permits clustering of the children by key, where several children may be associated with one key. This class may be especially useful when mirroring an external hierarchical system into a node hierarchy, such as Java class hierarchies, which need the children to be partitioned in a certain way (e.g. methods vs. fields).

    This document will not go into the details of subclassing children lists, since doing so is not likely to be required very frequently - the provided support classes should handle the common cases. If it is necessary to subclass, the documentation for Children should suffice.

    Indexing and reordering children

    Many structural constraints on children are probably satisfied by the children class itself - i.e. using Children.SortedArray guarantees that your children will be properly sorted without any work beyond providing the comparator. However, for an unsorted child list it may be useful to provide support for directed reordering of the children.

    Generally you will want to make the children rearrangeable by the user, as well as by external code. To do so, you should implement the Index cookie on your node, which exists to handle this case. This cookie provides ways for the user to move particular children around, or to undertake a complete rearrangement using a dialog box. There is a generic support class which implements the raw requirements of the cookie, but this is usually used in a more friendly form by using a special children implementation such as Index.ArrayChildren. This implementation stores a list of children and makes it straightforward for the user to manipulate the order in several ways.

    Cookie and action support

    One important ability provided by the Nodes API is to associate cookies and actions with nodes. What cookies are and how to create them is explained in the Datasystems API Actions in general are likewise described by the Actions API.

    (If your node is actually a DataNode representing a data object, there are already some conventions for attaching actions and cookies to the node, which prepopulate certain entries based on the data loader and/or data object. The Datasystems API describes these defaults.)

    Attaching cookies to a node, so that it will be considered to implement certain behaviors, is quite straightforward. The basic interface for retrieving a cookie is Node.getCookie(...). However, this is abstract in Node, and also Node itself does not set any policy for settings up the cookies for a node or changing them.

    Rather, if you are subclassing AbstractNode, you may use AbstractNode.setCookieSet(...) to specify a set of cookies to be returned by the node (and you should merge your cookies with those provided by the superclass, as a rule). The CookieSet is a simple container for cookies looked up by their representation class. The AbstractNode will then use this as an index for implementing getCookie(...).

    To attach actions to a node, which are listed by Node.getActions() (and sometimes a primary and obvious action in Node.getDefaultAction()), you should merge the superclass' actions into your own (if desired), and override e.g. AbstractNode.createActions(), which is called to set up the actions list when getActions() is first called.

    These actions may be used by various UI components to clearly associate commands with the node, e.g. by providing them in a pop-up menu. Node.getDefaultAction() and Node.getContextActions() provide more refined variants of the actions list which may be appropriate for different presentations. Nodes with unusual needs for action presentation can override Node.getContextMenu() to define a particular UI for this presentation.

    Installing special nodes from modules

    Frequently nodes will be created secondarily, especially as a result of being delegates to data objects (in which case their creation is under the control of the data loader pool). However, for some modules it is appropriate to specially install nodes into defined places in the system.

    Currently, system gives you ability to automatically install a node of your choice into (currently three) common places in the IDE:

    • Runtime nodes are installed in the Explorer's Runtime hierarchy. This may be used for modules which need to provide user-level access to some transient aspect of the module's operation not otherwise apparent. For example, an HTTP filesystem might want to provide a node under the Runtime displaying information about its cache, and permitting operations such as clearing the cache.
      Nodes of such type should be placed in the UI/Runtime/ folder using *.instance syntax, simply specifying class of the node in question.

    • Root nodes are installed as roots for a whole new hierarchy. These roots may be displayed as switchable tab panes in the Explorer, to visually represent each root in parallel. Please do not create a new root without a compelling UI justification.
      Nodes of type Root should be placed in the Windows/Components/ folder using *.settings syntax, defining org.openide.explorer.ExplorerPanel type of component. Use ability of *.settings file to specify creator method to asociate explorer panel with your root node. Consult Winsys API, xml layers section for details.

    • Session nodes, appropriate to items which are neither transient nor project-oriented, are installed in the Tools/Options area, highest level.
      Nodes of type Session should be placed in the UI/Services/ folder, again using *.instance syntax.

    The basic definition of how settings in layers work is given in the Services API.

    Special Node Usage

    There are a few sorts of special operations and techniques which it may be useful to apply to nodes, either in the course of implementing a node or node hierarchy, or just using nodes from other code.

    Serialization and traversal

    If you need to store (serialize) a node for any reason, this is generally impossible due to the welter of Java-level references connecting it to the rest of the system. Rather, you must use a special serializable handle which represents the node by its position in the hierarchy, and permits finding the original node again after deserialization (if it still exists). To create a handle, just call Node.getHandle(), and to restore the node call Node.Handle.getNode().

    Creation of a usable handle is implemented in AbstractNode, and you should not need to override it. However, note that a handle consists of a handle for the root node of the target node's hierarchy together with a path (by system name) down to the target node; so if you are creating a root node, and want it or its children to be serializable, then you should create a specific implementation of Node.Handle capable of reconstructing your root from scratch, and return it from Node.getHandle().

    The methods in NodeOp such as NodeOp.findPath(...) may also be used for general-purpose navigation along the hierarchy, should this be necessary.

    JavaBean bridging

    It is possible to create a node which picks up its node behavior from an underlying JavaBean. That is, Bean introspection will be used to determine its properties (also categorizing them into normal, expert, and hidden property sets), find a customizer for it if the Bean specifies one, look for an icon from the BeanInfo, implement copying via serialization, create children according to Bean Context, and so on.

    Since most of this behavior is automatic and driven by the JavaBeans API, you need do little to use it: just create a node using new BeanNode(...).

    Do not confuse such a bean node, which may be any sort of node that just happens to use the JavaBeans API to implement its behavior, with the specific kind of node created to represent a data object whose file is found to be a JavaBean (serialized, or as a class) - this latter type of node behaves in most respects like any other data node, and just adds a couple of features like a Customize action.

    Data object delegates

    Many nodes serve primarily to represent a data object, which would otherwise be invisible to the user. While such data nodes may be customized like any other node, including creating node hierarchies (even for non-folder data objects), there are special considerations and supports for creating these. Please refer to the Datasystems API for details.

    Filters and cloning

    Under some circumstances, it may be useful to create a node which does nothing except serve as a sort of symbolic link to another primary node; this may be used when the visual organization of a hierarchy requires one object to appear in more than one place. In such a case, you may use new FilterNode(...) to create such a proxy.

    Or, you may use AbstractNode.cloneNode() to create the filter if the node does not intrinsically support Cloneable, or to really clone it if it does. Note that a properly-designed node does not actually store real data, but just provides an interface to that data; and so it is reasonable to implement Cloneable to provide a new node attached to the same data, if that behavior is desired. Some nodes, such as DataNodes, do not do this, as such behavior would be contrary to the UI goal of having a data node live in one place in the Repository according to the position of the data object and primary file object.

    Event model

    Every interesting aspect of nodes may be listened to using the Java Event Model, as is routine in the IDE:
    • Changes to the basic structure or presence of nodes may be listened to. Attach a listener with Node.addNodeListener(...). This will report the changes mentioned directly in NodeListener, as well as several varieties of standard property changes (since NodeListener extends PropertyChangeListener): node name, parent, cookies, property sets (i.e. the available properties, not their values), and icons.
    • Changes to the values of node properties may be listened to; attach to Node.addPropertyChangeListener(...). This will report only changes relating to the exposed Bean-like properties of the node, not intrinsic properties like the parent.
    • Cookie sets, property sets, and property sheets may all have listeners attached to them individually, although generally it is easier just to listen to the node holding them.

    Edit operations on nodes

    Nodes can support a variety of mechanisms for the basic edit operations.

    Simple operations

    There are some simple node-level operations which do not need to use data transfer. AbstractNode.setName(...) and Node.destroy() may simply be overridden to handle customized renames and deletes. (Or, you could attach a NodeListener to take action after the fact, if that suffices.)

    Supporting creation of fresh children is possible by overriding Node.getNewTypes() to provide a list of new types of data which can be created under your node. Each of these should implement NewType.create() to actually create a new child. Make sure that you include NewAction in your list of actions.

    Data transfer

    By default, nodes provide hooks to permit other objects to receive them as pastes, and to permit other objects to paste to them, but do not provide any particular hookup between the two sides.

    Certain standard subclasses of AbstractNode (such as the DataNode commonly used to represent data objects) already have special implementations of data transfer appropriate to your task (such as actually moving a file object to a new folder), which may eliminate the need to deal with it directly.

    Flow of control during node data transfer
    If you want to do more complex customization of node cut-copy-paste, or if you are debugging such an implementation, you will want to understand the flow of control, which is fairly subtle. This section will also be helpful for understanding the NetBeans data-transfer system in general.

    This flow assumes a copy-and-paste operation. Cut-and-paste is rather similar (the source node would be destroyed rather than cloned, typically). Also, use of AbstractNodes is assumed; otherwise the nodes involved would have to implement more.

    The scenario is that Node B permits other nodes to be pasted into it, creating shortcuts; the user wants to create a shortcut to some arbitrary Node A.

    1. The user selects node A. The action Copy is enabled (from a context menu, the Edit menu, etc.), because node A indicated it could be copied using Node.canCopy() (turned on in AbstractNode). Note that ExplorerUtils provides the regular implementation of CopyAction for any TopComponent.
    2. The user invokes the Copy action. AbstractNode.clipboardCopy() is called. It creates a transferable supporting only one flavor, which is invisible to the APIs. The creation of this transferable is done by a special utility method which hides the data flavor and transferables contents: NodeTransfer.transferable(...). The copy action sets that transferable to the clipboard.
    3. Some time later, the user selects node B, a node capable of holding children. One effect of the change in selection is that the Paste action checks to see if it should be enabled. To do so, it checks node B's AbstractNode.getPasteTypes(...), which in turns calls AbstractNode.createPasteTypes(...) to do the work.

      Now, AbstractNode's implementation of createPasteTypes(...) only allows one data flavor to be accepted by the node (so-called "intelligent pastes"); this flavor is hidden from the APIs but can be tested for in a transferable using NodeTransfer.findPaste(Transferable). This is not the flavor that was provided by the copy, so no paste type is created in the super method. However, Node B in this example was specifically expecting to get copied nodes pasted into it, so it overrode createPasteTypes(...) like this:

      public class Shortcuts extends AbstractNode {
      public Shortcuts () {
      super (new Index.ArrayChildren ());
      setName ("Shortcuts");
      getCookieSet ().add (ch);
      }
      protected SystemAction[] createActions () {
      return new SystemAction[] {
      SystemAction.get (ReorderAction.class),
      null,
      SystemAction.get (PasteAction.class)
      };
      }
      protected void createPasteTypes(Transferable t, List ls) {
      final Node[] ns = NodeTransfer.nodes (t, NodeTransfer.COPY);
      if (ns != null) {
      ls.add (new PasteType () {
      public Transferable paste () throws IOException {
      Node[] nue = new Node[ns.length];
      for (int i = 0; i < nue.length; i++)
      nue[i] = ns[i].cloneNode ();
      getChildren ().add (nue);
      return null;
      }
      });
      }
      // Also try superclass, but give it lower priority:
      super.createPasteTypes(t, ls);
      }
      }
      Nothing is actually pasted yet. However, one paste type, that provided by Node B, has been added to the set of paste types. So, the Paste action sees that there is an option to paste, and provides a context menu item (by default labelled "Paste"), enables the toolbar button, etc.
    4. The user selects this action. The paste type's paste() method is actually called, making an alias of Node A and inserting it as one of B's children. The method returns null, so the clipboard is left alone.

    Directions of implementation
    The data transfer process, here looking at nodes, may be implemented from various directions:
    • Nodes may add more specialized data flavors in their cut or copy operations. Here is an example, permitting the display name of the node to be pasted (e.g. to the system clipboard) after it is copied:
      public Transferable clipboardCopy () throws IOException {
      Transferable deflt = super.clipboardCopy ();
      ExTransferable added = ExTransferable.create (deflt);
      added.put (new ExTransferable.Single (DataFlavor.stringFlavor) {
      protected Object getData () {
      return getDisplayName ();
      }
      });
      return added;
      }
    • Nodes may accept particular paste types to be pasted onto or into them. The example above demonstrated this; Node B specially indicated that it would accept other nodes to be pasted in. In a similar way, it could have permitted strings from the clipboard to be pasted onto it, performing some action of its choice.

      If the node winds up having multiple paste types available at once, NetBeans may display all of them, say in a submenu. They will be displayed in the same order as they were added.

    • Since AbstractNode by default just looks for the secret data flavor represented by NodeTransfer.createPaste(Paste) and NodeTransfer.findPaste(Transferable), any part of the system that wants to be able to paste to nodes can do so without rewriting the node - provided it knows exactly what to do with the target node, of course! For example, the following copy implementation sets the display name of the target node to be the same as that of the current node:
      public Transferable clipboardCopy () throws IOException {
      Transferable default = super.clipboardCopy ();
      ExTransferable added = ExTransferable.create (default);
      added.put (NodeTransfer.createPaste (new NodeTransfer.Paste () {
      public PasteType[] types (final Node target) {
      return new PasteType[] {
      new PasteType () {
      public Transferable paste () throws IOException {
      target.setDisplayName (getDisplayName ());
      // Clear the clipboard:
      return ExTransferable.EMPTY;
      }
      }
      };
      }
      }));
      return added;
      }
      Of course, it would be possible to directly insert a transferable such as the one created here into the system clipboard, without needing to have CopyAction be invoked on a node, if that were the desired behavior. Then the transferable derived from NodeTransfer.createPaste could be added directly to the system clipboard (use lookup on Clipboard), or added as an alternate flavor to any transferable already there.
    • Other parts of the system which want to enable themselves to be pasted to from nodes may do so by accepting transferables in the clipboard satisfying NodeTransfer.node(...), NodeTransfer.nodes(...), or NodeTransfer.cookie(...); then no special cooperation is required from the node (provided it is an AbstractNode or similarly implements clipboardCopy() and clipboardCut()).
    • Clipboard convertors permit any module in the system to manipulate how the clipboard flavors will be used, without needing direct control over either the source or target of the transfer. For example, you could write a convertor which would permit any string selection in the clipboard (say, placed there by the Editor) to be pasted onto node having ClassElement (from java-src-model.jar) as a cookie by way of inserting a new method with that name. Such a convertor should work with any editor, as well as with any implementation of the source hierarchy that provides the correct cookies. Care should be take, however, not to override existing flavors in the clipboard that might be more critical to users. E.g., do not add a DataFlavor.stringFlavor transferable if one already exists, or some important piece of functionality may be lost. In the case of intelligent node pastes, you could actually merge your own intelligent node paste into an existing one (several levels of inner classes would be required!).

    UML Diagrams

    First class diagram listed shows structure of the API in general, all other diagrams provides specialized view of specific section of the API.

    General node structure class diagram

    general UML

    Node children class diagram

    children UML

    Node events class diagram

    events UML

    Node ordering class diagram

    ordering UML

    Node properties class diagram

    properties UML

    Built on August 19 2007.  |  Portions Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.

    August 22

    SwingLab: JXTable的绚丽

    你是不是厌烦了Java Swing中丑陋的,简单的JTable?试一下SwingLab的JXTable吧!JXTable是标准Swing JTable的扩展。它增加了很多有用的特性:例如,运行时列选择(你的表格的右上角会出现一个小Button让你来实时改变表格的展现),支持背景行的高亮(这样奇偶行不同的背景颜色让你不会读错行!),排序功能,还有其他的特性。JXTable可以象一个JTable一样使用,更重要的是,你不必编写大量代码就可以提供给用户更加舒适的特性,怎么样?很酷吧!
    1. 列控制:表格右上角(列头)的小按钮能够弹出个菜单,用来设置表格的显示和行为。
    2. 列选择:用户可以通过列控制按钮选择表格中显示哪些列,这也可以通过程序来控制
    3. 高亮行:通过给JXTable,附加一个Highlighter来高亮你想要高亮的行
    4. 行排序:JXTable支持实时行排序,用户可以点击列头对这列的行进行排序,更棒的是,你可以为列创建自己的比较子Comparator,这样点击不同的列头,就可以按照不同的规则来进行行排序。
    5. 行过滤:你可以将一个或者多个过滤器附加给JXTable,用来控制哪些行显示给用户
    6. 重置列大小:用户可以使用列控制来自动调整列的大小,从而符合列中的内容
    7. 水平卷页控制:使用单一的命令就可以使表格根据viewport大小进行自动放大缩小
    August 21

    Netbeans Platform: 属性菜单

      你可能注意到Netbeans IDE包含一个属性表单能够显示一个节点的属性,这个功能非常有用。到底什么是属性,要取决于节点的实现。属性其实是具有Java类型的多个名值对,多个名值对可以分组显示在属性表单上。你可以在属性表单上通过属性编辑器修改可写的属性(参见java.beans.PropertyEditor). 所以,节点从设计上就可以拥有多个属性,这些属性在属性表单上能够浏览,或者编辑。使用属性表单非常简单,在Node API中,就有一个Sheet类,这个类代表了一个节点的整套属性。Sheet类还有一个嵌套类Sheet.Set类,它代表属性集,你可以为一个节点创建多个属性集,属性集在属性表单上分组显示。

      那么,如何在Node中使用属性表单呢?非常简单:覆盖Node中的createSheet()方法。例如:



    protected Sheet createSheet() {
    // 创建一个默认的表单
    Sheet sheet = Sheet.createDefault();
    // 创建一个属性集,你可以创建多个
    Sheet.Set set = sheet.createPropertiesSet();
    // 通过Lookup查找到你的对象
    APIObject obj = (APIObject) getLookup().lookup (APIObject.class);
    try {
    // 根据对象内容创建属性,通过PropertySupport的反射机制
    // 对象的getIndex方法返回Integer类型的数据
    Property indexProp = new PropertySupport.Reflection(obj, Integer.class,
    "getIndex", null);
    Property dateProp = new PropertySupport.Reflection(obj, Date.class,
    "getDate", null);
    // 将属性命名
    indexProp.setName("index");
    dateProp.setName ("date");
    // 将属性加入到属性集
    set.put (indexProp);
    set.put (dateProp);
    } catch (NoSuchMethodException ex) {
    ErrorManager.getDefault().notify (ex);
    }
    sheet.put(set);
    return sheet;
    }

      不过,属性的表单不仅仅可以用来显示属性,更重要的是要编辑属性,那么如何在属性表单中编辑呢?
    只需将上述代码中的一行修改一下:

    Property dateProp = new PropertySupport.Reflection(obj, Date.class, "date");

    注意:"getDate" 要变为"date", getDate是方法名,而date是属性名, 同时去掉null参数,这样创建的属性就可以编辑了。
      不过更重要的一点是,在属性表单中修改的属性要能够更新其他组件,也就是触发一些事件,例如如果节点显示
    名称中存在日期的话,当你修改完属性date的时候,节点的名字中的日期也要更新才对。实现这一点稍微复杂一些。
    1. 属性监听器 PropertyChangeListener - 首先要节点实现这个属性监听器接口,这个接口有一个方法:public void propertyChange(PropertyChangeEvent evt),在这个方法中你要实现属性变化时你要处理的事务,例如,如果要更新节点的名字,这里就要调用节点的fireDisplayNameChange()方法激发节点名字变化事件。
    2. 在数据模型上添加属性监听器。 我们的节点已经是一个属性监听器了,为了监听数据模型,我们还需要在数据模型上设置这个属性监听器,在数据模型的构造方法中添加:obj.addPropertyChangeListener(WeakListeners.propertyChange(this, obj));
    3. 除了在数据模型中添加属性监听器外,我们还需要额外作些处理。


    // 在数据模型中维护一个监听器列表
    private List listeners = Collections.synchronizedList(new LinkedList());

    // 在数据模型中可以添加监听器
    public void addPropertyChangeListener (PropertyChangeListener pcl) {
    listeners.add (pcl);
    }

    // 在数据模型中可以删除监听器
    public void removePropertyChangeListener (PropertyChangeListener pcl) {
    listeners.remove (pcl);
    }

    // 在数据模型中可以触发属性变化事件
    private void fire (String propertyName, Object old, Object nue) {
    //Passing 0 below on purpose, so you only synchronize for one atomic call
    PropertyChangeListener[] pcls = (PropertyChangeListener[])
    listeners.toArray(new PropertyChangeListener[0]);
    for (int i = 0; i < pcls.length; i++) {
    pcls[i].propertyChange(new PropertyChangeEvent (this,
    propertyName, old, nue));
    }
    }

      更重要的是:我们要在适当的地方触发属性变化事件。例如,如果属性表单中,我们可以编辑数据模型的日期属性。那么我们需要在数据模型中的setDate(Date date)方法中触发上述代码的fire()方法:

    public void setDate(Date date) {
            Date oldDate = this.date;
            this.date = date;
            this.fire("date",oldDate,date);

        }

      这一点非常重要。Netbeans Platform Node API Tutorial教程中就是缺少了这段代码,所以你如果按照他的教程测试的时候,节点不会根据属性表单的变化而变化,因为你在属性表单中使用setDate()方法的时候,没有触发fire()方法,所以不会有任何属性改变事件被触发,节点也就不会监听到变化。

    We will rock you

    We Will Rock You


    歌手:皇后乐队     
    Buddy you’re a boy make a big noise
    Playin’ in the street gonna be a big man some day
    You got mud on yo’ face
    You big disgrace
    Kickin’ your can all over the place

    We will we will rock you
    We will we will rock you

    Buddy you’re a young man hard man
    Shoutin’ in the street gonna take on the world some day
    You got blood on yo’ face
    You big disgrace
    Wavin’ your banner all over the place

    We will we will rock you
    We will we will rock you

    Buddy you’re an old man poor man
    Pleadin’ with your eyes gonna make you some peace some day

    You got mud on your face
    You big disgrace
    Somebody better put you back in your place

    We will we will rock you
    We will we will rock you
    August 20

    Netbeans Platform: 选择问题

      开发任何系统都必须面临一个问题:选择问题。Netbeans Platform中你可以使用Lookup进行组件水平的选择,可以使用Node API进行节点水平的选择。
      首先,先要为Node API进行一些必要的解释:Netbeans Platform中的Node,节点,跟图形中的节点概念完全不同,所以很多人看到Node,首先想到的是树形结构中的“节点”。这一点是误解,虽然此节点和彼节点多少有些关系。在Netbeans Platform中Node的概念其实可以被看成是一种代理机制,或者说是一种层,一种表现层。这种层位于数据层和UI层之间。数据层反映的是我们的业务对象,而UI层则是用户看到的表现层或者视图层。有人可能会问MVC就已经足够了,为什么还要增加这一层呢?原因很简单,因为这一层实际上带有一些MVC中的C的一些功能。举个简单的例子Node API中的Node可以提供一系列和这个节点关联的动作。

      作为表现层,节点向数据层添加了界面友好的属性,例如:

    • 显示名称 (Display Name)—人们可读的,界面友好的节点名字
    • 描述 (Description)—人们可读的,界面友好的描述,通常用来做提示
    • 图片 (Icon)—可以用来图形化显示数据类型或者状态的图片
    • 动作 (Actions)—在右键点击节点出现在上下文菜单中的动作,用户可以触发这些动作

    Netbeans Platform: 节点的Children

    节点的Children类在节点包中非常重要。Children类实际上是一个节点的子节点的工厂类。在Netbeans Platform中每个节点都包含一个Children对象,负责维护这个节点的子节点。Children类开始时并不被初始化。子节点根据需要实时创建(按需创建),符合Lazy Loading的思想。比如当一个节点被展开时,他的子节点才被创建。如果你知道这个节点没有子节点,使用Children.LEAF指明。通常情况下一个Children对象将从一个数据模型(你的业务模型)中创建一个对象集合,然后为每个对象按需创建一个或者多个节点(注意:一个对象可能对应多个节点)。需要注意的是如果一个节点的创建是一个耗时操作的话(比如从FTP站点上读取的文件,从海量数据库查询的内容),你应该实现ChildFactory接口,然后将他传递给Children.create (theFactory, true)方法。这样,子节点将会在通过异步方式在另外一个工作线程中创建。从而避免了阻塞。

      大部分情况下,你有两种方式使用Children类。第一种就是继承ChildFactory,然后将他传递给Children.create()方法。或者你继承Children.Keys类特别注意:千万不要继承Children类!
      很奇怪对吧?接下来我们要解释一下了。查看Children类的API你会发现这个类有5个静态内类,层次是这样的:
    org.openide.nodes.Children
                            |+ Children.Array
                                            |+ Children.Keys
                                                          |+
    Index.keysChildren
                                            |+ Children.SortedArray
                                            |+ Index.ArrayChildren
                            |+ Children.Map
                                          |+ Children.SortedMap

      从这个结构图来看,Children类的内部静态类都是Children类的子类,第一层次是Chilren.Array和Children.Map,无非是子节点的不同类型的容器。
      我们先看Children.Array, 这其实将子节点按照一个Array来存储,每个新的节点将被插入到Array的末尾。得到Children的顺序和Array中的顺序一致。需要注意的是,这个类不是抽象类,但是请不要直接继承这个类,而是要使用这个类的子类:Children.Keys. 原因我们稍后再说。
      Children.Keys类应该是我们打交道最多的关于Children子节点的一个类。前面我们说到,应该使用这个类,而不是直接继承Children.Array类,为什么呢?原因如下:
    1. Children.Keys更好的将模型和视图分离,这样你可以使用完全隔离的数据模型。
    2. 当你在一个树视图(或者其他视图)中保留一个存在的节点选择的时候,Children.Keys能够正确的处理子节点的添加,删除和重排。否则的话我们可能需要手工保留选择的节点,然后将新节点加入树中,然后再将以前的节点选择上,等等。
      那么,我们如何使用Children.Keys呢?
      一般我们通过7个步骤来使用它:
    1. 继承Children.Keys(或者继承他的子类)
    2. 决定你的Key的类型
    3. 实现createNodes()方法,根据每个Key创建一个或者多个(通常都是一个)节点。
    4. 覆盖Children.addNotify()方法,这个方法在Children被创建的时候调用,所以方法中要计算一系列的Key,然后用setKeys()方法设置Key,这些Key可以排序。
    5. 覆盖Children.removeNotify()方法,非常简单,仅仅需要使用Collections.EMPTY_SET来设置setKeys.
    6. 当你的模型变化时,注意了,调用setKeys()方法来更新新的Key.Children.Keys将自动计算他需要做的事情。怎么样,神奇吧?
    7. 如果你关心的给定Key的节点的变化,但是给定的Key还是一样的话,你可以调用refreshKey(),但通常并不需要这么做。
      还是那句老话,最简单的例子就是继承ChildFactory,然后将他传递给create()方法,这样能够有效的保证性能,因为它将使用后台线程来计算子节点。

    爱我,别走

    我到了这个时候还是一样
    夜里的寂寞容易叫人悲伤
    我不敢想的太多
    因为我一个人
    迎面而来的月光拉长身影
    漫无目的地走在冷冷的街
    我没有你的消息
    因为我在想你
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔
    我到了这个时候还是一样
    夜里的寂寞容易叫人悲伤
    我不敢想的太多
    因为我一个人
    迎面而来的月光拉长身影
    漫无目的地走在冷冷的街
    我没有你的消息
    因为我在想你
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔
    (music)
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔
    爱我别走
    如果你说你不爱我
    不要听见你真的说出口
    再给我一点温柔

    有一种爱叫放手

    如果两个人的天堂
    象是温馨的墙
    囚禁你的梦想
    幸福是否象是一扇铁窗
    候鸟失去了南方
    如果你对天空向往
    渴望一双翅膀
    放手让你飞翔
    你的羽翼不该伴随玫瑰
    听从凋谢的时光
    浪漫如果变成了牵绊
    我愿为你选择回到孤单
    缠绵如果变成了锁链
    抛开诺言
    有一种爱叫做放手
    为爱放弃天长地久
    我们相守若让你付出所有
    让真爱带我走
    为爱结束天长地久
    我的离去若让你拥有所有
    让真爱带我走说分手

    为了你失去你
    狠心扮演伤害你
    为了你离开你
    永远不分的离去
    !