托朋友从米国终于带回了Kindle 2,加上皮套一共才2000 RMB,比国内各大品牌的阅读器都要便宜很多,而且可以直接免费使用3G网络,自带的浏览器能直接浏览网站(估计是联通WCDMA信号?确实不知道为啥能免费使用)。

不过Kindle 2仍然不支持中文,需要经过hack才能支持。在国内搜索了半天,发现中文支持最后都是从Blog Kindle这个国外牛人搞出来的升级包,在Unicode Font Hack一文里有详细的升级步骤和所有升级包下载,大概读了一下,基本步骤如下:

1.首先,根据Kindle 2的型号(从Kindle机器后面的序列号看,Kindle 2是B002开头,Kindle 2国际版是B003开头,Kindle 2 DX是B004开通,Kindle 2 DX国际版是B005开头)下载对应的升级包,作者已经做了好几种字体的升级包,对于中文用户,如果你不想自己从Windows上搞微软雅黑字体,推荐直接使用Droid字体的升级包,这是Android手机默认的中文字体,显示效果很不错,完全够用。例如,Kindle 2国际版的升级包就是:

http://blogkindle.com/wp-content/uploads/2009/11/update_ufh_droid_install-k2i.bin

2.把下载的update_uth_xxx.bin通过USB复制到Kindle的根目录,然后依次按Home,Menu,Settings,Menu,最后选择“Update Your Kindle”开始升级。

3.升级过程大概一分钟,然后Kindle会自动重启,重启后,复制几本中文PDF或TXT进去,能正常显示中文就大功告成!

升级注意要点:确保下载的bin文件和你的Kindle型号对应,升级前确保电池有足够的电量,升级过程中不要做任何操作。

用Kindle看电子书效果非常出色,和普通LCD屏幕相比更有纸质书的感觉。Kindle 2最新版本已经能直接支持PDF格式,不过,由于Kindle 2的屏幕还较小(Kindle 2 DX好点),而且不支持PDF重排和字体大小调整,而一般的PDF都是按照A4纸的尺寸排版的,即使是横屏看PDF也觉得字体太小,而中文的TXT格式我发现Kindle会有乱码和不能翻页的问题,估计是一些特殊字符造成的。Kindle支持的最佳格式是AZW,也就是Amazon自家的电子书格式,不但字体可缩放,而且翻页速度极快,因此,在Kindle上看电子书最好的办法就是把PDF和TXT等格式转换为AZW格式,如何批量转换?下次再继续讨论。

阅读全文 »

在使用Subversion进行版本管理时,有时候需要将其他资源库的一部分链接到我们自己资源库中,例如,本地已经存在的Project已经被Subversion管理了,但是,需要引入gdata等第三方库,当然可以直接将第三方发布的lib放到本地,但是,无法做到实时更新。由于Subversion支持external形式的外链,我们就可以将外部库的一部分当作本地Project的一部分。

以gdata为例,我们需要在本地的src目录下引入gdata的两个目录:

  • atom:http://gdata-python-client.googlecode.com/svn/trunk/src/atom
  • gdata:http://gdata-python-client.googlecode.com/svn/trunk/src/gdata

则需要通过svn命令实现。首先切换到src所在目录的父目录,新建文本文件externals.txt,内容如下:

atom http://gdata-python-client.googlecode.com/svn/trunk/src/atom
gdata http://gdata-python-client.googlecode.com/svn/trunk/src/gdata

给src目录加上svn:externals属性,使用以下命令:

C:\projects\my_project> svn propset svn:externals -F externals.txt src

-F参数告诉SVN属性"svn:externals"的内容从文件externals.txt中读取,然后,对src目录做update操作,就会看到类似如下的输出:

Updating external location at: C:/projects/my_project/src/atom
  ...
Updating external location at: C:/projects/my_project/src/gdata
  ...

最后,在src目录下,可以看到,自动创建了引入的两个外部资源库的目录:atom和gdata。

阅读全文 »

本文最早发表于IBM developerWorks:

http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/

传统的JavaEE MVC框架如Struts等都是基于Action设计的后缀式映射,然而,流行的Web趋势是REST风格的架构。尽管使用Filter或者Apache mod_rewrite能够通过URL重写实现REST风格的URL,为什么不直接设计一个全新的REST风格的MVC框架呢?本文将讲述如何从头设计一个基于REST风格的Java MVC框架,配合Annotation,最大限度地简化Web应用的开发,您甚至编写一行代码就可以实现“Hello, world”。

Java开发者对MVC框架一定不陌生,从Struts到WebWork,Java MVC框架层出不穷。我们已经习惯了处理*.do或*.action风格的URL,为每一个URL编写一个控制器,并继承一个Action或者Controller接口。然而,流行的Web趋势是使用更加简单,对用户和搜索引擎更加友好的REST风格的URL。例如,来自豆瓣的一本书的链接是http://www.douban.com/subject/2129650/,而非http://www.douban.com/subject.do?id=2129650。

有经验的 Java Web 开发人员会使用 URL 重写的方式来实现类似的URL,例如,为前端Apache服务器配置mod_rewrite模块,并依次为每个需要实现URL重写的地址编写负责转换的正则表达式,或者,通过一个自定义的 RewriteFilter,使用Java Web服务器提供的Filter和请求转发(Forward)功能实现URL重写,不过,仍需要为每个地址编写正则表达式。

既然URL重写如此繁琐,为何不直接设计一个原生支持REST风格的MVC框架呢?

要设计并实现这样一个MVC框架并不困难,下面,我们从零开始,仔细研究如何实现REST风格的URL映射,并与常见的IoC容器如Spring框架集成。这个全新的MVC框架暂命名为 WebWind。

术语

MVC:Model-View-Controller,是一种常见的UI架构模式,通过分离Model(模型)、View(视图)和Controller(控制器),可以更容易实现易于扩展的UI。在Web应用程序中,Model 指后台返回的数据;View指需要渲染的页面,通常是JSP或者其他模板页面,渲染后的结果通常是HTML;Controller 指 Web 开发人员编写的处理不同URL的控制器(在Struts中被称之为 Action),而 MVC 框架本身还有一个前置控制器,用于接收所有的 URL 请求,并根据 URL 地址分发到 Web 开发人员编写的Controller中。 IoC:Invertion-of-Control,控制反转,是目前流行的管理所有组件生命周期和复杂依赖关系的容器,例如 Spring 容器。

Template:模板,通过渲染,模板中的变量将被Model的实际数据所替换,然后,生成的内容即是用户在浏览器中看到的 HTML。模板也能实现判断、循环等简单逻辑。本质上,JSP页面也是一种模板。此外,还有许多第三方模板引擎,如Velocity,FreeMarker等。

设计目标

和传统的Struts等MVC框架完全不同,为了支持REST风格的URL,我们并不把一个URL映射到一个Controller类(或者Struts的Action),而是直接把一个URL映射到一个方法,这样,Web开发人员就可以将多个功能类似的方法放到一个Controller中,并且,Controller没有强制要求必须实现某个接口。一个Controller通常拥有多个方法,每个方法负责处理一个URL。例如,一个管理Blog的Controller 定义起来就像清单1所示。

阅读全文 »

现在,Google Chrome浏览器已经有了unstable的Linux版本,而且是deb格式的!

dev.chromium.org/getting-involved/dev-channel这里下载32位或64位版本,我选择的是32位:

http://www.google.com/chrome/intl/en/eula_dev.html?dl=unstable_i386_deb

用dpkg -i xxx.deb安装,然后就可以直接启动google-chrome了:

显示的版本号是4.0.x:

阅读全文 »

最近才知道原来Eclipse还可以自己卸载已经安装的插件,方法是点击菜单“Help”,“Install New Software...”,在弹出的对话框中选择那个非常隐蔽的“already installed”链接:

然后就显示已经安装的插件:

现在就可以选择要卸载的插件,然后点“Uninstall...”把它卸载掉。

这个方法对Eclipse Galileo (3.5)有效,其他版本你需要自己试一下。

阅读全文 »

在JavaEE应用中,使用ORM操作数据库虽然简单快捷(参考“高效使用JavaEE ORM”),但是毕竟是对JDBC的封装,很多时候,ORM还是不能满足我们的需求,主要是两个问题:

1. 速度不如JDBC,毕竟是封装JDBC,有额外的开销;

2. ORM提供的xQL很多时候无法满足需求,还需要数据库相关的SQL,这时,必须使用JDBC。

使用JDBC虽然麻烦点,但是,按照软件设计的思想,一步一步封装必要的代码,还是可以做到性能与开发效率并存。

首先,要坚决避免的就是不断重复编写try... catch... finally...。对于查询、更新、插入和删除操作,每一种操作只允许编写一次try... catch... finally...。如何实现?有两个方法。

第一个办法是找一个现成的封装了这些JDBC操作的框架,最好的方案当然就是Spring的JDBC框架了,顺便可以参考JdbcTemplate的源码,以便提升自己的JavaEE功力。

如果不使用Spring,那就采用第二个办法,自己造轮子,封装一个JDBC框架。

很多人反对自己造轮子,原因不外乎费事。不过很多时候,造轮子并不麻烦,而且可以满足特定的需求。今天要造的轮子就是一个封装JDBC的框架:Express-Persist

Express-Persist是ExpressMe的持久化子项目,目标是封装JDBC并提供简单的数据库操作接口。

为什么不使用Spring JDBC呢?主要原因只有一个:Spring的JDBC目前还是1.4兼容的,不支持1.5的泛型。Express-Persist要提供的接口除了基本的数据库操作外,还要实现:

1. 简单的ORM映射,注意是简单的,没有Hibernate那样完整而强悍,本质上就是把ResultSet的每一条记录变成一个JavaBean,可以参考Spring JDBC的RowMapper,实现非常容易;

2. 充分利用Java 5泛型支持,都是类型安全的参数和返回值,不用做强制转化;

3. 利用Java 5的注解(Annotation)把SQL标记在接口方法上,比如:

@Query("select * from User where u.id=:id")
public User get(String id);

4. 最后,最重要的,只编写接口,没有实现类!

