如何调整Netbeans的字体

开发Rails应用有一阵子了,使用了朋友推荐的Netbeans作为主要的开发工具,功能很好,唯一让我别扭的地方便是无法设置一个好的字体。由于Netbeans是基于的Swing,所以字体的渲染要通过Java来实现。默认的配置下的monospaced字体是映射到宋体的,如果在字体中选择其他的字体,那么中文就无法显示了。那如何解决这个问题,可以按照以下几个步骤:

继续阅读“如何调整Netbeans的字体”

在Java2平台企业版中应用异步JavaScript技术和XML(AJAX)

作者Greg Murray, 2005年6月9日  翻译:ShiningRay@Nirvana Studio2005年9月9日
任何试过过Flickr、GMail、Google Suggest或者是Google Maps的人都会意识到一种新型的动态Web应用正在逐渐浮出水面。这些应用外观和表现都和传统的桌面应用程序很像,而他们不需要依赖于插件或者是特定于浏览器的功能。过去Web应用只是一系列HTML页面,他们任意一部份内容的更改都必须重新载入页面。像JavaScript编程语言和层叠样式表 (CSS)之类的技术已经成熟,可以有效地应用他们来创建高动态的Web应用,而且可以运行在所有的主流浏览器中。本文将会详细介绍你马上就可以使用的一些技术,让他们使你的Web应用像桌面应用更加丰富和更有交互性。

介绍异步JavaScript技术和XML(AJAX)


使用JavaScript技术,一个HTML页面可以异步地对服务器(一般是载入页面的服务器)发送请求并获取XML文档。然后JavaScript可以使用XML文档来更新或改动HTML页面的文档对象模型(DOM)。最近形成了一个术语AJAX(Asynchronous JavaScript Technology and XML)来描述这种交互模型。

AJAX其实不是很新的东西。这些技术对于Windows平台上专注于Internet Explorer的开发人员来说,已经存在好几年了。直到最近,这个技术才被作为Web远程技术或者远程脚本技术被大家了解。Web开发人员也有一段时间曾经使用过插件、Java applet和隐藏框架来模拟这种交互模型。最近发生的变化是,对XMLHttpRequest对象的支持已经成为所有平台上的主流浏览器都包括的特性了。JavaScript技术的XMLHttpRequest对象是。尽管在正式的JavaScript技术标准中并没有提到这种对象,然而今天主流的浏览器都对他提供了支持。而当代的浏览器如Firefox、Internet Explorer以及Safari在JavaScript技术和CSS的支持上有些细微的差别,但是这种差别是可以处理的。如果你要考虑支持较老的浏览器,AJAX也许就不能成为你的解决方法。

基于AJAX的客户端之所以独特的原因是客户端包含了用JavaScript嵌入的特定于页面的控制逻辑。应用JavaScript技术的页面基于事件进行交互,如文档载入、鼠标点击、焦点改变甚至是定时器。AJAX交互使得表现层逻辑更加清晰地与数据分离。一个HTML页面也可以根据需要每次读入适当的数据,而不是每次需要显示一个更改时都重新载入整个页面。AJAX要求一种不同的服务器架构来支持它这种交互模型。以前,服务器端Web应用关注于对每个导致服务器调用的客户端事件都生成HTML文档。然后客户端对每个回应都要重新读入并重新渲染完整的HTML页面。富Web应用(Rich Web Application)关注于,让一个客户端获取一个HTML文档让它表现为一个模板或者是一个容器,可以基于事件并使用从服务器端组件中获取的XML 数据来对文档注入内容。

一些AJAX交互的应用如:

  • 实时表单数据检验:像用户ID、序列号、邮政编码或者是特殊的票据代码这类需要服务器端验证的数据也可以在用户提交表单之前进行验证。
  • 自动补全:像电子邮件地址、姓名或城市名之类的表单数据都可以根据用户情况自动补全。
  • 处理细节操作:根据一个客户端事件,一个HTML页面可以根据现存的一些数据再去获取更多详细的信息,如现在有一个产品列表,客户端可以控制查看单独的产品信息而无需刷新页面。
  • 复杂的用户界面控件:像树型控件、菜单和进度条之类不要求页面刷新的控件也能实现。
  • 页面内刷新数据:HTML页面可以从服务器上查询最新的数据如分数、股指、天气还有其它的特定于应用的数据。
  • 服务器端通知:一个HTML页面可以通过对服务器进行定时查询来模拟一个服务器的事件通知推送,实现像通知客户端一个消息、刷新页面数据或将客户端重定向到另一个页面。

这个列表并未把所有的应用都列出来,但它已经显示了AJAX交互可以让Web应用比从前能做更多的事情。但尽管这些好处是值得关注的,这种方式也有一些缺点:

  • 复杂度:服务器端开发人员必需理解,HTML客户端页面中的表现层逻辑以及生成HTML客户端页面所需的XML内容的服务器端逻辑。HTML页面开发人员必须了解JavaScript技术。如果开发新的框架和发展已有的框架来支持这种交互模型,那么AJAX应用的创建就会越来越简单。
  • XMLHttpRequest对象的标准化:XMLHttpRequest对象还不是JavaScript技术标准的一部分,这就意味着根据客户端的不同,应用的行为也有所会不同。
  • JavaScript技术的实现:AJAX交互极大地依赖于JavaScript技术,而由于客户端的原因JavaScript还有一些细微的差别。见QuirksMode.org来了解更多关于浏览器之间区别的内容。
  • 调试:AJAX应用也难于调试,因为流程逻辑是同时嵌在客户端中和服务器上的。
  • 代码可见:客户端的JavaScript可以很容易通过“查看源代码”被人看见。一个没有良好设计的AJAX应用很可能被黑客攻击或被他人剽窃。

当开发人员在使用AJAX交互模型上获得更多的经验后,AJAX技术的框架和模式就会慢慢浮现出来。现在就关注于完全通用的AJAX交互框架,还为时过早。本文和相关的解决方案将关注于在现有的Java 2平台企业版(J2EE)上如何对AJAX进行支持,像servlet,JavaServer Page(JSP)软件、JavaServer Face应用和Java标准标签库(JSTL)。

AJAX交互剖析


现在我们已经讨论了AJAX是什么以及一些高层次的问题。那现在让我们把所有的零件放在一起来展示一个具有AJAX的J2EE应用。

