2009年2月26日星期四

Hibernate 的Configuration和它的ClassLoader.

还是在整理遗留系统的hibernate问题。
现状:
1)应用是eclipse RCP应用。多个自定义的plugin。其中一个是用于共享通用代码目的的base plugin。
2)Hibernte2.0版。在base plugin项目中包含了一个hibernte.cfg.xml,在其lib目录下有hibernate2.jar。其它plugin的lib目录也有hibnerate2.jar,也在使用自己的hibernate.cfg.xml.

问题:
1)开始试图让整个应用只有一个sessionFactory实例。发现这就有个项目依赖问题。如果把hibernate sessionFactory的管理放在base-plugin项目里。那么其它项目如果需要自己的hibernate dao 访问怎么办?虽然可以通过在base-plugin模块里定义一些配置文件读取点的方式加载额外的hibernate mapping文件,但是,如何把子项目的class加到base-plugin的classpath中呢?(也许可以写自己的classloader)。通过配置文件是解决依赖的一个好办法。但是,没有写classloader的经验,也没有时间去做这种尝试。这种逻辑放在框架里比较合适,而不是放在仅仅作为工具库的base project里。

2)接下来就考虑把Hibernate的jar文件只放在base-plugin的lib中。如果那个项目需要书库访问功能,就在自己的(子)项目中写new Configuration().configure().buildSessionFactory()。觉得这还算个差不多干净的办法。当进行代码实现时,技术性问题又冒出来了。以下蓝色文字仅代表自己对所遇问题的一些理解和猜测。plugin运行时有自己的classloader。而hibernte的Congfiguration().configure()在搜索hibernate.cfg.xml时是从加载Configuration这个类的classloader的classpath路径中寻找。这在hibernate2.jar仅存在于base项目的情况下是找不到你子项目的hibernate配置的,它又重复加载了base项目中的hibernate配置。查看Hibernat API文档,知道可以给configure()传入resource参数指定hibernate.cfg.xml的位置。通过AbcClass.class.getClassLoader().getResource("/abcHibernate.cfg.xml");的方式指定了abc项目用的.cfg.xml文件。Ok. Hibernate 被成功初始化了。但是,当使用hibernate query后,访问list的元素时发生了ClassCaseException,跟踪的SDK的source, List还是跟classloader有关系的。

3)接下来的想法就是把hibernate的jar文件放在各自project的lib里。应用程序在启动时,启动一个线程初始化sesssionFactory(),问题又来了。当第二个sessionFactory在同一个线程中初始化时,得到了另一个运行时 exception: Exception in thread "Thread-2" java.lang.LinkageError: Class a/b/c/d/Abc.class violates loader constraints. ... ... google到这段。没办法了。就先初始化一个sessionFactory吧。只是不知道Hibernte3.0是否也是同样的机制。
Hibernate always uses the classloader of the current running thread, which guarantees predictable behavior in all environments. In some environments with weak classloader/deployment configuration options (SAP, JONAS, etc.), it is necessary to tell Hibernate to use a different classloader, not the loader that loaded hibernate2.jar. Usually, this should be transparent to Hibernate and the application server would know what classloaders to use, depending on the packaging. Also note that this is not an issue if the "one application - one Hibernate version" strategy is used, which is the case in most production environments (you have dedicated libraries per application).

We currently allow a customizable classloader for addResource(), but this only loads the mappings with the given classloader. We would need an option to specify a classloader for the persistent classes as well, basically, for all Hibernate operations.
http://opensource.atlassian.com/projects/hibernate/browse/HB-1310
经验:
是不是common的功能,有时候不能仅仅从功能上划分,技术细节也是重要的。

====下面是程序运行时打印的log, 可以看出currentThread的classloader都是null(
Some implementations may use null to represent the bootstrap class loader.)。而不同plugin的classloader是不同的。
Current Thread's classLoader: null
BtafHibernateUtil's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4
Configuration's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4
cf's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1d520c4


Current Thread's classLoader: null
SessionFactory's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579
Configuration's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579
cf's classLoader: org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1c6f579

2009年2月25日星期三

网络中的Connection和业务中的Session

网络编程中总会建立Connection对象代表到某个endpoint的连接,或长或短,代表了一个逻辑上的并且真实的数据传输通道。
Session并不像connnection那样对应某个真实网络元素。它是应用程序中的一个逻辑单元,是某种业务(需求)的抽象。