没有实现类,那JDBC代码写在哪?当然由Express-Persist框架自动生成了。如何自动生成?运行一个命令自动生成Java类?在“高效使用JavaEE ORM”一文中我们已经对JDO的这种静态增强方式表示了强烈的鄙视和唾弃,因此绝不可重蹈覆辙。Express-Persist会在启动时根据接口动态创建出类,不过我们不采用Hibernate使用的CGLIB库,而是直接通过JDK的动态代理功能实现动态类。

如何绑定SQL参数

DAO接口的方法参数要自动绑定到SQL参数中,由于方法参数的顺序与SQL参数的顺序可能不一致,因此,只能使用命名参数来绑定,即:SQL参数定义为:xxx,对应的方法参数用@Param("xxx")标记。

当SQL参数很多的时候(尤其是INSERT语句),方法参数也非常多,调用起来非常不方便,比如:

@Update("insert into User values(:id, :emai

阅读全文 »

虽然Java领域有无数的ORM框架,如HibernateiBatis,TopLink,JDO,JPA……但是这些ORM框架基本上大同小异。很多初学者对JDBC的复杂性望而却步,就简单认为使用ORM就会省时省力,结果恰恰相反,任何好的框架都是给专家准备的,任何急功近利试图偷懒的方法往往适得其反。要正确使用ORM还真不是一件简单的事情。本文仅简单整理一下ORM的原理,基本用法,以及如何避免各种陷阱的基本编程原则。

ORM的原理

先说ORM的实现原理。其实,要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把JavaBean的属性值绑定到SQL语句中。但是,几乎所有的ORM都提供“按需读取”的功能,比如一个User有id,name,email和address这4个字段,但是address字段很少用,于是ORM只读取前3个字段,直到调用User的getAddress()方法时,才去数据库中读取address的值。这个功能显然不能通过User的get/set完成,因此,ORM需要采用某种方式生成一个User类的子类,并且覆写get/set方法,这样,才能在调用get方法时有机会从数据库中读取。类似的对User的修改检测也是这样实现的。

两种增强的方式

ORM为我们自己的JavaBean实现子类的方法很多,这个过程简单称之为“增强”,基本上有两种方法:Hibernate使用CGLIB在加载我们的User类时动态创建了一个子类,而JDO则要求编译完User类后再利用它提供的工具对User类进行改造,以便实现JDO需要的各种接口。请注意:就是这种极其变态的设计导致了使用JDO的极大困难,在我们编译完源码后,还需要额外执行一个增强命令,或者额外编写Ant任务,极大地影响了快速开发和单元测试,所以,凡是采用静态生成持久类的ORM,要在第一时间直接排除,切记!

理解持久和非持久状态

所有的ORM框架都有持久和非持久的概念。简单地说,当我们new一个User实例时,它是非持久对象,当我们调用ORM的save()之类的方法后,这个实例就变成持久对象了。当我们通过ORM从数据库读取到一个User对象时,这个对象是持久对象,当关闭当前的事务后,这个对象变成非持久对象。

虽然这个过程很容易理解,但是,难点在于当我们设计一个方法时,我们必须准确地知道当前操作的对象是持久对象还是非持久对象,否则,各种灵异事件会接踵而来,比如插入了重复记录等等。举例说明,当我们需要创建一个User对象时,save(User)方法必须传入非持久对象,当我们需要更新一个User对象时,update(User)方法必须传入一个持久对象,有些ORM比如Hibernate,为了方便用户,提供了saveOrUpdate()方法,自动判断是否是持久对象,是则更新,否则创建。我的建议是永远不要使用这些看上去很简单的方法,否则将很难判断ORM到底做了什么操作,也就很难追踪到逻辑错误。

正确使用CRUD

正因为我们需要时刻区分一个对象的持久化状态,所以,编写CRUD(Create,Retrieve,Update,Delete的缩写)要严格遵循以下原则:

Create:对于Create操作,传入的永远是非持久化对象,一旦调用了create方法,就变成持久化对象;

Retrieve:所有通过ORM从数据库读取的对象都是持久化对象,直到当前会话结束;

阅读全文 »

Windows虽然有简单易用的特点,不过,作为一名专业的软件开发人员,使用Linux作为开发平台,还是有很大的优越性的。

首先,Windows下的软件大多是收费的,虽然网上的破解也不少,不过,在公司使用说不定哪天就有麻烦了,而Linux下的软件基本上都是免费而且开源的,虽然公开源代码对我等只用不改造的用户来说意义不大,不过,免费却是实实在在的。

其次,作为一名软件开发人员,许多服务器软件只能在Linux下运行,有的虽然已经移植到了Windows上,运行效率和稳定性却要大打折扣。

不过,和Windows用户的担心一样,使用Linux,如果不能听MP3,不能看大片,仅仅在Linux上工作也不太爽,毕竟要劳逸结合嘛。好在Linux下的多媒体软件已经今非昔比了,今天,我们就一步一步打造一个工作+娱乐一体的Linux环境。

选择什么Linux?

Linux发行版众多,有商业公司支持的,也有开源社区支持的,不同的Linux发行版侧重也不同,有的Linux比如Gentoo,完全面向Linux发烧级用户的,从源代码编译开始。对于普通开发者而言,入门容易,安装软件快捷简便是最重要的两点。我的选择是Debian Linux,最新版本是5。相对于其他Linux版本,Debian的最大的优势就是软件众多,安装极为简单。有许多用户可能用过或听说过Red Hat的RPM软件包,不过,和Debian的DEB相比,RPM就差远了!

另外,Debian是一个社区维护的Linux发行版,和倾向于提供傻瓜式操作的Ubuntu相比,Debian显得更加“专业”一点,动手能力要求更高一点,便于和普通的Windows用户拉开更大的差距。

安装Debian Linux

闲话少说,要安装Debian Linux,先去Debian官方网站下载刻盘,根据计算机类型选相应的ISO,通常是i386,用AMD64处理器的可以选amd64,建议以HTTP/FTP方式下载第一张ISO光盘,比如i386对应的CD:

http://cdimage.debian.org/debian-cd/5.0.2a/i386/iso-cd/

选择netinst方式的ISO虽然下载较快,但是安装过程中需要联网,毕竟麻烦。

下载后刻盘,从光盘启动,安装过程很简单,主要是分区要注意,最好手动分区,然后把引导区安装到MBR上,Debian会自动发现已安装的Windows,双系统启动没有问题。

安装时会要求输入APT源,就是将来安装软件的下载地址了,我通常选择ftp.us.debian.org,速度那是非常地快。

安装过程中可以安装桌面,也可以不装。如果没有安装,用root登录后手动安装:

# apt-get install gnome

然后桌面就搞定了。

中文支持

使用Linux的第一个大问题就是要搞定中文。虽然Linux实际上完美支持各种语言,不过还是要稍微配置一下。在Debian中配置中文是相对简单的,运行命令

# dpkg-reconfigure locales

把以下的编码选中:

  • en_US.UTF-8 UTF-8
  • zh_CN GB2312
  • zh_CN.GB18030 GB18030
  • zh_CN.GBK GBK
  • zh_CN.UTF-8 UTF-8

也可以顺便把BIG5编码选上:

  • zh_TW BIG5
  • zh_TW.UTF-8 UTF-8

下一步是安装中文字体。Linux自带几种中文字体,输入以下命令安装Debian Linux默认的中文字体:

# apt-get install ttf-arphi

阅读全文 »

安装了Windows 7 Ultimate英文正式版后(注意是英文版!!!),发现很多中文软件显示为乱码,例如招商银行个人专业版,联系了他们的客服后,非常肯定地告诉我,目前专业版不支持Windows 7,!!! -_- !!!

然后在Google中搜索Windows 7乱码问题,发现好多网站把一篇改注册表的文章转来转去,结果我也被严重误导了!改完重启一点作用没有,最后终于找到简单而正确的方法:

打开Control Panel,Clock, Language and Region,选择Administrative:

选择Change system locale...按钮,把当前语言改为中文,重启即可:

所有中文软件均运行正常!

阅读全文 »

本文最早发表于IBM developerWorks:

http://www.ibm.com/developerworks/cn/java/j-lo-jeeflex/

传统的Java EE应用程序通常使用某种MVC框架(例如,Struts)作为前端用户界面,随着Flex的兴起,基于RIA的客户端能够给用户带来更酷的界面,更短的响应时间,以及更接近于桌面应用程序的体验。本文将讲述如何将Flex集成至一个现有的Java EE应用程序中,以及如何应用最佳实践高效率地并行开发Java EE和Flex。

开发环境

本文的开发环境为Windows 7 Ultimate,Eclipse 3.4,Flex Builder 3。Java EE服务器使用Resin 3.2,当然,您也可以使用Tomcat等其他Java EE服务器。

现有的Java EE应用

假定我们已经拥有了一个管理雇员信息的Java EE应用,名为EmployeeMgmt-Server,结构如图所示:

这是一个典型的Java EE应用,使用了流行的Spring框架。为了简化数据库操作,我们使用了内存数据库HSQLDB。对这个简单的应用,省略了DAO,直接在Façade中通过Spring的JdbcTemplate操作数据库。最后,EmployeeMgmt应用通过Servlet和JSP页面为用户提供前端界面:

该界面为传统的HTML页面,用户每次点击某个链接都需要刷新页面。由于Employee Management系统更接近于传统的桌面应用程序,因此,用Flex重新编写界面会带来更好的用户体验。

集成BlazeDS

如何将Flex集成至该Java EE应用呢?现在,我们希望用Flex替换掉原有的Servlet和JSP页面,就需要让Flex和Java EE后端通信。Flex支持多种远程调用方式,包括HTTP,Web Services和AMF。不过,针对Java EE开发的服务器端应用,可以通过集成BlazeDS,充分利用AMF协议并能轻易与Flex前端交换数据,这种方式是JavaEE应用程序集成Flex的首选。

BlazeDS是Adobe LifeCycle Data Services的开源版本,遵循LGPL v3授权,可以免费使用。BlazeDS为Flex提供了基于AMF二进制协议的远程调用支持,其作用相当于Java的RMI。有了BlazeDS,通过简单的配置,一个Java接口就可以作为服务暴露给Flex,供其远程调用。

尽管现有的EmployeeMgmt应用程序已经有了Façade接口,但这个接口是暴露给Servlet使用的,最好能再为Flex定义另一个接口FlexService,并隐藏Java语言的特定对象:

public interface FlexService {
    Employee createEmployee(String name, String title,

阅读全文 »

用WTK 2.5开发MIDP应用时,自己写了个冒泡排序,模拟器运行正常,真机上报NoClassDefFoundError,原来是没有java.lang.Comparable这个接口,但是WTK编译居然通过了!校验器也没验出任何问题。

解决办法:

自定义一个IsComparable接口,将要排序的类实现此接口:

public static void sort(Vector v) {
    int size = v.size();
    for (int i=0; i<size; i++) {
        for (int j=i+1; j<size; j++) {
            IsComparable o1 = (IsComparable) v.elementAt(i);
            Object o2 = v.elementAt(j);
            if (o1.compareTo(o2) > 0) {
                // swap:
                v.setElementAt(o1, j);
                v.setElementAt(o2, i);
            }
        }
    }
}

阅读全文 »

快速排序是一种基于分治的算法,其基本思想是将一个大数组按照一个基准数分成左右两份,左边的部份都不大于基准数,右边的部分都不小于基准数。然后,对这两份再分别应用快速排序,直到分到只剩2个数为止。

快速排序在通常情况下是最快的排序算法,以下是用Python实现的一个例子:

'''
qsort.py

Quick sort

Created on Jun 18, 2009

@author: Liao
'''

from random import Random

def quick_sort(arr):
    if len(arr) > 1:
        qsort(arr, 0, len(arr) - 1)

def qsort(arr, start, end):
    base = arr[start]
    pl = start
    pr = end
    while pl < pr:
        while pl < pr and arr[pr] >= base:
            pr -= 1
        if pl == pr:
            break
        else:
            arr[pl], arr[pr] = arr[pr], arr[pl]

        while pl < pr and arr[pl] <= base:
            pl += 1
        if pl == pr:
            break
        else:
            arr[pl], arr[pr] = arr[pr], arr[pl]
    # now pl == pr
    if pl - 1 > start:
        qsort(arr, start, pl - 1)
    if pr + 1 < end:
        qsort(arr, pr + 1, end)

r = Random()
a = []
for i in range(20):
    a.append(r.randint(0, 100))

print a
quick_sort(a)
print a

快速排序是一种不稳定排序,而冒泡排序则是稳定排序。

稳定排序是指如果排序前有两个相同的数,比如对[a=10, b=10, c=2]排序,a和b相等,排序前a在b的前面,稳定排序后结果为[c, a, b],a仍然在b的前面,而不稳定排序则不保证相等的两个数位置不会交换,排序结果可能变为[c, b, a]。

阅读全文 »

中华诗词(http://www.shi-ci.com)免费提供上至诗经下至当代共计6万余首诗词,供广大诗词爱好者在线阅读和搜索。此外,中华诗词还提供了一段免费的代码让您的网站或博客能每天都自动显示不同的诗词,只需在网站或博客的合适位置插入以下JavaScript代码即可:

<script language="javascript" charset="UTF-8" src="http://www.shi-ci.com/embeded.do"></script>

此外,如果您的博客是sohu博客,可以更方便地向您的博客添加“中华诗词”模块,在您的sohu博客中每天自动显示一首中华诗词:

地址:http://ow.blog.sohu.com/widget/771

或者,您还可以添加“唐诗300首”模块,在您的sohu博客中每天自动显示唐诗300首中的一首:

地址:http://ow.blog.sohu.com/widget/782

或者,您还可以添加“毛主席诗词”模块,在您的sohu博客中每天自动显示一首毛主席诗词:

地址:http://ow.blog.sohu.com/widget/785

添加后的博客效果如下:

根据您的sohu博客主题,显示效果会有所不同。

阅读全文 »

在Eclipse中,只需随时按住Ctrl并点击某个类名或方法名,即可跳转到相应的代码中。然而,如果引用一个开源的jar包,则会直 接打开其class的二进制码,这对于调试或研究代码内部流程颇为不便,尽管可以在Build Path中为每个jar指定源代码位置,但这样一来,对于同一个jar(例如spring.jar),每个工程都要指定,比较麻烦。

另一种更简单的方式是直接用WinZip或WinRAR之类的工具解开jar,再把源码也放进去,注意路径要正确,同一个Xxx.class和 Xxx.java应该在同一目录下,再用zip打包成jar包(jar格式其实就是zip格式),以后无论在哪个工程引用该jar包,Eclipse都可以直接从jar包中读出其对应的源代码,不必在Build Path中配置源代码位置,对于开源组件来说,大大方便了代码的跟踪和测试。

阅读全文 »

通过Google“博客搜索”Ping API, 用户可以程序化的方式将博客内容的更新通知给Google“博客搜索”引擎。这对于经常更新博客内容的用户尤其有用。博客服务提供商的管理人员也可以利用此API将其平台上的博客内容变化向Google通告,以便Google“博客搜索”及时抓取来自这一服务提供商的最新内容。

Google“博客搜索”支持XML-RPC客户端和REST客户端。使用XML-RPC时,需要构造一个XML,然后将其POST到Google的指定地址,比较麻烦,而REST则既简单又方便。

使用REST时,只需构造一个如下URL:

http://blogsearch.google.com/ping?name=xxx&url=xxx&changesURL=xxx

然后以GET发送,成功后会返回字符串“Thanks for the ping.”。

Google会根据url参数抓取blog页面并在最短的时间内索引。

阅读全文 »

Jetty是一个优秀的Web服务器,最大的特点是可嵌入应用程序,因此作为调试服务器非常方便,就像跟踪普通的main()方法一样可以在Eclipse中直接调试Web应用而无需远程连接。但是使用Jetty发现一个问题,即Windows上启动后Jetty会锁定已访问的静态文件,如HTML,CSS等,这给页面设计带来了不便。

其实Jetty官方站点对此问题已有回答,锁定文件据说是为了提高性能,但我觉得缓存也不一定需要长时间锁定文件:http://docs.codehaus.org/display/JETTY/Files+locked+on+Windows

其实可以修改Jetty默认的配置文件,在jetty-6.1.5.jar中找到org/mortbay/jetty/webapp/webdefault.xml,搜索useFileMappedBuffer:

<init-param>
    <param-name>useFileMappedBuffer</param-name>
    <param-value>true</param-value>
</init-param>

将param-value从true改为false即可。可以直接修改jar包内的这个文件,但是修改发行包毕竟不好,可以将此文件复制一份,在启动Jetty时用自己的这个webdefault.xml覆盖Jetty的设置即可。加上:

WebAppContext webapp = new WebAppContext();
webapp.setDefaultsDescriptor("./webdefault.xml");

重新启动后问题解决。

阅读全文 »

在开发web应用时,如果通过weblogic的控制台部署war包,则weblogic默认在运行期不会解压war,这对于调试jsp颇为不便。其实,只需一个简单的设置就可以强迫weblogic解开war,并且编辑jsp后weblogic会重新加载,方便调试。

以8.1 sp4为例,打开bea/user_projects/domains/<my-domain>/config.xml,找到相应的war包:

<Application Name="test"
    Path="C:\java\bea\user_projects\domains\mydomain\applications\test.war"
    StagingMode="nostage" TwoPhase="true">

将StagingMode由nostage改为stage,重启weblogic即可。解压后的目录在myserver目录下。

需要注意的是,一旦war包需要重新部署,除了更新war包外,还要删除bea/user_projects/domains/<my-domain>/myserver目录下的.wlnotdelete和stage目录,以便强迫weblogic重新解开最新的war包,否则将继续使用原来已解压的目录。

阅读全文 »

Subversion是新一代的开源版本控制系统,和CVS相比,Subversion最大的特点是支持事务,可以确保一个提交是原子操作。此外,Subversion还支持更多的协议,包括HTTP访问。在Eclipse中,使用Subverison和CVS一样简单,只需安装Subclipse插件就可以了。

本文以Eclipse 3.3为例,安装Subclipse非常容易,打开Eclipse,选择菜单Help->Software Updates->Find and Install…,在弹出的对话框中选择“Search for new features to install”,然后点击“New Remote Site…”,填入Subclipse的在线安装的URL:

按照提示安装完毕后,我们就可以打开Subversion的资源库了。选择Eclipse菜单Window->Show View->Other…,选择SVN->SVN Repository,然后添加一个新的资源库,例如http://livebookstore.googlecode.com/svn/trunk

添加完毕后,即可直接浏览SVN库的目录结构,然后通过右键菜单Checkout…检出为一个工程:

Eclipse提示将目录检出为一个工程:

点击Finish即完成。

阅读全文 »

线程的创建和启动

Java语言已经内置了多线程支持,所有实现Runnable接口的类都可被启动一个新线程,新线程会执行该实例的run()方法,当run()方法执行完毕后,线程就结束了。一旦一个线程执行完毕,这个实例就不能再重新启动,只能重新生成一个新实例,再启动一个新线程。

Thread类是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法:

Thread t = new Thread();
t.start();

start()方法是一个native方法,它将启动一个新线程,并执行run()方法。Thread类默认的run()方法什么也不做就退出了。注意:直接调用run()方法并不会启动一个新线程,它和调用一个普通的java方法没有什么区别。

因此,有两个方法可以实现自己的线程:

方法1:自己的类extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class MyThread extends Thread {
    public run() {
        System.out.println("MyThread.run()");
    }
}

在合适的地方启动线程:new MyThread().start();

方法2:如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口:

public class MyThread extends OtherClass implements Runnable {
    public run() {
        System.out.println("MyThread.run()");
    }
}

为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myt = new MyThread();
Thread t = new Thread(myt);
t.start();

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {
    if (target != null) {
        target.run();
    }
}

线程还有一些Name, ThreadGroup, isDaemon等设置,由于和线程设计模式关联很少,这里就不多说了。

线程的同步

由于同一进程内的多个线程共享内存空间,在Java中,就是共享实例,当多个线程试图同时修改某个实例的内容时,就会造成冲突,因此,线程必须实现共享互斥,使多线程同步。

最简单的同步是将一个方法标记为synchronized,对同一个实例来说,任一时刻只能有一个synchronized方法在执行。当一个方法正在执行某个synchronized方法时,其他线程如果想要执行这个实例的任意一个synchronized方法,都必须等待当前执行 synchronized方法的线程退出此方法后,才能依次执行。

但是,非synchronized方法不受影响,不管当前有没有执行synchronized方法,非synchronized方法都可以被多个线程同时执行。

此外,必须注意,只有同一实例的synchronized方法同一时间只能被一个线程执行,不同实例的synchronized方法是可以并发的。例如,class A定义了synchronized方法sync(),则不同实例a1.sync()和a2.sync()可以同时由两个线程来执行。

Java锁机制

多线程

阅读全文 »

概述

J2ME是Sun发布的运行在小型设备上的微型版Java的一系列标准,其中,最重要的标准便是运行在手机上的MIDP应用程序了。到目前为止,MIDP一共发布了两个版本:MIDP 1.0(JSR37)和MIDP 2.0(JSR118),2.0版本可以向后兼容1.0版本,也就是说,支持MIDP 2.0的手机可以同时运行MIDP 1.0和MIDP 2.0的应用程序。本文将重点讲述开发MIDP应用程序时非常有用的一些设计模式,开发技巧以及如何调试、优化J2ME应用程序。

本文将讨论J2ME开发的以下内容:

  • 如何自动适应用户手机配置
  • 如何在屏幕间导航
  • 如何实现一个灵活的联网应用
  • 如何实现一个灵活的RMS应用
  • 如何调试并优化J2ME程序

避免OutOfMemoryError

对于MIDP应用程序来说,由于手机设备上的资源非常有限,较弱的CPU计算能力,有限的内存(从几十KB到几百KB,虽然少数高端手机拥有超过1M的动态内存),很小的屏幕尺寸,因此,为了让一个MIDP应用程序能够不加改动地在多种不同手机上运行,程序必须有能力根据系统配置自动调整运行时的参数。比如,对于内存非常小的手机,如果从网络下载一幅较大的图像,需要分配巨大的缓冲区,就可能导致OutOfMemoryError错误,使应用程序直接终止,这会使用户感到不知所措,或者丢失用户的重要数据。因此,在试图分配一块大内存之前,首先使用System.gc()尝试让垃圾收集器释放无用对象占用的内存,然后,使用Runtime.getRuntime().freeMemory()方法获得可用的内存空间。如果可用空间太小,给用户一个“内存不足,无法完成操作”的Alert提示,从而尽可能地避免OutOfMemoryError错误。

// 示例代码:
System.gc();
int max_size = 102400; // 100KB
int free_size = (int)Runtime.getRuntime().freeMemory();
if(max_size>free_size*2/3) {
    // TODO: Alert!
}
else {
    byte[] buffer = new byte[max_size];
    // TODO: Download image...
}

减少图片以减小JAR文件大小

许多手机会因为JAR文件太大而无法运行MIDP应用程序,而减小JAR文件尺寸的有效方法之一是减少不必要的图片,例如,启动时的LOGO图片可以用文字来代替,列表项可以只显示文字而不显示图片。为了能适应不同配置的手机,我们的代码就应该编写得更加灵活。例如,从JAR包中加载图片时:

Image image = null;
try {
    image = Image.createImage("/logo.png");
}
catch(Exception ioe) {}
if(image==null) {
    g.setColor(0);
    g.drawString("info", getWidth()/2, getHeight()/2, Graphics.HCENTER|Graphics.BASELINE);
}
else {
    g.drawImage(image, getWidth()/2, getHeight()/2, Graphics.HCENTER|Graphics.VCENTER);
}

如果加载失败,程序会以文字方式显示,这样,对于低配置的手机,只需要把美化界面的图片删除掉,再重新打包即可得到一个可发布的尺寸较小的JAR包,同时应用程序的代码并没有改动。

类似的,在加载List之类的UI组件时:

Image image = null;
try {
    image = Image.createImage("/logo.png&

阅读全文 »

本文介绍如何在Resin中调试Web应用程序。测试环境为Windows 7 / Resin 3.2 / Eclipse 3.3

在Resin的resin.conf中找到<server-default>并添加加以下参数:

<resin xmlns="http://caucho.com/ns/resin"
       xmlns:resin="http://caucho.com/ns/resin/core">
    <log name="" level="info" path="stdout:"/>
    <cluster id="">
        <root-directory>.</root-directory>
        <server-default>
            <http server-id="" host="*" port="80"/>
            <jvm-arg>-Xmx128m</jvm-arg>
            <jvm-arg>-Xss1m</jvm-arg>
            <jvm-arg>-Xdebug</jvm-arg>
            <jvm-arg>-Xnoagent</jvm-arg>
            <jvm-arg>-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=12345</jvm-arg>

启动Resin后,打开Eclipse项目,选择 Run -> Debug... -> Remote Java Application -> New

新建一个Remote Java Application,填入Host: 127.0.0.1, Port: 12345, 注意这个Port就是Resin启动的address参数。

现在,就可以利用Eclipse强大而方便的调试界面对Web App断点调试并跟踪了!

阅读全文 »

Spring 2.5提供了自动在当前ClassPath搜索被标注有特定注解的类,这个特性非常有用,跟踪了一下源码,发现其实核心代码就是利用ClassLoader的方法:

public Enumeration<URL> getResources(String name)

于是自己动手,也写了一个能在ClassPath下搜索特定类的Scanner:

package com.liaoxuefeng.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassPathScanner {

    private static final String PROTOCOL_FILE = "file";
    private static final String PROTOCOL_JAR = "jar";

    private static final String PREFIX_FILE = "file:";

    private static final String JAR_URL_SEPERATOR = "!/";
    private static final String CLASS_FILE = ".class";

    private final String packageName;
    private final ClassFilter filter;

    public ClassPathScanner(String packageName) {
        this(packageName, null);
    }

    public ClassPathScanner(String packageName, ClassFilter filter) {
        this.packageName = packageName;
        this.filter = filter;
    }

    public List<Class<?>> scan() {
        List<Class<?>> list = new ArrayList<Class<?>>();
        Enumeration<URL> en = null;
        try {
            en = getClass().getClassLoader().getResources(dotToPath(packageName));
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        while (en.hasMoreElements()) {
            URL url = en.nextElement();
            if (PROTOCOL_FILE.equals(url.getProtocol())) {
                File root = new File(url.getFile());
                findInDirectory(list, root, root, packageName);
            }
            else if (PROTOCOL_JAR.equals(url.getProtocol())) {
                findInJar(list, getJarFile(url), packageName);
            }
    

阅读全文 »

在Spring framework中使用Velocity是非常方便的,只需在spring配置文件中申明:

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
</bean>

即可在spring mvc框架中直接返回new ModelAndView("velocity模板", map),但是中文一直为乱码。

为了解决中文问题,首先,考虑到国际化,将所有web页面都用UTF-8编码,然后在/WEB-INF/velocity.properties文件中覆盖velocity的默认编码ISO-8859-1:

input.encoding = UTF-8
output.encoding = UTF-8

最后,在spring配置文件中设置:

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="contentType"><value>text/html;charset=UTF-8</value></property>
</bean>

启动Web服务器,可以看到中文显示正常,输入也正常。你也可以使用GBK,但是不利于多语言移植。

附:Velocity简介

Velocity是apache的一个开放源代码项目,它实现了可替代JSP的View层,并且以很直观的方式来编写View。编写一个Velocity View就和编写一个纯HTML文件没有什么区别,完全可以在Dreamwaver中可视化编写,只需将数据部分用$xxx替换即可。

例如,要显示一个用户信息,Model传入的是一个Map,包含"username","email"和"address"三个Key:

<html>
<head>
    <title>User: $username</title>
</head>
<body>
    <p>Email: $email</p>
    <p>Address: $address</p>
</body>
</html>

这样你就完全不必担心嵌套的JSP标签在Dreamwaver中造成的语法错误。

阅读全文 »

上午通过3个小时奋战,终于成功在Debian Etch上通过xen成功安装Windows XP!

这是安装界面:

安装成功后进入Windows的界面:

要在Linux上按装Windows,除了VMWare这种通过软件全虚拟的方式,还可以通过xen实现硬件支持的虚拟。现在,各主要发行版都内置了xen,原本打算在Redhat Enterprise 5上试试,不过配置太麻烦,最好是通过安装包一次搞定,最终决定用Debian Etch,几条apt-get就搞定了,非常方便。

安装Windows的必须条件:

CPU必须支持Intel VT或AMD PT虚拟化技术,没有的话就不用考虑了。要检查CPU是否支持,用命令:

grep vmx /proc/cpuinfo

如果是AMD的CPU用:

grep svm /proc/cpuinfo

我的硬件配置:Intel Core2 Duo T7200 2GHz,2G RAM

先安装好Debian Etch和Gnome桌面,第一步是安装xen支持的内核,注意版本要和当前Linux内核一致。用apt-get安装:

  • xen-linux-system-2.6.18-5-xen-686
  • xen-tools
  • libc6-xen
  • xen-ioemu-3.0.3-1
  • xen-hypervisor-3.0.3-1-i386-pae
  • bridge-utils

安装完毕后重启系统,在GRUB就可以看到带xen的内核,启动后发现无线网卡不工作,需要再安装一个ipw3945-modules-2.6.18-5-686,重启后网卡工作正常。

第二步是安装准备,先创建一个4G的文件作为Windows的虚拟硬盘:

dd if=/dev/zero of=/home/xuefeng/xen/winxp/winxp.img bs=1M count=4096

准备好Windows XP的ISO文件,我放在/home/xuefeng/xen/winxp/winxp.iso。

编写配置文件/home/xuefeng/xen/winxp/winxp.cfg,以下是我的配置文件:

name='winxp'
kernel='/usr/lib/xen-3.0.3-1/boot/hvmloader'
device_model='/usr/lib/xen-3.0.3-1/bin/qemu-dm'
builder='hvm'
# 内存大小:
memory=1024
pae=1
# 配置一个硬盘和一个光盘:
disk=['file:/home/xuefeng/xen/winxp/winxp.img,ioemu:hda,w', 'file:/home/xuefeng/xen/winxp/winxp.iso,hdc:cdrom,r']
# 网络启动失败,暂时注释掉:
#vif=['type=ioemu,bridge=xenbr0']
# 先设置从d启动,等安装结束后改为c就可以直接从硬盘启动:
boot='d

阅读全文 »

安装Linux时,如果将GRUB安装在主引导扇区,则可以正常引导Linux和Windows XP,但同时也破坏了原Windows的主引导信息。当删除Linux后,GRUB无法正常引导,因为GRUB需要读取Linux的/boot信息,但已不存在。此时,可以通过Windows XP的安装光盘恢复。

用XP安装盘启动后,选择R进入修复模式,输入管理员口令后,可以使用命令fixmbr修复主引导扇区的信息,然后重启,自动进入到Windows XP的启动菜单。

此法同样可用于Windows 2000 / 2003 / Vista等。

阅读全文 »

本文最早发表于IBM developerWorks:

http://www.ibm.com/developerworks/cn/web/wa-lo-json/

JSON即JavaScript Object Notation,它是一种轻量级的数据交换格式,非常适合服务器与JavaScript的交互。

尽管有许多宣传关于XML如何拥有跨平台,跨语言的优势,然而,除非应用于Web Services,否则,在普通的Web应用中,开发者经常为XML的解析伤透了脑筋,无论是服务器端生成或处理XML,还是客户端用JavaScript解析XML,都常常导致复杂的代码,极低的开发效率。实际上,对于大多数Web应用来说,他们根本不需要复杂的XML来传输数据,XML的扩展性很少具有优势,许多AJAX应用甚至直接返回HTML片段来构建动态Web页面。和返回XML并解析它相比,返回HTML片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。

现在,JSON为Web应用开发者提供了另一种数据交换格式。让我们来看看JSON到底是什么,同XML或HTML片段相比,JSON提供了更好的简单性和灵活性。

JSON数据格式解析

和XML一样,JSON也是基于纯文本的数据格式。由于JSON天生是为JavaScript准备的,因此,JSON的数据格式非常简单,你可以用JSON传输一个简单的String,Number,Boolean,也可以传输一个数组,或者一个复杂的Object对象。

String,Number和Boolean用JSON表示非常简单。例如,用JSON表示一个简单的String“abc”,其格式为:

"abc"

除了字符",\,/和一些控制符(\b,\f,\n,\r,\t)需要编码外,其他Unicode字符可以直接输出。下图是一个String的完整表示结构:

一个Number可以根据整型或浮点数表示如下:

这与绝大多数编程语言的表示方法一致,例如:

12345(整数)
-3.9e10(浮点数)

Boolean类型表示为true或false。此外,JavaScript中的null被表示为null,注意,true、false和null都没有双引号,否则将被视为一个String。

JSON还可以表示一个数组对象,使用[]包含所有元素,每个元素用逗号分隔,元素可以是任意的Value,例如,以下数组包含了一个String,Number,Boolean和一个null:

["abc",12345,false,null]

Object对象在JSON中是用{}包含一系列无序的Key-Value键值对表示的,实际上此处的Object相当于Java中的Map<String, Object>,而不是Java的Class。注意Key只能用String表示。

例如,一个Address对象包含如下Key-Value:

city:Beijing
street:Chaoyang Road
postcode:100025(整数)

用JSON表示如下:

{
    "city" : "Beijing",
    "street"

阅读全文 »

本文最早发表于BEA dev2dev

——针对缓慢变化的小数据的缓存实现模型

在JavaEEdev站点(http://www.javaeedev.com)的设计中,有几类数据是极少变化的,如ArticleCategory(文档分类),ResourceCategory(资源分类),Board(论坛版面)。在对应的DAO实现中,总是一次性取出所有的数据,例如:

List<ArticleCategory> getArticleCategories();

此类数据的特点是:数据量很小,读取非常频繁,变化却极慢(几天甚至几十天才变化一次),如果每次通过DAO从数据库获取数据,则增加了数据库服务器的压力。为了在不影响整个系统结构的情况下透明地缓存这些数据,可以在Facade一层通过Proxy模式配合ReadWriteLock实现缓存,而客户端和后台的DAO数据访问对象都不受影响:

首先,现有的中间层是由Facade接口和一个FacadeImpl具体实现构成的。对ArticleCategory的相关操作在FacadeImpl中实现如下:

public class FacadeImpl implements Facade {
    protected CategoryDao categoryDao;
    public void setCategoryDao(CategoryDao categoryDao) {
        this.categoryDao = categoryDao;
    }
    // 读操作:
    public ArticleCategory queryArticleCategory(Serializable id) {
        return categoryDao.queryArticleCategory(id);
    }
    // 读操作:
    public List<ArticleCategory> queryArticleCategories() {
        return categoryDao.queryArticleCategories();
    }
    // 写操作:
    public void createArticleCategory(ArticleCategory category) {
        categoryDao.create(category);
    }
    // 写操作:
    public void deleteArticleCategory(ArticleCategory category) {
        categoryDao.delete(category);
    }
    // 写操作:
    public void updateArticleCategory(ArticleCategory category) {
        categoryDao.update(category);
    }
    // 其他方法省略...
}

设计代理类FacadeCacheProxy,让其实现缓存ArticleCategory的功能:

public class FacadeCacheProxy implements Facade {
    private Facade target;
    public void setFacadeTarget(Facade target) {
        this.target = target;
    }

    // 定义缓存对象:
    private FullCache<ArticleCategory> cache = new FullCache<ArticleCategory>(

阅读全文 »

Spring是一个非常优秀的轻量级框架,通过Spring的IoC容器,我们的关注点便放到了需要实现的业务逻辑上。对AOP的支持则能让我们动态增强业务方法。编写普通的业务逻辑Bean是非常容易而且易于测试的,因为它能脱离J2EE容器(如Servlet,JSP环境)单独进行单元测试。最后的一步便是在Spring框架中将这些业务Bean以XML配置文件的方式组织起来,它们就按照我们预定的目标正常工作了!非常容易!

本文将给出一个基本的Spring入门示例,并演示如何使用Spring的AOP将复杂的业务逻辑分离到每个方面中。

开发环境配置

首先,需要正确配置Java环境。推荐安装JDK1.4.2,并正确配置环境变量:

JAVA_HOME=(JDK安装目录)
CLASSPATH=.
Path=%JAVA_HOME%\bin;...

我们将使用免费的Eclipse 3.1作为IDE。新建一个Java Project,将Spring的发布包spring.jar以及commons-logging-1.0.4.jar复制到Project目录下,并在Project > Properties中配置好Java Build Path:

编写Bean接口及其实现

我们实现一个管理用户的业务Bean。首先定义一个ServiceBean接口,声明一些业务方法:

package com.crackj2ee.example.spring;

/**
 * Interface of service facade.
 * 
 * @author Xuefeng
 */
public interface ServiceBean {
    void addUser(String username, String password);
    void deleteUser(String username);
    boolean findUser(String username);
    String getPassword(String username);
}

然后在MyServiceBean中实现接口:

package com.crackj2ee.example.spring;

import java.util.*;

public class MyServiceBean implements ServiceBean {

    private String dir;
    private Map map = new HashMap();

    public void setUserDir(String dir) {
        this.dir = dir;
        System.out.println("Set user dir to: " + dir);
    }

    public void addUser(String username, String password) {
        if(!map.containsKey(username))
            map.put(username, password);
        else
            throw new RuntimeException("User already exist.");
    }

    public void deleteUser(String username) {
        if(map.remove(username)==null)
            throw new RuntimeException("User not exist.");
    }

    public boolean findUser(String username) {
        return map.cont

阅读全文 »

单元测试是XP极力推荐的测试驱动开发模式,是保证软件质量的重要方法。尽管如此,对许多类的单元测试仍然是极其困难的,例如,对数据库操作的类进行测试,如果不准备好数据库环境以及相关测试数据,是很难进行单元测试的;再例如,对需要运行在容器内的Servlet或EJB组件,脱离了容器也难于测试。

幸运的是,Mock Object可以用来模拟一些我们需要的类,这些对象被称之为模仿对象,在单元测试中它们特别有价值。

Mock Object用于模仿真实对象的方法调用,从而使得测试不需要真正的依赖对象。Mock Object只为某个特定的测试用例的场景提供刚好满足需要的最少功能。它们还可以模拟错误的条件,例如抛出指定的异常等。

目前,有许多可用的Mock类库可供我们选择。一些Mock库提供了常见的模仿对象,例如:HttpServletRequest,而另一些Mock库则提供了动态生成模仿对象的功能,本文将讨论使用EasyMock动态生成模仿对象以便应用于单元测试。

到目前为止,EasyMock提供了1.2版本和2.0版本,2.0版本仅支持Java SE 5.0,本例中,我们选择EasyMock 1.2 for Java 1.3版本进行测试,可以从http://www.easymock.org下载合适的版本。

我们首先来看一个用户验证的LoginServlet类:

package com.javaeedev.test.mock;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class LoginServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // check username & password:
        if("admin".equals(username) && "123456".equals(password)) {
            ServletContext context = getServletContext();
            RequestDispatcher dispatcher = context.getNamedDispatcher("dispatcher");
            dispatcher.forward(request, response);
        }
        else {
            throw new RuntimeException("Login failed.");
        }
    }
}

这个Servlet实现简单的用户验证的功能,若用户名和口令匹配“admin”和“123456”,则请求被转发到指定的dispatcher上,否则,直接抛出RuntimeException。

为了测试doPost()方法,我们需要模拟HttpServletRequest,ServletContext和RequestDispatcher对象,以便脱离J2EE容器来测试这个Servlet。我们建立TestCase,名为LoginServletTest:

public class LoginServletTest extends TestCase {
}

我们首先测试当用户名和口令验证失败的情形,演示如何使用EasyMock来模拟Ht

阅读全文 »

J2ME,即Java 2 Micro Edition,是SUN公司推出的在移动设备上运行的微型版Java平台,常见的移动设备有手机,PDA,电子词典,以及各式各样的信息终端如机顶盒等等。

由于移动终端的类型成千上万,而且计算能力差异非常大,不可能像桌面系统那样仅仅两三个版本的JVM即可满足Windows,Linux和Unix系统,因此,J2ME不是一个简单的微型版的JVM。为了满足千差万别的移动设备的需求,SUN定义了一系列的针对不同类型设备的规范,因此,J2ME平台便是由许多的规范组成的集合。

最重要的移动终端当然是手机了,因此,我们主要讨论手机相关的J2ME规范。

Configuration

SUN把不同的设备按照计算能力分为CLDC(Connected Limited Device Configuration)和CDC(Connected Device Configuration)两大类,这两个Configuration是针对设备软硬件环境严格定义的,比如CLDC1.0定义了内存大小为64-512k,任何设备如果支持CLDC1.0,就必须严格满足定义,不能有可选的或者含糊的功能。

CLDC1.0是针对计算能力非常有限的设备定义的,只支持整数运算,不支持浮点运算,早期的Java手机大部分都支持CLDC1.0,如Nokia 3650,Siemens 6688i。

CLDC1.1则增加了浮点运算,因此,在支持CLDC1.1的设备上,可以使用float和double类型的变量。现在的Java手机很多都能支持CLDC1.1,如Nokia 9500,Siemens S65。

CDC则是针对计算能力比较强的设备定义的,如PPC等,CDC平台的JVM基本上和桌面的JVM很接近了,只是可以使用的Package大大少于J2SE的包。支持CDC的非常高端的Java手机也会很快上市。

Profile

和Configuration相比,Profile更多是针对软件的定义,Profile定义有必须实现的,也有可选的功能,因此,Profile更灵活。

最重要的Profile当然是MIDP(Micro Information Device Profile),MIDP定义了能在Java手机上运行的Java程序的规范,符合MIDP规范的Java小程序被称为MIDlet,可以直接通过无线网络下载到手机并运行。

早期的MIDP1.0规范使我们能在手机上运行有UI界面的Java程序,但是MIDP1.0对游戏的支持不够,必须自己实现许多必须的代码,因此,MIDP2.0规范大大加强了对游戏开发的支持,使开发者编写更少的代码来创建游戏。

MIDP规范的图形界面基本上都是独立于J2SE的AWT和Swing组件,因为目前手机的计算能力还比较有限,但是,随着手机的CPU越来越快,使得AWT和Swing移植到手机上也将成为可能,因此,最新的PBP 1.0(Personal Basic Profile)和PP 1.0(Personal Profile)规范提供了部分AWT和Swing的支持,目前,部分高端PDA已经可以运行PBP和PP的Java程序了。可以预见,将来大部分的AWT和Swing组件都能移植到手机上。

前面已经说过,和Configuration相比,Profile有许多可选包,比较实用的Profile还有在JSR135定义的MMAPI(Mobile Media API),实现多媒体播放功能;在JSR184定义的M3G API(Mobile 3D Graphics API),实现3D功能;在JSR120定义的WMA(Wireless Message API)

阅读全文 »

Linux邮件服务器通常使用Sendmail,在网上Google了Sendmail的教程后,我决定知难而退,改用Postfix。

Postfix是用来替代Sendmail的,它的配置文件比Sendmail简单得多,配置相当容易。

在配置邮件服务器之前,先解释几个概念。

我们通常使用Email都很容易,但是Internet的邮件系统是通过几个复杂的部分连接而成的,对于最终用户而言,我们熟悉的Outlook,Foxmail等都是用来收信和发信的,称之为MUA:Mail User Agent,邮件用户代理。

MUA并非直接将邮件发送至收件人手中,而是通过MTA:Mail Transfer Agent,邮件传输代理代为传递,Sendmail和Postfix就是扮演MTA的角色。

一封邮件从MUA发出后,可能通过一个或多个MTA传递,最终到达MDA:Mail Delivery Agent,邮件投递代理,邮件到达MDA后,就存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为邮箱。

一旦邮件到达邮箱,就原地不动了,等用户再通过MUA将其取走,就是用Outlook,Foxmail等软件收信的过程。

所以一封邮件的流程是:

发件人:MUA --发送--> MTA -> 若干个MTA... -> MTA -> MDA <--收取-- MUA:收件人

MUA到MTA,以及MTA到MTA之间使用的协议就是SMTP协议,而收邮件时,MUA到MDA之间使用的协议最常用的是POP3或IMAP。

需要注意的是,专业邮件服务商都有大量的机器来为用户服务,所以通常MTA和MDA并不是同一台服务器,因此,在Outlook等软件里,我们需要分别填写SMTP发送服务器的地址和POP3接收服务器的地址。

下面开始用Postfix搭建Linux下的邮件服务器。目标服务器是RedHat Enterprise Linux 4,首先需要停止Sendmail:

# /etc/init.d/sendmail stop

并从启动组里移除:

# chkconfig sendmail off

然后,通过rpm包安装Postfix:

# rpm -Uvh postfix-2.x.x.xxx.rpm

Postfix只有一个/etc/postfix/main.cf需要修改,其他配置文件可以直接使用默认设置。

第一个需要修改的参数是myhostname,指向真正的域名,例如:

myhostname = mail.example.com

mydomain参数指向根域:

mydomain = example.com

myorigin和mydestination都可以指向mydomain:

myorigin = $mydomain
mydestination = $mydomain

Postfix默认只监听本地地址,如果要与外界通信,就需要监听网卡的所有IP:

inet_interfaces = all

Postfix默认将子网内的机器设置为可信任机器,如果只信任本机,就设置为host:

mynetworks_style = host

配置哪些地址的邮件能够被Postfix转发,当然是mydomain的才能转发,否则其他人都可以用这台邮件服务器转发垃圾邮件了:


            

阅读全文 »

J2ME并未提供类似J2SE的ResourceBuddle类,因此,为了实现国际化,我们只能根据类似的思路,自己实现一个ResourceBuddle。

首先,要准备一个资源文件,保存所有需要用到的语言字符串,在MIDP应用程序运行期,根据用户手机当前的语言设置自动读取相应的资源。

为了得到用户手机当前的区域设置,使用System.getProperty(“microedition.locale”),例如,返回zh-CN表示中国。

NetBeans的示例程序提供了一个Localization Support Example,如果安装了Mobility Pack,就可以直接打开这个示例工程,然后在此基础上开发自己的国际化程序。

实现国际化的主要类就是LocalizationSupport类,其思路就是读入messages.properties文件,然后根据locale和key取得对应的字符串。要添加一个新的Locale,只需选中messages.properties文件,右键点击,选择Add Locale:


 
即可添加新的语言。

阅读全文 »

本文最早发表于BEA dev2dev

单元测试作为保证软件质量及重构的基础,早已获得广大开发人员的认可。单元测试是一种细粒度的测试,越来越多的开发人员在提交功能模块时也同时提交相应的单元测试。对于大多数开发人员来讲,编写单元测试已经成为开发过程中必须的流程和最佳实践。

对普通的逻辑组件编写单元测试是一件容易的事情,由于逻辑组件通常只需要内存资源,因此,设置好输入输出即可编写有效的单元测试。对于稍微复杂一点的组件,例如Servlet,我们可以自行编写模拟对象,以便模拟HttpRequest和HttpResponse等对象,或者,使用EasyMock之类的动态模拟库,可以对任意接口实现相应的模拟对象,从而对依赖接口的组件进行有效的单元测试。

在J2EE开发中,对DAO组件编写单元测试往往是一件非常复杂的任务。和其他组件不通,DAO组件通常依赖于底层数据库,以及JDBC接口或者某个ORM框架(如Hibernate),对DAO组件的测试往往还需引入事务,这更增加了编写单元测试的复杂性。虽然使用EasyMock也可以模拟出任意的JDBC接口对象,或者ORM框架的主要接口,但其复杂性往往非常高,需要编写大量的模拟代码,且代码复用度很低,甚至不如直接在真实的数据库环境下测试。不过,使用真实数据库环境也有一个明显的弊端,我们需要准备数据库环境,准备初始数据,并且每次运行单元测试后,其数据库现有的数据将直接影响到下一次测试,难以实现“即时运行,反复运行”单元测试的良好实践。

本文针对DAO组件给出一种较为合适的单元测试的编写策略。在JavaEE开发网(http://www.javaeedev.com)的开发过程中,为了对DAO组件进行有效的单元测试,我们采用HSQLDB这一小巧的纯Java数据库作为测试时期的数据库环境,配合Ant,实现了自动生成数据库脚本,测试前自动初始化数据库,极大地简化了DAO组件的单元测试的编写。

在Java领域,JUnit作为第一个单元测试框架已经获得了最广泛的应用,无可争议地成为Java领域单元测试的标准框架。本文以最新的JUnit 4版本为例,演示如何创建对DAO组件的单元测试用例。

JavaEEdev的持久层使用Hibernate 3.2,底层数据库为MySQL。为了演示如何对DAO进行单元测试,我们将其简化为一个DAOTest工程:

由于将Hibernate的Transaction绑定在Thread上,因此,HibernateUtil类负责初始化SessionFactory以及获取当前的Session:

public class HibernateUtil {
    private static final SessionFactory sessionFactory;
    static {
        try {
            sessionFactory = new AnnotationConfiguration()
                                 .configure()
                                 .buildSessionFactory();
        }
        catch(Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
}

HibernateUtil还包含了一些辅助方法,如:

public static Object query(Class clazz, Serializable id);
public static void createEn

阅读全文 »

一:人们面临交替关系

“天下没有白吃的午餐。”为了得到一件东西,通常不得不放弃另一件东西。作出决策要求我们在一个目标与另一个目标之间有所取舍。

学生面临如何分配学习时间的交替,父母在购物,旅游和储蓄间面临交替,社会面临效率与平等的交替。

[名词解释]

效率:社会能从其稀缺资源中得到最多东西的特性。

平等:经济成果在社会成员中公平分配的特性。

二:某种东西的成本是为了得到它而放弃的东西

很多情况下,某种行动的成本并不像乍看时那么明显。

一种东西的机会成本是为了得到这种东西所放弃的东西。

考虑上大学的决策,成本不是住房和伙食,因为即使不上大学,也要租房和吃饭。最大的成本是时间,如果把上大学的时间用于工作,能赚到的工资就是上大学最大的单项成本。

因此,很多正值上大学年龄的职业运动员如果放弃运动而上大学,可能每年少赚几百万美元,因此他们上大学的成本比普通人高得多。这也是为什么许多职业运动员一定要退役后才去上大学的原因。

[名词解释]

机会成本:为了得到某种东西所必须放弃的东西。

三:理性人考虑边际量

许多决策涉及到对现有行动计划进行微小的增量调整,经济学家把这些调整称为边际变动。

假设一架200个座位的飞机飞一次的成本是10万美元,每个座位的成本是500美元,有人会说:票价决不应低于500美元。但是当飞机即将起飞时仍有10个空座,在登机口等退票的乘客愿意支付300美元买一张票,应该卖给他吗?当然应该。如果飞机有空座,多增加一位乘客的成本微乎其微。虽然一位乘客飞行的平均成本是500美元,但是边际成本仅仅是这位额外的乘客将消费的一包花生米和一杯饮料而已。

只有一种行动的边际收益大于边际成本,一个理性决策者才会采取这项行动。

[名词解释]

边际变动:对行动计划小的增量调整。

四:人们会对激励作出反应

由于人们通过比较成本与收益作出决策,所以,当成本或收益变动时,人们的行为也会改变。这就是说,人们会对激励作出反应。

例如,当苹果价格上涨时,人们就决定少吃苹果多吃梨,因为成本高了。同时,苹果园主决定雇用更多工人并多摘苹果,因为收益也高了。

通过立法要求汽车公司必须为汽车配备安全带带来的后果:安全带降低了驾驶员的车祸代价,因此驾驶员的反应是更快更放肆地开车,结果是更多的车祸次数,但是每次车祸中驾驶员死亡的概率降低了。但是对行人有不利的影响,他们遇上了更多的车祸但没有安全带。因此,汽车安全法导致的结果是:驾驶员死亡人数变动很小,行人的死亡人数增加了。

在分析任何一种决策时,不仅应该考虑直接影响,而且还应该考虑激励发生作用的间接影响。

五:贸易能使每个人状况更好

贸易使每个人可以专门从事自己最擅长的活动。通过与他人交易,人们可以按较低的价格买到各种各样的物品与劳务。

经济中每个家庭都与其他所有家庭竞争,但是把你的家庭与所有其他家庭隔绝开来并不会过得更好,如果是这样的话,你的家庭就必须自己种粮食,做衣服,盖房子。

国家和家庭一样也能从相互交易中获益。

六:市场通常是组织经济活动的一种好办法

在一个市场经济中,中央计划者的决策被千百万企业和家庭的决策所取代。这些企业和家庭在市场上相互交易,价格和个人利益引导着他们的决策,他们仿佛被一只“看不见的手”所指引,引起了合意的市场结果。

价格指引这些个别决策者在大多数情况下实现了整个社会福利最大化的结果。

[名词解释]

市场经济:当许多企业和家庭在物品与劳务市场上相互交易时通过他们的分散决策配置资源的经济。

七:政府有时可以改善市场结果

政府干预经济的原因有两类:促进效率和促进平等。

经济学家用市场失灵这个词来指市场本身不能有效配置资源的情况。

市场失灵的一个可能原因是外部性。污染的例子:如果一家化工厂不承担排放烟尘的全部成本,它就会大量排放。

另一个可能

阅读全文 »

本文最早发表于Sun技术社区:

http://gceclub.sun.com.cn/yuanchuang/week-14/iterator.html

java.util包中包含了一系列重要的集合类。本文将从分析源码入手,深入研究一个集合类的内部结构,以及遍历集合的迭代模式的源码实现内幕。

下面我们先简单讨论一个根接口Collection,然后分析一个抽象类AbstractList和它的对应Iterator接口,并仔细研究迭代子模式的实现原理。

本文讨论的源代码版本是JDK 1.4.2,因为JDK 1.5在java.util中使用了很多泛型代码,为了简化问题,所以我们还是讨论1.4版本的代码。

集合类的根接口Collection

Collection接口是所有集合类的根类型。它的一个主要的接口方法是:

boolean add(Object c)

add()方法将添加一个新元素。注意这个方法会返回一个boolean,但是返回值不是表示添加成功与否。仔细阅读doc可以看到,Collection规定:如果一个集合拒绝添加这个元素,无论任何原因,都必须抛出异常。这个返回值表示的意义是add()方法执行后,集合的内容是否改变了(就是元素有无数量,位置等变化),这是由具体类实现的。即:如果方法出错,总会抛出异常;返回值仅仅表示该方法执行后这个Collection的内容有无变化。

类似的还有:

boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);

Object[] toArray()方法很简单,把集合转换成数组返回。Object[] toArray(Object[] a)方法就有点复杂了,首先,返回的Object[]仍然是把集合的所有元素变成的数组,但是类型和参数a的类型是相同的,比如执行:

String[] o = (String[])c.toArray(new String[0]);

得到的o实际类型是String[]。

其次,如果参数a的大小装不下集合的所有元素,返回的将是一个新的数组。如果参数a的大小能装下集合的所有元素,则返回的还是a,但a的内容用集合的元素来填充。尤其要注意的是,如果a的大小比集合元素的个数还多,a后面的部分全部被置为null。

最后一个最重要的方法是iterator(),返回一个Iterator(迭代子),用于遍历集合的所有元素。

用Iterator模式实现遍历集合

Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:

for(int i=0; i<array.size(); i++) {
    // TODO: get(i) ...
}

而访问一个链表(LinkedList)又必须使用while循环:

while((e=e.next())!=null) {
    // TODO: e.data() ...
}

以上两种方法客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。

更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。

为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合

阅读全文 »

由于MIDP没有J2SE对应的java.net.URLEncoder类,因此,要向服务器发送HTTP请求,必须自己进行URL编码,参考JDK1.4.2的src代码,将其改为一个能用在MIDP环境中的URLEncoder类,源码如下:

package com.liaoxuefeng.javame.util;

import java.io.*;

/**
 * Encode url, just like java.net.URLEncoder.encode() in J2SE.
 * NOTE: This class is modified from java.net.URLEncoder class in J2SE 1.4.
 */
public class URLEncoder {

    private static final int MAX_BYTES_PER_CHAR = 10; // rather arbitrary limit, but safe for now

    private static boolean[] dontNeedEncoding;
    private static final int caseDiff = ('a' - 'A');

    static {
        dontNeedEncoding = new boolean[256];
        for (int i='a'; i<='z'; i++) {
            dontNeedEncoding[i] = true;
        }
        for (int i='A'; i<='Z'; i++) {
            dontNeedEncoding[i] = true;
        }
        for (int i='0'; i<='9'; i++) {
            dontNeedEncoding[i] = true;
        }
        dontNeedEncoding[' '] = true;
        dontNeedEncoding['-'] = true;
        dontNeedEncoding['_'] = true;
        dontNeedEncoding['.'] = true;
        dontNeedEncoding['*'] = true;
    }

    private URLEncoder() {}

    public static String encode(String s) {
        boolean wroteUnencodedChar = false;

        StringBuffer out = new StringBuffer(s.length());
        ByteArrayOutputStream buf = new ByteArrayOutputStream(MAX_BYTES_PER_CHAR);
        OutputStreamWriter writer = new OutputStreamWriter(buf);

        for (int i = 0; i < s.length(); i++) {
            int c = (int) s.charAt(i);
            if (c<256 && dontNeedEncoding[c]) {
                out.append((char) (c==' ' ? '+' : c));
                wroteUnencodedChar = true;
            } else {
                // convert to external encoding before hex conversion
                try {
                    if (wroteUnencodedChar) {
                        writer = new OutputStreamWriter(buf);
                        wroteUnencodedC

阅读全文 »

MIDP规范的出现使得我们在手机上开发Java游戏成为可能。今天我们要实现的是一个简单的拼图游戏。这个拼图游戏是一个3x3的拼图,由9个分割的小图片构成。这样,在手机上,就可以用按键1-9对应每个图片。需要移动某个图片时,用户只需要按下对应的数字键即可,非常方便。当然,对于键盘设计不规则的手机来说,就只能委屈了。当用户按下0键时,则显示整个原始图片。

虽然MIDP提供了许多高级和低级的UI API接口,但是整个MIDP应用程序的结构设计仍然至关重要,一个灵活的框架能大大降低游戏开发的复杂度。

MVC模式几乎是UI应用程序开发的标准模式了,通过Model-View-Controller的分工合作,使得整个应用程序的不同功能部分被分离开来,从而降低开发难度。

MVC有MVC1和MVC2两种模式,其不同之处在于Model能否主动通知View。在普通的Windows窗口程序中,Model可以主动通知View是否需要Update,因此应使用MVC1;在Web程序中,由于HTTP协议的限制,服务器端的Model无法主动通知View(如JSP页面),因此只能使用MVC2,由Controller取得Model并渲染View。

在窗口应用程序中,View通常仅有一个,但Model可能有很多;而在Web程序中,Model通常被放在服务器端,每一个JSP页面都是一个View,因此View有很多个。

微软的MFC框架也是一个基于MVC模式的框架,其View-Document框架是专门针对桌面应用程序设计的,因此,我们在MIDP程序中也可借鉴其思想。

在MIDP程序中,MIDlet起着Controller的作用,每个Screen或者Canvas就是一个View,而Model可以用一个单独的类来表示,用于存储程序运行中的数据。对于这个拼图游戏来说,设计以下几个类:

  • PuzzleMIDlet:控制整个游戏的生命周期,也是应用程序的入口;
  • MainCanvas:绘制游戏的主屏幕,完成所有绘图操作;
  • Document:存储游戏运行过程中的数据,并负责通知屏幕更新。

当用户通过MainCanvas输入命令后(例如,按下0-9的某个键),将可能引起Document数据的更新,如果需要更新屏幕,则Document应通知View更新显示,这是一个Observer模式的典型应用。

由于这个拼图游戏不需要频繁地更新画面,因此,连多线程也不必使用了,这样就大大简化了游戏逻辑的设计。下面是这个拼图游戏运行在真实手机上的效果图:

由于公司的手机还停留在CF62 / MIDP1.0的水平,因此,只好用MIDP1.0来编写这个拼图游戏了。不过好在我们的重点不是在如何绘制Canvas上,因此,MIDP2.0中提供的新的Game API绝大部分都用不上。

下面,我们开始设计每个类,并实现整个完整的游戏逻辑。

设计Document类

Document类需要保存游戏运行中所有的状态数据,对于这个拼图游戏来说,我们设计以下成员变量:

Updatable updatable;
int state;
Image[] images = new Image[9];
int[][] current = new int[3][3];
int hiddenX, hiddenY;
int steps; // 移动的步数

MainCanvas需要实现Updatable接口,因此,Document保存了一个View的引用,在恰当的时候,Document可以调用updatable.update()方法通知View需要重绘。这样,MainCanvas和Document就实现了Observer模式。

游戏中,state用于存储游戏状态,一共有3种状态:

  • PUZZLE_STATE:表示用户正在进行拼图中;
  • IMAGE_STATE:表示用户正在查看原始图片;
  • FINISH_STATE:表示用

阅读全文 »

闲来无事,开始研究JDK源码(JDK 1.5),先找了一个最简单的java.lang.Boolean开始解剖。

首先我们剔除所有的方法和静态变量,Boolean的核心代码如下:

public final class Boolean implements java.io.Serializable,Comparable {
    private final boolean value;
}

很明显,凡是成员变量都是final类型的,一定是immutable class,这个Boolean和String一样,一旦构造函数执行完毕,实例的状态就不能再改变了。

Boolean的构造方法有两个:

public Boolean(boolean value) {
    this.value = value;
}

public Boolean(String s) {
    this(toBoolean(s));
}

另外注意到Boolean类实际上只有两种不同状态的实例:一个包装true,一个包装false,Boolean又是immutable class,所以在内存中相同状态的Boolean实例完全可以共享,不必用new创建很多实例。因此Boolean class还提供两个静态变量:

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

这两个变量在Class Loader装载时就被实例化,并且申明为final,不能再指向其他实例。

提供这两个静态变量是为了让开发者直接使用这两个变量而不是每次都new一个Boolean,这样既节省内存又避免了创建一个新实例的时间开销。

因此,用

Boolean b = Boolean.TRUE;

Boolean b = new Boolean(true);

要好得多。

如果遇到下面的情况:

Boolean b = new Boolean(var);

一定要根据一个boolean变量来创建Boolean实例怎么办?推荐使用Boolean提供的静态工厂方法:

Boolean b = Boolean.valueOf(var);

这样就可以避免创建新的实例。看看valueOf()静态方法:

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

这个静态工厂方法返回的仍然是两个静态变量TRUE和FALSE之一,而不是new一个Boolean出来。虽然Boolean非常简单,占用的内存也很少,但是一个复杂的类用new创建实例的开销可能非常大,而且,使用工厂方法可以方便的实现缓存实例,这对客户端是透明的。所以,能用工厂方法就不要使用new。

和Boolean只有两种状态不同,Integer也是immutable class,但是状态上亿种,不可能用静态实例缓存所有状态。不过,SUN的工程师还是作了一点优化,Integer类缓存了-128到127这256个状态的Integer,如果使用Integer.valueOf(int i),传入的int范围正好在此内,就返回静态实例。

hashCode()方法很奇怪,两种Boolean的hash code分别是1231和1237。估计写Boolean.java的人对这两个数字有特别偏好:

public int hashCode() {
    return value ? 1231 : 1237;
}

eq

阅读全文 »

文件上传在Web应用中非常普遍,要在Java Web环境中实现文件上传功能非常容易,因为网上已经有许多用Java开发的组件用于文件上传,本文以使用最普遍的commons-fileupload组件为例,演示如何为Java Web应用添加文件上传功能。

commons-fileupload组件是Apache的一个开源项目之一,可以从http://commons.apache.org/fileupload/下载。该组件简单易用,可实现一次上传一个或多个文件,并可限制文件大小。

下载后解压zip包,将commons-fileupload-1.x.jar复制到tomcat的webapps/你的webapp/WEB-INF/lib/下,如果目录不存在请自建目录。

新建一个UploadServlet.java用于文件上传:

package com.liaoxuefeng.web;

public class FileUploadServlet extends HttpServlet {
    private String uploadDir = "C:\\temp";
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        // TODO:
    }
}

当servlet收到浏览器发出的Post请求后,在doPost()方法中实现文件上传,我们需要遍历FileItemIterator,获得每一个FileItemStream:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
{
    try {
        ServletFileUpload upload = new ServletFileUpload();
        // set max file size to 1 MB:
        upload.setFileSizeMax(1024 * 1024);
        FileItemIterator it = upload.getItemIterator(req);
        // handle with each file:
        while (it.hasNext()) {
            FileItemStream item = it.next();
            if (! item.isFormField()) {
                // it is a file upload:
                handleFileItem(item);
            }
        }
        req.getRequestDispatcher("success.jsp").forward(req, resp);
    }
    catch(FileUploadException e) {
        throw new ServletException("Cannot upload file.", e);
    }
}

在handleFileItem()方法中读取上传文件的输入流,然后写入到uploadDir中,文件名通过UUID随机生成:

void handleFileItem(FileItemStream item) throws IOException {
    System.out.println("upload file: " + item.getName());
    File new

阅读全文 »

书名:Spring 2.0核心技术与最佳实践

作者:廖雪峰

出版社:电子工业出版社

书号:9787121042621

出版日期:2007年6月

内容简介:

本书注重实践而又深入理论,由浅入深且详细介绍了Spring 2.0框架的几乎全部的内容,并重点突出2.0版本的新特性。本书将为读者展示如何应用Spring 2.0框架创建灵活高效的JavaEE应用,并提供了一个真正可直接部署的完整的Web应用程序——Live在线书店

在介绍Spring框架的同时,本书还介绍了与Spring相关的大量第三方框架,涉及领域全面,实用性强。本书另一大特色是实用性强,易于上手,以实际项目为出发点,介绍项目开发中应遵循的最佳开发模式。

本书还介绍了大量实践性极强的例子,并给出了完整的配置步骤,几乎覆盖了Spring 2.0版本的所有新特性。

本书适合有一定Java基础的读者,对JavaEE开发人员特别有帮助。本书既可以作为Spring 2.0的学习指南,也可以作为实际项目开发的参考手册。

编辑推荐:被China-Pub会员评为“2007年我最喜爱的十大技术图书”之一。

阅读全文 »

书名:Symbian OS J2ME编程指南

原书名:Programming Java 2 Micro Edition for Symbian OS

作者:Martin de Jode

译者:詹建飞 廖雪峰

出版社:人民邮电出版社

书号:7115136866

出版日期:2005年10月

内容简介:

本书介绍在Symbian操作系统上的J2ME编程,尤其是针对MIDP 2.0的编程。

全书共分3个部分,5个附录。第一部分包括第5章,介绍J2ME配置和简表的意义,然后集中说明新一代Symbian操作系统手机上构成Java平台的MIDP和附加API。第二部分包括第6章和第7章,研究编写高质量代码在设计和实现中的考虑。第三部分是第8章,介绍Java对无线生态系统的战略,并对Java在Symbian操作系统上的发展方向给出大概的描述。附录部分分别介绍了CLDC核心库、MIDP库、使用Wireless Toolkit的命令行工具、开发者资源和参考文献,以及Symbian系统手机规范。

本书适合于Symbian系统下进行J2ME应用开发的人员阅读,它能为开发者展示如何最大限度地发挥新一代Symbian操作系统手机的功能。本书也可作为Symbian系统下J2ME编程的教材和参考书。

阅读全文 »

长期为企业提供软件开发类培训,包括:

JavaEE平台:

Spring培训:覆盖Spring 2.x/3.0的基础(IoC,AOP),Web开发(集成Struts,WebWork,Velocity等常见框架),访问和发布Web Services,第三方组件集成(任务调度,邮件服务,消息服务等),缓存设计,分层模型设计与最佳实践。

Hibernate培训:覆盖Hibernate 3.x的基础(ORM映射),各种复杂映射,JPA Annotation应用,Sharding设计,JDBC集成,性能分析与优化以及最佳实践。

JUnit培训:覆盖JUnit 3.x/4.x的基础测试,数据库测试,Web测试,代码覆盖率测试,以及Ant集成和自动化测试的最佳实践。

Lucene培训:讲解全文搜索原理,覆盖Lucene 3.0/Compass的基础,如何快速实现站内搜索以及搜索优化。

Android培训:讲解Android平台基础,UI设计,多线程,网络连接,XML读写,SQLite数据库操作,多媒体操作,传感器访问,游戏设计基础,Android组件生命周期与设计,以及最佳实践。

 

Python培训:

覆盖Python2.5/2.6基础,和静态语言相比较的特点,Web开发,数据库开发,AppEngine开发以及动态语言的最佳实践。

 

实际课程内容可根据客户需求定制,有意请联系:askxuefeng@gmail.com

阅读全文 »

Hi, I'm Michael Liao, a software engineer in China.

I have 5 years experiences in software development, especially Java and Python.

   

Profile

Chinese Name: Liao Xuefeng
English Name: Michael Liao
Email: (hidden)
Mobile: (hidden)
Chinese Blog: http://www.liaoxuefeng.com
English Blog: http://michael.liaoxuefeng.com
   

Education

2000.9 - 2004.7 Beijing University of Posts & Telecommunications
  Bachelor Degree, major in Information Engineering.
   

阅读全文 »

自我介绍

姓名:廖雪峰

居住:北京

在线订阅

使用您喜爱的在线阅读器订阅:

RSS