首先考虑一个例子。一个Web应用包括了一个静态HTML页面,或者是一个由JSP生成的HTML页面,这个JSP中还包括了一个HTML表单,它需要服务器端逻辑来对表单中的数据进行检验,而不用刷新页面。一个名为ValidateServlet服务器端组件(servlet)用来提供这种验证逻辑。图一描述了这种具有验证逻辑的AJAX交互的细节。

AJAX Interaction
图1: 一个提供验证逻辑的AJAX交互

以下条目代表了图1中出来AJAX交互的过程:

  1. 发生一个客户端事件
  2. 创建和配置一个XMLHttpRequest对象。
  3. XMLHttpRequest对象进行一个调用。
  4. ValidateServlet对请求进行处理。
  5. ValidateServlet返回一个包含了结果的XML文档。
  6. XMLHttpRequest对象调用callback()函数并处理结果。
  7. 更新 HTML DOM。

现在让我们逐个研究这个AJAX模型的每一步。

1.发生一个客户端事件。

在一个事件发生时可以调用相应的JavaScript函数。在这里,validate()函数可以被映射到一个链接或者是表单组件的onkeyup事件上去。

<input type="text"
size="20"
id="userid"
name="id"
onkeyup="validate();">

每次用户在表单域中按下一个键时,表单元素将都调用validate()函数。 

2. 建立和配置一个XMLHttpRequest对象

创建和配置一个XMLHttpRequest对象

var req;

function validate() {
var idField = document.getElementById("idField");
var url = "validate?id=" + escape(idField.value);
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
} else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("GET", url, true);
req.onreadystatechange = callback;
req.send(null);
}

validate()函数建立了一个XMLHttpRequest对象并对象中的open函数。open函数需要两个参数:HTTP方法,可以是GETPOST; 和对象进行交互的服务器端组件的URL;一个布尔变量,表示是否要进行异步调用。API是XMLHttpRequest.open(String method, String URL, boolean asynchronous)。如果一个交互被设置为异步, (true) 那就必须指明一个回调函数。可以使用req.onreadystatechange = callback;来设置这个交互的回调函数。详细内容见第六节。

3.XMLHttpRequest对象进行调用

当收到了语句req.send(null);,就会进行一次调用。HTTPGET的情况下,内容可以是null或者留空。当调用XMLHttpRequest的这个函数时,也会对已经配置了的URL进行调用。在下面这个例子中,要发送的数据(id)将作为一个URL参数。

使用HTTPGET,两个重复的请求将返回同样的结果。当使用HTTPGET方法时,要注意URL的长度,包括已经转义的URL参数,可能会受到某些浏览器和服务器端的Web容器的限制。当发送的数据会影响到服务器端的应用程序的状态时,就应该使用HTTPPOST方法。使用HTTPPOST必须要对XMLHttpRequest对象设置一个Content-Type头,使用以下语句:

req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("id=" + escape(idTextField.value));

当从JavaScript中发送表单值得时候,你应该考虑对字段值进行编码。JavaScript中有一个函数escape(),应该用他来确保区域化的内容被正确编码,同时特殊字符也被正确转义。

4. ValidateServlet对请求进行处理.

一个映射到URI “validate” 的servlet将检验user ID是不是已经在数据库中存在了。

一个servlet处理一个XMLHttpRequest ,就像对待其它的HTTP请求一样。下面的例子显示了服务器从请求中抽取出id参数并检验是否被占用了。

public class ValidateServlet extends HttpServlet {

private ServletContext context;
private HashMap users = new HashMap();

public void init(ServletConfig config) throws ServletException {
this.context = config.getServletContext();
users.put("greg","account data");
users.put("duke","account data");
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {

String targetId = request.getParameter("id");

if ((targetId != null) && !users.containsKey(targetId.trim())) {
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>valid</message>");
} else {
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>invalid</message>");
}
}
}

在这个例子中,一个简单的HashMap用来存放存在的用户名。在这个例子中,我们假设用户的ID是duke

5.ValidateServlet返回一个包含结果的XML文档

用户ID “duke” 在users HashMap的用户ID列表中出现了。将在应答中写一个包含值为invalidmessage元素的XML文档。更复杂的用例将要求DOM、XSLT或其他API来生成这个应答。

response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>invalid</message>");

开发人员必须注意两个事情。第一,Content-Type必须设为text/xml。第二,Cache-Control必须设为no-cacheXMLHttpRequest对象只会处理Content-Typetext/xml的应答,同时把将Cache-Control设为no-cache将确保浏览器不会从缓存相同的URL(包括参数)返回的应答。

6.XMLHttpRequest对象调用callback()函数并处理结果。

XMLHttpRequest对象已经配置为当有readyState改变的时候就调用callback()函数。让我们假设已经ValidateServlet调用了而且ValidateServlet4,表示XMLHttpRequest的调用已经完成。HTTP状态代码200表示一个成功的HTTP交互。

function callback() {
if (req.readyState == 4) {
if (req.status == 200) {
// update the HTML DOM based on whether or not message is valid
}
}
}

浏览器维护了一个所显示的文档的对象形式(也就是所谓的Docuemt Object Model或DOM)。HTML页面中的JavaScript可以访问DOM,同时在页面载入完之后,可以使用API来修改DOM。

根据成功的请求,JavaScript代码可以修改HTML页面的DOM。从ValidateServlet获得的对象形式的XML文档可以通过req.responseXML在JavaScript中获得,req是一个XMLHttpRequest对象。DOM API给JavaScript提供了获取这个文档中的内容以及修改HTML页面的DOM的方法。所返回的字符串形式的XML文档可以通过req.responseText获得。现在我们看看如何在JavaScript中使用DOM API,先看以下从ValidateServlet返回的XML文档。

<message>
valid
</message>

这个例子是一个简单的只包含了一个message元素的XML片断,里面只有一个简单的字符串validinvalid。一个更高级的例子可以包含多于一个的消息和可以给用户看的有效的名字:

function parseMessage() {
var message = req.responseXML.getElementsByTagName("message")[0];
setMessage(message.childNodes[0].nodeValue);
}

parseMessages()函数将处理一个从ValidateServlet获取的XML文档。这个函数会调用setMessage()with the,并给出message作为参数来更新HTML DOM。

7.更新HTML DOM

JavaScript技术可以使用很多API从HTML DOM中获得任何元素对象的引用。推荐的获得元素引用的方法是调用document.getElementById("userIdMessage")"userIdMessage"是HTML文档中出现的一个元素的ID属性。有了这个元素的引用,就可以使用JavaScript来修改元素的属性、修改元素的样式、添加、删除或修改子元素。

一个常见的改变元素主体内容的方法是设置元素的innerHTML属性,如下所示:

<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
userMessageElement.innerHTML = "<font color=\"red\">" + message + " </font>";
}
</script>
<body>
<div id="userIdMessage"></div>
</body>

