alartin 的个人资料Windows Live 共享空间照片日志列表更多 工具 帮助

日志


8月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机制时常见的问题作出解答。

8月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
8月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
8月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.

8月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大小进行自动放大缩小
8月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
8月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)
爱我别走
如果你说你不爱我
不要听见你真的说出口
再给我一点温柔
爱我别走
如果你说你不爱我
不要听见你真的说出口
再给我一点温柔

有一种爱叫放手

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

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