举例说明(借机总结一下以前项目中的设计):
1)Neustar IM项目中,Connection类就是代表了一个网络连接。在客户端应用程序中有一个连接是到服务器的长连接,用于侦听服务器发送来的命令、通知。客户端还有一个连接是短的,用户每次发送消息时使用。而session,在这个应用中代表了用户与其它用户的一个(相对某段时间内连续的)会话。这个会话保存了聊天记录。这个会话可能是某种特别的session,比如群组聊天。(当时的项目没有把群聊的功能利用session作为接口。现在再说是马后炮了。)总之,session是你业务逻辑方面的概念。
2)现在的项目又遇到了类似的问题。到dslam设备需要真正的telnel连接。每套测试用例都要通过connection被发送到设备执行。如果需要并行执行一些测试,就需要同时存在若干到设备的连接。这里,我们就可以把执行一次测试,这样逻辑上的概念,当作一个session。

connection和session的区别应该是这样的了。它们之间的联系呢?
应该是session必然对应一个connection吧,并且至少一个吧,多数情况下恐怕也是一个。

ps 中VSZ、RSS的意义

工作中要通过ps监控unix设备中某些进程的内存使用情况。需求是统计进程使用内存的变化。

VSZ:虚拟内存大小。 RSS:常驻集大小。
VSZ表示如果一个程序完全驻留在内存的话需要占用多少内存空间,而RSS指明了当前实际占用了多少内存。
另外的输出STAT:S表示程序处于休眠状态,R表示处于运行状态(指在运行队列中)

我不知道的是VSZ是否会根据程序的运行不断的增长或变小,比如程序不断的分配内存。要写个程序跑到unix上试试了。呵呵。

今天先不管那么多,先抓取数据吧。

关于linux内存http://q.mq35.com/html/6/2/200810/06-655.html
IMB DW 系统管理员工具包-进程管理技巧: http://www.ibm.com/developerworks/cn/aix/library/es-unix-sysadmin1.html

可以学习、理解一下这段:
$ ps -A -o rss,vsz,command | grep bash | awk '{rss += $1; vsz += $2 } END { print "Real: ",rss, "Virtual: ",vsz }'
Real:  4004 Virtual:  305624

2009年2月24日星期二

SonarJ印象

hello2morrow is an independent software vendor specialized in tools for the management and monitoring of architecture and technical quality of software written in Java, C/C++ orC#.
SonarJ. 轻盈的色彩,简单的逻辑。读读tutoria就l很容易上手使用。
感觉就是一个依赖管理工具,要想依赖的漂亮,当然要先分层。它也就是一个分层管理工具。说管理也许不准确。它主要还是监控功能。
1)你先用横条竖棍把一个系统划分成architectural artifacts。
2)然后把程序中用package组织的class添加到格子里。(一个class只能在一个格子里。)
3)建立、设立各种允许、禁止的依赖关系。
4)在Exploration视图中验证你的code。

应该说SonarJ做的事情很基本很有限。它只是对你预先建立好的layer进行了monitor。而对你如何划分这些层次并没有给出任何回馈。虽然简单,个人感觉还是非常值得使用。就如今天发现的竟然在数据类中看到了import ui的类。这种超低级的错误足以让你的项目在维护期遭受无情的骂名。(今天看到一个同事的签名档是:前人种树,后人捉虫。哈)

适合于architect已经搭建完了框架后,在程序员完成项目过程中进行监管。如果SonarJ在提供脚本API进行报表生成,那么再利用持续集成工具进行驱动,就可以实现对违反架构的编码自动化报警了。

SonarJ可以以eclipse插件的形式与eclipse集成。正如其文档所说,在eclipse中仅仅适用于verify的功能。各种约束的设置还要在SonarJ App中完成。

关于SonarJ的论述及其它类似工具比较:http://www.infoq.com/news/2009/02/sonarj-community-edition
SonarJ主页:http://www.hello2morrow.com/

纠错对hibernate sessionFactory的使用

现状:
1 程序中使用username/password。硬编码。
2 待连接的数据库IP可能变化,并且不是从默认的hibernate.cfg.xml读取,而是从eclipse的preference本地存储读取。
3 获取hibernate sessionFactory的代码放在common包里。而数据库的IP配置是在btaf包里。

问题:

因为database IP可以动态改变,所以,HibernateUtil传统的静态初始化已经不能使用。同时,对preference中IP的依赖要传递到HibernateUtil中。
DBConnInfo在构造时保存从preference中获得的IP等信息,并提供方法返回DB URL。但是,当用户更改了IP后不能够构造新的sessionFactory.

解决:

1)在common包中创建HibernateUtil类。提高静态方法 public synchronized static SessionFactory getSessionFactory(String url);
这个方法根据url构建hibernate SessionFactory实例。如果传入的url不同于上个,则需要首先关闭以前创建的sessionFactory.
2) 所有获得sessionFactory的地方,都通过HibernateUtil.getSessionFactory(url)获得。不能把sessionFactory作为类变量保存。这样就保证了sessionFactory的单例。
3)在UI上更改DB IP后,要更新DBConnInfo实例。因为DBConnInfo是单例,所以获得url可以保证最新。

经验:
  • Hibernae的SessionFactory代表了对数据库的连接资源。
  • 如果你的应用中只需要用一个用户名密码对一个数据库连接,那么,一定要保证对SessionFactory实例是单例的。
  • 对sessionFactory 对象的引用生命期越短越好,最好不要出现在域变量中。
  • 如果你需要重新建立一个sessionFacotry,你需要保证对被废弃的sessionfactory object进行必要的清理工作。

**
DbConnInfo 这个类本来的目的就是保存数据库连接信息。但是,里面竟然出现了import ##.ui.preferences.PreferenceConstants;

2009年2月19日星期四

建模 --- 对称性及传递性

场景:
当用户点击启动后,一个线程T1开始运行某个Job队列中的各个job对象的代码。当用户点击stop按钮时,就需要让线程停止驱动job对象的代码。
T1就叫做JobExecutor, 被执行的工作队列叫做JobSuilt, JobSuilt中的对象就是Job。
现有的系统中,JobExecutor有start(), stop()方法。系统目前不能很好的控制job的停止。

问题:
当时肯定还没有把很多事情想清楚就开始编码了。根本没有很好的建模。当按下停止按钮时。目前的代码只是在想简单的让JobExecutor线程停止下来。而如何通知正在运行的job没有很好的设计。

解决:
start(), stop()是对称性的两个方法。一个很好的思路是:被启动(started)的对象也要有对应的start()方法来响应executor的start操作。被停止的对象也要有stop()方法。
这样这种操作就具有了一致的传递性。

扩展:
当Job遇到问题(Exception, Error之类)需要通知exector时,需要通知executor进行对应处理时,可以把executor传递给Job。但是这样executor和Job就有了较强的耦合。
一个办法就是声明一个接口比如叫JobListener,这个接口不妨定义void processException(Exception e), void processError(), void finished()...,而executor可以实现JobListener接口。
这样就有了从executor到JobListner的双向通讯机制。


《实现模式》这本书中提到了对称性,并把其发在了“原则”性的地位。在建模过程中,你越是能够抽象出真正的Mode(拥有确实是属于自己的属性、方法),发现mode之间真正的关系(Mode之间的关联),你未来的麻烦就越少。

2009年2月17日星期二

Eclipse RCP plugin之间的jar依赖及共享。

问题:
Eclipse的RCP编程中需要开发若干plugin,如果一个jar已经包含在某个plugin中,那么如何实现这个jar文件的共享?

Resolve:
1) 不妨定义一个generic的plugin,比如叫做common-plugin,这个plugin就是为了共享资源建立的。当然你也可以根据你的系统设计找到某个“最根本”的plugin,把必要的jar文件放到这个plugin里。
2)通过eclipse project properties设置页,把共享的lib都export出来。
3)编辑common-plugin,的plugin.xml。在RunTime页中把需要共享给其它plugin的包,加入到exported packages列表里。
4)打开需要common-plugin库的其它plugin的plugin.xml文件,在Dependencies tab中添加上common-plugin为被依赖的包。

2009年2月16日星期一

Executor为谁而生?---- 执行策略

Executor与Runnable的定义极为相似。为什么要加上个Executor?
public interface Executor {
    void execute(Runnable command);
}

public interface Runnable {
    void run()
}