受到影响的那部分HTML页面会立刻根据innerHTML的设置重新渲染。如果innerHTML属性包含类似<image>或者是<iframe>之类的元素,那么由那些元素所指定的内容同样会被获取并渲染。

这种途径的主要缺点是HTML元素是作为字符串硬编码在JavaScript中的。JavaScript中硬编码的HTML标记不是一种好的实践,因为它使代码难于阅读、维护和修改。我们应该考虑在JavaScript中使用DOM API来创建和修改HTML元素。把显示和JavaScript代码的字符串混在一起只会让页面更难于阅读和编辑。

另一种修改HTML DOM的方法是动态地产生新的元素并把他们作为子元素追加到目标元素,如下面的例子所示:

<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
var userIdMessageFont = document.getElementById("userIdMessageFont");
var messageElement = document.createTextNode(message);

if (userMessageElement.childNodes[0]) {
// 更新元素
userIdMessageFont.replaceChild(messageElement, userIdMessageFont.childNodes[0]);
} else {
// 建立一个新的元素
var fontElement = document.createTextNode("font");
fontElement.setAtribute("id", "userIdMessageFont");
fontElement.setAtribute("color", "red");
userMessageElement.appendChild(fontElement);
fontElement.appendChild(messageElement);
}
}
</script>
<body>
<div id="userIdMessage"></div>
</body>

这个范例展示了JavaScript技术的DOM API可以用来更有目的地建立或改变一个元素。当然JavaScript的DOM AP在不同的浏览器上也可能有差别,所以你必须在开发应用程序时小心。

Java BluePrint的解决方案目录

TheJava Blueprints Solutions Catalog是用来收集J2EE技术上AJAX的最佳实践的。每个解决方案包含一个问题和方法的描述、一个设计文档和可运行的源码。这些解决方案是为了让你根据需要在自己的应用程序中复用。以下是已经提供的AJAX交互:

自动补全

自动补全提供了当用户在一个HTML表单中输入一个请求时对数据浏览的简化方式。当用户面对一大片数据时,可以在输入数据时把可能的完整形式显示给用户。然后选择其中一个完整形式可以保证用户输入的数据已经存在在服务器上。

考虑一个大公司的一个名字查找的Web应用。如图2所示,只要输入姓或名的开头几个字母就可以得到人的列表。用户可以然后就只要点击一下就可以浏览用户的详细信息。

Autocompletion of a Name
图2:名字自动补全

进度条

在Web应用中,一个服务器端任务也可能要花一段时间去完成。这段时间很可能会超过HTTP交互的时间上限(超时)。当用户不知道这个任务什么时候才能完成时,用户很可能会重新提交一次表单或直接退出会话状态。一般来说,Web应用使用页面刷新来跟踪服务器端操作的状态,这种方式可能会让人厌烦而且也不准确。AJAX可以用来仅在一个HTML页面中跟踪服务器端操作的状态而无需刷新页面。用户可以以图形方式看到服务器端操作的进度,如图3。

Progress Bar
图3:进度条 

刷新数据

向一个HTML页面提供最新的数据或服务器消息提醒在现在的Web世界中也是十分重要的,因为现在的Web世界中数据一直不停变化。尽管它不是一个实实在在的推送技术,但它可以通过使用AJAX交互不断进行查询来模拟。当数据需要更新或者要进行提醒,HTML页面将会动态地改变。图4显示了HTML页面中的一个服务器端计数器。这个计数器会在页面后台自动更新。

Server-side Counter Shows Refreshing Data
图4:服务器端计数器在刷新数据

实时检验

不是所有的表单域都可以单独用JavaScript技术在客户端完成。某些表单数据要求服务器端的验证逻辑。传统和Web应用曾使用页面刷新来完成这种验证,但这可能有些让人烦。

考虑一个需要一个唯一用户ID的Web应用。使用AJAX交互,用户可以在输入的时候就知道ID是否有效(图5)。

Invalidating the ID as User Types
图5:指出用户ID无效

当一个用户输入了一个无效的用户ID,应用程序禁止了提交按钮并且向用户显示了一个信息(图6)。

Validating the ID as User Types
图6:用户ID通过验证

用户马上就能知道用户ID是可用的也是有效的。

最后的思考

我们已经看到AJAX交互可以解决很多问题。配合HTTP处理、数据库、Web服务、XML处理和业务对象等API,J2EE技术已经提供了一个开发和部属基于AJAX应用的一个良好的基础。有了对于这个交互模型的更好的理解,今天的应用程序可以变得更加有交互性,给最终用户更好的体验。

使用AJAX要求你使用支持XMLHttpRequest对象的最新浏览器版本。使用AJAX还要求大量对JavaScript技术和CSS的应用。作为一个应用程序架构师或是一个开发人员,你要会针对浏览器支持、架构复杂度和对开发人员的培训等方面来衡量开发一个富应用的需要。当AJAX编程模型不断地发展,现有的技术和框架会让这种转变更加容易。

很明显的是,突出的Web应用都越来越有交互性了。那么你的呢?

更多信息

关于作者


Greg Murray 是is a Sun Microsystems 的一名工程师,是servlet标准的领导人,BluePrint小组的前成员,在这个小组时他已经开始关注Web层次问题。他也是《Enterprise Applications With the Java 2 Platform,Enterprise EditionDesigning Web Services With the J2EE 1.4 Platform(Addison-Wesley)》一书的协助编撰者。

从Trails和Firebird开始

作者:Chris
翻译:ShiningRay @ Nirvana Studio

0. 前言

在Trails的首页上有一段Trails的指导视频,它比本文说得更为详细。你可以看看它,另外根据Firebird设置一节中的内容,设置一下和Firebird相关的东西。

