志岩's profile我的技术空间BlogListsNetwork Tools Help

志岩 高

Occupation
Location
Interests
用心去观察~

我的技术空间

用心去体验生活、用心去体验世界!
4/2/2007

JSP连接mysql数据库

一. 软件下载
  Mysql
  下载版本:4.1.11
  http://dev.mysql.com/downloads/mysql/4.1.html

  JDBC驱动
  下载版本:3.1.8
  http://dev.mysql.com/downloads/connector/j/3.1.html

  Mysql界面插件:mysql-front
  下载版本镜像:HongKong(下回来安装就是中文版了)
  http://www.mysqlfront.de/download.html

二. 软件安装
  1.安装mysql
  请参阅资料版区相关文章

  http://info.mysql.cn/install/2006/0208/81.html

  2.JDBC驱动:mysql-connector-java-3.1.8
  这只是一个压缩包,并不需要安装,只要将其解压,我么使用的是文件夹mysql-connector-java-3.1.8里的文件:mysql-connector-java-3.1.8-bin.jar.

  3. Mysql界面插件:mysql-front
  这是一个安装程序,按照提示安装就可以了.

三. 环境配置
   首先,我要说明的是我现在tomcat的安装路径是: D:\Program Files\Java\Tomcat;JDK的安装路径是:D:\Program Files\Java\j2sdk。

  在这里,需要配置环境变量的是JDBC驱动.在配置前先要把刚才说到的mysql-connector-java-3.1.8-bin.jar本地硬盘某处(我放的地方:D:\Program Files\Java\mysqlforjdbc),然后根据你放的地方,配置classpath,我的配置是这样的:

  D:\Program files\Java\j2sdk\lib\tools.jar;

  D:\Program Files\Java\j2sdk\lib\mysql-connector-java-3.1.8-bin-g.jar;

  D:\Program Files\Java\mysqlforjdbc\mysql-connector-java-3.1.8-bin.jar

  配置这个的目的是让你的java应用程序找到连接mysql的驱动.

  配置完环境变量后还有很重要一步就是为JSP连接数据库配置驱动,这个其实很简单,就是把mysql-connector-java-3.1.8- bin.jar拷到某些文件夹里就行了,我在网上看了很多资料问了很多人,各种说法都有,我综合了一下,为了保险,我都全做了,呵呵,反正就是拷一个 400K的文件而已,现列出要把mysql-connector-java-3.1.8-bin.jar拷进去的文件夹,如下:
  D:\Program Files\Java\Tomcat\common\lib
  D:\Program Files\Java\Tomcat\shared\lib

四. 数据库的使用

  Mysql安装完毕以后,还有一些要注意的地方(参考):

  http://info.mysql.cn/install/2006/0208/82.html

  就象在文章提到的,mysql安装好后最重要一样就是要看数据库有没有作为系统服务启动了,所以在大家进行数据库操作前,应要看看,在操作系统的开始->运行->输入services.msc,确定你在安装时你设置的关于mysql的那个服务已经启动,这样你在操作数据库时不会报连接不上的错误.

  上面提到了一个较方便的mysql界面插件,但是这个界面是我在已经开始使用mysql后才找到的,刚开始我是在dos下用命令行进行操作的.虽然那个界面也可以进行建库啊,设定权限等操作,但是,我觉得懂得在使用命令行也是一个很重要的技能,所以我先从命令行开始说,怎样简单使用mysql.到后面会谈及mysql-front的使用.

  现在我想在mysql里建一个数据库shujuku,以及在数据库里建一个表biao.具体的命令如下(假设mysql我是刚安装好的)

  1. 进入dos状态(记住命令行的要运行在mysql的安装目录下的bin目录的)

  2. 连接mysql
  输入:mysql –h localhost –u root –p
  输入在安装时已设好的密码,就近入了mysql的命令编辑界面了。

  3. 使用mysql的基本命令(在mysql命令行编辑每输入完命令后最后一定要有分号,不然会报错)
  显示数据库:show databases;
  使用数据库:use 数据库名;

  4.建库
  命令:create database shujuku;

  5.为数据库设置权限(用户和密码)
  命令:grant all privileges on shujuku.* to test@localhost identified by “123456”;
  当你执行完这个命令以后,只要你再以用户名:test,密码:123456登录时你就只可以对shujuku这个数据库操作,这样避开使用root,对数据库的安全有很大帮助.

  6.建表
  命令:create table biao(id int(8) primary key,name varchar(10));

  剩下来的与标准sqsl命令基本上是一样的,具体操作略
  值得一提的是,你在命令行上输入"?",就会有mysql命令的简单帮助,如下:

  呵呵,那样,我们还可以知道退出,就是"exit",呵呵!

五. 关于mysql-front的使用
  我找了好几个mysql的界面工具,觉得最简洁方便还是mysql-front,可惜的是要收费,不过还好有试用期,呵呵,可以先感受感受一下,最重要一点是mysql-front有简体中文版的,英文不好的我用起来就舒服多了.下面简单说说使用吧.

  首先,安装就不用说了,有向导,而且很简单.安装好后第一次运行时会跳出来一个要求添加对话的框,在这里你可以添加例如上面已经设定好的shujuku,过程如下:
  当你在注册的复选框里填上你在上面mysql设定好的用户名和密码后,在选择数据库框里就有shujuku 的数据库了,选上,按确定.进入mysql-fron后,你就会看到下面的界面,这是你就可以进行操作了.

  要说明的是,你还可以把root用户也加进去,这要你在mysql-fron的界面上选设置->对话->新建,再按上面进行就可以,出了root你还可以加入更多的用户,方法还是一样的,设置不同的用户,是方便对不同数据库进行管理,呵呵,root是权限最高的,可不要随便让别人使用你的root用户,保正你数据库的安全.

六. JSP连接mysql
  现在就是尝试用jsp连接mysql了
  我在eclipse里建了个test_mysql.jsp页面,代码如下:

<%@ page contentType="text/html; charset=gb2312" %>

<%@ page language="java" %>

<%@ page import="com.mysql.jdbc.Driver" %>

<%@ page import="java.sql.*" %>

<%

//驱动程序名

String driverName="com.mysql.jdbc.Driver";

//数据库用户名

String userName="cl41";

//密码

String userPasswd="123456";

//数据库名

String dbName="db";

//表名

String tableName="dbtest";

//联结字符串

String url="jdbc:mysql://localhost/"+dbName+"?user="+userName+"&password="+userPasswd;

Class.forName("com.mysql.jdbc.Driver").newInstance();

Connection connection=DriverManager.getConnection(url);

Statement statement = connection.createStatement();

String sql="SELECT * FROM "+tableName;

ResultSet rs = statement.executeQuery(sql);

//获得数据结果集合

ResultSetMetaData rmeta = rs.getMetaData();

//确定数据集的列数,亦字段数

int numColumns=rmeta.getColumnCount();

// 输出每一个数据值

out.print("id");

out.print("|");

out.print("num");

out.print("<br>");

while(rs.next()) { 

out.print(rs.getString(1)+" ");

out.print("|");

out.print(rs.getString(2));

out.print("<br>");

}

out.print("<br>");

out.print("数据库操作成功,恭喜你");

rs.close();

statement.close();

connection.close();

%>

  然后把test??_mysql.jsp部署到tomcat处,如何部署可参考"配置Eclpise+tomcat并实现JSP的编写与部署",在浏览器中就可以看到结果了。

如何学习Spring

1、如何学习Spring?
你可以通过下列途径学习spring:
(1) spring下载包中doc目录下的MVC-step-by-step和sample目录下的例子都是比较好的spring开发的例子。

(2) AppFuse集成了目前最流行的几个开源轻量级框架或者工具Ant,XDoclet,Spring,Hibernate(iBATIS),JUnit,Cactus,StrutsTestCase,Canoo's WebTest,Struts Menu,Display Tag Library,OSCache,JSTL,Struts 。
你可以通过AppFuse源代码来学习spring。
AppFuse网站:http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse

(3)Spring 开发指南(夏昕)(http://www.xiaxin.net/Spring_Dev_Guide.rar)
一本spring的入门书籍,里面介绍了反转控制和依赖注射的概念,以及spring的bean管理,spring的MVC,spring和hibernte,iBatis的结合。

(4) spring学习的中文论坛
SpringFramework中文论坛(http://spring.jactiongroup.net)
Java视线论坛(http://forum.javaeye.com)的spring栏目

2、利用Spring框架编程,console打印出log4j:WARN Please initialize the log4j system properly?
说明你的log4j.properties没有配置。请把log4j.properties放到工程的classpath中,eclipse的classpath为bin目录,由于编译后src目录下的文件会拷贝到bin目录下,所以你可以把log4j.properties放到src目录下。
这里给出一个log4j.properties的例子:

log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p (%F:%L) - %m%n


3、出现 java.lang.NoClassDefFoundError?
一般情况下是由于你没有把必要的jar包放到lib中。

比如你要采用spring和hibernate(带事务支持的话),你除了spring.jar外还需要hibernat.jar、aopalliance.jar、cglig.jar、jakarta-commons下的几个jar包。

http://www.springframework.org/download.html下载spring开发包,提供两种zip包
spring-framework-1.1.3-with-dependencies.zip和spring-framework-1.1.3.zip,我建议你下载spring-framework-1.1.3-with-dependencies.zip。这个zip解压缩后比后者多一个lib目录,其中有hibernate、j2ee、dom4j、aopalliance、jakarta-commons等常用包。


4、java.io.FileNotFoundException: Could not open class path resource [....hbm.xml],提示找不到xml文件?
原因一般有两个:
(1)该xml文件没有在classpath中。
(2)applicationContext-hibernate.xml中的xml名字没有带包名。比如:
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref bean="dataSource"/></property>
<property name="mappingResources">
<list>
<value>User.hbm.xml</value> 错,改为: <value>com/yz/spring/domain/User.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect"> net.sf.hibernate.dialect.MySQLDialect </prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>


5、org.springframework.beans.NotWritablePropertyException: Invalid property 'postDao' of bean class?
出现异常的原因是在application-xxx.xml中property name的错误。
<property name="...."> 中name的名字是与bean的set方法相关的,而且要注意大小写。
比如
public class PostManageImpl extends BaseManage implements PostManage { 
private PostDAO dao = null;
public void setPostDAO(PostDAO postDAO){ 
this.dao = postDAO;
}
}
那么xml的定义应该是:
<bean id="postManage" parent="txProxyTemplate">
<property name="target">
<bean class="com.yz.spring.service.implement.PostManageImpl">
<property name="postDAO"><ref bean="postDAO"/></property> 对
<property name="dao"><ref bean="postDAO"/></property> 错
</bean>
</property>
</bean>


6、Spring中如何实现事务管理?
首先,如果使用mysql,确定mysql为InnoDB类型。
事务管理的控制应该放到商业逻辑层。你可以写个处理商业逻辑的JavaBean,在该JavaBean中调用DAO,然后把该Bean的方法纳入spring的事务管理。

比如:xml文件定义如下:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

<bean id="userManage" parent="txProxyTemplate">
<property name="target">
<bean class="com.yz.spring.service.implement.UserManageImpl">
<property name="userDAO"><ref bean="userDAO"/></property>
</bean>
</property>
</bean>

com.yz.spring.service.implement.UserManageImpl就是我们的实现商业逻辑的JavaBean。我们通过parent元素声明其事务支持。


7、如何管理Spring框架下更多的JavaBean?
JavaBean越多,spring配置文件就越大,这样不易维护。为了使配置清晰,我们可以将JavaBean分类管理,放在不同的配置文件中。 应用启动时将所有的xml同时加载。
比如:
DAO层的JavaBean放到applicationContext-hibernate.xml中,商业逻辑层的JavaBean放到applicationContext-service.xml中。然后启动类中调用以下代码载入所有的ApplicationContext。

String[] paths = { "com/yz/spring/dao/hibernate/applicationContext-hibernate.xml",
"com/yz/spring/service/applicationContext-service.xml"};
ctx = new ClassPathXmlApplicationContext(paths);


8、web应用中如何加载ApplicationContext?
可以通过定义web.xml,由web容器自动加载。

<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-hibernate.xml</param-value>
<param-value>/WEB-INF/applicationContext-service.xml</param-value>
</context-param>

9、在spring中如何配置的log4j?
在web.xml中加入以下代码即可。
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>


10、Spring框架入门的编程问题解决了,我该如何更深地领会Spring框架呢?
这两本书你该去看看。这两本书是由Spring的作者Rod Johnson编写的。
Expert One on one J2EE Design and Development
Expert One on one J2EE Development Without EJB
你也该看看martinfowler的Inversion of Control Containers and the Dependency Injection pattern。
http://www.martinfowler.com/articles/injection.html

再好好研读一下spring的文档
http://www.jactiongroup.net/reference/html/index.html(中文版,未全部翻译)

将阿拉伯数字转化成大写的人民币数字

将阿拉伯数字转化成大写的人民币数字
public String getNumberToRMB(String m)...
    String num 
= "零壹贰叁肆伍陆柒捌玖";
    String dw 
= "圆拾佰仟万亿";
    String mm[] 
= null;
    mm 
= m.split("\.");
    String money 
= mm[0];

    String result 
= num.charAt(Integer.parseInt("" + mm[1].charAt(0))) + "" +
        num.charAt(Integer.parseInt(
"" + mm[1].charAt(1))) + "";

    
for (int i = 0; i < money.length(); i++...
      String str 
= "";
      
int n = Integer.parseInt(money.substring(money.length() - i - 1,
                                               money.length() 
- i));
      str 
= str + num.charAt(n);
      
if (i == 0...
        str 
= str + dw.charAt(i);
      }

      
else if ( (i + 4% 8 == 0...
        str 
= str + dw.charAt(4);
      }

      
else if (i % 8 == 0...
        str 
= str + dw.charAt(5);
      }

      
else ...
        str 
= str + dw.charAt(i % 4);
      }

      result 
= str + result;
    }

    result 
= result.replaceAll("零([^亿万圆角分])""");
    result 
= result.replaceAll("亿零+万","亿零");
    result 
= result.replaceAll("零+""");
    result 
= result.replaceAll("零([亿万圆])""");
    result 
=result.replaceAll("壹拾","");
    
    
return result;
}

编 程 之 道

编 程 之 道
Geoffrey James
 
  第一篇 静寂的空宇
  第二篇 古代的大师
  第三篇 设计
  第四篇 编码
  第五篇 维护
  第六篇 管理
  第七篇 公司里的学问
  第八篇 硬件和软件
  第九篇 尾声
 
 
第一篇 静寂的空宇
编程大师如是说:
“当你有本事夺走我手中的这块水晶石时,就是你出师的时侯了。”
1.1
在静寂的空宇里,一种神奇的物质形成并诞生了。它立刻便静止了,独自守侯着,豪无动静,然而又处于永恒的运动之中。它是所有程序的源头,我不知道它的名字,所以我将称它为编程之道。
 
如果此道是完美的,那些操作系统就是完美的,如果操作系统是完美的,那么编译程序就是完美的,如果编译程序是完美的,那么应用程序就是完美的。用户满意之至--和谐便应运而生。
 
编程之道流逝远去,又乘着晨风而归。
1.2
此道产生了机器语言,机器语言又产生了汇编语言,
汇编语言产生了编译程序,如今有了上万种的语言。
每一种语言都有其各自的卑微用途。每一种语言都表达出软件的阴和阳。每一种语言都在此道之中有其一席之地。
但是,应尽量避免用COBOL语言编写程序。
1.3
宇宙之初有道,道产生了空间和时间。空间和时间便是程序设计之阴阳。
不能领悟此道的编程者总是耗尽他们所要编写的程序的时间和空间;而领悟了此道的编程者却总有足够的时间和空间来达到他们的目标。
除此之外,难道还有其它的情形吗? 1.4
精明的编程者听说了此道,并遵循它;平庸的编程者听说了此道,并寻觅它;愚蠢的编程者听说了此道,却嘲笑它。
要不是因为有嘲笑,道也就不复存在了。
最高的声音最难被听见。前进也是一种倒退。大器晚成。即使是一个完美的程序也仍然会有隐患。
道深藏不露,难于理解。 
 
 
 
第二篇 古代的大师
编程大师如是说:
“倘若三天不编程,生活将变得毫无意义。”
2.1
老一辈的程序员是神秘的、深奥的。我们没法揣摩他们的想法,我们所能做的只是描述一下他们的表象。
  他们是清醒的,就像一只游过水面的狐狸;
  他们是警惕的,就像一位战场上的将军;
  他们是友善的,就像一位招待客人的女主人;
  他们是简单的,就像未经刻凿的木头;
  他们还是难以琢磨的,就像黑暗的洞穴中漆黑的池水。
谁能说出他们心中的秘密?
答案只存在于道中。
2.2
计算机科学巨擘图灵曾经梦到他是一台机器。当他醒来时,他惊叹道:
“我不知道--我是梦到了自己是台机器的图灵,还是一台梦到了自己是图灵的机器?”
2.3
一家大电脑公司的一名程序员参加了一次软件研讨会。他回来后向经理汇报说:“为其它公司工作的那些程序员都是些什么的人啊?他们行为拙劣,不顾及自己的仪表。他们的头发又长又乱,衣服又皱又旧。他们闯进我们的会客组,还在我演讲时发生粗鲁的喧闹。”
 
经理说:“我本不应该让你去参加这个会议。那些程序员是生活在物质世界之外的。他们认为生活是荒唐的,一种意外的巧合。他们来去自如。他们只为他们的程序而活着,无忧无虑地活着。为什么要用社会习俗来约束他们呢?
 
他们活在此道之中。”
2.4
一个礼堂者问大师:“有位程序员,从不构思、编写文档或测试他的程序,然而所有知道他的人都认为他是世界上最棒的程序员。这是为什么呢?”
大师回答说:“那个人掌握了道。他不需要事先进行构思,当系统崩溃时,他不会因此而闷闷不乐,而是心平气和地接受整个事实。他还从编写程序说明文档的需要之中超脱了出来,不在意是否有人看他的编码。他也不需要进行测试。他的每个程序都完美无缺。宁静而优雅,程序的用途也显而易见。 的的确确,他已经进入了道的神奇境界。” 

 
第三篇 设计
编程大师如是说:
“当程序被测试时,再修改设计方案就太迟了。”
3.1
曾经有个人去参加一次电脑展示会,每天当他进入展馆时,都对门卫说:
“我是个大盗,我偷盗的本领是出了名的。事先警告你,这次展示会也在劫难逃。”
这番话让门卫坐立不安,因为里面有价值数百万美元的电脑设备,所以他紧紧地盯住这个人。但这个人只是从一个展摊逛到另一个展摊,嘴里轻轻地哼着小曲。
当这个人出门时,门卫把他拉到一边,搜查他的衣服,但一无所获。
第二天,这个人又来了,并对着门卫嚣张地嚷着:“昨天我满载而归,但今天的收获会更大。”于是,门卫盯他盯得更紧了,但仍一无所获。
在展示会的最后一天,门卫再也抑制不住自己的好奇心了。“大盗先生,”门卫说,“我被你搞糊涂了,实在想不明白。请告诉我,你究竟在偷什么?”
这个人笑了。“我在偷想法。”他说。
3.2
曾经有位编程大师,喜欢编写非结构化的编程。一位初学者试图模仿他,也开始编写非结构化的程序。当这位徒弟请师父评价他的进展时,师父批评了他的做法。他说:“对一位编程高手适合的,对初学者来说并不一定适合。在超越结构化之前,你必须先领悟道。”
 
3.3
曾经有位程序员被派到IBM的军机大臣手下工作。军机大臣问程序员:“设计一个财务软件包,和设计一个操作系统,哪一个更容易?”
“操作系统。”程序员回答说。
军机大臣立刻发生一种不信任的惊叹,“与一个复杂的操作系统,一个财务软件包简直是小巫见大巫。”他说。
“并非如此,”程序员说,“在设计一个财务软件包时,编程人员是作为一个中介者在观念各异的人们之间起作用的:这个软件必须如何操作,它的报表必须是什么形式,它必须如何与税法一致,等等,一个操作系统则不为其外观所限制。当设计一个操作系统时,编程人员只要在机器与人的思维之间寻找一种最简单的和谐就可以了。这就是为什么操作系统更容易设计。”
 
军机大臣点点头,笑了。“说来也是。但要想检测和纠正其中的错误,哪个更容易呢?”
程序员没有回答。
3.4
一位经理到编程大师那里,交给他一份有关一个新应用程序的需求说明。经理问编程大师:“如果我分配五个程序员给你,你需要多久能设计好这个系统?”
“那将花费一年的时间。”大师立刻回答。
“但我们马上就需要这个系统,甚至要求更快!如果我分配十个程序员给你,你需要多长时间?”
大师皱了皱眉头,“那样的话,需要两年。”
“如果我分配一百个程序员给你怎么样?”
大师耸了耸肩膀,“那么这项设计将永远无法完成。”他说 
 
 
 
第四篇 编码
编程大师如是说:
“一个写得完美的程序是其自身的天堂,而一个写得糟糕的程序则是其自身的地狱。”
4.1
一个程序应当是轻盈的、灵活的,它的子程序就像一串珍珠一样连接着。它的精神和意图应该贯穿始终。在程序中,内容既不应太多,也不应太少;既不应该有不需要的循环结构,也不该有冗余的变量;既不缺乏结构性,又不过分僵化。
 
一个程序,无论多么复杂,都应该以一个整体的方式运行。程序应以其内在的逻辑为指引,而非外在形态。
如果一个程序不能达到这些要求,它将处于一种杂乱无章的混淆不清的状态。唯一的方法就是重写这个程序。
4.2
一位初学者问大师:“我有个程序,时灵时不灵。我一直都遵循着编程的规则,结果却整个儿搞糊涂了。这是什么原因呢?”
大师回答说:“因为你没有领悟道,所以你迷惑不解。只有傻瓜才会指望从人类身上看到理性的行为,你又能指望一台人类制造的机器怎么样呢?计算机模仿的是决定论,只有道才是尽善尽美。
 
编程用的那些条条框框式的规则仅仅是昙花一现,只有道才是永恒的。因此,在你受到道的启发之前,你必须沉思于道。”
“但是我怎样才能知道我已经受到了启发了呢?”初学者问。
“当你的程序运行无误时。”大师回答说。
4.3
一位大师正在向他的一名初学编程的弟子解释道的真谛。“此道体现在所有的软件当中--不管它看上去多么无足轻重。”大师说着。
“此道体现在手掌计算器中吗?”初学者问。
“是的。”
“此道在电子游戏中吗?”初学者继续问。
“此道甚至也体现在电子游戏之中。”大师说。
“那么此道也体现在个人电脑的DOS系统之中吗?”
大师咳嗽一声,并稍稍挪动了一下位置。“今天的课就到这里吧。”他说。
4.4
一位项目经理手下的一名程序员正编写软件。他的手指在键盘上飞舞着,在程序的编译过程中没有出现任何错误信息。程序运行起来就像一阵和风。
“太好了!”经理高兴地大叫了起来,“你的技艺简直是完美无缺。”
“技艺?”程序员说着便从他的终端机前转过身来,“我遵循的是道--所有的技艺远不能及!当我刚开始编程时,我眼前看见的是整个问题乱成一团。三年之后,我再也看不见这种一团糟的情形了。相反,我用了各种各样的子程序。但现在,我什么也看不见了。我的整个身心存在于一种无形的虚空里,我的知觉是空荡荡的。
 
我的精神随其本能而动,不无原则计划就能自由地工作。总而言之,是我的程序自己写出了自己。诚然,有时会有一些难题。我看见那些难题向我走来,于是我放慢了速度,默默地注视着他们。然后我更改了一行编码,那些问题就烟消云散了。然后我完成程序的编译。我静静地坐着,让工作的欢心情舒畅遍布我的全身。我闭上双眼,歇息片刻,然后退出系统。”
 
经理说,“希望我的所有的程序员都这么聪明!”

第五篇 维护
编程大师如是说:
“即使一个程序只有三行长,总有一天它也不得不需要维护。”
5.1
一记扇经常开启的门的绞链不需要润滑油。
一条湍急的河流不会变得污浊。
无论是声音还是想法都不可能在真空中传播。
软件如果不用就会腐朽。
这世界真奇妙。
5.2
一个程序员正在编写他的程序,经理问他还需要多长时间完成。
“明天就可以完成。”程序员立刻回答。
“我想你这是不切实际,”经理说,“实话实说,这需要多长时间?”
程序员想了一会儿。“我还想加进一些新的特色,这需要花至少两个星期的时间。”他最后说。
“即使那样也期望过高,”经理坚持说,“只要你编完程序时告诉我一声,我也就满足了。”
程序员答应了。
几年以后,那位经理要退休了。在他去退休午餐会的路上。他发现那个程 序员趴在他的终端机前睡着了。整个晚上都在忙于编写那个程序。
5.3
一次一位初学编程者被指派编写一个简单的财务软件包。这位初学者大张旗鼓地工作了许多天。但当他的师父检查这套程序时,发现其中包含有一个屏幕编辑器,一套通用图表程序,一个人工智能界面,然而却没有任何财务方面的东西。
 
当师父问及此事时,初学者显得愤愤不平。“不要这样没有耐心嘛,”他说,“我最后加些财务的素材进去就是了。”
5.4
难道一位好农民会漏掉他所种的一株庄稼吗?
难道一位好老师会放弃哪怕是最差的学生吗?
难道一位好父亲会允许哪一个孩子挨饿吗?
难道一位好程序员会不愿维护他的程序吗? 
 
 
 
第六篇 管理
编程大师如是说:
“让程序员多而经理少--然后生产效率就会高。”
6.1
当经理们没完没了地开会时,程序员就写些游戏玩;当财务主管们谈到季度利润时,开发用的预算马上就要被削减;当资深科学家们谈论蓝蓝的天空时,马上就会风起云涌。
其实,这并不是编程之道。
当经理们忠于职守时,游戏程序就会被搁置到一边;财务主管们制订出长 远的计划时,和谐秩序将很快恢复;当资深科学家们着手于眼前的问题时,这些问题不久就会解决。
其实,这才是编程之道。
6.2
为什么程序员没有效率?
因为他们的时间浪费在开会上。
为什么程序员很难管束?
因为管理层多管闲事。
为什么程序员接二连三地辞职?
因为他们的热情已耗尽。
在糟糕的管理之下工作,他们不再敬业。
6.3
一位经理即将被解职,但此时他手下的一名程序员发明了一个新的程序。这个程序流行起来,并且极为畅销。结果,经理又回到了原来的岗位上。
经理试图要发给那位程序员一笔资金,但程序员拒绝了。他说:“我写这个程序,因为我认为这是个很有意思的想法。我并不期望有所回报。”
经理听到这话,评论说:“这位程序员虽然身居卑位,却能很好地理解一位雇员应尽的职责。让我们提拔他到管理顾问的高位上吧!”
然而,接到通知后,那位程序员又一次拒绝了。他说:“我在属于我的位置上,才能较好地编程。如果我被提升了,除了浪费每个人的时间,我将一事无成。现在我可以走了吗?我还有一个程序要编呢。”
 
6.4
一位经理走过来对他的程序员们说:“关于你们的上班时间:你们要早上九点钟到,下午五点钟下班。”听了这话,所有的程序员都很气愤,其中有几位要当场辞职。
于是,经理只好又说:“好吧,那样的话,你们可以自己安排上班时间,只要你们能按时完成项目。”程序员们满意了。以后,他们中午来到办公室,一起工作到凌晨。 

 
第七篇 公司里的学问
编程大师如是说:
“你可以向一位公司总裁演示一个程序,但你无法使他学会使用电脑。”
7.1
一位初学者问大师:“在东方(此处喻指美国的东海岸,有许多大公司的总部--译注),有一个不寻常的树状结构,人们称它为‘公司总部’。它的副总裁们和财务主管们的数量之大,使它鼓得不成开关。它签发大师的便函,每份上都写着”归去”“来兮”,却没有人知道那是什么意思。它的那些分支机构每年都要换新的名字,但都毫无价值。如此一个不正常的实体怎么能继续存在呢?”
 
大师回答说:“你探察这个庞大的邓因其没有合理的用途而心神不定。难道你不能从它那无尽的回旋中得到乐趣吗?你不会享受一下在它所蔽护的部分里的编程的那种无忧无虑的轻松吗?你为什么要因为它毫无用处而心烦意乱呢?”
 
7.2
在东方,有一条大鱼,比其它所有的鱼都要大。它变成了一只鸟,它的翅膀就像云朵一个布满了天空。当这只鸟飞过陆地时,它带来了“公司总部”的消息,像蜻蜓点水一样把这个消息丢在了那些程序员中间。然后这只鸟驾着风,背负蓝天,返回了家园。
 
初学编程者惊奇地盯着那只鸟,因为他根本无法理解;平庸的程序员畏惧那只鸟的到来,因为他害怕鸟儿带来的消息;而编程大师却仍然在他的终端机前工作,因为他不知道那只鸟来了又去了。
 
7.3
象牙塔里的魔术师带来了他最新的发明,要让编程大师检验一下。魔术师把一只大黑箱子推进办公室,大师静静地等侯着。
“这是一台集成的、分布式的、通用的工作站,”魔术师开始,“运用人类工程学原理,使用享有专利的操作系统、第六代评议和多重状态用户界面而设计完成。建造这台工作站,花了我几百名助手几年的时间。这不足以令人惊奇吗?”
 
大师轻轻地扬了扬眉毛。“这的确令人惊奇。”他说。
“公司总部已经命令,”魔术师继续说,“每个人都得使用这台工作站作为操作平台来设计新的程序。你同意吗?”
“当然同意,”大师说,“我马上就把它运到数据中心去。”于是魔术师兴高采烈地回到了象牙塔去了。
几天后,一位初学者走进大师的办公室问道:“我找不到我的新程序清单了。你知道它会放在哪儿吗?”
“知道,”大师回答说,“那些清单就堆放在数据中心的台子(platform“可以指操作平台”,也可以指普通的台子--译注)上。”
7.4
编程大师从不惧怕在设计不同程序的岗位间调来调去;管理层的变动不可能对他有所损害;他不会被解雇,即使项目取消了。这是为什么呢?因为他胸有成“道”。 

 
 
 
第八篇 硬件和软件
编程大师如是说:
“没有风,草儿静止不动;没有软件,硬件则无所为用。”
8.1
一位初学者问大师:“我觉察到有一家电脑公司比其它所有的公司都要大得多。它在中遥遥领先,就如同鹤立鸡群一般。它的任意一个部门都可以组成一个完整的企业。这是为什么呢?”
 
大师回答说:“你怎么问这么愚蠢的问题呢?因为那家公司大,所以它就大嘛。如果它仅仅生产硬件,没人愿买;如果它仅仅生产软件,没人愿用;如果它仅仅维护一下系统,人们将把它当作是佣人。然而,因为它把所有这些东西都结合了起来,它便摇身一变,被人们看作是诸神之一。不费吹灰之力,它便能取胜。
 
8.2
一天,一位大师从初学编程者身边经过,他发现这位初学者正在全神贯注于掌上电脑游戏。“对不起,”他说,”我可以看一下吗?”
初学者立刻紧张起来,把那个玩意儿递给了大师。“我知道这种设备提供了三个游戏级别:容易、中等和高难,”大师说,“然而每个这样的设备都有另一个级别,在这一级,游戏机既不想赢人,也不想被人打败。”
 
“请问,尊敬的大师,”初学者恳求道,“怎样才能找到这个奇妙的级别设置呢?”
大师把那个玩意儿扔到地上,踩到粉碎。突然间,那个初学者明白了什么。
8.3
曾经有一个程序员,擅长在微电脑上编程,“瞧,我在这里过得多好呀,”他对另一位来访的程序员说,这位程序员是在大型主机上工作的,“我有自己的操作系统和文件存储设备,我不必和其他任何人分享我的资源。这里的软件自相一致,很容易使用。你为何不辞去现在的工作,加入到我这里来呢?”
 
于是,主机程序员开始向他的这位朋友描绘他的系统:“主机就像一位陷入沉思的圣人一样,端坐在数据中心。它的磁盘器首尾声相连,就如同机器的海洋。这里的软件既像钻石一样能多面反射光芒,又像原始丛林一样复杂难测。这里的程序,各具特色,它们像湍急的河流穿过系统。这就是我乐于此处的原因。
 
微电脑程序员听到这里,陷入了沉默。但这两位程序员至死都保持着友谊。
8.4
在去硅谷的路上,硬件碰上了软件。软件说:“你是阴,我是阳。如果我们携手同路,我们将闻名于世,并能赚大笔的钱。”于是,这一对阴和阳便一同往前走,想着怎么征服世界。
 
不一会儿,他们遇到了固件(firmware,硬件和软件结合在一起的部件,如IC卡--译注),他衣衫褴褛,手里拄着根带刺的拐杖,蹒跚地走着。固件对他们说:“道存在于阴、阳之外。它默默无闻,静如止水。它不追求名誉,所以没人知道它的存在;它不追求财富,因其自身完整圆满。它存在于空间和时间之外。”
 
软件和硬件,自觉惭愧,掉头回家去了。 

 
 
第九章 尾声
编程大师如是说:
“现在该是你出师的时侯了。”

关于23种设计模式的有趣见解

一、创建型模式

1、FACTORY
追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory

工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

2、BUILDER
MM最爱听的就是“我爱你”这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到MM我只要按对应的键,它就能够用相应的语言说出“我爱你”这句话了,国外的MM也可以轻松搞掂,这就是我的“我爱你”builder。(这一定比美军在伊拉克用的翻译机好卖)

建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。

3、FACTORY METHOD
请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说“要一个汉堡”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

4、PROTOTYPE
跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。(100块钱一份,你要不要)

原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。

5、SINGLETON
俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事)

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。

二、结构型模式

6、ADAPTER
在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)