Runnable规范了任务的入口。Thread提供了驱动任务的线程接口。
有了这两个interface,你就可以完成最简单的多线程程序。但是如果你实现一些执行策略,你将面临非常有挑战性的编程。什么是执行策略?执行策略定义了用线程执行任务的一些方式。如,可以有多少个任务可以并行?何种顺序(FIFO, FIFO,自定义的优先级算法)?最多有多少任务可以等待?如果超过了可等待的任务数,系统如何反应? 任务执行前做什么?任务执行后做什么?诸如此类。
Runnable和Thread没有从API上提供解决"执行策略"的任何线索。要实现你特定的执行策略就需要你利用Thread的sleep(),wait(),notify(), interrunppt(),isInterrupt()进行高水平的设计。
如果你的系统不同模块需要使用不同的执行策略,如果你的系统需要动态的调整执行策略,这些功能是不是更具挑战性?

Executor就是Thread和Runnable之间的一层隔离、解耦。这层隔离为Thread和Runnable之间实现多种交互提供了可能与方便。Executor的各种子类实际上就实现了各种不同的执行策略。

Exector的是java.util.concurrent的基石。

Link:Executor API
All Known Subinterfaces:
ExecutorService, ScheduledExecutorService
All Known Implementing Classes:
AbstractExecutorService, ScheduledThreadPoolExecutor, ThreadPoolExecutor

2009年2月15日星期日

Google App Engine很吸引人。

免费享受Google的云计算,只要你掌握GoogleAppEngine API。太诱人了。真不知道将如何影响搞虚拟主机业务的小公司了,也许取决于 python的流行。

开始做GAE的用户了。呵呵。

A webapp application has three parts:
  • one or more RequestHandler classes that process requests and build responses.
    相当于Servlet;
  • a WSGIApplication instance that routes incoming requests to handlers based on the URL.
    差不多是ServletFilter的功能。
  • a main routine that runs the WSGIApplication using a CGI adaptor.
    就是完成servlet engine里分发功能吧。
... ...

2009年2月13日星期五

TelnetEngine重构,Connection, Protocol 以及 Command各司其职

从这个class diagram中可以看到设计问题。
IConnection代表一个对设备的连接的抽象。具体有可能是Telnet, 或者HTTP之类。

TelnetConnection是基于Telnet协议的连接。

ResponseReceiverThread 是TelnetConnection被open()之后启动的接受connection output的线程。

IResponseListener抽象了对output输出进行监听的回调方法。

DslamTelnetProtocol实现了IResponseListener,希望这个类能够实时处理输出。

从类的名称到类提供的方法,都有不少问题。下面依次分析:
  1. TelnetConnection 的其中一个构造函数使用了用户名、密码。Login()功能应该从TelnetConnection中移走。

  2. 在TelnetConnection 中的connect()方法中编码实现了DslamTelnetProtocol到ResponseReceiverThread的注入、ResponseReceiverThread的启动。这样,依赖关系不明显。应当显式的把responseListener注入到connection。ResponseReceiverThread可直隐式的管理比较好。

  3. DslamTelnetProtocol是个名词。实际它完成对Dslam命令结果进行解析的工作。这个类应该分拆成两个类。并应该重命名。
    DslamDeviceConstantData: DslamTelnetProtocol中定义的与DSLAM相关的常数应该分离到这个类中。而且若干参数是用户设定不变的、一些是连接到设备后从中读取的。
    DslamCommandResultParser: 这个名称更能体现类的功能。
    【是否给DslamCommandResultParser创建一个接口ICommandResultParser?目前还感觉不到有什么意义,因为目前还只是处理一个设备,所以也就不知道到底该抽象出哪些东西。其实上面的IConnection, TelnetConnection 也有同样的问题。】

  4. 还有一些方法实现的细节需要改正。比如, wdy建议读取到一个命令结果后就自动把输出缓存清空。是啊,我为什么要保留2M的输出缓存呢?目前其没有对结果缓存的需求,也使得结果处理复杂了。

2009年2月12日星期四

控制线程 ---- 停止

写多线程的程序需要对并发带来的危险(竞争、死锁、脏读...)进行预防。同时还有一个重要的方面是对那些并行执行的代码进行控制,比如:停止、等待、继续。

让线程驱动的代码要么是被while括起来的需要长时间运行;要么是个临时的任务,运行到最后让线程自动终结。

为什么停止线程是个问题?因为线程的阻塞状态。
线程的四个状态:New, Runnable, Blocked, Dead
导致thread blocked的四个原因: sleep(sometime)、 wait()、I/O 操作、 synchronized导致的锁竞争。