1. 什么是Trails?

Trails是一个领域驱动开发框架,它使用了Hibernate、Spring和Tapestry。其中,Hibernate 被用作数据访问层,Tapestry用来给用户显示数据。而Spring 则是把Hibernate和Tapestry连接在一起。

Trails自带了几乎所以必需的jar文件——你只需要安装一下Firebird的数据库驱动,它可以在 http://firebirdsql.sf.net上下载。

1.1 Hibernate

Hibernate是一个O/RM工具。O/RM的意思是:对象关系映射。O/RM可以让你把 java 对象映射到后台数据库中。Hibernate使用了XDoclet来指明映射信息所需的信息,以便在数据库中存储复杂的对象。而XDoclet使用了javadoc注释来告诉Hibernate如何映射对象。例如:

[code lang=”java”]/**
* @hibernate.class table=”PERSON”
*/

public class Person {
}
[/code]

这段代码会告诉Hibernate要将一个指定的对象(在这里是Person)映射到PERSON表。

Hibernate 的主页是:www.hibernate.org.

1.2 Tapestry

Tapestry 是(简而言之)一个Web框架。更确切地说,Taspetry是一个基于组件的Web框架,它将表示和逻辑清晰地分离开来了。

在Tapestry中,一个页面和一个.html文件相关,这个文件负责这个页面的外观,和一个Java类,它负责对.html文件提供数据,还有一个.
page或者是一个.jwc文件(这由你是要作为一个真正的页面还是一个单独的组件),这两个负责把前两个层次连接起来。

组件是通过ognl语言来访问的,形式如:

jwcid 指明了使用的组件——这里是一个PageLink,它是一个HTML表现中的<a></a>标签的一个组件。page属性指明了你要链接的页面的名称。

Tapestry 的主页在jakarta.apache.org/tapestry

2. 安装Trails。

首先从trails.dev.java.net下载Trails.

当前的版本是0.5.1。先把下载的文件解压缩(在https://trails.dev.java.net上有一个很棒的演示,教你如何利用Trails来写应用程序)。

解压缩trails,并进入新建的文件夹。更改build.properties文件以匹配你的tomcat路径,
然后做以下这些事情:

通过运行下面的指令来创建trail.jar(我在我的机器上必须这样做)

3. 写一个新的应用程序

3.1 创建一个新的trail应用:

这是在问你,把工程的根目录放在哪里(在MacOS X上我输入的是/Users/chris/Desktop/projects——Windows用户可能要输入诸如c:\path\to\new\project的路径)

输入工程名称

然后,一个目录包括所有必须的jar文件就被创建好了。

3.2 启动

3.3 创建一个新的 “Java Project”

  • 输入工程名称
  • 选择 “Create project at external location”(在外部位置创建工程) 并指向新创建的文件夹。

  • 点击 “next”
  • 点击 “add folders”
  • 选择 “src”,然后点击OK

  • 点击 “finish”

新创建的工程就会被导入到Eclipse中。

4. 设置

4.1 改变tomcat的主目录

  • 打开Eclipse中的open build.properties 文件并将tomcat.home改称你的tomcat的目录。

4.2 更改数据库驱动:

  • 打开文件 context/WEB-INF/hibernate.properties 并把其中的设置改为::

4.3 在主机上创建一个新数据库(这里是127.0.0.1)

并且在 FIREBIRD_HOME/aliases.conf 中添加一个别名,这样写:

在生产系统中,你还要为以后要连接的用户更改用户名和密码 :-)

4.4 安装Firebird数据库驱动

firebird.sf.net下载Firebird的JCA-JDBC驱动,解压缩并把jar文件安装到CATALINE_HOME/common/lib下(详细内容参见 JayBird FAQ)。

5. 开始编码

5.1 创建一个领域对象(Domain Object)

领域对象可以简单地说成是POJO(Plain Old Java Objects),它是Hibernate用来在数据库中存放数据的。

  • 在Eclipse中选择src文件夹并点击 File -> New -> Class(文件->新建->类)
  • 输入一个包名(de.test.data)
  • 在Name输入框中输入类名:Person
  • 点击 “finish”

成功新建并打开了一个类。

5.2 告诉Hibernate要使用的表格:

把表格名称作为XDoclet的hibernate.classabove标签添加到类的说明中。XDoclet任务以 @ 符号开始,后面跟着一个名空间(在这里是hibernate)和一个动作,指明要做什么(更多信息参见hibernate.sf.net)。

XDoclet标签 @hibernate.class table=”PERSON” 将告诉Hibernate把这个对象中的数据存储在一个叫做PERSON的表中。

表格的名称无需和类名相匹配——你也可以指定一个MY_PERSON作为名称 :-)。

5.3 创建属性:

  • 先输入以下内容:

  • 在“Outline”(大纲)中选择PersonID并右键点击它。
  • 选择Source -> Generate Getters and Setters
  • 两个都选中并点击OK

现在已经有了getter和setter例程,而且已经添加了注释。然后我们添加下面的注释:

这将告诉Hibernate使用表格的PERSON_ID列来存储表格的主键,同时通过使用一个生成器来自动新建这个字段的值。
键将由SQ_PERSON_ID生成器来生成。

在大纲中选择name并且右键点击它(选择 source
-> generate getters and setters,选择Name和SurName然后点OK :-) )

这次,我们不再需要主键——我们仅仅需要一个字段来存放姓名。所以我们继续并添加以下javadoc注释:

[code lang=”java”] /**
* @hibernate.property column=”LAST_NAME” not-null=”true”
*/
public String getName() {
return Name;
}
[/code]

以及surname属性

由名称属性而来的getter上的列是必须出现的,因为我们要覆盖默认的行为。Hibernate会假设一个叫“NAME”的字段——但这在Firebird中是一个关键字,所以我们必须另外选择一个名称。

记住:Hibernate标记必须总是设置在getter上——而不是setter。

如果需要,我们可以通过在Hibernate的XDoclet标记中添加not-null=”true”属性来设置一个null值——默认值为false。

5.4 创建查询表

现在提示有一个错误——是LSalutation。

这时把你的鼠标移到LSaltutation上并且按下Ctrl+1(在Apple Macintosh上你要按Command + 1)。然后会出现以下对话框: 