适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。

7、BRIDGE
早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了

桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。

8、COMPOSITE
Mary今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite模式了,你会了没有?

合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。

9、DECORATOR
Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?

装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

10、FACADE
我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。

门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

11、FLYWEIGHT
每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。

享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

12、PROXY
跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

三、行为模式

13、CHAIN OF RESPONSIBLEITY
晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!

责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

14、COMMAND
俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。”,:-(

命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

15、INTERPRETER
俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了。

解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。

 

16、ITERATOR
我爱上了Mary,不顾一切的向她求婚。

Mary:“想要我跟你结婚,得答应我的条件”

我:“什么条件我都答应,你说吧”

Mary:“我看上了那个一克拉的钻石”

我:“我买,我买,还有吗?”

Mary:“我看上了湖边的那栋别墅”

我:“我买,我买,还有吗?”

Mary:“你的小弟弟必须要有50cm长”

我脑袋嗡的一声,坐在椅子上,一咬牙:“我剪,我剪,还有吗?”

……

迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。

17、MEDIATOR
四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。

调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。

18、MEMENTO
同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。

备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

19、OBSERVER
想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦

观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

20、STATE
跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说“有事情啦”,对你不讨厌但还没喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。

状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

21、STRATEGY
跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。

策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

22、TEMPLATE METHOD
看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现);

模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

23、VISITOR
情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;

访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。

ANT打包编译部署工具

1.安装apache-ant-1.5.4或其他版本

2.配置环境变量:

    ANT_HOME=ant安装目录   

    PATH=%PATH%;%ANT_HOME%\bin                                                                                             

3.build.xml

<!--Ant(another neat tool---另一个整洁的工具)-->

<?xml version="1.0"?>
<project default="dist" name="Project A">
    
<description>
        一段描述信息,没有实际作用。
    
</description>
    
<!--property 元素相当于变量,存放一定的值,一旦给定以后不能改变,只能通过控制台命令行给初始值。
                一般用 value 给其赋值,这里用 location 代替 value 是因为 location 属性专门设
                计用于以平台无关的方式包含文件系统路径,也就是当前路径。
-->
    
<property name="srcDir" location="."/>
    
<property name="buildDir" location="classes"/>
    
<property name="distDir" location="dist"/>
    
    
<!--一个target 元素就是一个任务,一个步骤,如果命令行没有指定开始任务,则由 project 元素的 default 属性决定要执行的任务-->
    
<target name="init" description="在元素中指定描述信息!">
        
<echo message="这里的信息将被输出到控制台!">
        
<!--tstamp 元素一般不定义属性和内容,不产生任何输出;相反,它根据当前系统时间和日期设置 Ant 以下属性:
        属性                说明                                    例子
        DSTAMP            设置为当前日期,默认格式为yyyymmdd        20031217
        TSTAMP            设置为当前时间,默认格式为hhmm            1603
        TODAY            设置为当前日期,带完整的月份            2003 年12 月17 日
-->
        
<tstamp/>
        
<!--mkdir 元素用来创建目录 dir 属性用来指定目录路径-->
        
<!--delete 元素用来删除目录 ${ buildDir} 引用 property 元素指定的 name 为 buildDir 的属性变量-->
        
<mkdir dir="${ buildDir}"/>
        
<mkdir dir="${ distDir}"/>
        
<!--文件操作:
            <copy file="src/Test.java" tofile="src/TestCopy.java"/> 把文件 Test.java 拷贝一份 TestCopy.java 
            <move file="src/Test.java" tofile="src/TestCopy.java"/> 把文件 Test.java 重新命名为 TestCopy.java
            <copy file="src/Test.java" todir="archive"/> 把文件 Test.java 从 src 目录复制一份到 archive 目录下
            <move file="src/Test.java" todir="archive"/> 把文件 Test.java 从 src 目录下移动到 archive 目录下
            <replace file="input.txt" token="old" value="new"/>replace 任务,它执行文件中的查找和替换操作。
            token 属性指定要查找的字符串,
            value 属性指定一个新的字符串,
            查找到的标记字符串的所有实例都被替换为这个新的字符串.
            替换操作将在文件本身之内的适当位置进行。为了提供更详细的输出,可把 summary 属性设置为
            true。这将导致该任务输出找到和替换的标记字符串实例的数目。
            <copy todir="archive">---使用模式匹配复制多个文件
                <fileset dir="src">--fileset 代替 file 属性
                    <include name="*.java"/>--包含 src 目录下所有的 *.java 文件
                    <exclude name="*.class"/>--不包含 src 目录下所有的 *.class 文件
                </fileset>
            </copy>
-->
    
</target>

    
<!--target 元素的 depends 属性用来设置多个 target 元素之间的依赖关系,如果要执行某个任务,必须先执行 depends 属性指定的所有
        它所依赖的 target 元素,只有它所依赖的所有 target 元素都执行完了才能执行它自己
-->
    
<target name="com" depends="init">
        
<!--javac 元素把 srcdir 指定目录下所有 *.java 文件编译成 *.class 文件到 destdir 指定的目录下!
            如果不指定 destdir 属性,那么默认编译到同一目录下。其他属性:
             classpath:等价于javac 的-classpath 选项。
             debug="true":指示编译器应该带调试信息编译源文件。
             fork="true":当你希望指定编译器的某些内存选项,或者需要使用一种不同级别的编译器的时候。该属性设置为“true”
             executable="d:sdk141injavac":指定一个不同的 javac 可执行文件。
             memoryMaximumSize="128m":向上边指定的 javac 传递一个最大内存设置
-->
        
<javac srcdir="${ srcDir}" destdir="${ buildDir}"/>
    
</target>

    
<target name="dist" depends="com">
        
<!--jar 元素:在编译 Java 源文件之后,结果类文件通常被打包到一个 JAR 文件中,这个文件类似 zip 归档文
            件。每个 JAR 文件都包含一个清单文件,它可以指定该 JAR 文件的属性。
            还可以把目录下的所有内容打包为 WAR 文件
            destfile:打包后的文件全名
            basedir:要打包的文件路径
-->
        
<jar destfile="${ distDir}/package-${ DSTAMP}.jar" basedir="${ buildDir}">
            
<!--manifest 属性允许指定一个用作该 JAR 文件的清单的文件。清单文件的内容还可以使用 manifest
                任务在生成文件中指定。这个任务能够像文件系统写入一个清单文件,或者能够实际嵌套在 jar 之
                内,以便一次性地创建清单文件和 JAR 文件。
-->
            
<manifest>
                
<attribute name="Built-By" value="${ user.name}"/>
                
<attribute name="Main-Class" value="package.Main"/>
            
</manifest>
        
</jar>
        
<jar destfile="${ distDir}/package-src-${ DSTAMP}.jar" basedir="${ srcDir}"/>
    
</target>

    
<target name="zip" depends="dist">
        
<!--zip 元素用来创建 zip 包,和 jar 元素相似-->
        
<zip destfile="output.zip" basedir="output"/>
        
<!--相同的语法也可用于创建 tar 文件。 还可以使用 GZip 和 BZip 任务来压缩文件。-->
        
<tar destfile="output.tar" basedir="output"/>
        
<gzip src="output.tar" zipfile="output.tar.gz"/>
        
<!--解压缩和提取文件同样也很简单.-->
        
<unzip src="output.tar.gz" dest="extractDir"/>
        
<!--还可以包括 overwrite 属性来控制覆盖行为。默认设置是覆盖与正在被提取的归档文件中的条目相
            匹配的所有现有文件。相关的任务名称是 untar、unjar、gunzip 和 bunzip2。
-->
    
</target>

    
<!--编写一个 clean目标来从目标目录移除生成的任何类文件是个很好的习惯。如果想要确
        保所有源文件都已编译,就可以使用这个任务。这种行为刻画了 Ant 的许多任务的特点:
        如果某个任务能够确定所请求的操作不需要执行,那么该操作就会被跳过。
-->
    
<target name="clean">
        
<delete dir="${ buildDir}"/>
        
<delete dir="${ distDir}"/>
    
</target>
</project>

网页常用小技巧

1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键 
<table border oncontextmenu=return(false)><td>no</table> 可用于Table 

2. <body onselectstart="return false"> 取消选取、防止复制 

3. onpaste="return false" 不准粘贴 

4. oncopy="return false;" oncut="return false;" 防止复制 

5. <link rel="Shortcut Icon" href="favicon.ico"> IE地址栏前换成自己的图标 

6. <link rel="Bookmark" href="favicon.ico"> 可以在收藏夹中显示出你的图标 

7. <input style="ime-mode:disabled"> 关闭输入法 

8. 永远都会带着框架 
<script language="javascript"><!-- 
if (window == top)top.location.href = "frames.htm"; //frames.htm为框架网页 
// --></script> 

9. 防止被人frame 
<script LANGUAGE=javascript><!--  
if (top.location != self.location)top.location=self.location; 
// --></script> 

10. 网页将不能被另存为 
<noscript><iframe src=*.html></iframe></noscript>  

11. <input type=button value=查看网页源代码  
onclick="window.location = "view-source:"+ "http://www.gonet8.com""> 

12.删除时确认 
<a href="javascript:if(confirm("确实要删除吗?"))location="boos.asp?&areyou=删除&page=1"">删除</a>  