需要停止线程操作的通常都是要终止长时间运行的线程。让线程停下来,大致有以下几种方法:
1)利用代码中while的条件判断语句,检测一(多)个是否需要停止的标志。
这个标志的访问需要同步控制。同时可以被其它线程代码改变。
2)使用interrupt()方法,让线程正在驱动的的代码产生InterruptedException
你可以在代码中捕获这个exception,进行适当的判断,然后让线程优雅的停止。
3)但是第二种方法对于因为以下两种原因造成的阻塞不起作用:IO和等待进入synchronized块。
针对IO造成的阻塞,可以关闭IO资源,这样迫使IO操作产生exception而让线程从阻塞状态返回,从而获得停止线程的机会。
4)因为等待锁而阻塞的线程利用传统的thread的api似乎没有什么好办法。一个建议是不要长时间的占用那些很多代码都想获得的锁
5)在提倡使用concurrent包的情况下。可以利用Executor.shutdownNow()停止所有其管理的线程。利用Executor.submit()获得的Future cancel(true)来对单独的线程进行停止操作。

**
现象:
启动一个线程执行一串代码,其中某步抛出了Error,注意是error不是Exception。这意味着你的线程嘎然而止。这种退出方式实在是太暴力了。:)
实践:
1)需要在你的线程执行的代码中捕获任何异常、错误。
Throwable 是Error、Exception的父类。而Exception又分为Runtime的exception和不是runtime的。我的实践是在run()的代码中catch (Throwable t),这样就可以保证你不遗漏任何异常了。
2)对产生错误的原因进行分析。具体问题具体分析。可以或应该继续执行的,请记录异常信息,继续代码的执行。如果需要退出线程的,需要注意释放在线程执行中申请的资源以及修改程序状态标志(某些应用数据)。

2009年2月11日星期三

Fat Client中使用Hibernate C3P0

BTAF项目使用hibernate。作为胖客户端使用hibernate应该注意对连接池的配置。因为你不能控制客户端的在线时间,所以基本的原则是:尽少占用数据库连接、尽快释放连接。
所以
1)连接池最少连接数应该为零或者1;
2)最多缓存的连接数应该根据具体应用不同而不同;
3)从连接池中的撤销的周期应该短;
4) 检查过期连接的频率也应该频繁些。
下面是个示例:
<property name="c3p0.min_size">0</property>
<property name="c3p0.max_size">5</property>
<property name="c3p0.timeout">180</property>
<property name="c3p0.idleTestPeriod">60</property>
从hibernate的一些输出:
BasicResourcePool:1935 - Checking for expired resources - Thu Feb 12 11:04:32 CST 2009 [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]
BasicResourcePool:1447 - BEGIN check for expired resources. [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]
BasicResourcePool:1468 - FINISHED check for expired resources. [com.mchange.v2.resourcepool.BasicResourcePool@9ddde9]


关于如何在hibernate中配置C3P0:http://www.hibernate.org/214.html
关于c3p0更详细的介绍:http://www.mchange.com/projects/c3p0/index.html#configuration_properties

注意, hibernate文档中介绍说有些参数Must be set in hibernate.cfg.xml(or hibernate.properties),而有些Must be set in c3p0.properties。为什这样?因为必须在hibernate中设置的参数肯定经过了hibernate的封装,也许是因为这些参数太基本(重要)了。

使用google应用

1)创建google账户
https://www.google.com/accounts/NewAccount?continue=http%3A%2F%2Fwww.google.cn%2F&hl=zh-CN

2)下载用于浏览器的google工具栏
http://www.google.com/tools/firefox/toolbar/FT5/intl/zh-CN/index.html

3)登录google Docs在线文档应用程序。
建立、维护自己的的文章。(word, PPT, PDF ...) 这些文章可以发布成:网页、发布到博客、发布到自己建立的google site。尽量在docs.google.com里维护自己的文档。
http://docs.google.com/


4)用第一步创建的google帐号创建博客.
http://www.blogger.com/

5) 利用google的服务创建自己的site.
http://sites.google.com/
如果自己审美能力强,可以做出比较漂亮的站点:http://www.google.com/sites/help/intl/zh-CN/overview.html

6)我还是用了google code, google reader, goolge analytics ... 都非常好。