输入要创建的文件的包名,点击“finish”,然后做以下几步动作:

  • 给他一个ID
  • 个它一个名称
  • 生成getter和setter
  • 并且指明给表格设置的类名,以及ID和属性字段

现在,又是另一个对象可以被Hibernate映射了。对于Hibernate,这已经可以用了,但对于Trails,还少个东西:

  • 一个 equals() 和一个 toString() 方法

toString() 方法是用来在列表视图中能正确显示属性,而equals方法是用来标示对象的。所以只要添加以下代码到LSalutation类中:

返回到Person.java中并且为Saltuation属性创建getter和setter。

由于我们要给salutations使用另一个表中的数据,所以这里我们要用另一个属性。所以这里我们要用一个多对一的关系。也就是,我们要把javadoc注释改成:

现在Hibernate已经了解如何引用salutation类了。

这就是我们要写的全部代码了。 :-)

6. 构建应用程序:

打开build.xml文件,右件点击war目标并选择 ant -> build

这将调用ant来构建.war文件(Trails的指南说,使用deploy目标可以部署程序——这不能在我的机器上运行)。然后我把project.war文件复制到TOMCAT_HOME/webapps中去。

7. 访问应用程序:

打开浏览器并输入127.0.0.1:8080/project_name

现在,享受一下浏览和输入数据的乐趣吧 :-)

无任何担保——Chris

为什么继承是有害的?

通过把具体的基类转变成接口来改进你的代码

作者:Allen Holub  翻译:ShiningRay @ Nirvana Studio

摘要

大多数优秀的设计师避免出现继承(extends描述的关系),就像躲避瘟疫似的。你的代码80%应该完全以接口的方式来书写,而不是继承具体的基类。其实,Gang of Four 这本关于设计模式的书(以下简称GoF)很大程度上关于如何把类继承转变成接口实现。本文将叙述为什么设计师们会有这种古怪的信条。(2,300 words;2003 年 8月 1日)

译注:本文其实已经有人翻译,当时没有具体了解就开始翻译了,如果另一位译者看到这篇文章,希望不要理解为我抄袭的。

extends关键字是很有害的;也许不仅仅是在Charles Mason的级别上,还坏到了只要可能都应该避免的程度。GoF中详细讨论了把类继承(extends)如何转变成接口实现(implements)。

优秀的设计师的大部分代码都是根据接口写的,而不是根据具体的基类。本文将会讲述为什么设计师们会有这种古怪的癖好,同时也将介绍一些基于接口的编程基础。

接口 VS 类

我曾经参加了一个Java用户小组会议,那次刚好是James Gosling(Java的发明者)作特邀演讲人。在那次难忘的Q&A对话(提问)上,一个人问他:“如果你可以重新将Java搞一遍,你会做哪些修改?”“我会去掉类,”他回答道。在笑声渐渐消失之后,他解释了真正的问题不是类的本质,而是类继承(extends关系)。接口实现(implements关系)却是完美的。只要有可能,你们就应该避免类继承。

弹性的丧失

为什么你应该避免类继承?第一个问题是明确的使用具体类的名称会把你框在特定的实现中,让以后的更改会十分困难。

当代,敏捷开发方法学的核心是设计和开发同步。你在完全详细描述程序之前,就开始编写代码了。这种技术完全违背了传统的理念——设计应该在编程之前完成——但是很多成功的项目已经证实了,用这个方法,你可以比传统流水线作业更快速地开发高质量的代码(同时付出很有效)。然而,在并行开发的核心是,弹性的概念。你必须以这种方式来写你的代码,以便你可以尽可能以无痛的方式加入新发现的需求到现有的代码中。

你只要实现确实需要的特性,而不是实现那些可能需要的特性,但要用一种可以适应变化的方法。如果你没有这种弹性,并行开发明显是不行的。

接口编程正是这个弹性接口的核心。要了解为什么,先让我们看看如果你不使用接口会发生什么。考虑以下代码:

现在假设一个紧急的新需求,需要进行更快速的查找,已经暴露出来了,这样LinkedList就达不到要求了,你就要把它换成HashSet。在现有的代码中,因为你必须同时修改f()还有g()(它用一个LinkedList作为参数),因此更改不是局限在一处的,还有一切传列表给g()的地方。

现在把代码改成这样:

现在我们要把链表改成哈希表就只把new LinkedList()改成new HashSet()。就完成了。不需要更改其他的地方。

另外一个例子,比较一下代码:

以及:

g2()方法现在遍历Collection的派生对象以及你从Map中得到键和值。事实上,你可以写一个不断产生数据的迭代子而不是遍历一个集合。你可以写很多不同的迭代子,比如可以从测试台中或者一个文件中不断给出信息。这就是这里最重要的弹性所在。

耦合

关于类继承的一个更加关键的问题是耦合——程序中不期望的一个部分对另一个部分的依赖。全局变量提供了一个经典的例子来说明为什么强耦合会造成很多问题。例如,如果你更改了全局变量的类型,所有使用这个变量的函数(也就是,对这个变量有耦合)就会受到影响,这样所有这样的代码必须被检查、修改和重新测试。此外,所有使用这个变量的函数也会通过这个变量产生耦合。也就是,一个函数可能会不正确地更改了这个变量从而造成了其他函数的行为,如果变量的值在某些特殊的时间被更改的话。这个问题在多线程的程序中特别突出。

作为一个设计者,你应该力争做到最低的耦合度。当然你不可能完全消除耦合,因为一个类的对象调用另一个对象就是一种松散耦合的形式。你不可能写出一个一点耦合都没有的程序。尽管这样,你可以通过绝对服从OO的原则(最重要的是一个对象的实现细节应该对使用它的对象是隐藏的)来相当可观地最小化耦合。例如,一个对象的实例变量(非常量的成员字段),总是应该为私有private。这没有任何例外的情况(你可以偶尔很有效地使用protected方法,但是protected实例变量是相当讨厌的)同样的原因,你也绝不能使用 set/get 函数——他们只是另一种让字段变成公共的稍复杂方式而已。(虽然返回处理过的对象而不是一个基本类型的值的访问函数在某些情况下还是合理的,如果返回的对象的类是设计中的一个关键的抽象的话。)