13. 取得控件的绝对位置 
//javascript 
<script language="javascript"> 
function getIE(e){  
var t=e.offsetTop; 
var l=e.offsetLeft; 
while(e=e.offsetParent){  
t+=e.offsetTop; 
l+=e.offsetLeft; 

alter("top="+t+"/nleft="+l); 

</script> 

//VBscript 
<script language="VBscript"><!-- 
function getIE() 
dim t,l,a,b 
set a=document.all.img1 
t=document.all.img1.offsetTop 
l=document.all.img1.offsetLeft 
while a.tagName<>"BODY" 
set a = a.offsetParent 
t=t+a.offsetTop 
l=l+a.offsetLeft 
wend 
msgbox "top="&t&chr(13)&"left="&l,64,"得到控件的位置" 
end function 
--></script> 

14. 光标是停在文本框文字的最后 
<script language="javascript"> 
function cc() 
{  
var e = event.srcElement; 
var r =e.createTextRange(); 
r.moveStart("character",e.value.length); 
r.collapse(true); 
r.select(); 

</script> 
<input type=text name=text1 value="123" onfocus="cc()"> 

15. 判断上一页的来源 
javascript: 
document.referrer 

16. 最小化、最大化、关闭窗口 
<object id=hh1 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">  
<param name="Command" value="Minimize"></object> 
<object id=hh2 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">  
<param name="Command" value="Maximize"></object> 
<OBJECT id=hh3 classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"> 
<PARAM NAME="Command" VALUE="Close"></OBJECT> 
<input type=button value=最小化 onclick=hh1.Click()> 
<input type=button value=最大化 onclick=hh2.Click()> 
<input type=button value=关闭 onclick=hh3.Click()> 
本例适用于IE 

17.屏蔽功能键Shift,Alt,Ctrl 
<script> 
function look(){   
if(event.shiftKey)  
alter("禁止按Shift键!"); //可以换成ALT CTRL 
}  
document.onkeydown=look;  
</script> 

18. 网页不会被缓存 
<META HTTP-EQUIV="pragma" CONTENT="no-cache"> 
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> 
<META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 
或者<META HTTP-EQUIV="expires" CONTENT="0"> 

19.怎样让表单没有凹凸感? 
<input type=text style="border:1 solid #000000">  
或 
<input type=text style="border-left:none; border-right:none; border-top:none; border-bottom:  

1 solid #000000"></textarea> 

20.<div><span>&<layer>的区别?  
<div>(division)用来定义大段的页面元素,会产生转行  
<span>用来定义同一行内的元素,跟<div>的唯一区别是不产生转行  
<layer>是ns的标记,ie不支持,相当于<div> 



21.让弹出窗口总是在最上面: 
<body onblur="this.focus();"> 

22.不要滚动条?  
让竖条没有:  
<body style="overflow:scroll;overflow-y:hidden">  
</body>  
让横条没有:  
<body style="overflow:scroll;overflow-x:hidden">  
</body>  
两个都去掉?更简单了  
<body scroll="no">  
</body>  

23.怎样去掉图片链接点击后,图片周围的虚线? 
<a href="#" onFocus="this.blur()"><img src="logo.jpg" border=0></a> 

24.电子邮件处理提交表单 
<form name="form1" method="post" action="mailto:****@***.com" enctype="text/plain">  
<input type=submit> 
</form> 

25.在打开的子窗口刷新父窗口的代码里如何写? 
window.opener.location.reload() 

26.如何设定打开页面的大小 
<body onload="top.resizeTo(300,200);"> 
打开页面的位置<body onload="top.moveBy(300,200);"> 

27.在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动  
<STYLE>  
body  
{ background-image:url(logo.gif); background-repeat:no-repeat;  
background-position:center;background-attachment: fixed}  
</STYLE>  

28. 检查一段字符串是否全由数字组成 
<script language="javascript"><!-- 
function checkNum(str){ return str.match(//D/)==null} 
alter(checkNum("1232142141")) 
alter(checkNum("123214214a1")) 
// --></script> 

29. 获得一个窗口的大小 
document.body.clientWidth; document.body.clientHeight 

30. 怎么判断是否是字符 
if (/[^/x00-/xff]/g.test(s)) alter("含有汉字"); 
else alter("全是字符"); 

31.TEXTAREA自适应文字行数的多少 
<textarea rows=1 name=s1 cols=27 onpropertychange="this.style.posHeight=this.scrollHeight"> 
</textarea> 

32. 日期减去天数等于第二个日期 
<script language=javascript> 
function cc(dd,dadd) 
{  
//可以加上错误处理 
var a = new Date(dd) 
a = a.valueOf() 
a = a - dadd * 24 * 60 * 60 * 1000 
a = new Date(a) 
alter(a.getFullYear() + "年" + (a.getMonth() + 1) + "月" + a.getDate() + "日") 

cc("12/23/2002",2)  

JSP生成图片验证码

1。建立一个JSP页面(image.jsp),把下面的代码全部复制到image.jsp下。
<%@   pagecontentType="image/jpeg" import="java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*"   %>
<%!
Color   getRandColor(
int   fc,int   bc)...//给定范围获得随机颜色
                Random   random   =   new   Random();
                
if(fc>255)   fc=255;
                
if(bc>255)   bc=255;
                
int   r=fc+random.nextInt(bc-fc);
                
int   g=fc+random.nextInt(bc-fc);
                
int   b=fc+random.nextInt(bc-fc);
                
return   new   Color(r,g,b);
                }

%>
<%
//设置页面不缓存
response.setHeader("Pragma","No-cache");
response.setHeader(
"Cache-Control","no-cache");
response.setDateHeader(
"Expires",   0);

//   在内存中创建图象
int   width=60,   height=20;
BufferedImage   image   
=   new   BufferedImage(width,   height,   BufferedImage.TYPE_INT_RGB);

//   获取图形上下文
Graphics   g   =   image.getGraphics();

//生成随机类
Random   random   =   new   Random();

//   设定背景色
g.setColor(getRandColor(200,250));
g.fillRect(
0,   0,   width,   height);

//设定字体
g.setFont(new   Font("Comic   Sans   MS",Font.PLAIN,20));

//画边框
//g.setColor(new   Color());
//g.drawRect(0,0,width-1,height-1);


//   随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
g.setColor(getRandColor(160,200));
for   (int   i=0;i<155;i++)
...
  
int   x   =   random.nextInt(width);
  
int   y   =   random.nextInt(height);
                
int   xl   =   random.nextInt(12);
                
int   yl   =   random.nextInt(12);
  g.drawLine(x,y,x
+xl,y+yl);
}


//   取随机产生的认证码(4位数字)
String   sRand="";
for   (int   i=0;i<4;i++)...
        String   rand
=String.valueOf(random.nextInt(10));
        sRand
+=rand;
        
//   将认证码显示到图象中
        g.setColor(new   Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
//调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
        g.drawString(rand,13*i+6,16);
}


//   将认证码存入SESSION
session.setAttribute("rand",sRand);

//   图象生效
g.dispose();

//   输出图象到页面
ImageIO.write(image,   "JPEG",   response.getOutputStream());
%>

2。然后在要使用的地方象引用图片一样引用此JSP文件:

<img src="image.jsp">

struts+hibernate项目debug总结

javax.servlet.ServletException: Cannot retrieve mapping for action /companyNews

struts-config.xml中没有写相关companyNews的action.

============================================
在myeclipse增加一个jar包时候,先打开项目properties,选择java build path --> libraries -->add external JARs 选择到需要的jar包,加入后,发现,他这个包加载的是绝对路径,而我们的项目需要cvs共享,无法

commit到cvs服务器。myeclipse有以下提示信息:
2 build path entries are missing.

解决办法是,关闭myeclipse,用notepad打开项目目录下的 .classpath, 手动修改成相对路径,并且检查,指定的相对路径中是否真正添加了jar包,
启动myeclipse就可以上传新添加的jar包了。


=============================================
在使用junit/StrutsTest时候,报错:
java.lang.UnsupportedClassVersionError: junit/framework/TestListener (Unsupported major.minor version 49.0)

原因是如果是jdk1.4的话,只能使用junit3.8以下版本,如果是使用jdk1.5的话,必须使用junit4.0以上版本。

 

 


==========================================================

Cannot find ActionMappings or ActionformBeans collection
原因是:web.xml文件中没有配置struts-config.xml的相关信息。


============================================================

org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application


缺少jstl的相关jar和web.xml配置
jstl.jar
<taglib>
    <taglib-uri>http://java.sun.com/jstl/fmt</taglib-uri>
    <taglib-location>/WEB-INF/fmt.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/fmt-rt</taglib-uri>
    <taglib-location>/WEB-INF/fmt-rt.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/core</taglib-uri>
    <taglib-location>/WEB-INF/c.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/core-rt</taglib-uri>
    <taglib-location>/WEB-INF/c-rt.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/sql</taglib-uri>
    <taglib-location>/WEB-INF/sql.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/sql-rt</taglib-uri>
    <taglib-location>/WEB-INF/sql-rt.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/x</taglib-uri>
    <taglib-location>/WEB-INF/x.tld</taglib-location>
</taglib>

<taglib>
    <taglib-uri>http://java.sun.com/jstl/x-rt</taglib-uri>
    <taglib-location>/WEB-INF/x-rt.tld</taglib-location>
</taglib>


==========================================================

Failed to load or instantiate TagLibraryValidator class: org.apache.taglibs.standard.tlv.JstlCoreTLV


standard.jar没有放在lib里面


===========================================================

创建oracle表的时候,使用了,role和comment关键字,建议不要将这两个关键字作为表名和字段名。
建议用toad或plsql developer创建表,这样软件会提示关键字。
=============================================================

[ERROR] XMLHelper - Error parsing XML: XML InputStream(18) Attribute name "column" associated with an element type "key" must be followed by the ' = ' character.
[ERROR] Configuration - Could not configure datastore from input stream <org.dom4j.DocumentException: Error on line 18 of document  : Attribute name "column" associated with an element type "key"

must be followed by the ' = ' character. Nested exception: Attribute name "column" associated with an element type "key" must be followed by the ' = ' character.>org.dom4j.DocumentException: Error

on line 18 of document  : Attribute name "column" associated with an element type "key" must be followed by the ' = ' character. Nested exception: Attribute name "column" associated with an element

type "key" must be followed by the ' = ' character.

 

xml 语法错误,key语法中应该类似这样的写法 <key column="id">
=============================================================
hibernate3,对象中一对多的one方,必须写private Set pays=new HashSet();,否则包错java.lang.NullPointerException ,
而hibernate2中private Set pays;却不报错;

=============================================================
[DEBUG] AbstractSaveEventListener - generated identifier: 1, using strategy: org.hibernate.id.IncrementGenerator
org.hibernate.PropertyValueException: not-null property references a null or transient value: com.xxx.yyy.company
 at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)

<many to one >中的设置应该设置为not-null="false" ,设置为not-null="true"则报以上错误
================================================================


org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.xxx.yyy.Company

在save的同时也需要save其他的表,然后再flush()
=========================================================================

Parse Fatal Error at line 12 column 1

struts-config.xml文件被修改,语法错误。检查语法。

========================================

org.hibernate.QueryException: could not resolve property: userid of: com.xxx.yyy.Pay

使用到外键userid的时候,必须使用userinfo.userid方法才能得到。

=========================================
javax.naming.NameNotFoundException: Name hibernate_connection_factory is not bound in this Context
原因:hibernate的数据库映射.xml文件有配置错误,导致hibernate_connection_factory无法绑定数据库。
例如many-to-one设置了以后,仍然在其中设置相冲突的<property>属性。

==========================================

[WARN] RequestProcessor - Unhandled Exception thrown: class java.lang.NullPointerException
必须将使用到的对象new起来。

============================================
GROUP BY 表达式的查询必须满足如下:
select 子句后的每一项必需出现在group by 子句中,除非该项使用了聚集函数。

===========================================
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.xxx.yyy.Company

要级联保存,多次session.save()
==============================================
java.lang.IllegalArgumentException: id to load is required for loading
原因:

session.load(Company.class,payForm.getCompanyId());
load()方法第二个参数必须是searlizable,并且必须是和数据库映射类的属性值类型一致,即使强制转换都不行。


=============================================
[INFO] DefaultLoadEventListener - Error performing load command <org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.xxx.yyy.Company#0]

>org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.xxx.yyy.Company#0]


表示你现在查询的对象所关联的对象有问题,一般是因为数据的问题(该对象所关联的对象找不到),数据的错误,影响了程序正常执行。

========================================================
eclipse 3.1,myeclipse 4错误
Deployment is out of date due to changes in the underlying project contents . You'll need to mannally 'Redeploy' the project to update the deployed archive.
原因
tomcat 中部署的某个文件的拒绝访问影响了部署。
重启后,去除tomcat中部署的文件。重新在eclipse中设置部署。
为什么需要重启?
因为:google desktop软件正在对我部署的一个300MB大文件进行索引,锁定了这个大文件,我估计google desktop需要对这个文件索引半个小时以上。因此,eclipse无法对过去部署的文件,做先删除后重新部署的工作。

===================================================
ERROR LazyInitializationException:19 - could not initialize proxy - the owning Session was closed
 
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
解决办法。
 cmpy=(CompanyEdit)ss.load(CompanyEdit.class,companyId1);
   Hibernate.initialize(cmpy);//强制初始化cmpy,否则ss.close()后,cmpy将消失.

====================================================
javascript错误
行: 56
字符: 45
错误: 未结束的字符串常量
代码: 0

是编码的问题!用ANSI编码另存后就好了。

====================================================
[WARN] JDBCExceptionReporter - SQL Error: 904, SQLState: 42000
[ERROR] JDBCExceptionReporter - ORA-00904: 无效列名

[INFO] DefaultLoadEventListener - Error performing load command <org.hibernate.exception.SQLGrammarException: could not load an entity: [com.xxx.yyy.Sellinfo#1]

>org.hibernate.exception.SQLGrammarException: could not load an entity: [com.xxx.yyy.Sellinfo#1]

***.hbm.xml文件中的某个列名和数据库中的不同。


=======================================================
ConnectionManager - unclosed connection, forgot to call close() on your session?


原因:没有关闭hibernate的session的transaction。或者没有关闭session

=======================================================
[WARN] SellCommentDAO - org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.xxx.yyy.SellComment#7]

原因:父亲对象(one方)设置cascade="save-update" 时,直接删除子对象时,会报错,
处理方法:save信息需要级联操作,delete时候也要用相同的原理。
SellComment sellComment=new SellComment();
sellComment = (SellComment)session.load(SellComment.class,sellCommentId);//获取儿子对象
Long sellInfoId=sellComment.getSellInfo().getSellId();//获取父亲id
SellInfo sellinfo=(SellInfo)session.load(SellInfo.class,sellInfoId);//获取父亲对象
sellInfo.getSellComments().remove(sellComment);//断绝父子关系
sellComment.setSellInfo(null);//断绝子父关系
session.delete(sellComment);//删除儿子
session.flush();

Struts 应用转移到 Struts 2 一

   有很多人都很熟悉 Struts, 无论是从项目中直接获得的实战经验还是从书中了解到的。我们这一系列文章,将通过一个由 Stuts 转移到 Struts2 简单的例子向大家展现Struts2的所有特征。
    在我们开始这个例子之前,你需要去知道一点 Struts2的背景知识。 在第一部分的文章中,我们将介绍Struts2与Struts的核心框架的不同点,以助于更好地了解其他方面的整合。第二部分中,我们将深入探讨 actions 的差别, action相关的框架特征,和action配置。在最后一部分中,我们将会讲述 user interface,我们也会讲到其架构,UI构件,themes 和标签。 还有如何为你的应用加上新的外观。
    我们并不打算谈及迁移过程的所有细节方面,我们只是从出发点开始介绍Struts2 的概念和现在可用的所有特征。但拥有这些知识,你将在以后Struts2的应用中无往而不利。
   
Struts的历史
    Struts的第一个版本 是在 2001年5月份发布。它提供了一个Web应用的解决方案,如何让 JSPs 和 servlets 共存去提供清晰的分离视图和业务和应用逻辑的架构。在Struts之前,最通常的做法是在JSP中加入业务和应用逻辑,或者在servlets中生成视图。
    自从第一个版本的发布, Struts 实际上已成为业界公认的Web应用标准。但随着时间的推移,Web应用框架经常变化的需求,产生了几个下一代 Struts的解决方案。其中两个可选方案是Shale 和 Struts Ti。 Shale 是一个基于构建的框架,并在最近成为 Apache 中的重要项目。而 Struts Ti 则是继续坚持 MVC模式的基础上改进,继续Struts的成功经验。
    WebWork项目是在2002年3月发布的,它对Struts式框架进行了革命性改进,引进了不少新的思想,概念和功能,但和原Struts代码并不兼容。WebWork是一个成熟的框架,经过了好几次重大的改进与发布。在2005年12月,WebWork与Struts Ti决定合拼, 再此同时, Struts Ti 改名为 Struts Action Framework 2.0,成为Struts真正的下一代。
请求如何运作
    在我们开始详细探讨如何转移Struts到Struts2之前,让我们来看看整个请求流程在新架构中是如何运作的。你会注意到在整个请求的生命周期,仍是以controller作主体,而且所有的概念还都是你以前所熟悉的, 就如:

通过URL请求的参数来调用Actions来把数据传给server.
所有的Servlet objects (request, response, session,之类.) 仍然可以在Action中获取
 
下图展示了整个请求的概要过程:

整个请求过程可以分为六步骤:

一个请求产生并经由框架处理 - 框架根据请求匹配相应的配置,如使用哪些拦截器,action 类和结果。
请求通过一系列的拦截器 - 拦截器,和拦截器组经配置后,能处理不同等级的请求,它们为请求提供了各种预处理,切面处理。这和Struts的使用 Jakarta Commons Chain 构件的 RequestProcessor类很相似。
调用 Action - 产生一个新的action对象实例,并提供请求所调用的处理逻辑的方法。Struts2 可以在配置action时为请求分配其指定的方法。我们在第二部文章中将对这步骤进行进一步讨论;
调用产生的结果 - 获取通过action的方法处理后返回来的结果,匹配其result class并调用产生的实例。有种情况是在UI模板去生成HTML时才去处理这些结果。如果在这种情况下,在Struts2 模板中的tags能直接返回到 action 中,取结果来呈现界面。 
请求再次经过一系列的拦截器处理后返回 - 请求反顺序通过与原来进入时的拦截器链, 当然,你也可以配置在这个过程中减少或增加拦截器处理.
请求返回到用户 - 最后一步是由 control 返回到servlet。通常是生成HTML返回到user, 但你也可以指定返回的HTTP头或HTTP重定向。
 
你应该已注意到,Struts2与Struts的差别。最明显的就是Struts2是pull-MVC 架构,就是可以直接从Action中获取所需要的数据,而不是像Struts那样必须把 beans 存到page, request,或者session中才能获取。这个我们将在下一章中详细提及。
配置框架
首先最重要的是,让框架能通过web.xml在servlet containers里运行。
下面这个就是大家都熟悉的 Struts在 web.xml里的配置方法
   
  <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
在 Struts2 中,这个有少许改变,最明显的是dispatcher 由servlet转为servlet filter, 其配置和servlet一样简单,如下:
   
 <filter>
         <filter-name>webwork</filter-name>
           <filter-class>
            org.apache.struts.action2.dispatcher.FilterDispatcher
   </filter-class>
    </filter>
    <filter-mapping>
   <filter-name>webwork</filter-name>
   <url-pattern>/*</url-pattern>
    </filter-mapping>
和servlet配置一样,filter配置定义了名称(供关联)和filter的类。filter mapping让URI匹配成功的的请求调用该filter。默认情况下,扩展名为".action"。
这个是在default.properties文件里的"struts.action.extension" 属性定义的。

工具箱:  "default.properties"是配置选项定义文件。通过在classpath中包含一个叫"struts.properties"的文件,并设置不同的属性值,你可以覆盖这个默认的配置,实现自己的配置。
对于Struts, servlet配置提供了初始化tag的参数和使用的文件,而Struts2没有这样的配置参数,取而代之的是在classpath下的默认配置文件"struts.xml"。

工具箱/提示: Struts actions(扩展名".do"),Struts2 actions(扩展名".action"),所以Struts和Struts2可以在一个系统中共存。所以最好是保持原先的系统,在新功能的开发上用Struts2, 如果时间和资源允许的情况下再逐步迁移。另一种方法是只是把Struts2的扩展名改为".do",可重用JSPs.
分析Actions
在上面介绍的请求运作流程中,我们谈及了一些Struts和Struts2的不同点。现在我们将较深入地探讨这两个框架中action结构的具体差别。
让我们来回顾一下 Struts 的 action 结构, 主要的形式如下:

public class MyAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
            throws Exception {
        // do the work
        return (mapping.findForward("success"));
    }
}
当实现一个Struts action时, 你需要注意一下问题:

所有的action 都必须继承于base Action 类.
所有的action都必须是线程安全的,因为action是单例的,singleton的.
因为所有的action都必须是线程安全的,所以所有的对象都不能是类属性, 都必须以方法参数的形式传值。
调用action的方法必须命名为 "execute" ( 在Struts中的  DispatchAction 类好像可以用其它方法去执行同一个action ,但实际上在框架中调用的仍然是 "execute" 方法。).
ActionForward 的结果是通过ActionMapping 类中的方法来产生的,通常是"findForward"方法.
 
相比较之下, Struts2的action 提供了很多简单的实现。下面就是个例子

public class MyAction {
   public String execute() throws Exception {
        // do the work
        return "success";
   }
}
首先你会注意到的是,Struts2中的action不再继承于任何类或需要实现任何接口。实际上,它还远不只这些。按照惯例,只有"execute"方法能调用action, 但在Struts2中并非必要,任何声明为public String methodName() 方法都能通过配置来调用action.
另外,你会注意到返回值不再是"ActionForward ",而是String, 如果你需喜欢String的形式,那在Action接口里有个帮助方法可以提供简单的结果常量,如"success", "none", "error", "input" 和 "login"。
最后,和Struts最大的革命性的不同是, 调用action不再是带参数的。那你如何在获得你所需要的值呢?答案是"inversion of control" 或 "dependency injection", 反转控制(想了解更多可以看Martin Fowler的文章 http://www.martinfowler.com/articles/injection.html)。
为了更好地了解反转控制,让我们来看看一个例子,如何在action处理过程中可以访问到HttpServerRequest 。在我们的例子中,我们用ServletRequestAware 接口,这个接口包含了相应属性的setter,如下

public interface ServletRequestAware {
    public void setServletRequest(HttpServletRequest request);
}
当我们继承这个接口时,我们需要通过setter为我们的HttpServerRequest 属性变量赋值:

public class MyAction implements ServletRequestAware {
   private HttpServletRequest request;
   public void setServletRequest(HttpServletRequest request) {
        this.request = request;
   }
   public String execute() throws Exception {
        // do the work using the request
        return Action.SUCCESS;
   }
}
看起来现在这些属性是类级别的,并不是线程安全的,但是在Struts2里并没有问题,因为每个请求过来的时候都会产生一个新的action对象实例,它并没有和其他请求共享一个对象,所以不需要考虑线程安全问题。
现在我们还有最后一步,就是把action关联上ServletConfigInterceptor 拦截器。这个拦截器继承了ServletRequestAware 接口,并提供了把HttpServletRequest 注入到action中的功能。但是你现在不用担心如何配置这些,我们将在下一篇文章中具体讲述。最重要的是我们明白了拦截器和接口共同为action提供了反转控制的功能。
这个设计的好处是能让action完全和框架解耦。action仅仅是一个被框架使用的简单的POJO。这对于单元测试但来极大的好处, 你能方便的为Struts action实现 StrutsTestCase 或  MockStrutsTestCase 单元测试。
总结
By到现在为止,你应该已经了解了Struts2的整个请求流程,还有高层的框架概念, 你也应该能自己动手配置Struts2的action,和讲出Struts和Struts2的差别了。
在下篇文章中,我们将会介绍一个详细的Struts应用向Struts2迁移的例子,同时我们也会介绍迁移中相关的知识,会讲述如何综合使用JSTL, JSP 和 Struts2,进一步讲述Struts和Struts2的action的差别,Struts2的配置和其他框架元素,和谈到更多的其他相关框架的特征。
 

   在这篇文章中,我们将会更详细地讲述如何由Struts 的action转为Struts 2的action。
一个应用的例子
这个例子选择了大家都熟悉的 - weblog. 简单地介绍下这例子的功能需求:
增加一个新的日志
察看一个日志
修改一个日志
删除一个日志
列出所有日至  
     增删修改(CRUD),是项目中最为普遍的应用。
     业务逻辑类在Struts 和 Struts2 应用都是可共用的。如:

public class BlogService {
     private static List<Blog> blogs = new ArrayList<Blog>();
     public List<Blog> list() {  ... }
     public Blog create(Blog blog) {  ... }
     public void update(Blog blog) {  ... }
     public void delete(int id){  ... }
     public Blog findById(int id) {  ... }
}
 
BlogService 只是个简单的业务逻辑类,并不是接口,Struts 和 Struts2 的action皆可调用其实例。虽然这样设计在实际项目中会带来不必要的耦合,但我们的例子只是集中在讨论web层上,所以无关重要。

工具箱: 在第一篇文章中,我们谈论了在Struts2 actions中的依赖注入的接口注入方式。这个是servlet 相关类(HttpServletRequest, HttpServletResponse, PrincipalProxy, 等.)的主要注入方式,但这并不是唯一的方式。
Struts2 可以使用Spring框架作为默认的容器时,依赖注入的setter方法就可用了。通过在action中加入setter方法(如下演示), Struts2 框架将能从Spring框架中获得正确的信息,并通过setter加载在action中。
public void setBlogService(BlogService service) {
     this.blogService = service;
}
和接口注入方式类似,我们需要一个拦截器来帮助我们完成任务,这就是 ActionAutowiringInterceptor 拦截器。这样我们的业务逻辑类就通过Spring框架管理自动在action被调用之前注入到Struts2得action中。有多种的配置参数(如by name, by type 或 automatically)可供选择,可以让对象和setter匹配的注入的方式根据你的需要而定。

Struts 应用中的代码
     我们首先从Struts讲起。在Struts中,最普遍的做法是,对于每个需求用例(如save,update,remove,list)来说都会有对应的action类,同时也会有相应的action form类。在我们的应用中的这个方式或许不是最佳的实现方式(其他的解决方案包括使用dynamic form或者使用request来分发action),但我们例子中的做法是所有Struts开发者最熟悉的一种形式。了解了这种简单的实现方法,你有能力在迁移到Struts2时,使用更为优秀的方法。
在第一篇文章中我们谈过Struts 和 Struts2 中action的区别。现在我们从UML中再次看看他们的差别。一般来说form在Struts action中的表现形式是:
 
这action form将会在多个action中使用,让我们来看看它:
public class BlogForm extends ActionForm {
     private String id;
     private String title;
     private String entry;
     private String created;
     // public setters and getters for all properties
}
如UML中展示的那样,其中一个限制就是必须继承ActionForm类,另外一个限制就是form中所有属性都必须是String类型,所以所有的getter和setter都必须只能接受String参数和返回String结果。
然后我们来看看action。我们这个例子中的action有view, create 和 update action。
The View Action:
The Create Action:

public class ViewBlogAction extends Action {
     public ActionForward execute(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws Exception {
         BlogService service = new BlogService();
         String id = request.getParameter("id");
         request.setAttribute("blog",service.findById(Integer.parseInt(id)));
          return (mapping.findForward("success"));
     }
}
 
public class SaveBlogEntryAction extends Action {
      public ActionForward execute(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws Exception {
         BlogService service = new BlogService();
         BlogForm blogForm = (BlogForm) form;
         Blog blog = new Blog();
         BeanUtils.copyProperties( blog, blogForm );
         service.create( blog );
         return (mapping.findForward("success"));
     }
}

The Update Action:

public class UpdateBlogEntryAction extends Action {
     public ActionForward execute(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws Exception {
         BlogService service = new BlogService();
         BlogForm blogForm = (BlogForm) form;
         Blog blog = service.findById( Integer.parseInt(blogForm.getId()));
         BeanUtils.copyProperties( blog, blogForm );
         service.update( blog );
         request.setAttribute("blog",blog);
         return (mapping.findForward("success"));
     }
}

这三个action都跟随着同一个模式:
产生一个新的业务逻辑对象实例 - 如前面所提到的,我们使用最直接的方式在action中使用业务逻辑对象,这表示在每个action中都会产生新的业务逻辑对象实例。
从请求中获得数据 - 这是两种形式之一。在view action中,"id"是从HttpServletRequest 对象中直接获取的。而在create 和 update action 中,则从ActionForm 中取值。ActionForm 与 HttpServletRequest 的调用方式其实很相似,唯一不同的ActionForm 是bean的从field中取值。
调用业务逻辑- 现在开始生成调用业务逻辑所需的参数并调用逻辑。如果参数(在view action中)是一个简单对象类型,则转换值时会自动转为正确的类型(如从String转到Integer)。如果参数是复杂的对象类型,,则ActionForm 需要通过BeanUtil 来帮忙转成相应的对象。
设定返回的数据 - 如果需要把数据返回显示给用户,那则要把这个数据设在HttpServletRequest 的attribute 中返回。
返回一个 ActionForward - 所有 Struts action的最后都需要找到并返回其相应的 ActionForward 对象.
     最后的两个action,remove和list action, 只有很少的差别。remove action如下所示,没有用BlogForm类. 通过从request的attribute中获取"id"(和view action相似),就能调用业务逻辑完成其需要的工作。在下面我们介绍配置时,你可以看到它并没有返回任何数据,因为它的"success"返回结果其实是执行remove后再执行了list action来返回信息的。

public class RemoveBlogEntryAction extends Action {
     public ActionForward execute(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws Exception {
         BlogService service = new BlogService();
         String id = request.getParameter("id");
         service.delete(Integer.parseInt(id));
         return (mapping.findForward("success"));
     }
}

list action并不需要任何的用户输入,它只是简单地调用了业务逻辑的无参方法,同时返回所有的Blog对象。
public class ListBlogsAction extends Action {
     public ActionForward execute(ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest request,
                                  HttpServletResponse response)
             throws Exception {
         BlogService service = new BlogService();
         request.setAttribute("bloglist",service.list());
         return (mapping.findForward("success"));
     }
}

Struts 应用转移到 Struts 2 二

向 Struts2 迁移
     在Struts2中,可选的实现方式有很多,可以像Struts那样每个需求用例对应一个action,也可以用一个action对应所有需求用例。但在我们的例子中,使用的方法是我认为最佳的解决方案 - 在一个action类中实现整套CRUD功能。
     也许你人为把list需求用例也同样地整合到同一个action类里会比较好,而我认为把list的功能分到另外一个action中,会减少容易产生的混淆,因为list用例中并不需要Blog这个类作为属性,而在其他用例中则需要。

对于 Struts2的例子, 它的UML模型展示如下:


     每个用例在action中都有自己所对应的方法。从上图中我们可以看到,在BlogAction 中我们有save, update 和 remove三个方法。而ListBlogAction中,没有list这个方法,因为ListBlogAction继承了ActionSupport 类,实际上就是在默认的execute 方法中实现list功能。
     为了更容易看,图中的BlogAction并没有画出它所实现了的三个接口。它们分别是ServletRequestAware 接口,  Prepareable 接口和 ModelDriven 接口。
     首先回顾一下ServletRequestAware, 我们在第一篇文章中已经详细介绍它了。这个ParametersInterceptor 拦截器提供了把HttpServletRequest 自动set到action中的功能,让我们能通过request, 把所需的值传回到JSPs。
     接着看看Preparable 接口, 它会联合PrepareInterceptor拦截器一起工作,让action在执行execute() 方法前, 执行一个prepare()方法,实现在执行前设定,配置或预设一些值在action中。 在我们的例子里,prepare方法会检查blogId 属性,如果为零则这是一个新日志,非零则该日志已经存在,根据blogId取出日志。
     最后我们说说ModelDriven 接口,在上一篇文章中,我们已经了解到 Struts action的很大的不同在于它是需要线程安全的,而在Struts2中则没有这个限制,因为每次的请求都会有一次action对象的初始化和调用。没有了这个限制,能允许Struts2使用类级别的属性变量(特别是getters和setters),从而获得更多编码优势。

和拦截器的功能结合起来, 把HttpServletRequest 中的attribute 注入action中的流程如下所示:

  • 循环读取HTTP request中的attribute
  • 查找当前request attribute中是否有和action中的setter中的属性匹配的
  • 有则根据attribute从HttpServletRequest 里取出其值
  • 把取出来的值由String转成setter中相应的类型
  • 调用setter把该转换后的值注入action中
提示: 当调用action时,如果发现不明原因使不能正确地通过setter注入值情况下,第一步最好是先检查下各拦截器,确保它们都已作用于该action。因为这些意外通常有时由拦截器设置不当形成的,检查是否各个拦截器都已起作用,并看看它们作用的顺序,因为有些情况下它们间会相互影响而产生错误。

    现在我们已经有基于String类型的form bean中取值的方法或者是自动把request的attributes 注入到action的方法,那下一步就是如何把值传入 domain object 或 value / transfer object的属性中去。其实这很简单,你只需要实现ModelDriven 接口(即实现getModel()方法)就可以了,确保ModelDrivenInterceptor 拦截器已作用于action。
    除了会调用action中的setter外,model 首先检查是否有和setter可以匹配当前的attribute名。如果在model中没有这个attribute相应的setter,则会再在action上找相应的setter来设值。
    在BlogAction 的例子中我们可以看到如何很灵活地使用这些方法,首先通过prepare() 方法根据Id获取相应的 Blog model object 或新建一个instance, 然后再根据把request中相应的属性注入Blog instance中和action中。
    以上的两个功能使得现在调用action那么简单 - 调用具体的业务逻辑,和把数据设在HttpServletRequest供返回用


public class BlogAction extends ActionSupport
         
implements ModelDriven, Preparable, ServletRequestAware 

     
private int blogId;
     
private Blog blog;
     
private BlogService service = new BlogService();
     
private HttpServletRequest request;

     
public void setServletRequest(HttpServletRequest httpServletRequest){ 
         
this.request = httpServletRequest;
     }


      
public void setId(int blogId) {
         
this.blogId = blogId;
     }


      
public void prepare() throws Exception 
         
if( blogId==0 ) 
             blog 
= new Blog();
         }
 else 
             blog 
= service.findById(blogId);
         }

     }


      
public Object getModel() 
         
return blog;
     }


      
public String save() 
         service.create(blog);
         
return SUCCESS;
     }

      
public String update() 
         service.update(blog);
         request.setAttribute(
"blog",blog);
         
return SUCCESS;
     }


      
public String remove() 
         service.delete(blogId);
         
return SUCCESS;
     }


      
public String execute() 
         request.setAttribute(
"blog",blog);
         
return SUCCESS;
     }



}
 


最后就是说说 list这个用例了。它同样需要访问HttpServletRequest对象去返回数据给JSP,所以也需要实现ServletRequestAware 接口。但是,因为它并不需要任何输入值,所以就不需要实现其他的接口了。以下是它的具体实现:

public class ListBlogsAction extends ActionSupport implements ServletRequestAware 

     
private BlogService service = new BlogService();
     
private HttpServletRequest request;

     
public void setServletRequest(HttpServletRequest httpServletRequest) 
         
this.request = httpServletRequest;
     }


     
public String execute() 
         request.setAttribute(
"bloglist",service.list());
         
return SUCCESS;
     }


}


这样就完成了我们该实现的action代码了。 在下一篇文章中,当我们新的Struts2用户界面结合时,我们还会进一步简化action的代码。


配置Actions
    在我们调用action之前,我们必须通过XML配置文件去配置它们。
    在Struts中, 我们习惯用在WEB-INF 目录的"struts-config.xml"配置文件,在这里我们需要配置action form和action属性。在Struts2中, 用的是在classpath中的"struts.xml"配置文件, 它看起来好象会稍微复杂一些,因为它需要在配置action的同时也配置其拦截器。

    在Struts中配置 form-beans 节点很容易, 只需要一个唯一的名字,还有就是继承ActionForm类的class作为type。

<struts-config>

     
<form-beans>
         
<form-bean name="blogForm"
    type
="com.fdar.articles.infoq.conversion.struts.BlogForm"/>
     
</form-beans>
     ...

</struts-config> 


在我们的例子中,我们的配置文件有3点不相同:
1. 重定向配置
    在Struts的配置中,每个mapping 都需要提供调用action时所需要对应的路径,Struts默认为".do", 例如paht是"/struts/add"对应于URL"/struts/add.do"。同时也需要一个forward 属性来提供给URL去转向,如"/struts/add.jsp".

<struts-config>
     ...

      
<action-mappings>

          
<action path="/struts/add" forward="/struts/add.jsp"/>
         ...

      
</action-mappings>
</struts-config> 


而Struts2的需要更多的一些配置,如:

<struts>
      
<include file="struts-default.xml"/>

      
<package name="struts2" extends="struts-default" namespace="/struts2">

          
<action name="add" >
             
<result>/struts2/add.jsp</result>
         
</action>
         ...

      
</package>
</struts> 


首先你会注意到的是,代替action-mappings 节点的是includepackage 节点。Struts2可以把配置细分到任意数目的配置文件中,来实现配置可模块化管理。每个配置文件的结构其实都是一样的,不同的只是文件名。
    include 节点中,以文件名作为file 属性,可把所include的文件内容包含到当前文件中。
   package 节点把actions组成一组,其name 属性的值必须是唯一的。
   在 Struts action的配置中, paht属性需要指定完整的URL路径。而在Struts2中,URL是通过package节点中的namespace属性,还有在action 节点中的name 属性, 和action扩展(默认是".action")共同起作用的。在上面的例子中,则URL为"/struts2/add.action"时会调用action。
   package节点除了可以分离命名空间外, package 节点中的 extends 属性,还提供了某种可复合的组成结构。通过继承另外一个package节点,你就能继承那个节点的配置,包括其actions, results, interceptors, exception,等值。在我们的例子中,"struts2" package节点继承了 "struts-default" package 节点(在"struts-default.xml" 文件里定义了该节点) ,注意这个是主要的include文件,所以必须在所有配置之前的第一行中写出。 这个功能有助于大大减少你重复性输入默认配置所浪费的时间。
    最后是result 节点, 它只是存放你这个action所需要转向的URL. 在这里我们没有提及nametype 属性。如果你不想改变它们的默认属性的话,你能忽略不写它们,让你的配置文件看起来更清晰。从action返回的 "success" 的结果将组成这个JSP显示给用户。


2. Action 配置
    在Struts 中forward 节点指定了action处理后,结果将重定向到哪个相应的页面。type属性指定了action的类,scope 属性保证了form beans只在request范围内。

<struts-config>
     ...

      
<action-mappings>

          
<action path="/struts/list" scope="request"
                 type
="com.fdar.articles.infoq.conversion.struts.ListBlogsAction" >
             
<forward name="success" path="/struts/list.jsp"/>
         
</action>
         ...

      
</action-mappings>
</struts-config>


Struts2 的 XML配置和上面提到的基本相同。唯一不同的就是通过class属性为action节点提供了它所需要调用的类的完整路径

<struts>
     ...

      
<package name="struts2" extends="struts-default" namespace="/struts2">

          
<default-interceptor-ref name="defaultStack"/>

          
<action name="list"
                 class
="com.fdar.articles.infoq.conversion.struts2.ListBlogsAction">
             
<result>/struts2/list.jsp</result>
             
<interceptor-ref name="basicStack"/>
         
</action>
         ...

      
</package>
</struts>


如果是用其他的方法而不是用默认的execute 方法去调用action(在BlogAction 类中大多数方法如此), 则需要在action节点的 method 属性里加入方法名,下面就是个例子,这时候update方法将会被调用。

<action name="update" method="update"
    class
="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
        ...
    
</action> 


default-interceptor-refinterceptor-ref 节点有几点不同。在第一篇文章中,我们看到在action被调用之前必须通过一系列的拦截器,而这两个节点就是用来配置拦截器组的。default-interceptor-ref 节点为该package提供了默认的拦截器组。当在action节点中提供 interceptor-ref节点时 ,它就会覆盖默认的拦截器(interceptor-ref 节点能够和单独一个拦截器相关联,或者跟一个拦截器组相关联),在action节点中可以存在多个interceptor-ref节点,处理拦截器组的顺序会和该节点列出的顺序一致。


3. 再重定向配置
    当我们提交表格的时候,我们需要重定向到更新后的结果页面。这个通常称为 "post-redirect pattern" 或, 最近出现的, "flash scope."
    由于这是一个form, 所以在Struts中我们需要为Struts指定一个ActionForm。需要在name属性中提供form的名称,同样地,我们也需要在forward 节点中举加入redirect属性为true。

<struts-config>
     ...

      
<action-mappings>
          
<action path="/struts/save"
                 type
="com.fdar.articles.infoq.conversion.struts.SaveBlogEntryAction"
                 name
="blogForm" scope="request">
             
<forward name="success" redirect="true" path="/struts/list.do"/>
          
</action>
         ...

      
</action-mappings>
</struts-config> 


Struts2 在result 节点里提供了type 属性, 默认情况下是"dispatch", 如果需要重定向,则需要设为 "redirect"。

<struts>
     ...

      <package name
="struts2" extends="struts-default" namespace="/struts2">

          <action name
="save" method="save"
                 class
="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
             <result type
="redirect">list.action</result>
             <interceptor-ref name
="defaultStack"/>
          </action>
         ...

      </package>
</struts> 

总结
    我们并不可能在这篇文章中覆盖所有的内容,如果你需要更好的了解整个框架,还有其他的实现方式和选项,这里有几点可以供你参考:

  • 配置拦截器和拦截器组 - 以Struts2-core JAR 包里的"struts-default.xml" 文件作为例子。"struts-default.xml" 演示了如何配置你自己的拦截器组,包含新的拦截器,你可以尝试实现自己的拦截器。
  • 配置文件中的通配符模式 - 你可以选择使用Struts2中的通配符模式来简化你的配置。
  • 通过 ParameterAware 接口把form值传入maps中 - 你可以在Struct2中配置,让所有request的form属性都存于action的一个map中,这样就不需要专门再为action指定model / transfer / value object了。这和Struts的dynamic form特点很相似。

    也许到现在为,也许你有个疑问,"迁移后我们的界面是否可以完全重用呢?",答案是yes。你能从这里, 下载到我这篇文章中的完整源代码,你可以自己尝试把URL的扩展名由".do" 改为 ".action",使用的页面时一样的。除此之外,其实用JSTL来代替Struts taglib也是很容易的。

4/1/2007

Thinking:Java中static、this、super、final用法

一、static

 

  请先看下面这段程序:

  public class Hello{ 
    public static void main(String[] args){  //(1)
      System.out.println("Hello,world!");   //(2)
    }
  }

  看过这段程序,对于大多数学过Java 的从来说,都不陌生。即使没有学过Java,而学过其它的高级语言,例如C,那你也应该能看懂这段代码的意思。它只是简单的输出“Hello,world”,一点别的用处都没有,然而,它却展示了static关键字的主要用法。

  在1处,我们定义了一个静态的方法名为main,这就意味着告诉Java编译器,我这个方法不需要创建一个此类的对象即可使用。你还得你是怎么运行这个程序吗?一般,我们都是在命令行下,打入如下的命令(加下划线为手动输入):

javac Hello.java
java Hello
Hello,world!

  这就是你运行的过程,第一行用来编译Hello.java这个文件,执行完后,如果你查看当前,会发现多了一个Hello.class文件,那就是第一行产生的Java二进制字节码。第二行就是执行一个Java程序的最普遍做法。执行结果如你所料。在2中,你可能会想,为什么要这样才能输出。好,我们来分解一下这条语句。(如果没有安装Java文档,请到Sun的官方网站浏览J2SE API)首先,System是位于java.lang包中的一个核心类,如果你查看它的定义,你会发现有这样一行:public static final PrintStream out;接着在进一步,点击PrintStream这个超链接,在METHOD页面,你会看到大量定义的方法,查找println,会有这样一行:

public void println(String x)。

  好了,现在你应该明白为什么我们要那样调用了,out是System的一个静态变量,所以可以直接使用,而out所属的类有一个println方法。

静态方法

  通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法。如下所示:

class Simple{ 
   static void go(){ 
     System.out.println("Go...");
   }
}
public class Cal{ 
  public static void main(String[] args){ 
    Simple.go();
  }
}

  调用一个静态方法就是“类名.方法名”,静态方法的使用很简单如上所示。一般来说,静态方法常常为应用程序中的其它类提供一些实用工具所用,在Java的类库中大量的静态方法正是出于此目的而定义的。

静态变量

  静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,当然对于final则另当别论了。看下面这段代码:

class Value{ 
  static int c=0;
  static void inc(){ 
    c++;
  }
}
class Count{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
  public static void main(String[] args){ 
    Value v1,v2;
    v1=new Value();
    v2=new Value();
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    v1.inc();
    prt("v1.c="+v1.c+"  v2.c="+v2.c); 
  }
}

  结果如下:

v1.c=0  v2.c=0
v1.c=1  v2.c=1

  由此可以证明它们共享一块存储区。static变量有点类似于C中的全局变量的概念。值得探讨的是静态变量的初始化问题。我们修改上面的程序:

class Value{ 
  static int c=0;
  Value(){ 
    c=15;
  }
  Value(int i){ 
    c=i;
  }
  static void inc(){ 
    c++;
  }
}
class Count{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
    Value v=new Value(10);
    static Value v1,v2;
    static{ 
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
      v1=new Value(27);
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
      v2=new Value(15);
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
    }

  public static void main(String[] args){ 
    Count ct=new Count();
    prt("ct.c="+ct.v.c);
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    v1.inc();
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    prt("ct.c="+ct.v.c);
  }
}

运行结果如下:

v1.c=0  v2.c=0
v1.c=27  v2.c=27
v1.c=15  v2.c=15
ct.c=10
v1.c=10  v2.c=10
v1.c=11  v2.c=11
ct.c=11

  这个程序展示了静态初始化的各种特性。如果你初次接触Java,结果可能令你吃惊。可能会对static后加大括号感到困惑。首先要告诉你的是,static定义的变量会优先于任何其它非static变量,不论其出现的顺序如何。正如在程序中所表现的,虽然v出现在v1和v2的前面,但是结果却是v1和v2的初始化在v的前面。在static{ 后面跟着一段代码,这是用来进行显式的静态变量初始化,这段代码只会初始化一次,且在类被第一次装载时。如果你能读懂并理解这段代码,会帮助你对static关键字的认识。在涉及到继承的时候,会先初始化父类的static变量,然后是子类的,依次类推。非静态变量不是本文的主题,在此不做详细讨论,请参考Think in Java中的讲解。

静态类

  通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。如下代码所示:

public class StaticCls{ 
  public static void main(String[] args){ 
    OuterCls.InnerCls oi=new OuterCls.InnerCls();
  }
}
class OuterCls{ 
  public static class InnerCls{ 
    InnerCls(){ 
      System.out.println("InnerCls");
    }
   }
}

  输出结果会如你所料:

InnerCls

  和普通类一样。内部类的其它用法请参阅Think in Java中的相关章节,此处不作详解。

二、this & super

  在上一篇拙作中,我们讨论了static的种种用法,通过用static来定义方法或成员,为我们编程提供了某种便利,从某种程度上可以说它类似于C语言中的全局函数和全局变量。但是,并不是说有了这种便利,你便可以随处使用,如果那样的话,你便需要认真考虑一下自己是否在用面向对象的思想编程,自己的程序是否是面向对象的。好了,现在开始讨论this&super这两个关键字的意义和用法。

  在Java中,this通常指当前对象,super则指父类的。当你想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个目的,当然,this的另一个用途是调用当前对象的另一个构造函数,这些马上就要讨论。如果你想引用父类的某种东西,则非super莫属。由于this与super有如此相似的一些特性和与生俱来的某种关系,所以我们在这一块儿来讨论,希望能帮助你区分和掌握它们两个。

在一般方法中

  最普遍的情况就是,在你的方法中的某个形参名与当前对象的某个成员有相同的名字,这时为了不至于混淆,你便需要明确使用this关键字来指明你要使用某个成员,使用方法是“this.成员名”,而不带this的那个便是形参。另外,还可以用“this.方法名”来引用当前对象的某个方法,但这时this就不是必须的了,你可以直接用方法名来访问那个方法,编译器会知道你要调用的是那一个。下面的代码演示了上面的用法:

public class DemoThis{ 
  private String name;
  private int age;
  DemoThis(String name,int age){ 
    setName(name); //你可以加上this来调用方法,像这样:this.setName(name);但这并不是必须的
    setAge(age);
    this.print();
  }  
  public void setName(String name){ 
    this.name=name;//此处必须指明你要引用成员变量
  }
  public void setAge(int age){ 
    this.age=age;
  }
  public void print(){ 
    System.out.println("Name="+name+" Age="+age);//在此行中并不需要用this,因为没有会导致混淆的东西
  }
  public static void main(String[] args){ 
    DemoThis dt=new DemoThis("Kevin","22");
  }
}

  这段代码很简单,不用解释你也应该能看明白。在构造函数中你看到用this.print(),你完全可以用print()来代替它,两者效果一样。下面我们修改这个程序,来演示super的用法。

class Person{ 
  public int c;
  private String name;
  private int age;
  protected void setName(String name){ 
    this.name=name;
  }
  protected void setAge(int age){ 
    this.age=age;
  }
  protected void print(){ 
    System.out.println("Name="+name+" Age="+age);
  }
}
public class DemoSuper extends Person{ 
  public void print(){ 
    System.out.println("DemoSuper:");
    super.print();
  }
  public static void main(String[] args){ 
    DemoSuper ds=new DemoSuper();
    ds.setName("kevin");
    ds.setAge(22);
    ds.print();
  }
}

  在DemoSuper中,重新定义的print方法覆写了父类的print方法,它首先做一些自己的事情,然后调用父类的那个被覆写了的方法。输出结果说明了这一点:

DemoSuper:
Name=kevin Age=22

  这样的使用方法是比较常用的。另外如果父类的成员可以被子类访问,那你可以像使用this一样使用它,用“super.父类中的成员名”的方式,但常常你并不是这样来访问父类中的成员名的。

在构造函数中

  构造函数是一种特殊的方法,在对象初始化的时候自动调用。在构造函数中,this和super也有上面说的种种使用方式,并且它还有特殊的地方,请看下面的例子:

class Person{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
  Person(){ 
    prt("A Person.");
  }
  Person(String name){ 
    prt("A person name is:"+name);
  }
}
public class Chinese extends Person{ 
  Chinese(){ 
    super();  //调用父类构造函数(1)
    prt("A chinese.");//(4)
  }
  Chinese(String name){ 
    super(name);//调用父类具有相同形参的构造函数(2)
    prt("his name is:"+name);
  }
  Chinese(String name,int age){ 
    this(name);//调用当前具有相同形参的构造函数(3)
    prt("his age is:"+age);
  }
  public static void main(String[] args){ 
    Chinese cn=new Chinese();
    cn=new Chinese("kevin");
    cn=new Chinese("kevin",22);
  }
}

  在这段程序中,this和super不再是像以前那样用“.”连接一个方法或成员,而是直接在其后跟上适当的参数,因此它的意义也就有了变化。super后加参数的是用来调用父类中具有相同形式的构造函数,如1和2处。this后加参数则调用的是当前具有相同参数的构造函数,如3处。当然,在Chinese的各个重载构造函数中,this和super在一般方法中的各种用法也仍可使用,比如4处,你可以将它替换为“this.prt”(因为它继承了父类中的那个方法)或者是“super.prt”(因为它是父类中的方法且可被子类访问),它照样可以正确运行。但这样似乎就有点画蛇添足的味道了。

  最后,写了这么多,如果你能对“this通常指代当前对象,super通常指代父类”这句话牢记在心,那么本篇便达到了目的,其它的你自会在以后的编程实践当中慢慢体会、掌握。另外关于本篇中提到的继承,请参阅相关Java教程。

三、final

  final在Java中并不常用,然而它却为我们提供了诸如在C语言中定义常量的功能,不仅如此,final还可以让你控制你的成员、方法或者是一个类是否可被覆写或继承等功能,这些特点使final在Java中拥有了一个不可或缺的地位,也是学习Java时必须要知道和掌握的关键字之一。

final成员

  当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。下面这段代码演示了这一点:

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class Bat{ 
    final PI=3.14;          //在定义时便给址值
    final int i;            //因为要在构造函数中进行初始化,所以此处便不可再给值
    final List list;        //此变量也与上面的一样
    Bat(){ 
        i=100;
        list=new LinkedList();
    }
    Bat(int ii,List l){ 
        i=ii;
        list=l;
    }
    public static void main(String[] args){ 
        Bat b=new Bat();
        b.list.add(new Bat());
        //b.i=25;
        //b.list=new ArrayList();
        System.out.println("I="+b.i+" List Type:"+b.list.getClass());
        b=new Bat(23,new ArrayList());
        b.list.add(new Bat());
        System.out.println("I="+b.i+" List Type:"+b.list.getClass());
    }
}

  此程序很简单的演示了final的常规用法。在这里使用在构造函数中进行初始化的方法,这使你有了一点灵活性。如Bat的两个重载构造函数所示,第一个缺省构造函数会为你提供默认的值,重载的那个构造函数会根据你所提供的值或类型为final变量初始化。然而有时你并不需要这种灵活性,你只需要在定义时便给定其值并永不变化,这时就不要再用这种方法。在main方法中有两行语句注释掉了,如果你去掉注释,程序便无法通过编译,这便是说,不论是i的值或是list的类型,一旦初始化,确实无法再更改。然而b可以通过重新初始化来指定i的值或list的类型,输出结果中显示了这一点:

I=100 List Type:class java.util.LinkedList
I=23 List Type:class java.util.ArrayList

  还有一种用法是定义方法中的参数为final,对于基本类型的变量,这样做并没有什么实际意义,因为基本类型的变量在调用方法时是传值的,也就是说你可以在方法中更改这个参数变量而不会影响到调用语句,然而对于对象变量,却显得很实用,因为对象变量在传递时是传递其引用,这样你在方法中对对象变量的修改也会影响到调用语句中的对象变量,当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用,如下代码所示:

public class INClass{ 
   void innerClass(final String str){ 
        class IClass{ 
            IClass(){ 
                System.out.println(str);
            }
        }
        IClass ic=new IClass();
    }
  public static void main(String[] args){ 
      INClass inc=new INClass();
      inc.innerClass("Hello");
  }
}

final方法

  将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。另外有一种被称为inline的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。

final类

  当你将final用于类身上时,你就需要仔细考虑,因为一个final类是无法被任何人继承的,那也就意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。对于final类中的成员,你可以定义其为final,也可以不是final。而对于方法,由于所属类为final的关系,自然也就成了final型的。你也可以明确的给final类中的方法加上一个final,但这显然没有意义。

  下面的程序演示了final方法和final类的用法:

final class final{ 
    final String str="final Data";
    public String str1="non final data";
    final public void print(){ 
        System.out.println("final method.");
    }
    public void what(){ 
        System.out.println(str+"\n"+str1);
    }
}
public class FinalDemo {    //extends final 无法继承
    public static void main(String[] args){ 
        final f=new final();
        f.what();
        f.print();
    }
}

  从程序中可以看出,final类与普通类的使用几乎没有差别,只是它失去了被继承的特性。final方法与非final方法的区别也很难从程序行看出,只是记住慎用。

final在设计模式中的应用

  在设计模式中有一种模式叫做不变模式,在Java中通过final关键字可以很容易的实现这个模式,在讲解final成员时用到的程序Bat.java就是一个不变模式的例子。如果你对此感兴趣,可以参考阎宏博士编写的《Java与模式》一书中的讲解。

  到此为止,this,static,supert和final的使用已经说完了,如果你对这四个关键字已经能够大致说出它们的区别与用法,那便说明你基本已经掌握。然而,世界上的任何东西都不是完美无缺的,Java提供这四个关键字,给程序员的编程带来了很大的便利,但并不是说要让你到处使用,一旦达到滥用的程序,便适得其反,所以在使用时请一定要认真考虑。

  请先看下面这段程序:

  public class Hello{ 
    public static void main(String[] args){  //(1)
      System.out.println("Hello,world!");   //(2)
    }
  }

  看过这段程序,对于大多数学过Java 的从来说,都不陌生。即使没有学过Java,而学过其它的高级语言,例如C,那你也应该能看懂这段代码的意思。它只是简单的输出“Hello,world”,一点别的用处都没有,然而,它却展示了static关键字的主要用法。

  在1处,我们定义了一个静态的方法名为main,这就意味着告诉Java编译器,我这个方法不需要创建一个此类的对象即可使用。你还得你是怎么运行这个程序吗?一般,我们都是在命令行下,打入如下的命令(加下划线为手动输入):

javac Hello.java
java Hello
Hello,world!

  这就是你运行的过程,第一行用来编译Hello.java这个文件,执行完后,如果你查看当前,会发现多了一个Hello.class文件,那就是第一行产生的Java二进制字节码。第二行就是执行一个Java程序的最普遍做法。执行结果如你所料。在2中,你可能会想,为什么要这样才能输出。好,我们来分解一下这条语句。(如果没有安装Java文档,请到Sun的官方网站浏览J2SE API)首先,System是位于java.lang包中的一个核心类,如果你查看它的定义,你会发现有这样一行:public static final PrintStream out;接着在进一步,点击PrintStream这个超链接,在METHOD页面,你会看到大量定义的方法,查找println,会有这样一行:

public void println(String x)。

  好了,现在你应该明白为什么我们要那样调用了,out是System的一个静态变量,所以可以直接使用,而out所属的类有一个println方法。

静态方法

  通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法。如下所示:

class Simple{ 
   static void go(){ 
     System.out.println("Go...");
   }
}
public class Cal{ 
  public static void main(String[] args){ 
    Simple.go();
  }
}

  调用一个静态方法就是“类名.方法名”,静态方法的使用很简单如上所示。一般来说,静态方法常常为应用程序中的其它类提供一些实用工具所用,在Java的类库中大量的静态方法正是出于此目的而定义的。

静态变量

  静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控此块存储空间,当然对于final则另当别论了。看下面这段代码:

class Value{ 
  static int c=0;
  static void inc(){ 
    c++;
  }
}
class Count{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
  public static void main(String[] args){ 
    Value v1,v2;
    v1=new Value();
    v2=new Value();
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    v1.inc();
    prt("v1.c="+v1.c+"  v2.c="+v2.c); 
  }
}

  结果如下:

v1.c=0  v2.c=0
v1.c=1  v2.c=1

  由此可以证明它们共享一块存储区。static变量有点类似于C中的全局变量的概念。值得探讨的是静态变量的初始化问题。我们修改上面的程序:

class Value{ 
  static int c=0;
  Value(){ 
    c=15;
  }
  Value(int i){ 
    c=i;
  }
  static void inc(){ 
    c++;
  }
}
class Count{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
    Value v=new Value(10);
    static Value v1,v2;
    static{ 
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
      v1=new Value(27);
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
      v2=new Value(15);
      prt("v1.c="+v1.c+"  v2.c="+v2.c);
    }

  public static void main(String[] args){ 
    Count ct=new Count();
    prt("ct.c="+ct.v.c);
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    v1.inc();
    prt("v1.c="+v1.c+"  v2.c="+v2.c);
    prt("ct.c="+ct.v.c);
  }
}

运行结果如下:

v1.c=0  v2.c=0
v1.c=27  v2.c=27
v1.c=15  v2.c=15
ct.c=10
v1.c=10  v2.c=10
v1.c=11  v2.c=11
ct.c=11

  这个程序展示了静态初始化的各种特性。如果你初次接触Java,结果可能令你吃惊。可能会对static后加大括号感到困惑。首先要告诉你的是,static定义的变量会优先于任何其它非static变量,不论其出现的顺序如何。正如在程序中所表现的,虽然v出现在v1和v2的前面,但是结果却是v1和v2的初始化在v的前面。在static{ 后面跟着一段代码,这是用来进行显式的静态变量初始化,这段代码只会初始化一次,且在类被第一次装载时。如果你能读懂并理解这段代码,会帮助你对static关键字的认识。在涉及到继承的时候,会先初始化父类的static变量,然后是子类的,依次类推。非静态变量不是本文的主题,在此不做详细讨论,请参考Think in Java中的讲解。

静态类

  通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。如下代码所示:

public class StaticCls{ 
  public static void main(String[] args){ 
    OuterCls.InnerCls oi=new OuterCls.InnerCls();
  }
}
class OuterCls{ 
  public static class InnerCls{ 
    InnerCls(){ 
      System.out.println("InnerCls");
    }
   }
}

  输出结果会如你所料:

InnerCls

  和普通类一样。内部类的其它用法请参阅Think in Java中的相关章节,此处不作详解。

二、this & super

  在上一篇拙作中,我们讨论了static的种种用法,通过用static来定义方法或成员,为我们编程提供了某种便利,从某种程度上可以说它类似于C语言中的全局函数和全局变量。但是,并不是说有了这种便利,你便可以随处使用,如果那样的话,你便需要认真考虑一下自己是否在用面向对象的思想编程,自己的程序是否是面向对象的。好了,现在开始讨论this&super这两个关键字的意义和用法。

  在Java中,this通常指当前对象,super则指父类的。当你想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个目的,当然,this的另一个用途是调用当前对象的另一个构造函数,这些马上就要讨论。如果你想引用父类的某种东西,则非super莫属。由于this与super有如此相似的一些特性和与生俱来的某种关系,所以我们在这一块儿来讨论,希望能帮助你区分和掌握它们两个。

在一般方法中

  最普遍的情况就是,在你的方法中的某个形参名与当前对象的某个成员有相同的名字,这时为了不至于混淆,你便需要明确使用this关键字来指明你要使用某个成员,使用方法是“this.成员名”,而不带this的那个便是形参。另外,还可以用“this.方法名”来引用当前对象的某个方法,但这时this就不是必须的了,你可以直接用方法名来访问那个方法,编译器会知道你要调用的是那一个。下面的代码演示了上面的用法:

public class DemoThis{ 
  private String name;
  private int age;
  DemoThis(String name,int age){ 
    setName(name); //你可以加上this来调用方法,像这样:this.setName(name);但这并不是必须的
    setAge(age);
    this.print();
  }  
  public void setName(String name){ 
    this.name=name;//此处必须指明你要引用成员变量
  }
  public void setAge(int age){ 
    this.age=age;
  }
  public void print(){ 
    System.out.println("Name="+name+" Age="+age);//在此行中并不需要用this,因为没有会导致混淆的东西
  }
  public static void main(String[] args){ 
    DemoThis dt=new DemoThis("Kevin","22");
  }
}

  这段代码很简单,不用解释你也应该能看明白。在构造函数中你看到用this.print(),你完全可以用print()来代替它,两者效果一样。下面我们修改这个程序,来演示super的用法。

class Person{ 
  public int c;
  private String name;
  private int age;
  protected void setName(String name){ 
    this.name=name;
  }
  protected void setAge(int age){ 
    this.age=age;
  }
  protected void print(){ 
    System.out.println("Name="+name+" Age="+age);
  }
}
public class DemoSuper extends Person{ 
  public void print(){ 
    System.out.println("DemoSuper:");
    super.print();
  }
  public static void main(String[] args){ 
    DemoSuper ds=new DemoSuper();
    ds.setName("kevin");
    ds.setAge(22);
    ds.print();
  }
}

  在DemoSuper中,重新定义的print方法覆写了父类的print方法,它首先做一些自己的事情,然后调用父类的那个被覆写了的方法。输出结果说明了这一点:

DemoSuper:
Name=kevin Age=22

  这样的使用方法是比较常用的。另外如果父类的成员可以被子类访问,那你可以像使用this一样使用它,用“super.父类中的成员名”的方式,但常常你并不是这样来访问父类中的成员名的。

在构造函数中

  构造函数是一种特殊的方法,在对象初始化的时候自动调用。在构造函数中,this和super也有上面说的种种使用方式,并且它还有特殊的地方,请看下面的例子:

class Person{ 
  public static void prt(String s){ 
    System.out.println(s);
  }
  Person(){ 
    prt("A Person.");
  }
  Person(String name){ 
    prt("A person name is:"+name);
  }
}
public class Chinese extends Person{ 
  Chinese(){ 
    super();  //调用父类构造函数(1)
    prt("A chinese.");//(4)
  }
  Chinese(String name){ 
    super(name);//调用父类具有相同形参的构造函数(2)
    prt("his name is:"+name);
  }
  Chinese(String name,int age){ 
    this(name);//调用当前具有相同形参的构造函数(3)
    prt("his age is:"+age);
  }
  public static void main(String[] args){ 
    Chinese cn=new Chinese();
    cn=new Chinese("kevin");
    cn=new Chinese("kevin",22);
  }
}

  在这段程序中,this和super不再是像以前那样用“.”连接一个方法或成员,而是直接在其后跟上适当的参数,因此它的意义也就有了变化。super后加参数的是用来调用父类中具有相同形式的构造函数,如1和2处。this后加参数则调用的是当前具有相同参数的构造函数,如3处。当然,在Chinese的各个重载构造函数中,this和super在一般方法中的各种用法也仍可使用,比如4处,你可以将它替换为“this.prt”(因为它继承了父类中的那个方法)或者是“super.prt”(因为它是父类中的方法且可被子类访问),它照样可以正确运行。但这样似乎就有点画蛇添足的味道了。

  最后,写了这么多,如果你能对“this通常指代当前对象,super通常指代父类”这句话牢记在心,那么本篇便达到了目的,其它的你自会在以后的编程实践当中慢慢体会、掌握。另外关于本篇中提到的继承,请参阅相关Java教程。

三、final

  final在Java中并不常用,然而它却为我们提供了诸如在C语言中定义常量的功能,不仅如此,final还可以让你控制你的成员、方法或者是一个类是否可被覆写或继承等功能,这些特点使final在Java中拥有了一个不可或缺的地位,也是学习Java时必须要知道和掌握的关键字之一。

final成员

  当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。下面这段代码演示了这一点:

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class Bat{ 
    final PI=3.14;          //在定义时便给址值
    final int i;            //因为要在构造函数中进行初始化,所以此处便不可再给值
    final List list;        //此变量也与上面的一样
    Bat(){ 
        i=100;
        list=new LinkedList();
    }
    Bat(int ii,List l){ 
        i=ii;
        list=l;
    }
    public static void main(String[] args){ 
        Bat b=new Bat();
        b.list.add(new Bat());
        //b.i=25;
        //b.list=new ArrayList();
        System.out.println("I="+b.i+" List Type:"+b.list.getClass());
        b=new Bat(23,new ArrayList());
        b.list.add(new Bat());
        System.out.println("I="+b.i+" List Type:"+b.list.getClass());
    }
}

  此程序很简单的演示了final的常规用法。在这里使用在构造函数中进行初始化的方法,这使你有了一点灵活性。如Bat的两个重载构造函数所示,第一个缺省构造函数会为你提供默认的值,重载的那个构造函数会根据你所提供的值或类型为final变量初始化。然而有时你并不需要这种灵活性,你只需要在定义时便给定其值并永不变化,这时就不要再用这种方法。在main方法中有两行语句注释掉了,如果你去掉注释,程序便无法通过编译,这便是说,不论是i的值或是list的类型,一旦初始化,确实无法再更改。然而b可以通过重新初始化来指定i的值或list的类型,输出结果中显示了这一点:

I=100 List Type:class java.util.LinkedList
I=23 List Type:class java.util.ArrayList

  还有一种用法是定义方法中的参数为final,对于基本类型的变量,这样做并没有什么实际意义,因为基本类型的变量在调用方法时是传值的,也就是说你可以在方法中更改这个参数变量而不会影响到调用语句,然而对于对象变量,却显得很实用,因为对象变量在传递时是传递其引用,这样你在方法中对对象变量的修改也会影响到调用语句中的对象变量,当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用,如下代码所示:

public class INClass{ 
   void innerClass(final String str){ 
        class IClass{ 
            IClass(){ 
                System.out.println(str);
            }
        }
        IClass ic=new IClass();
    }
  public static void main(String[] args){ 
      INClass inc=new INClass();
      inc.innerClass("Hello");
  }
}

final方法

  将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。另外有一种被称为inline的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。

final类

  当你将final用于类身上时,你就需要仔细考虑,因为一个final类是无法被任何人继承的,那也就意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。对于final类中的成员,你可以定义其为final,也可以不是final。而对于方法,由于所属类为final的关系,自然也就成了final型的。你也可以明确的给final类中的方法加上一个final,但这显然没有意义。

  下面的程序演示了final方法和final类的用法:

final class final{ 
    final String str="final Data";
    public String str1="non final data";
    final public void print(){ 
        System.out.println("final method.");
    }
    public void what(){ 
        System.out.println(str+"\n"+str1);
    }
}
public class FinalDemo {    //extends final 无法继承
    public static void main(String[] args){ 
        final f=new final();
        f.what();
        f.print();
    }
}

  从程序中可以看出,final类与普通类的使用几乎没有差别,只是它失去了被继承的特性。final方法与非final方法的区别也很难从程序行看出,只是记住慎用。

final在设计模式中的应用

  在设计模式中有一种模式叫做不变模式,在Java中通过final关键字可以很容易的实现这个模式,在讲解final成员时用到的程序Bat.java就是一个不变模式的例子。如果你对此感兴趣,可以参考阎宏博士编写的《Java与模式》一书中的讲解。

  到此为止,this,static,supert和final的使用已经说完了,如果你对这四个关键字已经能够大致说出它们的区别与用法,那便说明你基本已经掌握。然而,世界上的任何东西都不是完美无缺的,Java提供这四个关键字,给程序员的编程带来了很大的便利,但并不是说要让你到处使用,一旦达到滥用的程序,便适得其反,所以在使用时请一定要认真考虑。

JAVA学习之路:不走弯路,就是捷径

  0.引言
  
  在ChinaITLAB导师制辅导中,笔者发现问得最多的问题莫过于"如何学习编程?JAVA该如何学习?"。类似的问题回答多了,难免会感觉厌烦,就萌生了写下本文的想法。到时候再有人问起类似的问题,我可以告诉他(她),请你去看看《JAVA学习之路》。拜读过台湾蔡学镛先生的《JAVA夜未眠》,有些文章如《JAVA学习之道》等让我们确实有共鸣,本文题目也由此而来。
  
  软件开发之路是充满荆棘与挑战之路,也是充满希望之路。JAVA学习也是如此,没有捷径可走。梦想像《天龙八部》中虚竹一样被无崖子醍醐灌顶而轻松获得一甲子功力,是很不现实的。每天仰天大叫"天神啊,请赐给我一本葵花宝典吧",殊不知即使你获得了葵花宝典,除了受自宫其身之苦外,你也不一定成得了"东方不败",倒是成"西方失败"的几率高一点。
  
  "不走弯路,就是捷径",佛经说的不无道理。
  
  1.如何学习程序设计?
  
  JAVA是一种平台,也是一种程序设计语言,如何学好程序设计不仅仅适用于JAVA,对C++等其他程序设计语言也一样管用。有编程高手认为,JAVA也好C也好没什么分别,拿来就用。为什么他们能达到如此境界?我想是因为编程语言之间有共通之处,领会了编程的精髓,自然能够做到一通百通。如何学习程序设计理所当然也有许多共通的地方。
  
  1.1 培养兴趣
  
  兴趣是能够让你坚持下去的动力。如果只是把写程序作为谋生的手段的话,你会活的很累,也太对不起自己了。多关心一些行业趣事,多想想盖茨。不是提倡天天做白日梦,但人要是没有了梦想,你觉得有味道吗?可能像许多深圳本地农民一样,打打麻将,喝喝功夫茶,拜拜财神爷;每个月就有几万十几万甚至更多的进帐,凭空多出个"食利阶层"。你认为,这样有味道吗?有空多到一些程序员论坛转转,你会发现,他们其实很乐观幽默,时不时会冒出智慧的火花。
  
  1.2 慎选程序设计语言
  
  男怕入错行,女怕嫁错郎。初学者选择程序设计语言需要谨慎对待。软件开发不仅仅是掌握一门编程语言了事,它还需要其他很多方面的背景知识。软件开发也不仅仅局限于某几个领域,而是已经渗透到了各行各业几乎每一个角落。
  
  如果你对硬件比较感兴趣,你可以学习C语言/汇编语言,进入硬件开发领域。如果你对电信的行业知识及网络比较熟悉,你可以在C/C++等之上多花时间,以期进入电信软件开发领域。如果你对操作系统比较熟悉,你可以学习C/Linux等等,为Linux内核开发/驱动程序开发/嵌入式开发打基础。如果你想介入到应用范围最广泛的应用软件开发(包括电子商务电子政务系统)的话,你可以选择J2EE或.NET,甚至LAMP组合。每个领域要求的背景知识不一样。做应用软件需要对数据库等很熟悉。总之,你需要根据自己的特点来选择合适你的编程语言。
  
  1.3 要脚踏实地,快餐式的学习不可取
  
  先分享一个故事。
  
  有一个小朋友,他很喜欢研究生物学,很想知道那些蝴蝶如何从蛹壳里出来,变成蝴蝶便会飞。 有一次,他走到草原上面看见一个蛹,便取了回家,然后看着,过了几天以后,这个蛹出了一条裂痕,看见里面的蝴蝶开始挣扎,想抓破蛹壳飞出来。 这个过程达数小时之久,蝴蝶在蛹里面很辛苦地拼命挣扎,怎么也没法子走出来。这个小孩看着看着不忍心,就想不如让我帮帮它吧,便随手拿起剪刀在蛹上剪开,使蝴蝶破蛹而出。 但蝴蝶出来以后,因为翅膀不够力,变得很臃肿,飞不起来。
  
  这个故事给我们的启示是:欲速则不达。
  
  浮躁是现代人最普遍的心态,能怪谁?也许是贫穷落后了这么多年的缘故,就像当年的大跃进一样,都想大步跨入共产主义社会。现在的软件公司、客户、政府、学校、培训机构等等到处弥漫着浮躁之气。就拿笔者比较熟悉的深圳IT培训行业来说吧,居然有的打广告宣称"参加培训,100%就业",居然报名的学生不少,简直是藐视天下程序员。社会环境如是,我们不能改变,只能改变自己,闹市中的安宁,弥足珍贵。许多初学者C++/JAVA没开始学,立马使用VC/JBuilder,会使用VC/JBuilder开发一个Hello World程序,就忙不迭的向世界宣告,"我会软件开发了",简历上也大言不惭地写上"精通VC/JAVA"。结果到软件公司面试时要么被三两下打发走了,要么被驳的体无完肤,无地自容。到处碰壁之后才知道捧起《C++编程思想》《JAVA编程思想》仔细钻研,早知如此何必当初呀。
  
  "你现在讲究简单方便,你以后的路就长了",好象也是佛经中的劝戒。
  
  1.4 多实践,快实践
  
  彭端淑的《为学一首示子侄》中有穷和尚与富和尚的故事。
  
  从前,四川边境有两个和尚,一个贫穷,一个有钱。一天,穷和尚对富和尚说:"我打算去南海朝圣,你看怎么样?"富和尚说:"这里离南海有几千里远,你靠什么去呢?"穷和尚说:"我只要一个水钵,一个饭碗就够了。"富和尚为难地说:"几年前我就打算买条船去南海,可至今没去成,你还是别去吧!" 一年以后,富和尚还在为租赁船只筹钱,穷和尚却已经从南海朝圣回来了。
  
  这个故事可解读为:任何事情,一旦考虑好了,就要马上上路,不要等到准备周全之后,再去干事情。假如事情准备考虑周全了再上路的话,别人恐怕捷足先登了。软件开发是一门工程学科,注重的就是实践,"君子动口不动手"对软件开发人员来讲根本就是错误的,他们提倡"动手至上",但别害怕,他们大多温文尔雅,没有暴力倾向,虽然有时候蓬头垢面的一副"比尔盖茨"样。有前辈高人认为,学习编程的秘诀是:编程、编程、再编程,笔者深表赞同。不仅要多实践,而且要快实践。我们在看书的时候,不要等到你完全理解了才动手敲代码,而是应该在看书的同时敲代码,程序运行的各种情况可以让你更快更牢固的掌握知识点。
  
  1.5 多参考程序代码
  
  程序代码是软件开发最重要的成果之一,其中渗透了程序员的思想与灵魂。许多人被《仙剑奇侠传》中凄美的爱情故事感动,悲剧的结局更有一种缺憾美。为什么要以悲剧结尾?据说是因为写《仙剑奇侠传》的程序员失恋而安排了这样的结局,他把自己的感觉融入到游戏中,却让众多的仙剑迷扼腕叹息。
  
  多多参考代码例子,对JAVA而言有参考文献[4.3],有API类的源代码(JDK安装目录下的src.zip文件),也可以研究一些开源的软件或框架。
  
  1.6 加强英文阅读能力
  
  对学习编程来说,不要求英语, 但不能一点不会,。最起码像JAVA API文档(参考文献[4.4])这些东西还是要能看懂的,连猜带懵都可以;旁边再开启一个"金山词霸"。看多了就会越来越熟练。在学JAVA的同时学习英文,一箭双雕多好。另外好多软件需要到英文网站下载,你要能够找到它们,这些是最基本的要求。英语好对你学习有很大的帮助。口语好的话更有机会进入管理层,进而可以成为剥削程序员的"周扒皮"。
  
  1.7 万不得已才请教别人
  
  笔者在ChinaITLab网校的在线辅导系统中解决学生问题时发现,大部分的问题学生稍做思考就可以解决。请教别人之前,你应该先回答如下几个问题。
  
  你是否在google中搜索了问题的解决办法?
  
  你是否查看了JAVA API文档?
  
  你是否查找过相关书籍?
  
  你是否写代码测试过?
  
  如果回答都是"是"的话,而且还没有找到解决办法,再问别人不迟。要知道独立思考的能力对你很重要。要知道程序员的时间是很宝贵的。
  
  1.8 多读好书
  
  书中自有颜如玉。比尔·盖茨是一个饱读群书的人。虽然没有读完大学,但九岁的时候比尔·盖茨就已经读完了所有的百科全书,所以他精通天文、历史、地理等等各类学科,可以说比尔·盖茨不仅是当今世界上金钱的首富,而且也可以称得上是知识的巨富。
  
  笔者在给学生上课的时候经常会给他们推荐书籍,到后来学生实在忍无可忍开始抱怨,"天呐,这么多书到什么时候才能看完了","学软件开发,感觉上了贼船"。这时候,我的回答一般是,"别着急,什么时候带你们去看看我的书房,到现在每月花在技术书籍上的钱400元,这在软件开发人员之中还只能够算是中等的",学生当场晕倒。(注:这一部分学生是刚学软件开发的)
  
  对于在JAVA开发领域的好书在笔者另外一篇文章中会专门点评。该文章可作为本文的姊妹篇。
  
  1.9 使用合适的工具
  
  工欲善其事必先利其器。软件开发包含各种各样的活动,需求收集分析、建立用例模型、建立分析设计模型、编程实现、调试程序、自动化测试、持续集成等等,没有工具帮忙可以说是寸步难行。工具可以提高开发效率,使软件的质量更高BUG更少。组合称手的武器。到飞花摘叶皆可伤人的境界就很高了,无招胜有招,手中无剑心中有剑这样的境界几乎不可企及。在笔者另外一篇文章中会专门阐述如何选择合适的工具(该文章也可作为本文的姊妹篇)。


  
  2.软件开发学习路线
  
  两千多年的儒家思想孔孟之道,中庸的思想透入骨髓,既不冒进也不保守并非中庸之道,而是找寻学习软件开发的正确路线与规律。
  
  从软件开发人员的生涯规划来讲,我们可以大致分为三个阶段,软件工程师→软件设计师→架构设计师或项目管理师。不想当元帅的士兵不是好士兵,不想当架构设计师或项目管理师的程序员也不是好的程序员。我们应该努力往上走。让我们先整理一下开发应用软件需要学习的主要技术。
  
  A.基础理论知识,如操作系统、编译原理、数据结构与算法、计算机原理等,它们并非不重要。如不想成为计算机科学家的话,可以采取"用到的时候再来学"的原则。
  
  B.一门编程语言,现在基本上都是面向对象的语言,JAVA/C++/C#等等。如果做WEB开发的话还要学习HTML/JavaScript等等。
  
  C.一种方法学或者说思想,现在基本都是面向对象思想(OOA/OOD/设计模式)。由此而衍生的基于组件开发CBD/面向方面编程AOP等等。
  
  D.一种关系型数据库,ORACLE/SqlServer/DB2/MySQL等等
  
  E.一种提高生产率的IDE集成开发环境JBuilder/Eclipse/VS.NET等。
  
  F.一种UML建模工具,用ROSE/VISIO/钢笔进行建模。
  
  G.一种软件过程,RUP/XP/CMM等等,通过软件过程来组织软件开发的众多活动,使开发流程专业化规范化。当然还有其他的一些软件工程知识。
  
  H.项目管理、体系结构、框架知识。
  
  正确的路线应该是:B→C→E→F→G→H。
  
  还需要补充几点:
  
  1).对于A与C要补充的是,我们应该在实践中逐步领悟编程理论与编程思想。新技术虽然不断涌现,更新速度令人眼花燎乱雾里看花;但万变不离其宗,编程理论与编程思想的变化却很慢。掌握了编程理论与编程思想你就会有拨云见日之感。面向对象的思想在目前来讲是相当关键的,是强势技术之一,在上面需要多投入时间,给你的回报也会让你惊喜。
  
  2).对于数据库来说是独立学习的,这个时机就由你来决定吧。
  
  3).编程语言作为学习软件开发的主线,而其余的作为辅线。
  
  4).软件工程师着重于B、C、E、 D;软件设计师着重于B、C、E、 D、F;架构设计师着重于C、F、H。
  
  3.如何学习JAVA?
  
  3.1 JAVA学习路线
  
  3.1.1 基础语法及JAVA原理
  
  基础语法和JAVA原理是地基,地基不牢靠,犹如沙地上建摩天大厦,是相当危险的。学习JAVA也是如此,必须要有扎实的基础,你才能在J2EE、J2ME领域游刃有余。参加SCJP(SUN公司认证的JAVA程序员)考试不失为一个好方法,原因之一是为了对得起你交的1200大洋考试费,你会更努力学习,原因之二是SCJP考试能够让你把基础打得很牢靠,它要求你跟JDK一样熟悉JAVA基础知识;但是你千万不要认为考过了SCJP就有多了不起,就能够获得软件公司的青睐,就能够获取高薪,这样的想法也是很危险的。获得"真正"的SCJP只能证明你的基础还过得去,但离实际开发还有很长的一段路要走。
  
  3.1.2 OO思想的领悟
  
  掌握了基础语法和JAVA程序运行原理后,我们就可以用JAVA语言实现面向对象的思想了。面向对象,是一种方法学;是独立于语言之外的编程思想;是CBD基于组件开发的基础;属于强势技术之一。当以后因工作需要转到别的面向对象语言的时候,你会感到特别的熟悉亲切,学起来像喝凉水这么简单。
  
  使用面向对象的思想进行开发的基本过程是:
  
  ●调查收集需求。
  
  ●建立用例模型。
  
  ●从用例模型中识别分析类及类与类之间的静态动态关系,从而建立分析模型。
  
  ●细化分析模型到设计模型。
  
  ●用具体的技术去实现。
  
  ●测试、部署、总结。
  
  3.1.3 基本API的学习
  
  进行软件开发的时候,并不是什么功能都需要我们去实现,也就是经典名言所说的"不需要重新发明轮子"。我们可以利用现成的类、组件、框架来搭建我们的应用,如SUN公司编写好了众多类实现一些底层功能,以及我们下载过来的JAR文件中包含的类,我们可以调用类中的方法来完成某些功能或继承它。那么这些类中究竟提供了哪些方法给我们使用?方法的参数个数及类型是?类的构造器需不需要参数?总不可能SUN公司的工程师打国际长途甚至飘洋过海来告诉你他编写的类该如何使用吧。他们只能提供文档给我们查看,JAVA DOC文档(参考文献4.4)就是这样的文档,它可以说是程序员与程序员交流的文档。
  
  基本API指的是实现了一些底层功能的类,通用性较强的API,如字符串处理/输入输出等等。我们又把它成为类库。熟悉API的方法一是多查JAVA DOC文档(参考文献4.4),二是使用JBuilder/Eclipse等IDE的代码提示功能。
  
  3.1.4 特定API的学习
  
  JAVA介入的领域很广泛,不同的领域有不同的API,没有人熟悉所有的API,对一般人而言只是熟悉工作中要用到的API。如果你做界面开发,那么你需要学习Swing/AWT/SWT等API;如果你进行网络游戏开发,你需要深入了解网络API/多媒体API/2D3D等;如果你做WEB开发,就需要熟悉Servlet等API啦。总之,需要根据工作的需要或你的兴趣发展方向去选择学习特定的API。
  
  3.1.5 开发工具的用法
  
  在学习基础语法与基本的面向对象概念时,从锻炼语言熟练程度的角度考虑,我们推荐使用的工具是Editplus/JCreator+JDK,这时候不要急于上手JBuilder/Eclipse等集成开发环境,以免过于关注IDE的强大功能而分散对JAVA技术本身的注意力。过了这一阶段你就可以开始熟悉IDE了。
  
  程序员日常工作包括很多活动,编辑、编译及构建、调试、单元测试、版本控制、维持模型与代码同步、文档的更新等等,几乎每一项活动都有专门的工具,如果独立使用这些工具的话,你将会很痛苦,你需要在堆满工具的任务栏上不断的切换,效率很低下,也很容易出错。在JBuilder、Eclipse等IDE中已经自动集成编辑器、编译器、调试器、单元测试工具JUnit、自动构建工具ANT、版本控制工具CVS、DOC文档生成与更新等等,甚至可以把UML建模工具也集成进去,又提供了丰富的向导帮助生成框架代码,让我们的开发变得更轻松。应该说IDE发展的趋势就是集成软件开发中要用到的几乎所有工具。
  
  从开发效率的角度考虑,使用IDE是必经之路,也是从一个学生到一个职业程序员转变的里程碑。
  
  JAVA开发使用的IDE主要有Eclipse、JBuilder、JDeveloper、NetBeans等几种;而Eclipse、JBuilder占有的市场份额是最大的。JBuilder在近几年来一直是JAVA集成开发环境中的霸主,它是由备受程序员尊敬的Borland公司开发,在硝烟弥漫的JAVA IDE大战中,以其快速的版本更新击败IBM的Visual Age for JAVA等而成就一番伟业。IBM在Visual Age for JAVA上已经无利可图之下,干脆将之贡献给开源社区,成为Eclipse的前身,真所谓"柳暗花明又一村"。浴火重生的Eclipse以其开放式的插件扩展机制、免费开源获得广大程序员(包括几乎所有的骨灰级程序员)的青睐,极具发展潜力。
  
  3.1.6 学习软件工程
  
  对小型项目而言,你可能认为软件工程没太大的必要。随着项目的复杂性越来越高,软件工程的必要性才会体现出来。参见"软件开发学习路线"小节。
  
  3.2学习要点
  
  确立的学习路线之后,我们还需要总结一下JAVA的学习要点,这些要点在前文多多少少提到过,只是笔者觉得这些地方特别要注意才对它们进行汇总,不要嫌我婆婆妈妈啊。
  
  3.2.1勤查API文档
  
  当程序员编写好某些类,觉得很有成就感,想把它贡献给各位苦难的同行。这时候你要使用"javadoc"工具(包含在JDK中)生成标准的JAVA DOC文档,供同行使用。J2SE/J2EE/J2ME的DOC文档是程序员与程序员交流的工具,几乎人手一份,除了菜鸟之外。J2SE DOC文档官方下载地址:http://java.sun.com/j2se/1.5.0/download.jsp,你可以到google搜索CHM版本下载。也可以在线查看:http://java.sun.com/j2se/1.5.0/docs/api/index.html。
  
  对待DOC文档要像毛主席语录,早上起床念一遍,吃饭睡觉前念一遍。
  
  当需要某项功能的时候,你应该先查相应的DOC文档看看有没有现成的实现,有的话就不必劳神费心了直接用就可以了,找不到的时候才考虑自己实现。使用步骤一般如下:
  
  ●找特定的包,包一般根据功能组织。
  
  ●找需要使用类,类命名规范的话我们由类的名字可猜出一二。
  
  ●选择构造器,大多数使用类的方式是创建对象。
  
  ●选择你需要的方法。
  
  3.2.2 查书/google->写代码测试->查看源代码->请教别人
  
  当我们遇到问题的时候该如何解决?
  
  这时候不要急着问别人,太简单的问题,没经过思考的问题,别人会因此而瞧不起你。可以先找找书,到google中搜一下看看,绝大部分问题基本就解决了。而像"某些类/方法如何使用的问题",DOC文档就是答案。对某些知识点有疑惑是,写代码测试一下,会给你留下深刻的印象。而有的问题,你可能需要直接看API的源代码验证你的想法。万不得已才去请教别人。
  
  3.2.3学习开源软件的设计思想

  JAVA领域有许多源代码开放的工具、组件、框架,JUnit、ANT、Tomcat、Struts、Spring、Jive论坛、PetStore宠物店等等多如牛毛。这些可是前辈给我们留下的瑰宝呀。入宝山而空手归,你心甘吗?对这些工具、框架进行分析,领会其中的设计思想,有朝一日说不定你也能写一个XXX框架什么的,风光一把。分析开源软件其实是你提高技术、提高实战能力的便捷方法。

  3.2.4 规范的重要性

  没有规矩,不成方圆。这里的规范有两层含义。第一层含义是技术规范,多到http://www.jcp.org下载JSRXXX规范,多读规范,这是最权威准确最新的教材。第二层含义是编程规范,如果你使用了大量的独特算法,富有个性的变量及方法的命名方式;同时,没给程序作注释,以显示你的编程功底是多么的深厚。这样的代码别人看起来像天书,要理解谈何容易,更不用说维护了,必然会被无情地扫入垃圾堆。JAVA编码规范到此查看或下载http://java.sun.com/docs/codeconv/,中文的也有,啊,还要问我在哪,请参考3.2.2节。

  3.2.5 不局限于JAVA

  很不幸,很幸运,要学习的东西还有很多。不幸的是因为要学的东西太多且多变,没时间陪老婆家人或女朋友,导致身心疲惫,严重者甚至导致抑郁症。幸运的是别人要抢你饭碗绝非易事,他们或她们需要付出很多才能达成心愿。
  JAVA不要孤立地去学习,需要综合学习数据结构、OOP、软件工程、UML、网络编程、数据库技术等知识,用横向纵向的比较联想的方式去学习会更有效。如学习JAVA集合的时候找数据结构的书看看;学JDBC的时候复习数据库技术;采取的依然是"需要的时候再学"的原则。

  4.结束语

  需要强调的是,学习软件开发确实有一定的难度,也很辛苦,需要付出很多努力,但千万不要半途而废。本文如果能对一直徘徊在JAVA神殿之外的朋友有所帮助的话,笔者也欣慰了。哈哈,怎么听起来老气横秋呀?没办法,在电脑的长期辐射之下,都快变成小老头了。最后奉劝各位程序员尤其是MM程序员,完成工作后赶快远离电脑,据《胡播乱报》报道,电脑辐射会在白皙的皮肤上面点缀一些小黑点,看起来鲜艳无比……

  5.参考文献
  5.1《JAVA夜未眠》
  5.2 http://www.chinaitlab.com/www/news/article_show.asp?id=33934
  5.3 http://javaalmanac.com/egs/
  5.4 http://java.sun.com/j2se/1.5.0/docs/api/index.html

当前Java软件开发中几种认识误区

  越来越多人开始使用Java,但是他们大多数人没有做好足够的思想准备(没有接受OO思想体系相关培训),以致不能很好驾驭Java项目,甚至 导致开发后的Java系统性能缓慢甚至经常当机。很多人觉得这是Java复杂导致,其实根本原因在于:我们原先掌握的关于软件知识(OO方面)不是太贫乏就是不恰当,存在认识上和方法上的误区。

软件的生命性

  软件是有生命的,这可能是老调重弹了,但是因为它事关分层架构的原由,反复强调都不过分。

  一个有生命的软件首先必须有一个灵活可扩展的基础架构,其次才是完整的功能。

  目前很多人对软件的思想还是焦点落在后者:完整的功能,觉得一个软件功能越完整越好,其实关键还是架构的灵活性,就是前者,基础架构好,功能添加只是时间和工作量问题,但是如果架构不好,功能再完整,也不可能包括未来所有功能,软件是有生命的,在未来成长时,更多功能需要加入,但是因为基础架构不灵活不能方便加入,死路一条。

  正因为普通人对软件存在短视误区,对功能追求高于基础架构,很多吃了亏的老程序员就此离开软件行业,带走宝贵的失败经验,新的盲目的年轻程序员还是使用老的思维往前冲。其实很多国外免费开源框架如ofbiz compiere和slide也存在这方面陷阱,貌似非常符合胃口,其实类似国内那些几百元的盗版软件,扩展性以及持续发展性严重不足。

  那么选择现在一些流行的框架如Hibernate、Spring/Jdonframework是否就表示基础架构打好了呢?其实还不尽然,关键还是取决于你如何使用这些框架来搭建你的业务系统。

存储过程和复杂SQL语句的陷阱

  首先谈谈存储过程使用的误区,使用存储过程架构的人以为可以解决性能问题,其实它正是导致性能问题的罪魁祸首之一,打个比喻:如果一个人频临死亡,打一针可以让其延长半年,但是打了这针,其他所有医疗方案就全部失效,请问你会使用这种短视方案吗?

  为什么这样说呢?如果存储过程都封装了业务过程,那么运行负载都集中在数据库端,要中间J2EE应用服务器干什么?要中间服务器的分布式计算和集群能力做什么?只能回到过去集中式数据库主机时代。现在软件都是面向互联网的,不象过去那样局限在一个小局域网,多用户并发访问量都是无法确定和衡量,依靠一台数据库主机显然是不能够承受这样恶劣的用户访问环境的。(当然搞数据库集群也只是五十步和百步的区别)。

   从分层角度来看,现在三层架构:表现层、业务层和持久层,三个层次应该分割明显,职责分明:持久层职责持久化保存业务模型对象,业务层对持久层的调用只是帮助我们激活曾经委托其保管的对象,所以,不能因为持久层是保管者,我们就以其为核心围绕其编程,除了要求其归还模型对象外,还要求其做其做复杂的业务组合。打个比喻:你在火车站将水果和盘子两个对象委托保管处保管,过了两天来取时,你还要求保管处将水果去皮切成块,放在盘子里,做成水果盘给你,合理吗?

  上面是谈过分依赖持久层的一个现象,还有一个正好相反现象,持久层散发出来,开始挤占业务层,腐蚀业务层,整个业务层到处看见的是数据表的影子(包括数据表的字段),而不是业务对象。这样程序员应该多看看OO经典PoEAAPoEAA 认为除了持久层,不应该在其他地方看到数据表或表字段名。

  当然适量使用存储过程,使用数据库优点也是允许的。按照Evans DDD理论,可以将SQL语句和存储过程作为规则Specification一部分。

Hibernate等ORM问题
  现在使用Hibernate人也不少,但是他们发现Hibernate性能缓慢,所以寻求解决方案,其实并不是 Hibernate性能缓慢,而是我们使用方式发生错误:

  “最近本人正搞一个项目,项目中我们用到了struts1.2+hibernate3, 由于关系复杂表和表之间的关系很多,在很多地方把lazy都设置false,所以导致数据一加载很慢,而且查询一条数据更是非常的慢。”

  Hibernate是一个基于对象模型持久化的技术,因此,关键是我们需要设计出高质量的对象模型,遵循DDD领域建模原则,减少降低关联,通过分层等有效办法处理关联。如果采取围绕数据表进行设计编程,加上表之间关系复杂(没有科学方法处理、侦察或减少这些关系),必然导致 系统运行缓慢,其实同样问题也适用于当初对EJB的实体Bean的CMP抱怨上,实体Bean是Domain Model持久化,如果不首先设计Domain Model,而是设计数据表,和持久化工具设计目标背道而驰,能不出问题吗?关于这个问题N多年就在Jdon争论过。

  这里同样延伸出另外一个问题:数据库设计问题,数据库是否需要在项目开始设计?
如果我们进行数据库设计,那么就产生了一系列问题:当我们使用Hibernate实现持久保存时,必须考虑事先设计好的数据库表结构以及他们的关系如何和业务对象实现映射,这实际上是非常难实现的,这也是很多人觉得使用ORM框架棘手根本原因所在。

  当然,也有脑力相当发达的人可以 实现,但是这种围绕数据库实现映射的结果必然扭曲业务对象,这类似于两个板块(数据表和业务对象)相撞,必然产生地震,地震的结果是两败俱伤, 软的一方吃亏,业务对象是代码,相当于数据表结构,属于软的一方,最后导致业务对象变成数据传输对象DTO, DTO满天飞,性能和维护问题随之而来。

  领域建模解决了上述众多不协调问题,特别是ORM痛苦使用问题,关于ORM/Hibernate使用还是那句老话:如果你不掌握领域建模方法,那么就不要用Hibernate,对于这个层次的你:也许No ORM 更是一个简单之道: No ORM: The simplest solution
http://www.theserverside.com/blogs/thread.tss?thread_id=41715

Spring分层矛盾问题
  Spring是以挑战EJB面貌出现,其本身拥有的强大组件定制功能是优点,但是存在实战的一些问题,Spring作为业务层框架,不支持业务层Session 功能。

  具体举例如下:当我们实现购物车之类业务功能时,需要将购物场合保存到Session中,由于业务层没有方便的Session支持,我们只得将购物车保存到 HttpSession,而HttpSession只有通过HttpRequest才能获得,再因为在Spring业务层容器中是无法访问到HttpRequest这个对象的,所以, 最后我们只能将“购物车保存到HttpSession”这个功能放在表现层中实现,而这个功能明显应该属于业务层功能,这就导致我们的Java项目层次混乱,维护性差。 违背了使用Spring和分层架构最初目的。

  相关案例:请教一个在完整提交前临时保存的问题:
  http://www.jdon.com/jive/article.jsp?forum=46&thread=28429

领域驱动设计DDD
  现在回到我们讨论的重点上来,分层架构是我们使用Java的根本原因之一,域建模专家Eric Evans在他的“Domain Model Design”一书中开篇首先强调的是分层架构,整个DDD理论实际是告诉我们如何使用模型对象oo技术和分层架构来设计实现一个Java项目。

  我们现在很多人知道Java项目基本有三层:表现层 业务层和持久层,当我们执著于讨论各层框架如何选择之时,实际上我们真正的项目开发工作还没有开始, 就是我们选定了某种框架的组合(如Struts+Spring+Hibernate或Struts+EJB或Struts+JdonFramework),我们还没有意识到业务层工作还需要大量工作,DDD提供了在业务层中再划分新的层次思想,如领域层和服务层,甚至再细分为作业层、能力层、策略层等等。通过层次细化方式达到复杂软件的松耦合。DDD提供了如何细分层次的方式

  当我们将精力花费在架构技术层面的讨论和研究上时,我们可能忘记以何种依据选择这些架构技术?选择标准是什么?领域驱动设计DDD 回答了这样的问题,DDD会告诉你如果一个框架不能协助你实现分层架构,那就抛弃它,同时,DDD也指出选择框架的考虑目的,使得你不会 人云亦云,陷入复杂的技术细节迷雾中,迷失了架构选择的根本方向。

  现在也有些人误以为DDD是一种新的理论,其实DDD和设计模式一样,不是一种新的理论,而是实战经验的总结,它将前人 使用面向模型设计的方法经验提炼出来,供后来者学习,以便迅速找到驾驭我们软件项目的根本之道。

  现在Evans DDD概念很火,因为它将著名的PoEAA进行了具化,实现了PoEAA可操作性,这也是MF大力推崇的原因。最近(8月8日)一位老外博客上用微软的.NET架构和Evans DDD比较的文章:http://weblogs.asp.net/pgielens/archive/2006/08/08/Organizing-Domain-Logic.aspx,这篇文章比较了微软的三层服务应用架构[Microsoft TLSA]和Evans DDD的架构, 使用Microsoft .NET Pet Shop 4为例子,解释两个目标的区别,并且表明
微软是如何在案例中更好地实现支持后者。这篇文章帮助哪些.NET平台上有域设计知识的人实现更好地提高。

  另外一本关于.NET的DDD书籍也已经出版,这些都说明Evans DDD这把火已经烧到.NET领域,当然DDD在Java领域生根开花多年,Evans的DDD书籍就是以Java为例子的,笔者板桥里人也率先在2005年推出DDD框架JdonFramework 1.3版本,这些都说明,Java在整个软件业先进思想的实践上总是领先一步。

Unicode,GBK,GB2312,UTF-8概念基础

为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问题,特别是乱码问题,我觉得组成一个系列来描述和分析更好一些,包括三篇文章:
第一篇:JAVA字符编码系列一:Unicode,GBK,GB2312,UTF-8概念基础
第二篇:JAVA字符编码系列二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换
第三篇:JAVA字符编码系列三:Java应用中的编码问题
 
第一篇:JAVA字符编码系列一:Unicode,GBK,GB2312,UTF-8概念基础
本部分采用重用,转载一篇文章来完成这部分的目标。
来源:holen'blog   对字符编码与Unicode,ISO 10646,UCS,UTF8,UTF16,GBK,GB2312的理解
地址:http://blog.donews.com/holen/archive/2004/11/30/188182.aspx
 
Unicode:

unicode.org制定的编码机制, 要将全世界常用文字都函括进去.
在1.0中是16位编码, 由U+0000到U+FFFF. 每个2byte码对应一个字符; 在2.0开始抛弃了16位限制, 原来的16位作为基本位平面, 另外增加了16个位平面, 相当于20位编码, 编码范围0到0x10FFFF.

UCS:

ISO制定的ISO10646标准所定义的 Universal Character Set, 采用4byte编码.

Unicode与UCS的关系:

ISO与unicode.org是两个不同的组织, 因此最初制定了不同的标准; 但自从unicode2.0开始, unicode采用了与ISO 10646-1相同的字库和字码, ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值, 使得两者保持一致.

UCS的编码方式:

  • UCS-2, 与unicode的2byte编码基本一样.
  • UCS-4, 4byte编码, 目前是在UCS-2前加上2个全零的byte.

    UTF: Unicode/UCS Transformation Format
  • UTF-8, 8bit编码, ASCII不作变换, 其他字符做变长编码, 每个字符1-3 byte. 通常作为外码. 有以下优点:
    * 与CPU字节顺序无关, 可以在不同平台之间交流
    * 容错能力高, 任何一个字节损坏后, 最多只会导致一个编码码位损失, 不会链锁错误(如GB码错一个字节就会整行乱码)
  • UTF-16, 16bit编码, 是变长码, 大致相当于20位编码, 值在0到0x10FFFF之间, 基本上就是unicode编码的实现. 它是变长码, 与CPU字序有关, 但因为最省空间, 常作为网络传输的外码.
    UTF-16是unicode的preferred encoding.
  • UTF-32, 仅使用了unicode范围(0到0x10FFFF)的32位编码, 相当于UCS-4的子集.

    UTF与unicode的关系:

    Unicode是一个字符集, 可以看作为内码.
    而UTF是一种编码方式, 它的出现是因为unicode不适宜在某些场合直接传输和处理. UTF-16直接就是unicode编码, 没有变换, 但它包含了0x00在编码内, 头256字节码的第一个byte都是0x00, 在操作系统(C语言)中有特殊意义, 会引起问题. 采用UTF-8编码对unicode的直接编码作些变换可以避免这问题, 并带来一些优点.

    中国国标编码:
  • GB 13000: 完全等同于ISO 10646-1/Unicode 2.1, 今后也将随ISO 10646/Unicode的标准更改而同步更改.
  • GBK: 对GB2312的扩充, 以容纳GB2312字符集范围以外的Unicode 2.1的统一汉字部分, 并且增加了部分unicode中没有的字符.
  • GB 18030-2000: 基于GB 13000, 作为Unicode 3.0的GBK扩展版本, 覆盖了所有unicode编码, 地位等同于UTF-8, UTF-16, 是一种unicode编码形式. 变长编码, 用单字节/双字节/4字节对字符编码. GB18030向下兼容GB2312/GBK.
    GB 18030是中国所有非手持/嵌入式计算机系统的强制实施标准.


    -------------------------------


     

    什么是 UCS 和 ISO 10646?

    国际标准 ISO 10646 定义了 通用字符集 (Universal Character Set, UCS). UCS 是所有其他字符集标准的一个超集. 它保证与其他字符集是双向兼容的. 就是说, 如果你将任何文本字符串翻译到 UCS格式, 然后再翻译回原编码, 你不会丢失任何信息.

    UCS 包含了用于表达所有已知语言的字符. 不仅包括拉丁语,希腊语, 斯拉夫语,希伯来语,阿拉伯语,亚美尼亚语和乔治亚语的描述, 还包括中文, 日文和韩文这样的象形文字, 以及 平假名, 片假名, 孟加拉语, 旁遮普语果鲁穆奇字符(Gurmukhi), 泰米尔语, 印.埃纳德语(Kannada), Malayalam, 泰国语, 老挝语, 汉语拼音(Bopomofo), Hangul, Devangari, Gujarati, Oriya, Telugu 以及其他数也数不清的语. 对于还没有加入的语言, 由于正在研究怎样在计算机中最好地编码它们, 因而最终它们都将被加入. 这些语言包括 Tibetian, 高棉语, Runic(古代北欧文字), 埃塞俄比亚语, 其他象形文字, 以及各种各样的印-欧语系的语言, 还包括挑选出来的艺术语言比如 Tengwar, Cirth 和 克林贡语(Klingon). UCS 还包括大量的图形的, 印刷用的, 数学用的和科学用的符号, 包括所有由 TeX, Postscript, MS-DOS,MS-Windows, Macintosh, OCR 字体, 以及许多其他字处理和出版系统提供的字符.

    ISO 10646 定义了一个 31 位的字符集. 然而, 在这巨大的编码空间中, 迄今为止只分配了前 65534 个码位 (0x0000 到 0xFFFD). 这个 UCS 的 16位子集称为 基本多语言面 (Basic Multilingual Plane, BMP). 将被编码在 16 位 BMP 以外的字符都属于非常特殊的字符(比如象形文字), 且只有专家在历史和科学领域里才会用到它们. 按当前的计划, 将来也许再也不会有字符被分配到从 0x000000 到 0x10FFFF 这个覆盖了超过 100 万个潜在的未来字符的 21 位的编码空间以外去了. ISO 10646-1 标准第一次发表于 1993 年, 定义了字符集与 BMP 中内容的架构. 定义 BMP 以外的字符编码的第二部分 ISO 10646-2 正在准备中, 但也许要过好几年才能完成. 新的字符仍源源不断地加入到 BMP 中, 但已经存在的字符是稳定的且不会再改变了.

    UCS 不仅给每个字符分配一个代码, 而且赋予了一个正式的名字. 表示一个 UCS 或 Unicode 值的十六进制数, 通常在前面加上 "U+", 就象 U+0041 代表字符"拉丁大写字母A". UCS 字符 U+0000 到 U+007F 与 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 与 ISO 8859-1(Latin-1) 也是一致的. 从 U+E000 到 U+F8FF, 已经 BMP 以外的大范围的编码是为私用保留的.

    什么是组合字符?

    UCS里有些编码点分配给了 组合字符.它们类似于打字机上的无间隔重音键. 单个的组合字符不是一个完整的字符. 它是一个类似于重音符或其他指示标记, 加在前一个字符后面. 因而, 重音符可以加在任何字符后面. 那些最重要的被加重的字符, 就象普通语言的正字法(orthographies of common languages)里用到的那种, 在 UCS 里都有自己的位置, 以确保同老的字符集的向后兼容性. 既有自己的编码位置, 又可以表示为一个普通字符跟随一个组合字符的被加重字符, 被称为 预作字符(precomposed characters). UCS 里的预作字符是为了同没有预作字符的旧编码, 比如 ISO 8859, 保持向后兼容性而设的. 组合字符机制允许在任何字符后加上重音符或其他指示标记, 这在科学符号中特别有用, 比如数学方程式和国际音标字母, 可能会需要在一个基本字符后组合上一个或多个指示标记.

    组合字符跟随着被修饰的字符. 比如, 德语中的元音变音字符 ("拉丁大写字母A 加上分音符"), 既可以表示为 UCS 码 U+00C4 的预作字符, 也可以表示成一个普通 "拉丁大写字母A" 跟着一个"组合分音符":U+0041 U+0308 这样的组合. 当需要堆叠多个重音符, 或在一个基本字符的上面和下面都要加上组合标记时, 可以使用多个组合字符. 比如在泰国文中, 一个基本字符最多可加上两个组合字符.

    什么是 UCS 实现级别?

    不是所有的系统都需要支持象组合字符这样的 UCS 里所有的先进机制. 因此 ISO 10646 指定了下列三种实现级别:

    级别1
    不支持组合字符和 Hangul Jamo 字符 (一种特别的, 更加复杂的韩国文的编码, 使用两个或三个子字符来编码一个韩文音节)
    级别2
    类似于级别1, 但在某些文字中, 允许一列固定的组合字符 (例如, 希伯来文, 阿拉伯文, Devangari, 孟加拉语, 果鲁穆奇语, Gujarati, Oriya, 泰米尔语, Telugo, 印.埃纳德语, Malayalam, 泰国语和老挝语). 如果没有这最起码的几个组合字符, UCS 就不能完整地表达这些语言.
    级别3
    支持所有的 UCS 字符, 例如数学家可以在任意一个字符上加上一个 tilde(颚化符号,西班牙语字母上面的~)或一个箭头(或两者都加).

    什么是 Unicode?

    历史上, 有两个独立的, 创立单一字符集的尝试. 一个是国际标准化组织(ISO)的 ISO 10646 项目, 另一个是由(一开始大多是美国的)多语言软件制造商组成的协会组织的 Unicode 项目. 幸运的是, 1991年前后, 两个项目的参与者都认识到, 世界不需要两个不同的单一字符集. 它们合并双方的工作成果, 并为创立一个单一编码表而协同工作. 两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何未来的扩展.

    那么 Unicode 和 ISO 10646 不同在什么地方?

    Unicode 协会公布的 Unicode 标准 严密地包含了 ISO 10646-1 实现级别3的基本多语言面. 在两个标准里所有的字符都在相同的位置并且有相同的名字.

    Unicode 标准额外定义了许多与字符有关的语义符号学, 一般而言是对于实现高质量的印刷出版系统的更好的参考. Unicode 详细说明了绘制某些语言(比如阿拉伯语)表达形式的算法, 处理双向文字(比如拉丁与希伯来文混合文字)的算法和 排序与字符串比较 所需的算法, 以及其他许多东西.

    另一方面, ISO 10646 标准, 就象广为人知的 ISO 8859 标准一样, 只不过是一个简单的字符集表. 它指定了一些与标准有关的术语, 定义了一些编码的别名, 并包括了规范说明, 指定了怎样使用 UCS 连接其他 ISO 标准的实现, 比如 ISO 6429 和 ISO 2022. 还有一些与 ISO 紧密相关的, 比如 ISO 14651 是关于 UCS 字符串排序的.

    考虑到 Unicode 标准有一个易记的名字, 且在任何好的书店里的 Addison-Wesley 里有, 只花费 ISO 版本的一小部分, 且包括更多的辅助信息, 因而它成为使用广泛得多的参考也就不足为奇了. 然而, 一般认为, 用于打印 ISO 10646-1 标准的字体在某些方面的质量要高于用于打印 Unicode 2.0的. 专业字体设计者总是被建议说要两个标准都实现, 但一些提供的样例字形有显著的区别. ISO 10646-1 标准同样使用四种不同的风格变体来显示表意文字如中文, 日文和韩文 (CJK), 而 Unicode 2.0 的表里只有中文的变体. 这导致了普遍的认为 Unicode 对日本用户来说是不可接收的传说, 尽管是错误的.

    什么是 UTF-8?

    首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII 字节前插入三个 0x00.

    在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.

    在 ISO 10646-1 Annex RRFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.

    UTF-8 有一下特性:

    • UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.
    • 所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分.
    • 表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0x80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.
    • 可以编入所有可能的 231个 UCS 代码
    • UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.
    • Bigendian UCS-4 字节串的排列顺序是预定的.
    • 字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.

    下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.

    U-00000000 - U-0000007F: 0xxxxxxx
    U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
    U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
    U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

    xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.

    例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

    11000010 10101001 = 0xC2 0xA9

    而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:

    11100010 10001001 10100000 = 0xE2 0x89 0xA0

    这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.

    什么编程语言支持 Unicode?

    在大约 1993 年之后开发的大多数现代编程语言都有一个特别的数据类型, 叫做 Unicode/ISO 10646-1 字符. 在 Ada95 中叫 Wide_Character, 在 Java 中叫 char.

    ISO C 也详细说明了处理多字节编码和宽字符 (wide characters) 的机制, 1994 年 9 月 Amendment 1 to ISO C 发表时又加入了更多. 这些机制主要是为各类东亚编码而设计的, 它们比处理 UCS 所需的要健壮得多. UTF-8 是 ISO C 标准调用多字节字符串的编码的一个例子, wchar_t 类型可以用来存放 Unicode 字符.

  • Unicode,ISO-8859,GBK,UTF-8编码及相互转换

    为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问题,特别是乱码问题,我觉得组成一个系列来描述和分析更好一些,包括三篇文章:
    第一篇:JAVA字符编码系列一:Unicode,GBK,GB2312,UTF-8概念基础
    第二篇:JAVA字符编码系列二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换
    第三篇:JAVA字符编码系列三:Java应用中的编码问题
     
     
    第二篇:JAVA字符编码系列二:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换
     
    1、函数介绍
    在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有关的两个主要函数为:
    1)将字符串用指定的编码集合解析成字节数组,完成Unicode-〉charsetName转换
    public byte[] getBytes(String charsetName) throws UnsupportedEncodingException 
     
    2)将字节数组以指定的编码集合构造成字符串,完成charsetName-〉Unicode转换
    public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
     
    2、Unicode与各编码之间的直接转换
    下面以对中文字符串"a中文"的编码转换为例,来了解各种编码之间的转换
    1)Unicode和GBK
    测试结果如下,每个汉字转换为两个字节,且是可逆的,即通过字节可以转换回字符串
    StringGBKByteArray:\u0061\u4E2D\u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4
    ByteArrayGBKString:0x61 0xD6 0xD0 0xCE 0xC4-〉\u0061\u4E2D\u6587(a中文)
     
    2)Unicode和UTF-8
    测试结果如下,每个汉字转换为三个字节,且是可逆的,即通过字节可以转换回字符串
    StringUTF-8ByteArray:\u0061\u4E2D\u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87
    ByteArrayUTF-8String:0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉\u0061\u4E2D\u6587(a中文)
    3)Unicode和ISO-8859-1
    测试结果如下,当存在汉字时转换失败,非可逆,即通过字节不能再转换回字符串
    StringISO-8859-1ByteArray:\u0061\u4E2D\u6587(a中文)-〉0x61 0x3F 0x3F
    ByteArrayISO-8859-1String:0x61 0x3F 0x3F-〉\u0061\u003F\u003F(a??)
    3、Unicode与各编码之间的交叉转换
    在上面直接转换中,由字符串(Unicode)生成的字节数组,在构造回字符串时,使用的是正确的编码集合,如果使用的不是正确的编码集合会怎样呢?会正确构造吗?如果不能正确构造能有办法恢复吗?会信息丢失吗?
     
    下面我们就来看看这种情况,这部分可以说明在某些情况下虽然我们最终正确显示了结果,但其间仍然进行了不正确的转换。
     
    1)能够正确显示的中间不正确转换
    我们知道StringGBKByteArrayGBKString是正确的,但如果我们采用StringGBKByteArrayISO-8859-1String呢?通过测试结果如下:
    StringGBKByteArrayISO-8859-1String:\u0061\u4E2D\u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉\u0061\u00D6\u00D0\u00CE\u00C4(a????)
     
    这时我们得到的字符串为?乱码“a????”,但是通过继续转换我们仍然可以复原回正确的字符串“a中文”,过程如下:
    StringGBKByteArrayISO-8859-1StringISO-8859-1ByteArrayGBKString
    对应:\u0061\u4E2D\u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉\u0061\u00D6\u00D0\u00CE\u00C4(a????)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉\u0061\u4E2D\u6587(a中文)
     
    也就是我们在首次构造字符串时,我们用了错误的编码集合得到了错误的乱码,但是我们通过错上加错,再用错误的编码集合获取字节数组,然后再用正确的编码集合构造,就又恢复了正确的字符串。这时就属于是“能够正确显示的中间不正确转换”。在Jsp页面提交数据处理时常常发生这种情况。
     
    此外能够正确显示的中间不正确转换还有:
    StringUTF-8ByteArrayISO-8859-1StringISO-8859-1ByteArrayUTF-8String
    StringUTF-8ByteArrayGBKStringGBKByteArrayUTF-8String
    对应:\u0061\u4E2D\u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉\u0061\u6D93\uE15F\u6783(a涓枃)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉\u0061\u4E2D\u6587(a中文)
     
    4、编码过程中错误诊断参考
    1)一个汉字对应一个问号
    在通过ISO-8859-1从字符串获取字节数组时,由于一个Unicode转换成一个byte,当遇到不认识的Unicode时,转换为0x3F,这样无论用哪种编码构造时都会产生一个?乱码。
    2)一个汉字对应两个问号
    在通过GBK从字符串获取字节数组时,由于一个Unicode转换成两个byte,如果此时用ISO-8859-1或用UTF-8构造字符串就会出现两个问号。
    若是通过ISO-8859-1构造可以再通过上面所说的错上加错恢复(即再通过从ISO-8859-1解析,用GBK构造);
    若是通过UTF-8构造则会产生Unicode字符"\uFFFD",不能恢复,若再通过String-UTF-8〉ByteArray-GBK〉String,则会出现杂码,如a锟斤拷锟斤拷
    3)一个汉字对应三个问号
    在通过UTF-8从字符串获取字节数组时,由于一个Unicode转换成三个byte,如果此时用ISO-8859-1构造字符串就会出现三个问号;用GBK构造字符串就会出现杂码,如a涓枃。

    Java应用中的编码问题

    为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问题,特别是乱码问题,我觉得组成一个系列来描述和分析更好一些,包括三篇文章:
    第一篇:JAVA字符编码系列一:Unicode,GBK,GB2312,UTF-8概念基础
    第二篇:JAVA字符编码系列二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换
    第三篇:JAVA字符编码系列三:Java应用中的编码问题
     
    第三篇:JAVA字符编码系列三:Java应用中的编码问题
    这部分采用重用机制,引用一篇文章来完整本部分目标。
    来源:  Eceel东西在线 问题研究--字符集编码 
    地址:http://china.eceel.com/article/study_for_character_encoding_java.htm

    问题研究

    --字符集编码

    1. 概述

    本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。

    在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。

    2. 编码基本知识

    最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。

    2.1. iso8859-1

    属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97。

    很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

    2.2. GB2312/GBK

    这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。

    2.3. unicode

    这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母'a'为"00 61"。

    需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。

    2.4. UTF

    考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。

    注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。

    3. java对字符的处理

    在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

    3.1. getBytes(charset)

    这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

    3.2. new String(charset)

    这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。

    因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

    3.3. setCharacterEncoding()

    该函数用来设置http请求或者相应的编码。

    对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。

    对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

    3.4. 处理过程

    下面分析两个有代表性的例子,说明java对编码有关问题的处理方法。

    3.4.1. 表单输入

    User input  *(gbk:d6d0 cec4)  browser  *(gbk:d6d0 cec4)  web server  iso8859-1(00d6 00d 000ce 00c4)  class,需要在class中进行处理:getbytes("iso8859-1")为d6 d0 ce c4,new String("gbk")为d6d0 cec4,内存中以unicode编码则为4e2d 6587

    l 用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。

    l 从browser到web server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。

    l Web server接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。

    l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。

    3.4.2. 文件编译

    假设文件是gbk编码保存的,而编译有两种编码选择:gbk或者iso8859-1,前者是中文windows的默认编码,后者是linux的默认编码,当然也可以在编译时指定编码。

    Jsp  *(gbk:d6d0 cec4)  java file  *(gbk:d6d0 cec4)  compiler read  uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  compiler write  utf(gbk: e4b8ad e69687; iso8859-1: *)  compiled file  unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  class。所以用gbk编码保存,而用iso8859-1编译的结果是不正确的。

    class  unicode(4e2d 6587)  system.out / jsp.out  gbk(d6d0 cec4)  os console / browser。

    l 文件可以以多种编码方式保存,中文windows下,默认为ansi/gbk。

    l 编译器读取文件时,需要得到文件的编码,如果未指定,则使用系统默认编码。一般class文件,是以系统默认编码保存的,所以编译不会出问题,但对于jsp文件,如果在中文windows下编辑保存,而部署在英文linux下运行/编译,则会出现问题。所以需要在jsp文件中用pageEncoding指定编码。

    l Java编译的时候会转换成统一的unicode编码处理,最后保存的时候再转换为utf编码。

    l 当系统输出字符的时候,会按指定编码输出,对于中文windows下,System.out将使用gbk编码,而对于response(浏览器),则使用jsp文件头指定的contentType,或者可以直接为response指定编码。同时,会告诉browser网页的编码。如果未指定,则会使用iso8859-1编码。对于中文,应该为browser指定输出字符串的编码。

    l browser显示网页的时候,首先使用response中指定的编码(jsp文件头指定的contentType最终也反映在response上),如果未指定,则会使用网页中meta项指定中的contentType。

    3.5. 几处设置

    对于web应用程序,和编码有关的设置或者函数如下。

    3.5.1. jsp编译

    指定文件的存储编码,很明显,该设置应该置于文件的开头。例如:<%@page pageEncoding="GBK"%>。另外,对于一般class文件,可以在编译的时候指定编码。

    3.5.2. jsp输出

    指定文件输出到browser是使用的编码,该设置也应该置于文件的开头。例如:<%@ page contentType="text/html; charset= GBK" %>。该设置和response.setCharacterEncoding("GBK")等效。

    3.5.3. meta设置

    指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" />

    如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。

    需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。

    3.5.4. form设置

    当浏览器提交表单的时候,可以指定相应的编码。例如:<form accept-charset= "gb2312">。一般不必不使用该设置,浏览器会直接使用网页的编码。

    4. 系统软件

    下面讨论几个相关的系统软件。

    4.1. mysql数据库

    很明显,要支持多语言,应该将数据库的编码设置成utf或者unicode,而utf更适合与存储。但是,如果中文数据中包含的英文字母很少,其实unicode更为适合。

    数据库的编码可以通过mysql的配置文件设置,例如default-character-set=utf8。还可以在数据库链接URL中设置,例如: useUnicode=true&characterEncoding=UTF-8。注意这两者应该保持一致,在新的sql版本里,在数据库链接URL里可以不进行设置,但也不能是错误的设置。

    4.2. apache

    appache和编码有关的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,该功能会将所有静态页面的编码设置为UTF-8,最好关闭该功能。

    另外,apache还有单独的模块来处理网页响应头,其中也可能对编码进行设置。

    4.3. linux默认编码

    这里所说的linux默认编码,是指运行时的环境变量。两个重要的环境变量是LC_ALL和LANG,默认编码会影响到java URLEncode的行为,下面有描述。

    建议都设置为"zh_CN.UTF-8"。

    4.4. 其它

    为了支持中文文件名,linux在加载磁盘时应该指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。

    另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但可以通过tomcat的配置文件指定字符集,在tomcat的server.xml文件中,形如:<Connector ... URIEncoding="GBK"/>。这种方法将统一设置所有请求,而不能针对具体页面进行设置,也不一定和browser使用的编码相同,所以有时候并不是所期望的。

    5. URL地址

    URL地址中含有中文字符是很麻烦的,前面描述过使用GET方法提交表单的情况,使用GET方法时,参数就是包含在URL中。

    5.1. URL编码

    对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。

    IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87";而UTF-8选项无效时,将发送链接"\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字节,这主要时URL编码的原因。

    当web server(tomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用URLEncoding来设置成其它编码)识别。上述例子的结果分别是"\ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87"和"\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87",注意前者前面的"中文"两个字恢复成了6个字符。这里用"\u",表示是unicode。

    所以,由于客户端设置的不同,相同的链接,在服务器上得到了不同结果。这个问题不少人都遇到,却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过,下面会描述一个更好的处理办法。

    5.2. rewrite

    熟悉的人都知道,apache有一个功能强大的rewrite模块,这里不描述其功能。需要说明的是该模块会自动将URL解码(去除%),即完成上述web server(tomcat)的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能,但我试验并未成功,可能是因为版本(我使用的是apache 2.0.54)问题。另外,当参数中含有"?& "等符号的时候,该功能将导致系统得不到正常结果。

    rewrite本身似乎完全是采用字节处理的方式,而不考虑字符串的编码,所以不会带来编码问题。

    5.3. URLEncode.encode()

    这是Java本身提供对的URL编码函数,完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是,java已经不赞成不指定编码来使用该方法(deprecated)。应该在使用的时候增加编码指定。

    当不指定编码的时候,该方法使用系统默认编码,这会导致软件运行结果得不确定。比如对于"中文",当系统默认编码为"gb2312"时,结果是"%4e%2d%65%87",而默认编码为"UTF-8",结果却是"%e4%b8%ad%e6%96%87",后续程序将难以处理。另外,这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的,曾经出现过tomcat重启后就出现乱码的问题,最后才郁闷的发现是因为修改修改了这两个环境变量。

    建议统一指定为"UTF-8"编码,可能需要修改相应的程序。

    5.4. 一个解决方案

    上面说起过,因为浏览器设置的不同,对于同一个链接,web server收到的是不同内容,而软件系统有无法知道这中间的区别,所以这一协议目前还存在缺陷。

    针对具体问题,不应该侥幸认为所有客户的IE设置都是UTF-8有效的,也不应该粗暴的建议用户修改IE设置,要知道,用户不可能去记住每一个web server的设置。所以,接下来的解决办法就只能是让自己的程序多一点智能:根据内容来分析编码是否UTF-8。

    比较幸运的是UTF-8编码相当有规律,所以可以通过分析传输过来的链接内容,来判断是否是正确的UTF-8字符,如果是,则以UTF-8处理之,如果不是,则使用客户默认编码(比如"GBK"),下面是一个判断是否UTF-8的例子,如果你了解相应规律,就容易理解。

    public static boolean isValidUtf8(byte[] b,int aMaxCount){ 

           int lLen=b.length,lCharCount=0;

           for(int i=0;i<lLen && lCharCount<aMaxCount;++lCharCount){ 

                  byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)

                  if(lByte>=0) continue;//>=0 is normal ascii

                  if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;

                  int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4

                         :lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;

                  if(i+lCount>lLen) return false;

                  for(int j=0;j<lCount;++j,++i) if(b[i]>=(byte)0xc0) return false;

           }

           return true;

    }

    相应地,一个使用上述方法的例子如下:

    public static String getUrlParam(String aStr,String aDefaultCharset)

    throws UnsupportedEncodingException{ 

           if(aStr==null) return null;

           byte[] lBytes=aStr.getBytes("ISO-8859-1");

           return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);

    }

    不过,该方法也存在缺陷,如下两方面:

    l 没有包括对用户默认编码的识别,这可以根据请求信息的语言来判断,但不一定正确,因为我们有时候也会输入一些韩文,或者其他文字。

    l 可能会错误判断UTF-8字符,一个例子是"学习"两个字,其GBK编码是" \xd1\xa7\xcf\xb0",如果使用上述isValidUtf8方法判断,将返回true。可以考虑使用更严格的判断方法,不过估计效果不大。

    有一个例子可以证明google也遇到了上述问题,而且也采用了和上述相似的处理方法,比如,如果在地址栏中输入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=学习",google将无法正确识别,而其他汉字一般能够正常识别。

    最后,应该补充说明一下,如果不使用rewrite规则,或者通过表单提交数据,其实并不一定会遇到上述问题,因为这时可以在提交数据时指定希望的编码。另外,中文文件名确实会带来问题,应该谨慎使用。

    6. 其它

    下面描述一些和编码有关的其他问题。

    6.1. SecureCRT

    除了浏览器和控制台与编码有关外,一些客户端也很有关系。比如在使用SecureCRT连接linux时,应该让SecureCRT的显示编码(不同的session,可以有不同的编码设置)和linux的编码环境变量保持一致。否则看到的一些帮助信息,就可能是乱码。

    另外,mysql有自己的编码设置,也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候,可能无法处理中文字符,查询结果也会出现乱码。

    对于Utf-8文件,很多编辑器(比如记事本)会在文件开头增加三个不可见的标志字节,如果作为mysql的输入文件,则必须要去掉这三个字符。(用linux的vi保存可以去掉这三个字符)。一个有趣的现象是,在中文windows下,创建一个新txt文件,用记事本打开,输入"连通"两个字,保存,再打开,你会发现两个字没了,只留下一个小黑点。

    6.2. 过滤器

    如果需要统一设置编码,则通过filter进行设置是个不错的选择。在filter class中,可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使用的例子SetCharacterEncodingFilter。

    6.3. POST和GET

    很明显,以POST提交信息时,URL有更好的可读性,而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容,也能够用于收藏。

    从统一的角度考虑问题,建议采用GET方法,这要求在程序中获得参数是进行特殊处理,而无法使用setCharacterEncoding()的便利,如果不考虑rewrite,就不存在IE的UTF-8问题,可以考虑通过设置URIEncoding来方便获取URL中的参数。

    6.4. 简繁体编码转换

    GBK同时包含简体和繁体编码,也就是说同一个字,由于编码不同,在GBK编码下属于两个字。有时候,为了正确取得完整的结果,应该将繁体和简体进行统一。可以考虑将UTF、GBK中的所有繁体字,转换为相应的简体字,BIG5编码的数据,也应该转化成相应的简体字。当然,仍旧以UTF编码存储。

    例如,对于"语言 語言",用UTF表示为"\xE8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80",进行简繁体编码转换后应该是两个相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。

    35岁前成功的12条黄金法则

    第一章:一个目标

    一艘没有航行目标的船,任何方向的风都是逆风

    1、你为什么是穷人,第一点就是你没有立下成为富人的目标
    2、你的人生核心目标是什么?
    杰出人士与平庸之辈的根本差别并不是天赋、机遇,而在于有无目标。
    3、起跑领先一步,人生领先一大步:成功从选定目标开始
    4、贾金斯式的人永远不会成功
    为什么大多数人没有成功?真正能完成自己计划的人只有5%,大多数人不是将自己的目标舍弃,就是沦为缺乏行动的空想
    5、 如果你想在35岁以前成功,你一定在25至30岁之间确立好你的人生目标
    6、 每日、每月、每年都要问自己:我是否达到了自己定下的目标





    第二章:两个成功基点

    站好位置,调正心态,努力冲刺,35岁以前成功

    (一)人生定位
    1、 人怕入错行:你的核心竞争力是什么?
    2、 成功者找方法,失败者找借口
    3、 从三百六十行中选择你的最爱
    人人都可以创业,但却不是人人都能创业成功
    4、 寻找自己的黄金宝地
    (二)永恒的真理:心态决定命运,35岁以前的心态决定你一生的命运
    1、 不满现状的人才能成为富翁
    2、 敢于梦想,勇于梦想,这个世界永远属于追梦的人
    3、 35岁以前不要怕,35岁以后不要悔
    4、 出身贫民,并非一辈子是贫民,只要你永远保持那颗进取的心。中国成功人士大多来自小地方
    5、 做一个积极的思维者
    6、 不要败给悲观的自己
    有的人比你富有一千倍,他们也会比你聪明一千倍么?不会,他们只是年轻时心气比你高一千倍。
    人生的好多次失败,最后并不是败给别人,而是败给了悲观的自己。
    7、 成功者不过是爬起来比倒下去多一次
    8、 宁可去碰壁,也不要在家里面壁
    克服你的失败、消极的心态
    (1) 找个地方喝点酒
    (2) 找个迪厅跳跳舞
    (3) 找帮朋友侃侃山
    (4) 积极行动起来



    第三章:三大技巧


    1、管理时间:你的时间在哪里,你的成就就在哪里。
    把一小时看成60分钟的人,比看作一小时的人多60倍
    2、你不理财,财不理你
    3、自我管理,游刃有余
    (1) 创业不怕本小,脑子一定要好
    (2) 可以开家特色店
    (3) 做别人不愿做的生意



    第四章:四项安身立命的理念

    35岁以前一定要形成个人风格

    1、做人优于做事
    做事失败可以重来,做人失败却不能重来
    (1) 做人要讲义气
    (2) 永不气馁
    2、豁达的男人有财运,豁达的女人有帮夫运
    35岁以前搞定婚姻生活
    3、忠诚的原则:35岁以前你还没有建立起忠诚美誉,这一缺点将要困扰你的一生
    4、把小事做细,但不要耍小聪明
    中国人想做大事的人太多,而愿把小事做完美的人太少




    第五章:五分运气

    比尔·盖茨说:人生是不公平的,习惯去接受它吧

    1、人生的确有很多运气的成人:谋事在人,成事在天:中国的古训说明各占一半
    2、机会时常意外地降临,但属于那些不应决不放弃的人
    3、抓住人生的每一次机会
    机会就像一只小鸟,如果你不抓住,它就会飞得无影无踪
    4、 者早一步,愚者晚一步



    第六章:六项要求

    1、智慧
    (1)别人可你以拿走你的一切,但拿不走你的智慧
    (2)巧妙运用自己的智慧
    (3)智者与愚者的区别
    2、勇气
    (1)勇气的力量有时会让你成为"超人"
    (2)敢于放弃,敢于"舍得"
    3、培养自己的"领导才能、领袖气质"
    (1) 激情感染别人
    (2) "三o七法则"实现领袖气质
    (3) 拍板决断能力
    (4) 人格魅力
    4、创造性:不要做循规蹈矩的人
    25-35岁是人生最有创造性的阶段,很多成功人士也都产生在这一阶段
    5、明智
    (1) 知道自己的长处、短处,定向聚焦
    (2) 尽量在自己的熟悉的领域努力
    6、持之以恒的行动力:在你选定行业坚持十年,你一定会成为大赢家



    第七章:七分学习

    1、知识改变命运
    2、35岁以前学会你行业中必要的一切知识
    a) 每天淘汰你自己
    b) 在商言商
    3、太相信的书人,只能成为打工仔
    4、思考、实践、再思考、再实践



    第八章:八分交际

    朋友多了路好走
    1、智商很重要,情商更重要:35岁以前建立起人际关系网
    2、人脉即财脉:如何搞好人际关系
    3、交友有原则
    4、善于沟通:35岁以前要锻炼出自己的演讲才能



    第九章:九分习惯

    习惯的力量是惊人的,35岁以前养成的习惯决定着你的成功的大小
    1、积极思维的好习惯
    2、养成高效工作的好习惯
    (1) 办公室
    (2) 生活可以不拘小节,但要把工作做细
    (3) 学习聆听,不打断别人说话
    3、养成锻炼身体的好习惯
    4、广泛爱好的好习惯
    5、快速行动的好习惯



    第十章:十分自信

    1、自信是成功的精神支柱
    2、自信方能赢得别人的信任
    3、把自信建立在创造价值的基础上
    4、如何建立自信
    (1) 为自己确立目标
    (2) 发挥自己的长处
    (3) 做事要有计划
    (4) 做事不拖拉
    (5) 轻易不要放弃
    (6) 学会自我激励
    (7) 不要让自己成为别人



    第十一章:11个需要避开的成功陷阱

    1、只有功劳,没有苦劳
    2、不要"怀才不遇",而要寻找机遇
    3、不要想发横财
    4、不要为钱而工作,而让钱为你工作
    5、 盲目跟风,人云亦云,人做我也做
    6、 小富即安,不思进取,知足常乐
    7、 承认错误而非掩饰错误
    8、 脚踏实地而非想入非非
    9、 野心太大而不是信心十足
    10、反复跳槽不可取
    11、眼高手低
    12、不择手段



    第十二章:十二分努力

    没有人能随随便便成功
    1、小不是成功,大不是成功,由小变大才是成功
    2、中国社会进入微利时代:巧干+敢干+实干=成功
    3、努力尝试就有成功的可能
    4、做任何事情,尽最大努力
    5、把事情当成事业来做
    6、我看打工者
    7、祝你早日掘到第一桶金

    养成助你成功的十个习惯

    一 热诚的态度

    我们的态度决定了我们的未来一个人能否成功,取决了他的态度!

      成功人士与失败之间的判别是:
      成功人士始终有最热诚的态度最积极的思考,最乐观的精神和最辉煌的以经验支配和控制自己的人生,失败者则相反,他们的人生是受过人生的种种失败怀疑虑所引导和支配。 我们的态度决定了我们人生的成功

      1,我们怎样对待生活,生活就怎样对待我们。
      2,我们怎样对待别人,别人怎样对待我们。
      3,我们在一项任务刚开始时的态度就决定了最后的多大成功。

      我们的环境---心里的\感情的\精神的\----完全由我们自己的态度来创造。

    二 目标明确\目标管理

      1目标明确

      没有线路图什么地方也去不了。
      目标就是构筑成功的砖石。
      目标使我们产生积极性,你给自己定了目标,有两个方面的作用:一是你努力的依据二是你的鞭策。目标给你一个看得着的射击靶,随着你努力去实现这些目标,你就会有成就感。有9 8%的人对心目中的世界没有一幅清晰的图画。如果计划不具体,无法衡量是否实现了----那会降低了你的积极性。

      2目标管理

      把整体目标分解成一个个易记的目标把你的目标象成一金字塔,塔顶就是你的人生目标,你定的目标和为达目标而做的每一件事都必须指向你的人生目标。
      金字塔由五层组成,最上的一层最小,最核心的。这一层包含着你的人生总目标。
      下面每层是为实现上一层较大目标而要达到的较小目标。

    三 一勤天下无难事

      一心向着自己目标前进的人,整个世界都给他让路。

    四 擅于理财、预算时间和金钱

    五 喜欢运动

      健康的体魄是成就事业的资本。成功人士几乎都有自己喜欢的体育项目。

    六 自律

      自控能力的强弱对人生的成功也有很大的影响。

      1 当你生气时,你能沉默不语吗?
      2 你习惯于三思而行吗?
      3 你的性情一般是平和的吗?
      4 你习惯让你的情绪控制你的理智。

    七 谦虚好学

      1 你是否把不断的学习更多的知识作为你的职责?
      2 你是否有一种习惯:对你所不熟悉的问题发表"意见"?
      3 当你需要知识时,你知道如何寻找吗?

      越是成功的人,他们越会抓住一切可以学习的机会。

    八 良好的人际关系

      成功意味着别人的参与

    九 信念

    十 立即行动

      有了价值连城的目标计划,成功已向你展示。有位先生几年以来一直暗恋着某位小姐,可是,连续几年过去了,他一直没有采取任何行动,他一直在等待,直到那位小姐成为他人之妻,他紧张起来,但是为时已晚! 别再犹豫,请立即行动吧!

    Hibernate为什么如此成功?

    Why This Project Is Successful

    这篇文章是Gavin King写的,非常有趣,充分体现了Hibernate的设计理念,我粗略的意译如下:

    下面是对Hibernate开发工作的个人想法,正是这些工作使得Hibernate如此迅速的得到广泛的欢迎。

    1、飞快的版本发布

    保持活跃的开发速度,经常进行版本发布,甚至几天之内就从前一个版本开发到下一个版本。这样是保证软件远离Bug的最好的办法,也可以让用户感到很放心,确信Hibernate的开发十分活跃,另外这样做也有一大好处,就是可以发现哪些功能是用户真正需要的。

    2、回归测试

    我想现在整个Java社区一定都很重视自动回归测试。如果软件的功能和设计有比较大的修改,那么一个综合性的test suite对于软件可维护性和稳定性来说实在是太重要了。我们应该有这样的意识:如果对软件的一个新功能没有进行回归测试,我们根本就不该去做它。

    3、把一个功能做到最好

    要么不做,要做,就一定做到最好。那些我们做不到最好的功能,我们根本不去做,扔给其他软件去做吧。

    4、避免过度设计

    浪费大量的时间和精力进行软件功能的抽象和扩充软件的灵活性,还不如多花点时间来解决你的用户面临的实际问题呢!简单一点! 软件能跑起来就OK,不要尝试去解决你的用户根本不关心的问题。就算你的软件设计的不够优雅也没有关系,反正还是initial阶段嘛!以后再 refactor,你应该关注的问题是及时的把有用的功能给做出来。

    5、集权

    在你需要由民主投票来下决定之前,至少你已经把软件轮廓做好了。软件开发需要由一两个开明的人来领导,这样可以保证软件开发的连贯性而不至于产生太大的分歧,可以保证开发团队集中火力把要实现的功能做到最好。我觉得,OSS软件最大的风险就是意见不统一,摊子铺的太大,结果最后搞的什么都没有做好。

    (译者按:非常赞同,凡是成功的OSS软件,都是在某个牛人已经把软件做好了之后,发布出来,然后由大家往里面添加功能的,并且在牛人的领导下不断进步。缺乏牛人的OSS软件都不算很成功,比如Mozilla)

    6、文档

    没有什么比文档更重要的了。如果你的用户不知道你的软件有这么一个功能,就等于没有这个功能,干脆把它去掉得了,省得给源代码增加复杂度。

    7、避免标准化

    好的标准可以带来软件的互用性和可移植性,坏的标准能够窒息软件创新!“支持XXX标准”根本就不是真实的用户需求,特别是当这个XXX标准是那些在其位不谋其政“所谓”的专家委员会制订出来的。(译者按:莫非指Sun,IBM等几个big name?)最好的软件是在不断的尝试,不断的出错,不断的经验积累的过程中产生的。 事实上的标准往往更加贴近用户需求。

    8、10分钟之内把Hibernate跑起来

    潜在的Hibernate的用户在他们下载了Hibernate,第一次使用的时候根本就不可能花半个小时那么多时间来安装、配置和 troubleshooting,他们早就丧失了对Hibernate的兴趣了。我们的口号就是新用户(假设有足够的JDBC知识)5分钟之内把 Hibernate的Demo跑起来,而他们能够在1个小时之内写出“Hello World”式的最简单的Hibernate程序并且正常运行。

    9、开发人员的责任感

    用户总是不可避免的碰到问题,开发团队有责任有义务提供帮助。用户让我们知道了文档的漏洞,用户让我们知道了测试用例的小bug。此外,没有用户来用我们的Hibernate,我们还开发它做什么,不是浪费时间吗!

    有个关于bug的笑话:用户根本不介意发现新功能的bug(译者按:Windows的用户好像都是如此),只要你能迅速的改掉bug。“责任感”意味着 bug修复应该在1周之内。从收到bug报告到bug修复代码提交到CVS上要做到平均在24小时左右,这才是一个理想的目标。

    10、易用的、可更新的wiki网页

    让FCKeditor在 JSP和JSF 网站项目中跑起来

    让FCKeditor在 JSP和JSF 网站项目中跑起来

    一、准备功夫
    1.1 下载最新版的FCKeditor
    http://www.fckeditor.net/download/
    目前最新版是:FCKeditor 2.3.1(FCKeditor_2.3.1)和 FCKeditor.Java(FCKeditor-2.3)
    1.2 如果需要使用到jsf的标签库,则还需要下载fck-faces-1.5.1, 你可以到
    http://sourceforge.net/projects/fck-faces去下载

    说明:
    FCKeditor 2.3.1 是功能完善的可视化编辑器,文件上传管理部分功能已经支持asp,php,aspx...等等,
    唯独尚未支持jsp,幸运的是Simone Chiaretta制作了Java 的整合文件FCKeditor.Java。


    二、部署到项目中去
    2.1 把解压后的\FCKeditor_2.3.1\FCKeditor放到项目的WebContent目录下,等候修改
    2.2 把解压后的\FCKeditor-2.3\src目录复制到项目源码目录, 等候修改
    2.3 把解压后的fck-faces-1.5.1\org目录复制到项目源码目录, 等候修改
    注意,暂时不要把解压后的jar包放到lib目录下,因为有些问题需要修改

    三、修改
    3.1 \FCKeditor\fckeditor.js
     修改约第33行的 this.BasePath  = '/fckeditor/' ;
     为 this.BasePath  = '/FCKeditor/' ;
     
    3.2 \FCKeditor\fckconfig.js
     修改约第48行的 FCKConfig.DefaultLanguage  = 'en' ;
     为你喜欢的语言,前提是要它支持。
     
     修改约第134行的
     var _FileBrowserLanguage = 'asp' ;
     var _QuickUploadLanguage = 'asp' ;
     为
     var _FileBrowserLanguage = 'jsp' ;
     var _QuickUploadLanguage = 'jsp' ;
     (不过这个改不改都没有所谓,因为可以通过修改web.xml来实现servlet的url影射。)
     
    3.3 WEB-INF\web.xml
    <!-- FCKEditor -->
     <servlet> 这个是“浏览服务器”功能所用到,包括读取和上传
      <servlet-name>Connector</servlet-name>
      <servlet-class>com.fredck.FCKeditor.connector.ConnectorServlet</servlet-class>
      <init-param>
       <param-name>baseDir</param-name>
       <param-value>/UserFiles/</param-value> 这个根据需要修改
      </init-param>
      <init-param>
       <param-name>debug</param-name>
       <param-value>true</param-value> 调试时候打开
      </init-param>
      <load-on-startup>1</load-on-startup>
     </servlet>

     <servlet> 这个是对话框的简单上传功能所用到,可以上传文件
      <servlet-name>SimpleUploader</servlet-name>
      <servlet-class>com.fredck.FCKeditor.uploader.SimpleUploaderServlet</servlet-class>
      <init-param>
       <param-name>baseDir</param-name>
       <param-value>/UserFiles/</param-value> 这个根据需要修改
      </init-param>
      <init-param>
       <param-name>debug</param-name>
       <param-value>true</param-value> 调试时候打开
      </init-param>
      <init-param>
       <param-name>enabled</param-name>
       <param-value>true</param-value> 如果允许
      </init-param>
      <init-param>
       <param-name>AllowedExtensionsFile</param-name>
       <param-value></param-value>
      </init-param>
      <init-param>
       <param-name>DeniedExtensionsFile</param-name>
       <param-value>php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi</param-value>
      </init-param>
      <init-param>
       <param-name>AllowedExtensionsImage</param-name>
       <param-value>jpg|gif|jpeg|png|bmp</param-value>
      </init-param>
      <init-param>
       <param-name>DeniedExtensionsImage</param-name>
       <param-value></param-value>
      </init-param>
      <init-param>
       <param-name>AllowedExtensionsFlash</param-name>
       <param-value>swf|fla</param-value>
      </init-param>
      <init-param>
       <param-name>DeniedExtensionsFlash</param-name>
       <param-value></param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
     </servlet>

      <servlet-mapping> 通过观察,发现filemanager请求服务器的路径,就修改如下
        <servlet-name>Connector</servlet-name>
        <url-pattern>/FCKeditor/editor/filemanager/browser/default/connectors/jsp/connector.jsp</url-pattern>
      </servlet-mapping>
     
      <servlet-mapping> 这个是Simone Chiaretta的配置,暂时未发现用处
        <servlet-name>SimpleUploader</servlet-name>
        <url-pattern>/FCKeditor/editor/filemanager/upload/simpleuploader.jsp</url-pattern>
      </servlet-mapping>
      <servlet-mapping> 通过观察,发现filemanager请求服务器的路径,就修改如下
        <servlet-name>SimpleUploader</servlet-name>
        <url-pattern>/FCKeditor/editor/filemanager/upload/jsp/upload.jsp</url-pattern>
      </servlet-mapping>
    <!-- End of FCKEditor -->

    3.4 修改 com.fredck.FCKeditor.uploader.SimpleUploaderServlet
     在约第113行附近,就如下面代码,避免构建null路径:
     if (typeStr==null || typeStr.trim().equals("")) typeStr = "File";
     (这个是仿照fckeditor在upload.php,upload.asp等源码来修改的)
     
    3.5 修改jsf标签
    你可以去这里获得fck-faces的源代码,或者联系作者让他修正错误(忘记处理编辑器的宽高)
    http://sourceforge.net/forum/forum.php?forum_id=606070
    (实在找不到,你可以考虑反编译其中这个文件)

    找到 org.fckfaces.taglib.html.FCKFaceEditorTag 类,修改下面方法
     protected void setProperties(UIComponent component)
        { 
            super.setProperties(component);
            Tags.setString(component, "toolbarSet", toolbarSet);
        }
      为: 
      protected void setProperties(UIComponent component)
        { 
            super.setProperties(component);
            Tags.setString(component, "toolbarSet", toolbarSet);
            Tags.setString(component, "height", height);
            Tags.setString(component, "width", width);
        }
    相应的,你还需要修改 org.fckfaces.component.html.FCKFaceEditor的saveState(FacesContext context)和restoreState(FacesContext context, Object state)方法,来保存和恢复宽高属性。

    public Object saveState(FacesContext context)
        { 
            Object values[] = new Object[4];
            values[0] = super.saveState(context);
            values[1]=height;
            values[2]=width;
            values[3] = toolbarSet;
            return ((Object) (values));
        }

        public void restoreState(FacesContext context, Object state)
        { 
            Object values[] = (Object[])(Object[])state;
            super.restoreState(context, values[0]);
            System.out.println(values.length);
            height=(String)values[1];
            width=(String)values[2];
            toolbarSet = (String)values[3];
        }


    找到org.fckfaces.util.Util类

    在调试fck-faces的时候,发现fck-faces的标签非常强大,居然不用再jsp页面引入fckeditor.js,
    它竟然能否自动做到。可惜分析一下这个功能是建立它要求你固定的把FCKeditor的代码放在
    /fckfaces/FCKeditor目录下。如果像我把它放在/FCKeditor就需要作出以下修改:

    public class Util{ 

        public Util() { }

        public static final String internalPath(String path)
        { 
            return (new StringBuilder()).append(FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath()).append(FCK_FACES_RESOURCE_PREFIX).append(path).toString();
        }

        public static final String FCK_FACES_RESOURCE_PREFIX = "/";  //这里原来是/fckfaces
    }

    如果你是反编译得到的源代码,则编译成功之后,需要替换掉原来jar包里面的这个class文件。
    (你可以使用jar命令行来解压jar,换了文件之后在打包。)

    如果你是获得源码修改,可能需要注意配置fck-faces.taglib.xml,fck-faces.tld和faces-config.xml。这几个文件在压缩包里面都有。

    四、使用

    4.1 使用javascipt替换textarea方法
    /////////////////////////////////////////////////////
    <script type="text/javascript" src="<c:url value="/FCKeditor/fckeditor.js"/>"></script>

    <h:form id="newsadd">
    <h:outputText value="Title:"/>
    <h:inputText value="#{ newsAddForm.title }"/>
    <br/>
    <h:outputText value="Content:"/>
    <h:inputTextarea id="content" value="#{ newsAddForm.content }" cols="80" rows="5"/>
    <br/>
    <h:commandButton value="Submit" action="#{ newsAddForm.submit }"/>

    <script type="text/javascript">
    var oFCKeditor = new FCKeditor('newsadd:content') ; //这里设置textarea的id或name
    oFCKeditor.BasePath = '<c:url value="/FCKeditor/"/>' ;
    oFCKeditor.Height = "80%"; //这里设置高度
    oFCKeditor.ToolbarSet = "Default" ;
    oFCKeditor.ReplaceTextarea();
    </script>
    </h:form>
    /////////////////////////////////////////////////////

    4.2 使用jsf标签
    /////////////////////////////////////////////////////
    <%@ taglib uri="http://www.fck-faces.org/fck-faces" prefix="fck"%>
    <h:form id="form1">
     <fck:editor toolbarSet="Default"  value="#{ fckText.text}"  width="80%" height="80%" id="myComponentId" cols="80" rows="18"/>
     
     <h:commandButton action="#{ fckText.print}"/>
    </h:form>
    /////////////////////////////////////////////////////

    五、严重注意事项
    我的web.xml配置了JSF拦截*.html文件,现在FCKeditor使用了大量的*.html,结果导致
    它们都被Faces Servlet拦截了,一个页面都出不来,怎么办呢?
    <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>*.html</url-pattern>
     </servlet-mapping>
     
    想来想去,没有什么好办法,唯有把原来项目中使用*.html的全部改为*.jsf,真惨!
    不知道你有什么好方法呢?还请发表评论或者来信告知! samland@21cn.com
     <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>*.jsf</url-pattern>
     </servlet-mapping>

    JAVA基础:提升JSP应用程序的七大绝招

    提升JSP应用程序的七大绝招

    你时常被客户抱怨JSP页面响应速度很慢吗?你想过当客户访问次数剧增时,你的WEB应用能承受日益增加的访问量吗?本文讲述了调整JSP和servlet的一些非常实用的方法,它可使你的servlet和JSP页面响应更快,扩展性更强。而且在用户数增加的情况下,系统负载会呈现出平滑上长的趋势。在本文中,我将通过一些实际例子和配置方法使得你的应用程序的性能有出人意料的提升。其中,某些调优技术是在你的编程工作中实现的。而另一些技术是与应用服务器的配置相关的。在本文中,我们将详细地描述怎样通过调整servlet和JSP页面,来提高你的应用程序的总体性能。在阅读本文之前,假设你有基本的servlet和JSP的知识。

    方法一:在servlet的init()方法中缓存数据

      当应用服务器初始化servlet实例之后,为客户端请求提供服务之前,它会调用这个servlet的init()方法。在一个servlet的生命周期中,init()方法只会被调用一次。通过在init()方法中缓存一些静态的数据或完成一些只需要执行一次的、耗时的操作,就可大大地提高系统性能。

      例如,通过在init()方法中建立一个JDBC连接池是一个最佳例子,假设我们是用jdbc2.0的DataSource接口来取得数据库连接,在通常的情况下,我们需要通过JNDI来取得具体的数据源。我们可以想象在一个具体的应用中,如果每次SQL请求都要执行一次JNDI查询的话,那系统性能将会急剧下降。解决方法是如下代码,它通过缓存DataSource,使得下一次SQL调用时仍然可以继续利用它:

    public class ControllerServlet extends HttpServlet

     private javax.sql.DataSource testDS = null;
     public void init(ServletConfig config) throws ServletException
     { 
      super.init(config);
      Context ctx = null;
      try
      { 
       ctx = new InitialContext();
       testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
      }
      catch(NamingException ne)
      { 
       ne.printStackTrace();
      }
      catch(Exception e)
      { 
       e.printStackTrace();
      }
     }

     public javax.sql.DataSource getTestDS()
     { 
      return testDS;
     }
     ...
     ...
    }

    方法2: 禁止servlet和JSP 自动重载(auto-reloading)

      Servlet/JSP提供了一个实用的技术,即自动重载技术,它为开发人员提供了一个好的开发环境,当你改变servlet和JSP页面后而不必重启应用服务器。然而,这种技术在产品运行阶段对系统的资源是一个极大的损耗,因为它会给JSP引擎的类装载器(classloader)带来极大的负担。因此关闭自动重载功能对系统性能的提升是一个极大的帮助。

    方法3: 不要滥用HttpSession

      在很多应用中,我们的程序需要保持客户端的状态,以便页面之间可以相互联系。但不幸的是由于HTTP具有天生无状态性,从而无法保存客户端的状态。因此一般的应用服务器都提供了session来保存客户的状态。在JSP应用服务器中,是通过HttpSession对像来实现session的功能的,但在方便的同时,它也给系统带来了不小的负担。因为每当你获得或更新session时,系统者要对它进行费时的序列化操作。你可以通过对HttpSession的以下几种处理方式来提升系统的性能:

      (1如果没有必要,就应该关闭JSP页面中对HttpSession的缺省设置: 如果你没有明确指定的话,每个JSP页面都会缺省地创建一个HttpSession。如果你的JSP中不需要使用session的话,那可以通过如下的JSP页面指示符来禁止它:

    <%@ page session="false"%>

      (2)不要在HttpSession中存放大的数据对像:如果你在HttpSession中存放大的数据对像的话,每当对它进行读写时,应用服务器都将对其进行序列化,从而增加了系统的额外负担。你在HttpSession中存放的数据对像越大,那系统的性能就下降得越快。

      (3) 当你不需要HttpSession时,尽快地释放它:当你不再需要session时,你可以通过调用HttpSession.invalidate()方法来释放它。

      (4) 尽量将session的超时时间设得短一点:在JSP应用服务器中,有一个缺省的session的超时时间。当客户在这个时间之后没有进行任何操作的话,系统会将相关的session自动从内存中释放。超时时间设得越大,系统的性能就会越低,因此最好的方法就是尽量使得它的值保持在一个较低的水平。

    方法4: 将页面输出进行压缩

      压缩是解决数据冗余的一个好的方法,特别是在网络带宽不够发达的今天。有的浏览器支持gzip(GNU zip)进行来对HTML文件进行压缩,这种方法可以戏剧性地减少HTML文件的下载时间。因此,如果你将servlet或JSP页面生成的HTML页面进行压缩的话,那用户就会觉得页面浏览速度会非常快。但不幸的是,不是所有的浏览器都支持gzip压缩,但你可以通过在你的程序中检查客户的浏览器是否支持它。下面就是关于这种方法实现的一个代码片段:

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

     OutputStream out = null
     String encoding = request.getHeader("Accept-Encoding");
     if (encoding != null && encoding.indexOf("gzip") != -1)
     { 
      request.setHeader("Content-Encoding" , "gzip");
      out = new GZIPOutputStream(request.getOutputStream());
     }
     else if (encoding != null && encoding.indexOf("compress") != -1)
     { 
      request.setHeader("Content-Encoding" , "compress");
      out = new ZIPOutputStream(request.getOutputStream());
     }
     else
     { 
      out = request.getOutputStream();
     }
     ...
     ...
    }

    方法5: 使用线程池

      应用服务器缺省地为每个不同的客户端请求创建一个线程进行处理,并为它们分派service()方法,当service()方法调用完成后,与之相应的线程也随之撤消。由于创建和撤消线程会耗费一定的系统资源,这种缺省模式降低了系统的性能。但所幸的是我们可以通过创建一个线程池来改变这种状况。另外,我们还要为这个线程池设置一个最小线程数和一个最大线程数。在应用服务器启动时,它会创建数量等于最小线程数的一个线程池,当客户有请求时,相应地从池从取出一个线程来进行处理,当处理完成后,再将线程重新放入到池中。如果池中的线程不够地话,系统会自动地增加池中线程的数量,但总量不能超过最大线程数。通过使用线程池,当客户端请求急剧增加时,系统的负载就会呈现的平滑的上升曲线,从而提高的系统的可伸缩性。

    方法6: 选择正确的页面包含机制

      在JSP中有两种方法可以用来包含另一个页面:1、使用include指示符(<%@ includee file=”test.jsp” %>)。2、使用jsp指示符(<jsp:includee page=”test.jsp” flush=”true”/>)。在实际中我发现,如果使用第一种方法的话,可以使得系统性能更高。

    方法7: 正确地确定javabean的生命周期

      JSP的一个强大的地方就是对javabean的支持。通过在JSP页面中使用<jsp:useBean>标签,可以将javabean直接插入到一个JSP页面中。它的使用方法如下:

    <jsp:useBean id="name" scope="page|request|session|application" class=
    "package.className" type="typeName">
    </jsp:useBean>

      其中scope属性指出了这个bean的生命周期。缺省的生命周期为page。如果你没有正确地选择bean的生命周期的话,它将影响系统的性能。

      举例来说,如果你只想在一次请求中使用某个bean,但你却将这个bean的生命周期设置成了session,那当这次请求结束后,这个bean将仍然保留在内存中,除非session超时或用户关闭浏览器。这样会耗费一定的内存,并无谓的增加了JVM垃圾收集器的工作量。因此为bean设置正确的生命周期,并在bean的使命结束后尽快地清理它们,会使用系统性能有一个提高

    其它一些有用的方法

      (1)在字符串连接操作中尽量不使用“+”操作符:在java编程中,我们常常使用“+”操作符来将几个字符串连接起来,但你或许从来没有想到过它居然会对系统性能造成影响吧?由于字符串是常量,因此JVM会产生一些临时的对像。你使用的“+”越多,生成的临时对像就越多,这样也会给系统性能带来一些影响。解决的方法是用StringBuffer对像来代替“+”操作符。

      (2) 避免使用System.out.println()方法:由于System.out.println()是一种同步调用,即在调用它时,磁盘I/O操作必须等待它的完成,因此我们要尽量避免对它的调用。但我们在调试程序时它又是一个必不可少的方便工具,为了解决这个矛盾,我建议你最好使用Log4j工具(http://Jakarta.apache.org ),它既可以方便调试,而不会产生System.out.println()这样的方法。

      (3)ServletOutputStream 与 PrintWriter的权衡:使用PrintWriter可能会带来一些小的开销,因为它将所有的原始输出都转换为字符流来输出,因此如果使用它来作为页面输出的话,系统要负担一个转换过程。而使用ServletOutputStream作为页面输出的话就不存在一个问题,但它是以二进制进行输出的。因此在实际应用中要权衡两者的利弊。

    总结

      本文的目的是通过对servlet和JSP的一些调优技术来极大地提高你的应用程序的性能,并因此提升整个J2EE应用的性能。通过这些调优技术,你可以发现其实并不是某种技术平台(比如J2EE和.NET之争)决定了你的应用程序的性能,重要是你要对这种平台有一个较为深入的了解,这样你才能从根本上对自己的应用程序做一个优化!

    ---------------------------------------------------------------------------

    http://community.csdn.net/Expert/topicview.asp?id=5237902

    shine333(enihs) 对本文的补充

    方法 4: 将页面输出进行压缩

    其他的不知道,Tomcat直接修改server.xml:

        <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
        <Connector port="8080" maxHttpHeaderSize="8192"
                   maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
                   enableLookups="false" redirectPort="8443" acceptCount="100"
                   connectionTimeout="20000" disableUploadTimeout="true" />
        <!-- Note : To disable connection timeouts, set connectionTimeout value
         to 0 -->

    <!-- Note : To use gzip compression you could set the following properties :

       compression="on"
       compressionMinSize="2048"
       noCompressionUserAgents="gozilla, traviata"
       compressableMimeType="text/html,text/xml"
    -->

    当上面一段被添加到Connector后,只要是大于2048字节的Content Type为text/html,text/xml,且客户端不是gozilla, traviata的,都会被gzip,建议实际应用楼主的Servlet的时候考虑上述设置,即只有对文本类型,且过大的内容进行压缩,才会有益

    在Struts+Hibernate+Spring中解决中文乱码的方法

    软件环境:JDK1.4.2_09+Eclipse3.1+MS SQL SERVER200+SP3+JTDS1.0.2+Struts1.1+Hibernate3.0.5+Spring1.2.4。

    由于刚开始学习这个Framework,所以很多东西也不是特别清楚,以前在JB环境下也没怎么遇到乱码问题。这次试了很多方法都不行,于是决定加个Fileter了,web.xml部分内容如下:

     <filter>
      <filter-name>SetCharacterEncoding</filter-name>
      <filter-class>
       org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
       <param-name>encoding</param-name>
       <param-value>GBK</param-value>
      </init-param>
     </filter>
     <!-- 要过滤得类型 -->
     <filter-mapping>
      <filter-name>SetCharacterEncoding</filter-name>
      <url-pattern>*.jsp</url-pattern>
     </filter-mapping>

    通过在Action中加断点调试,发现使用超连接的跳转是可以使用Filter的;但是如果是以.do为后缀的请求就不行了,抱着试试看的心理,我修改了web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
     <!--Spring ApplicationContext-->
     <servlet>
      <servlet-name>context</servlet-name>
      <servlet-class> org.springframework.web.context.ContextLoaderServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
     </servlet>
     <servlet>
      <servlet-name>action</servlet-name>
      <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
      <init-param>
       <param-name>config</param-name>
       <param-value>/WEB-INF/struts-config.xml</param-value>
      </init-param>
      <init-param>
       <param-name>debug</param-name>
       <param-value>3</param-value>
      </init-param>
      <init-param>
       <param-name>detail</param-name>
       <param-value>3</param-value>
      </init-param>
      <load-on-startup>0</load-on-startup>
     </servlet>
     <servlet-mapping>
      <servlet-name>action</servlet-name>
      <url-pattern>*.do</url-pattern>
     </servlet-mapping>
     <filter>
      <filter-name>SetCharacterEncoding</filter-name>
      <filter-class>
       org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
       <param-name>encoding</param-name>
       <param-value>GBK</param-value>
      </init-param>
     </filter>
     <!-- 要过滤得类型 -->
     <filter-mapping>
      <filter-name>SetCharacterEncoding</filter-name>
      <url-pattern>*.jsp</url-pattern>
     </filter-mapping>
     <filter-mapping>
      <filter-name>SetCharacterEncoding</filter-name>
      <url-pattern>*.do</url-pattern>
     </filter-mapping>

     <welcome-file-list>
      <welcome-file>main.jsp</welcome-file>
     </welcome-file-list>
     <taglib>
      <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
     </taglib>
     <taglib>
      <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
     </taglib>
     <taglib>
      <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
     </taglib>
     <taglib>
      <taglib-uri>/WEB-INF/struts-template.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-template.tld</taglib-location>
     </taglib>
     <taglib>
      <taglib-uri>/WEB-INF/struts-tiles.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
     </taglib>
     <taglib>
      <taglib-uri>/WEB-INF/struts-nested.tld</taglib-uri>
      <taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
     </taglib>
    </web-app>

    主要在这里多加了一个过滤内容!其他的,为防止万一,在页面(jsp)上也加了些东西:

    <%@ page contentType="text/html; charset=GBK" pageEncoding="GBK"%>

    <meta http-equiv="content-type" content="text/html; charset=GBK">

    呵呵,可以说是武装到牙齿了,开始调试:这次在Debug的时候,显示出从页面中传来的值终于不是乱码了,保存在数据库中后,也不是乱码。这个问题目前是部分解决了。因为我还没有测试在页面上哪些是不用写的,还有就是页面回现汉字是是否会有问题,不过这里先把自己的所得记录下来,如果有高人就此事谈论过,就算我孤陋寡闻吧,呵呵。

    另外给出我的Hibernate.cfg.xml的部分内容:

        <session-factory>
            <property name="hibernate.connection.url">jdbc:jtds:sqlserver://192.168.0.3:1433;DatabaseName=HomeConsume;charset=GBK</property>
            <property name="hibernate.cglib.use_reflection_optimizer">true</property>
            <property name="hibernate.connection.password">sju</property>
            <property name="hibernate.connection.username">sa</property>
            <property name="hibernate.connection.driver_class">net.sourceforge.jtds.jdbc.Driver</property>
            <property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property>
            <mapping resource="net/magicyang/homeconsume/pojo/Test.hbm.xml" />
            <mapping resource="net/magicyang/homeconsume/pojo/Consumeinfo.hbm.xml" />
            <mapping resource="net/magicyang/homeconsume/pojo/Consumetype.hbm.xml" />
        </session-factory>

    实战Struts+Spring+Hibernate

    实战Struts+Spring+Hibernate

    经过几天在网上的搜索,查看了关于Struts、Spring、Hibernate的文章,在实际的使用后得到了一些体会。鉴于很多资料不全或较旧,特写下一篇较为完整的实战指南,包括主要程序类和配置文件。

    一、准备篇

     实战前需准备以下开发组件(都是目前最新版本)。
     1、Struts 1.2.9,从www.apache.org网站可下载。
     2、spring-framework-1.2.8,从www.springframework.org网站可下载。
     3、Hibernate 3.1.3,从www.hibernate.org网站可下载。
     4、Tomcat 5.0.28,从www.apache.org网站可下载。

     
    二、程序篇

     java包我们建立如下结构
     hibernate存放hibernate相关类
     impl存放service实现类
     interfaces存放接口类
     player存放struts相关类
     
    1、Hiberate相关类(HBM、PO)可以使用工具直接生成,这里不再列出

    2、IPlayerDAO.java 持久层的接口类

    public interface IPlayerDAO

     public abstract List findTop(final int count);
     public abstract TblPlayer findById(String id);
    }


    3、TblPlayerDAO.java 持久层的接口的实现类,HibernateDaoSupport提供了对Hibernate良好的支持,下例的两个方法显示了HibernateTemplate的两种最常用的方法。

    public class TblPlayerDAO extends HibernateDaoSupport implements IPlayerDAO

     public List findTop(final int count)
     { 
      return (List) getHibernateTemplate().execute(new HibernateCallback()
      { 
       public Object doInHibernate(Session session) throws HibernateException
       { 
        Query q = session.createQuery("select player from TblPlayer player order by player.Id");
        q.setFirstResult(0);
        q.setMaxResults(count);
        return q.list();
       }
      });
     }

     public TblPlayer findById(String id)
     { 
      return (TblPlayer) getHibernateTemplate().get(TblPlayer.class, id);
     }
    }

    4、IPlayerService 业务层接口类

    public interface IPlayerService

     public abstract void setPlayerDAO(IPlayerDAO playerDAO);
     public abstract List findTop(final int count);
     public abstract TblPlayer findById(String id);
    }

    5、PlayerServiceImpl 业务层接口实现类
    public class PlayerServiceImpl implements IPlayerService

     IPlayerDAO playerDAO = null;
     
     public void setPlayerDAO(IPlayerDAO playerDAO)
     { 
      this.playerDAO = playerDAO;
     }
     

     public List findTop(final int count)
     { 
      return playerDAO.findTop(count);
     }
     
     public TblPlayer findById(String id)
     { 
      TblPlayer player = playerDAO.findById(id);
      return player;
     }
    }

    6、PlayerAction.java
    public class PlayerAction extends Action

     private IPlayerService playerService;
     
     public void setPlayerService(IPlayerService playerService)
     {   
      this.playerService = playerService;
     }
     
        public ActionForward execute(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response)
        { 
         PlayerForm form = (PlayerForm)actionForm;
         try
         {       
          TblPlayer player = playerService.findById(form.getPlayer().getId());
          request.setAttribute("player",player);
         }
         catch(Exception e)
         { 
          request.setAttribute("errorMsg","选手未找到");
          log.error(e,e);
         }
         return mapping.findForward("ViewDetail");
        }
    }

    7、PlayerForm
    public class PlayerForm extends ActionForm

     static final long serialVersionUID = -9073943177741915925L;
     TblPlayer player = new TblPlayer();
     public TblPlayer getPlayer()
     { 
      return player;
     }
     public void setPlayer(TblPlayer player)
     { 
      this.player = player;
     }
     
    }

    三、配置篇

    1、web.xml
    <?xml version="1.0" encoding="ISO-8859-1"?>

    <!DOCTYPE web-app
      PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
      "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

    <web-app>
      <display-name>Tour Web Application</display-name>
      <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/applicationContext-hibernate.xml</param-value>
      </context-param> 
     
      <filter>
         <filter-name>CharacterEncoding</filter-name>
         <filter-class>com.lutong.CharsetFilter</filter-class>
      </filter>
       
      <filter>
         <filter-name>OpenSessionInViewFilter</filter-name>
         <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
      </filter>
     
      <filter-mapping>
         <filter-name>CharacterEncoding</filter-name>
         <servlet-name>action</servlet-name>
      </filter-mapping>
     
      <filter-mapping>
         <filter-name>OpenSessionInViewFilter</filter-name>
         <url-pattern>/*</url-pattern>
      </filter-mapping>
     
      <servlet>
        <servlet-name>WebLog</servlet-name>
        <servlet-class>com.kehaoinfo.log.WebLog</servlet-class>
        <init-param>
          <param-name>log4j-init-file</param-name>
          <param-value>/WEB-INF/classes/log4j.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
     
      <servlet>
        <servlet-name>SpringContextServlet</servlet-name>
        <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
      </servlet>
     
      <!-- Standard Action Servlet Configuration (with debugging) -->
      <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
          <param-name>config</param-name>
          <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
          <param-name>debug</param-name>
          <param-value>2</param-value>
        </init-param>
        <init-param>
          <param-name>detail</param-name>
          <param-value>2</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
      </servlet>


      <!-- Standard Action Servlet Mapping -->
      <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
      </servlet-mapping>


      <!-- The Usual Welcome File List -->
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>


      <!-- Struts Tag Library Descriptors -->
      <taglib>
        <taglib-uri>/tags/struts-bean</taglib-uri>
        <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
      </taglib>

      <taglib>
        <taglib-uri>/tags/struts-html</taglib-uri>
        <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
      </taglib>

      <taglib>
        <taglib-uri>/tags/struts-logic</taglib-uri>
        <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
      </taglib>

      <taglib>
        <taglib-uri>/tags/struts-nested</taglib-uri>
        <taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
      </taglib>

      <taglib>
        <taglib-uri>/tags/struts-tiles</taglib-uri>
        <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
      </taglib>

      <taglib>
        <taglib-uri>/tags/spring</taglib-uri>
        <taglib-location>/WEB-INF/spring.tld</taglib-location>
      </taglib>
     
    </web-app>

    2、applicationContext-hibernate.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

    <!--
      - Root application context for the Countries application.
      - Web-specific beans are defined in "countries-servlet.xml".
      -->
    <beans> 
     <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
      <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"/>
      <property name="url" value="jdbc:jtds:sqlserver://192.167.0.107:1433/tour;charset=gb2312"/>
      <property name="username" value="sa"/>
      <property name="password" value=""/>
     </bean>
     
     <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
      <property name="mappingResources">
       <list>
        <value>com/lutong/tour/hibernate/hbm/TblPlayer.hbm</value>
       </list>
      </property>
      
      <property name="hibernateProperties">
       <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.SQLServe