2009年2月10日星期二

Jelly ---- 为XML赋予执行能力

Jelly的思想看上去很简单。预定义了一些tag,关键是你可以自定义tag,然后用自己的xml引擎回调自己的tag processor。恩。就是这样的原理吧。

Jelly的这种能力正好与我现在的问题域相匹配:
1)我需要自动的通过telnet去执行很多的DSLAM命令;
2)对命令的结果进行处理,最终获得命令成果或是失败的结果;
3)结果的true/false会控制后续的命令是否执行
4)需要能够让用户自定义变量给DSLAM命令使用;
5)用户执行dslam命令时需要while和if 的能力。
浏览了一下Jelly的预定义tag,已经包含了很丰富的用于逻辑判断的tag。太棒了!只要很好总结、抽象出对命令的处理,我一定是可以写出功能强大的CLTF的。:)

感谢zero.liu的一篇摘读,让我看到了Jelly。曾经的一个想法是去使用groovy这样的脚本语言的。

多态与适配器模式

背景:N2X和TestCenter的很多功能都很类似。以前同样的功能都是针对不同的设备写的独立代码。
提出一个需求:测试人员希望一个测试step的代码对于设备是透明的,既能在n2x上跑,也能在TC上跑。
问题:虽然这两个设备的多数功能都有重叠,但是每个功能API的接口参数、格式还是有差别的,而且具体的功能能力也是有些差别的。

解决:
抽象出一个参数类。
抽象出一个设备接口,定义出设备能力。
直接用OO的多态特性对调用进行设备匹配。
有的同事说用适配器模式。适配器模式是针对接口不一致时进行中间层的适配。这里其实体现的是一种动态地对某种实现的选择。似乎没有必要立刻就考虑使用哪种模式。
【UI层通过使用tab等组件根据设备的不同接受参数输入。】

困难:
功能的差异还是很难解决。

2009年2月9日星期一

对敏捷的一点思考 ---- 敏捷是强调结果的

现在软件工程项目管理流行使用“敏捷”。经历了一些敏捷项目,有了些感想。
现在的项目组强制执行结对编程。我喜欢敏捷因为敏捷是山寨版的CMMI。而山寨代表着先进生产力、代表着具体问题具体分析的思想与实践。

先看看Agile 宣言与原则。你就发现敏捷其实是强调结果的。它用结果督促、指导项目的进行。
但是我觉得敏捷开发忽略了对总体架构或者系统设计的要求与指导。

在最近的几个项目中,都号称用敏捷的模式进行项目管理:每天早上的15分钟会议、结对编程、与用户的直接沟通。但这些手段都不能很好的解决在软件框架的设计问题。因为大多数程序员的经验与水平还不能够为项目建立框架(spring, struts这些现有框架确实解决了很多问题。但是一但项目需要定制自己的框架时,问题就凸显出来。)
如何根据具体的项目特点搭建适合自己业务需求的框架能够敏捷出来么?我个人觉得是不大可能的。因为这要求开发人员在对业务、技术比较了解的情况下进行更深一个层次的抽象。如何抽象?哪些可以抽象?抽象后如何向外提供服务(提供调用接口或IoC)?都是需要有比较专业的思考,同时很多也是经验。
想说的是,敏捷并不能替代全部的传统的软件工程流程,尤其是系统设计这一块
敏捷是方法论,并不是保证。就如,设计也需要敏捷一样。

虽然我自己对敏捷有了以上负面的感觉,但我依旧喜欢敏捷,因为它思想包括:个体和交互、客户合作。

Manifesto for Agile Software Development
个体和交互 胜过 工具和过程
可以运行的软件 胜过 面面俱到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划


敏捷原则

1. 优先级最高的是,通过早期和持续交付有价值的软件来满足客户。
2. 欢迎变更需求,即使在开发的后期提出。敏捷过程为客户的竞争优势而控制变更。
3. 以两周到两月为周期,频繁地交付可运行的软件,首推较短的时间定量。
4. 在整个项目过程中,每一天开发人员都要和业务人员合作。
5. 由个体推动项目的建设,为个体提供所需的环境,支持和信任。
6. 在开发团队中或开发团队间传递信息的最为有效和高效的方法是面对面的交谈。
7. 衡量进展的重要尺度是可运行的软件。
8. 敏捷过程提倡可持续的开发。
9. 发起人,开发者和用户应该步调一致。
10.不断地关注技术上优越的设计会提高敏捷性。
11.简洁是最重要的,简洁就是尽量减少工作量的艺术。
12.最佳的架构,需求和设计来自于自组织的团队。
13.团队要定期反省如何使工作更有效,然后相应地调整行为。