这里我不是在卖弄学问。我发现了一个OO方式的严格性、快速代码开发、和简单的代码维护之间的直接的相关性。无论什么时候我违反了一个核心的OO原则比如隐藏实现细节,我只能结束代码的修改(通常是因为这个代码不可能进行调试)。我没有时间重写程序,所以我只能遵循这些规则。我关注的是完全实际的内容——我对为了设计而设计没有兴趣。

脆基类问题

现在,我们把耦合的概念应用到继承上。在一个使用实现-继承系统中,派生类对基类有十分紧密的耦合,同时这个闭合的连接是不受欢迎的。设计师们因此给这种行为起了一个绰号——“脆基类问题”。基类是被认为十分脆弱的,因为你可以通过一个表面上十分安全的方法修改一个基类,但这个新的行为,当被派生类继承的时候,可能会造成派生类运行出错。你不能简单孤立地通过检查基类的方法来判断你对基类的改变是不是安全;你也必须查看(并测试)所有的派生类。此外,你必须检查所有同时使用了基类和派生类对象的代码,因为这些代码可能会被新的行为所破坏。对关键的基类的小小的改变都会导致整个程序无法运行。

我们来一起检验这个脆基类和基类耦合这两个问题。下面的类扩展了Java的ArrayList类,来模拟栈的行为:

甚至像这样简单的一个类,都存在着问题。思考一下如果用户利用继承直接使用ArrayListclear()方法来把所有的元素都从栈中弹出去,会发生什么:

这个代码可以成功地编译,但是由于基类并不知道任何关于栈指针的信息,Stack对象现在处在一个不确定的状态。下面再调用push()会把新的条目放到索引2种(栈指针stack_pointer当前的值),这样栈看上去就有三个元素了——但底下两个已经被垃圾收集了。(Java类库中的Stack类就是这种问题,所以不要用)

对于不需要的方法继承,一种解决方式是,对于Stack要重写所有ArrayList的可能修改数组状态的方法,这样覆盖的函数可以正确处理栈指针或者抛出一个异常。(removeRange()方法是一个较好的抛出异常的候选。)

这个方法有两个缺点。第一,如果你覆盖所有的东西,基类就实际上成为了一个接口,而不是一个类。如果你不使用任何继承的方法,类继承就毫无意义。 第二,也是更为重要的一点,你并不希望一个栈能支持所有ArrayList的方法。比如,那个讨厌的removeRange()方法没什么用处。实现一个没用的方法的唯一合理的方式,就是让他抛出一个异常,这样他就不可能被调用了。这个方法却将一个编译时错误变成了运行时错误。这并不好。如果方式只是没有被声明,那么编译器会直接扔出一个“未找到方法”的错误。如果这个方法存在但是他抛出异常,你就不会发现错误直到程序运行的时候。

一个更好的解决方法是封装一个数据结构而不使用继承。这下面是一个Stack的改进过的新版本:

目前为止还不错,但是还要考虑到脆基类的问题。让我们假设你想创建一个Stack的变体,可以跟踪运行一段时间之后栈出现过的最大值。一种可能的实现如下:

新的类运行得很好,至少目前这样。但不幸的是,代码暴露了push_many()方法是通过调用push()来完成它的任务的。首先,这个细节看起来还不算一个糟糕的选择。他简化了代码,同时你可以获得派生类的push()版本,即使当Monitorable_stack是通过一个Stack类型的引用也能完成,所以,high_water_mark的更新是正确的。

某一天,有个人也许会运行一个测试工具并且发现了Stack还不够快,而且他要被频繁地使用。你可以重写一个不使用ArrayListStack,由此改进Stack的性能。下面是最新的版本:

注意push_many()不再是重复调用push(),而是采用了块传送。新版本的Stack运行很好;事实上,它要比原先的版本更 。但很不幸,派生类 Monitorable_stack 就不能再正常工作了,因为如果调用的是push_many()那么他不能正确跟踪栈的使用情况了(派生类的push()版本不再被继承了的push_many()所调用,所以他不会再更新high_water_mark)。现在Stack就是一个脆基类。正如上面显示的,事实上不可能仅仅靠小心就能消除这类问题。

值得注意的是如果你使用接口继承,就不会有这种问题,因为不会继承任何功能,就不会产生不良影响。如果Stack是一个接口,同时通过Simple_stackMonitorable_stack来实现,那么代码就会更加强壮。

在表0.1种,我提供了一个基于接口的解决方案。这个方法和类继承的方法有相等的弹性:你可以根据Stack抽象来写你的代码而不用担心你要具体处理那种类型的栈。由于这两种实现都必须提供公共接口中的所有方法,要出现问题也很难。我也有且仅有一次从写相同的基类代码中获益,因为我使用了封装而不是派生。从负面,我必须通过一个封装类中的细小的访问器方法去访问默认的实现。(例如Monitorable_Stack.push(...)(41行)必须要调用Simple_stack的中等价的方法。)程序员总是抱怨写这种一行就完了的代码,但是仅仅就写这么额外的一行的代价就可以消除潜在的巨大Bug。

表 0.1. 使用接口消除脆基类

框架(Frameworks)

关于脆基类的讨论如果不提到基于框架的编程,就不会是完整的讨论。像MFC(微软基础类库)这种框架已经成为一种建立类库的流行手段。虽然MFC正在急流勇退,但MFC的结构已经深深扎根在无数微软的车间——这里面的程序员都认为微软的方法就是最好的方法。

一个基于框架的系统,一般都是以半成品类的库作为起始,这些半成品的类不会完成所有事情,而是要依赖于派生类提供未完成的功能。Java中的一个典型的例子就是Componentpaint()方法,它其实只算一个占位符;而派生类则必须提供真正的版本。

你可以,但是一个整个类框架都依赖于基于派生的自定义是极其脆弱的。基类也很脆弱。当我用MFC编程的时候,每次微软发布一个新版本的MFC,我都必须重写自己的应用程序。代码会经常编译但接下来却不能正确工作因为一些基类的方法改变了。

所有的Java包都可以即开即用(Out of box)且运行良好。你无需扩展任何东西来让他们执行功能。 即开即用的结构比基于派生的框架要好。它更容易维护和使用,并且即使Sun提供的类改变了他的实现也不会让你的代码处于危险中。

脆基类的总结

一般来说,最好能避免继承具体基类和extends关系,而使用接口和implements关系。凭我的经验,代码中最少有80%应该是完全用接口的方式来写。比如,我从来不引用一个HashMap ;我通常会用指向Map接口的引用。(这里的“接口”是广义的。一个 InputStream也算一个有效的接口,你可以看看是如何使用它的,虽然他在Java中是描述为一个抽象类。)

你加入越多的抽象,弹性就会越好。在今天的商业环境中,通常在程序开发时需求就在不断变化,弹性是十分必要的。 此外,大多数敏捷开发方法(比如Crystal方法和极限编程)完全不能正常运作,除非代码是先用抽象写得。

如果你仔细研究Gang of Four的模式,你就会发现他们中很多都是提供了各种消除类继承的方法,而偏重于使用接口,这也是大多数模式的一个共性。我们一开始就要明白一个重要的事实:模式是被发现的,而不是被发明的。当你看了那些写得好的、易于维护可以很好运行的代码,模式自然就会浮现出来。这告诉我们这么多优秀的代码都会以各种方式避免类继承。

本文是从我即将发表的书中节选出来的,书暂时命名为《 Holub on Patterns: Learning Design Patterns by Looking at Code 》,将会在今年秋季通过Apress (www.apress.com)出版。

关于作者