2009年2月6日星期五

Telnet命令执行器--BlockingQueue

.
昨天完成了DslamTelnetProtocol类,这个类实现了IResponseListener接口,用于对接收到的输入流进行实时的解析、处理。DslamTelnetProtocol这个类的名称也许改成...ProtocolFilter比较合适。
目前启动了一个线程处理telnet的输出。对telnet的输入还是在主线程中调用telnetConnection.send()实现的。下一步应该把输入也用另一个线程来处理。

TODO: 对设备发送的命令放到一个BlockingQueue commandQueue中。DslamTelnetProtocol解析获得的命令结果放BlockingQueue resultQueue中。BlockingQueue确实为生产者-消费者模式简化了很多代码。否则你自己必须封装一个合适的queue,对这个queue进行适当的同步保证并发性。而且需要很好的设计wait() notify()来进行线程之间的协调。

2009年2月4日星期三

Telnet命令执行器----对the end of a stream的理解

现在工作中的软件框架中有一个比较严重的问题是网络命令的执行request-reponse之间没有进行很好的协调而是使用sleep, check这种糟糕的方式。
很自然联想起给Neustar做的IM软件。但telnet协议又不同于IM系统所使用的协议。前者是基于长连接,而后者是短连接。对于短链接,请求与回应很容易进行匹对。而长连接,则需要依靠同一个网络连接顺序地、依次地进行请求-回应的处理。
对于利用像telnet协议基于长连接来执行的命令,同步是其天然特性。所以对于命令的执行是没有必要进行异步处理的。(当然也可以进行异步处理,但是意义基本不大,这里异步/同步的选择应该是基于应用的不同而变化。)

先不考虑接口、抽象。直接用两个类封装逻辑。TelnetConnection利用common-net项目中的TelnetClient建立连接,获得input, output。TelnetCommond封装通过telnet协议发出的命令。
public String receiveText() {
StringBuilder sb = null;
try {
sb = new StringBuilder();
byte ch = (byte) in.read();
while (ch > 0) {
sb.append((char) ch);
ch = (byte) in.read(); // 当读到-1之后再用out发送指令之后再也不能读出response了。
// 而且读取这个-1的值时程序被阻塞!

}
} catch (IOException e) {
Log.warn(e);
}
Log.debug(sb.toString());

return sb.toString();
}


改为使用结束字符串做检查标志后就正常了。
byte ch = (byte) in.read();
while (ch > 0) {
sb.append((char) ch);
if (sb.indexOf(endFlagStr) >=0) {
break;
}
ch = (byte) in.read();
}


很奇怪,难道是apache common-net里的类TelnetInputStream实现得有问题?当inputstream读到末尾后,有新的数据进入后没有进行适当的处理?
呵呵,是我的错误!inputStream.read()返回-1说明已经读到了the end of the stream。对于有限容量的stream(比如来自文件的流)来说就是读尽了,对于未知容量的stream比如网络流,如果 read()返回-1,说明这个流可能已经被对端关闭了。总之就应该对这种情况进行处理。再读下去,也是-1。这篇文章对此有些所描述:http://publib.boulder.ibm.com/infocenter/zos/v1r9/index.jsp?topic=/com.ibm.zos.r9.rexa100/h1981605209.htm


Telnet概述及RFC: http://en.wikipedia.org/wiki/Telnet
common-net 的使用:http://www.informit.com/guides/content.aspx?g=java&seqNum=40

2009年2月3日星期二

对synchronized 的又一点理解

java里的synchronized给我的一个强烈印象是“同步资源”,或者说是利用对象的monitor进行序列化访问的手段。
当使用object.wait()而没有事先synchronized object时,问题出现了:IllegalMonitorStateException。

wait() 之前必须先过的这个对象上的锁。这就要依靠synchronized。因为
synchronized的基本作用是“获取对象上的monitor”。所谓的同步、序列化访问都是建立在monitor的作用之上的。也许把synchronized理解成获得锁的语句可能更接近事实。


sleep() yield()并没有释放锁,这是和wait()的巨大区别。