Allen Holub 从1979年开始从事计算机产业的工作。他目前是一个顾问,通过对行政人员提供建议、培训以及设计、编程服务,来帮助企业不需要再软件上浪费钱。他撰写了8本书籍,包括 Taming Java Threads (Apress, 2000) 和 Compiler Design in C (Pearson Higher Education, 1990), 并且在加州大学伯克利分校教学。请在他的网站 ( http://www.holub.com ) 查询更多关于他的信息。


资源

jMemorize 介绍

jMemorize是一个 Java 应用程序,可以使用著名的Leither系统来管理你的抽认卡,让记忆事情不仅有效而且有趣。它可以管理你的整个学习过程和特点分类、统计和一个优雅的外观。

Leitner 原理

Wikipedia的文章这样定义Flashcard (抽认卡):

一个抽认卡是一小块纸片,在学校里用来作辅助教学(主要是英语国家)。抽认卡可以用来记录词汇、历史事件时间、公式等等。使用抽认卡的目的主要是帮助[[记忆]]。你在每个卡片上写下一个问题(同时在背面记下答案),用它们来测试自己,并根据你的测试、学习结果把它们进行排序、分组。

这种策略使得学习更有选择性,也是就:在一组的卡片越难,你就越要花时间复习这一组。于是,你就能节省很多学习时间。

这个方法是由德国心理学家 Sebastian Leitner 在 19世纪70年代提出的。 Pimsleur 语言课也是基于类似的想法。

这个基本思路是根据它们对你的难易程度把卡片划分成不同的级别。这是通过你尝试回答一些重复问题来完成的。每次你了解的一个卡片的正确回答,它就会放到下一个较高的卡片级别中。如果某个卡片你没有回答出来,它就会被放回到开始的级别。

这个系统是和时间表结合在一起的。已经学习过的卡片,会在一个特定的失效期过后,重新被确认为要学习。级别越高,失效时间就越长。例如一个卡片第一
次成功检测过之后,会被安排在一天之后再次学习,当这个卡片连续三次都回答正确了之后,系统会在一周内都认为学过了。只要这个卡片被认为是学过了,它就不
会在当前学习阶段出现。

总之,这个系统将管理你的个人学习过程并让你关注于学习本身,同时它会自动决定哪个事物现在要再学习,尽可能让这些事情从你的时间中抽出来。

特性

  • 新建、编辑、删除和重置卡片。
  • 通过建立卡片分类来安排卡片。
  • 你当前的学习情况的可视化演示。
  • 可方便地自定义你的学习会话设置。这包括以下功能:
    • 选择要学习的分类。
    • 选择只学习新的卡片、过期卡片和所有卡片。
    • 选择卡片/时间的限制。
    • 选择你是否想以普通模式(顺序)学习还是随机模式。
    • 选择预设的时间表或自己创建自定义时间表。
  • 可以搜索你的卡片
  • 还有很多很多

许可证

本软件由 Riad Djemili ( riad.de) 开发以 GPL license.发布。这表示 jMemorize 是自由软件,不仅可以自由分发二进制代码,同时还提供完整的源代码。

Python 不是 Java

作者:Phillip J. Eby.

翻译:ShiningRay @ NirvanaStudio

原文地址:http://dirtsimple.org/2004/12/python-is-not-java.html


我最近正在看一个基于wxPython的GUI应用程序,大概45.5KLOC的样子,但我没有计算它用到的库的大小(如Twisted)。代码是由那些对Python相对生疏的Java的开发者写的,所以程序有很严重的性能问题(如三十秒的启动时间)。我在检查代码的时候发现他们写了很多对Java有意义但是对Python却很恐怖的东西。并不是因为“Python比Java慢”,而是因为在Python中有更方便的方法去完成同样的目标,甚至在Java中不可能的事情。

所以,可悲的事就是这些可怜人事倍功半,产生了很多很多不需要写的代码,从而比相应合乎Python习惯的写法慢得多得多。我们来看一些例子:

  • 在Java中一个静态的方法(static)不能翻译成一个Python的类方法(classmethod)。哦,当然,多多少少他最终产生类似的效果,但类方法的目的实际上是做了一些通常在Java中不可能的事(如继承一个非默认的构造函数)。Java静态方法的习惯翻译通常是一个模块级函数,而不是一个类方法或静态方法(staticmethod)。(同时静态封闭(final)字段应该翻译成模块级常量。)

    这并不是一个性能上的问题,但是一个Python程序员要用像这些类似Java习惯的代码的话,可能就会被在该输入Foo.someFunction时却要输入Foo.Foo.someMethod这种情况给惹毛了。但是请注意:调用一个类方法将会比调用一个静态方法和函数要多一部分额外的内存。

    啊,那些Foo.Bar.Baz也不是省油的。在Java中,这些点分割的名称是由编译器去查找的,所以运行时根本无所谓你有多少点。在Python中,每次运行时都要查找,所以每个点都要计算在内。(Python中一定要记住这点,“平铺比嵌套好”,尽管比起性能,他和“可读性”和“简单就是美”更靠近。)

  • 要用switch语句?Python翻译将是一个哈希表,不是一堆if-then语句。用一堆if-then在Java中也不是switch语句,如果有字符串参与了呢?他其实是一个哈希表。CPython字典实现用了性能最佳—在我们宇宙中目前所知道的—的哈希表的实现之一。你自己所写的代码也不会比这个再好了,除非你是Guido、Tim Peters和Raymond Hettinger的“私生子”——还是遗传增强了的。
  • XML不是答案。它也不是一个问题。要在正则表达式上解释Jamie Zawinski,“一些人,当遇到一个问题的时候,就想‘我知道,我要用XML’那这个时候,他们就有两个问题了。”

    和Java比,这是一个不同的情况。因为比起Java代码,XML是轻巧而且有弹性的。但比起Python的代码来,XML就是一个船锚,一个绊脚石。在Python中,XML是用来做交换,而不是你的核心功能,因为你不需要这么做。在Java中,XML可能是你的大救星因为他让你实现了特定领域的语言并“不通过编码”提高了你的应用程序的适应性。在Java中,避免编码是一个很大的优势,因为编码意味着重新编译。但在Python中,更常见的是,写代码比写XML更方便简单。同时Python处理代码要远远比处理XML快。(不仅仅是这个,你必须书XML处理代码,同时Python自身就已经为你准备好了。)

    如果你是一个Java程序员,对于你是否要在你的Python核心应用中使用XML作为一部分,不要相信你的本能。如果你不是因为信息交互的原因去实现一个已经存在的XML标准或是建立某种导入、导出格式或者建立某种XML编辑器或处理工具,那么就不要这么做。一次也别。甚至连想都不要想。现在,扔掉那个XML模式把你的手解放吧!如果你的应用程序或者平台要被Python开发者使用,他们只会感谢你不要在他们的工作量中添加使用XML的负担。

    (这里唯一的例外是如果你的受众的的确确,确确实实需要XML,出于某种奇怪的理由。像,他们拒绝学习Python并只对你使用了XML而付钱给你,或者你打算给他们一个编辑XML的GUI,同时这个写XML的GUI呢是另一个人写的,同时你得到免费使用的权利。还有一些很少见的架构上的原因需要用到XML。相信我,他们不会出现在你的程序中。如果有疑问,对一个资深的Python开发员解释你的用例。或者,如果你脸皮厚的话,试试向一个Lisp程序解释你的程序为什么要用XML!)

  • Getter和setter是坏蛋。坏蛋,魔鬼!Python对象不是Java Bean。不要写什么getter和setter,然后还把它们包装在“属性”里面。它直到你能证明你需要比一个简单访问复杂一点的功能时才有意义,否则,不要写getter和setter。它们是CPU时间的浪费,更要紧的是,它们还是程序员宝贵时间的极大浪费。不仅仅对于写代码和测试的人,对于那些要阅读和理解它们的人也是。

    在Java中,你必须使用getter和setter因为公共字段不允许你以后改变想法再去使用getter和setter。在Python中,这样做很傻,因为你可以以一个普通特性开始并可以在任何时间改变你的想法,而不用影响到这个类的任何客户。所以不要写getter和setter。

  • 代码重复在Java中常常是一个不得不要的魔鬼,你必须经常一遍一遍写同一个方法而只有一点点的变化(通常是因为静态类型约束)。在Python中这样做是没有必要的也是不值得的(除了极少数一些特定的场合需要内联一些要求性能的函数)。如果你发现自己一遍一遍在写同样的代码而且变化很少,你就需要去学一下闭包。他们并不是真的很可怕。

    这就是你要做的。你写了一个包含了函数的函数。这里内部的函数就是你要一遍遍写的函数的模版,但是在里面加入了针对不同情况的函数要使用变量。外部的函数需要刚刚提高的那种变量作为参数,并且将内部的函数作为结果返回。然后,每次你要写另一种略微不同的函数的时候,你只要调用这个外部的函数,并且把返回值赋给你要让“重复”函数出现的名字。现在,如果你需要改变这个工作方式,你只要改变一个地方:这个模版。

在我所看过的应用程序/平台中,只有一个很微不足道的程序使用了这个技术之后可以去掉数百行重复代码。事实上,自从开发者使用了特别的样板文件来为这平台开发插件,这会节省很多很多第三方开发人员的代码,同时也使那些程序员要学习的东西简化了。

这只是Java->Python思维方式转变的冰山一角而已,现在我可以让他转变成正确的而不用钻研这个程序的细节。本质上,如果你曾经用过一段时间Java,而且对Python比较陌生,不要太相信自己的本能。你的本能已经为Java调节,而不是Python。向后退一步,最重要的,不要写这么多代码了。

要这样做,让自己觉得更加需要Python。假装好像Python是可以做任何你想做的魔棒,却让你无须动一个手指。问一下,“Python是怎样解决我的问题的?”还有“Python语言的哪个特点和我的问题最相似?”你绝对会惊讶于你需要的东西其实已经有了某种固定形式。事实上,这种现象实在是太普遍了,甚至在很有经验的Python程序员中也会出现,以至于Python社区中给这种现象起了个名字。我们称之为“GUIDO的时间机器”,因为有时候看上去得到我们所需要的东西好像只有他知道的一种方法,但当我们自己知道了就不一样了。

所以,如果你不能感到你在使用Python时至少比用Java要多出10倍的生产力,!(同时如果你还怀念你的Java IDE,考虑一下这种可能性:因为你写的Python程序比他所需要的要复杂得多)


附录:(翻译自此篇文章的评论)

确实,哈希表==字典。举个最简单的例子,从Python

标准库中检出“pickle”和“copy”模块,这两个模块会从字典中查找类型并调用相应的函数。另一个有些诡异的例子是范型函数,我已经在最近的Blog中写了一下。

关于闭包的例子,我这里给出一个很笨的例子。假设你要写很多这样的函数:

def addOne(x): return x+1
def addTwo(x): return x+2

然后你可以这样写:

def makeAdder(addend):
… def add_it(x): return x+addend
… return add_it

并且这样使用:

addOne = makeAdder(1)
addTwo = makeAdder(2)

这样就可以等同于原来的定义了。

相关资料:http://www.razorvine.net/python/PythonForJavaProgrammers