2007年5月29日
在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进 行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对 于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
理解抽象类
abstract class和interface在Java语言中都是用来进行抽象类(本文 中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法, 请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?
在 面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是 所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、 设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、 三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念 在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。 我们可以构造出一个固定的一组行为的抽象描 述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个 抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知 道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
从语法定义层面看abstract class 和 interface
在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。
使用abstract class的方式定义Demo抽象类的方式如下:
abstract class Demo{
abstract void method1();
abstract void method2();
…
}
使用interface的方式定义Demo抽象类的方式如下:
interface Demo{
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的数据成员,也可以有非 abstract的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final 的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的 abstract class。
从编程的角度来看,abstract class和interface都可以用来实现 "design by contract" 的思想。但是在具体的使用上面还是有一些区别的。
首先,abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系(因为Java不支持多继承 -- 转注)。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会增加一些复杂性,有时会造成很大的麻烦。
在 抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因 为如果后来想修改类的界面(一般通过 abstract class 或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添 加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那 么可能就只需要修改定义在abstract class中的默认行为就可以了。
同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了 "one rule,one place" 原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。
从设计理念层面看 abstract class 和 interface
上面主要从语法定义和编程的角度论述了abstract class和interface的区 别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。
前面已经提到过,abstract class在Java语言中体现了一种继承关系,要想使得 继承关系合理,父类和派生类之间必须存在"is-a"关系,即父类和派生类在概念本质上应该是相同的。对于interface来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的, 仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door{
abstract void open();
abstract void close();
}
使用interface方式定义Door:
interface Door{
void open();
void close();
}
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中, 主要是为了展示 abstract class 和interface 反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解 决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door{
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door{
void open();
void close();
void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door{
void open(){…}
void close(){…}
void alarm(){…}
}
或者
class AlarmDoor implements Door{
void open(){…}
void close(){…}
void alarm(){…}
}
这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反 之依然。
解决方案二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定 义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用 abstract class 方式定义;两个概念都使用interface方式定义;一个概念 使用 abstract class 方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有 理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分 析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用 interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报 警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系 在本质上是"is-a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说 明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door{
abstract void open();
abstract void close();
}
interface Alarm{
void alarm();
}
class Alarm Door extends Door implements Alarm{
void open(){…}
void close(){…}
void alarm(){…}
}
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其 实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
小结
1.abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。
2.在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。
3.abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。
4.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。
5.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。
6.抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。
7.接口中的方法默认都是 public,abstract 类型的。
结论
abstract class 和 interface 是 Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。
posted @
2008-11-26 11:24 larryjava 阅读(167) |
评论 (0) |
编辑 收藏
1.为什么要用抽象类实现接口
答:
抽象类和接口比起来,有一个好处,就是某些函数可以实现具体的方法,而并不一定是声明抽象的方法,而接口只能声明抽象方法,所以用一个抽象类来实现某个接口可以实现一些通用的方法,而这些具体实现的方法里还可以调用抽象方法,所以减少了子类中的重复代码。
java库里也有不少这这样的设计,
比如java.util.AbstractList实现了java.util.List,而其实某些方法就是调用了抽象方法。
还有一个好处就是可以给子类一个默认的实现,而不必给所有的子类实现所有的方法。
另外还有一个好处就是可以提供一些公用的方法给子类使用。
2.
posted @
2008-11-26 11:23 larryjava 阅读(140) |
评论 (0) |
编辑 收藏
第一种通过spring中的CronTrigger复杂触发器实现
<!--要调度的对象-->
<bean id="job" class="com.ApManager.util.TJob">
<property name="scheduler" ref="schedulerFactory"/>
<property name="scheduleInfoManager" ref="scheduleInfoManager"/>
</bean>
<!-- 定义目标bean和bean中的方法 -->
<bean id="jobtask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref local="job"/>
</property>
<property name="targetMethod">
<value>doAuth</value>
</property>
<property name="concurrent" value="false"/><!-- 对于相同的JobDetail,当指定多个Trigger时, 很可能第一个job完成之前,第二个job就开始了。指定concurrent设为false,多个job不会并发运行,第二个job将不会在第一个job完成之前开始 -->
</bean>
<!-- 定义触发的时间 -->
<bean id = "cron" class = "org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="jobtask"/>
</property>
<property name="cronExpression">
<value>0 * 11 * * ?</value>
</property>
</bean>
<!-- 总管理 -->
<bean autowire = "no" class = "org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers"><!-- triggers:通过再添加其他的ref元素可在list中放置多个触发器 -->
<list>
<ref local ="cron"/>
</list>
</property>
</bean>
第二通过spring中的SimpleTrigger简单触发器实现
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail">
<ref bean="methodInvokingJobDetail"/>
</property>
<property name="startDelay">
<value>1000</value>
</property>
<property name="repeatInterval">
<value>3000</value>
</property>
</bean>
参考:
http://www.javaeye.com/topic/115666
http://www.javaeye.com/topic/117244
posted @
2008-11-06 17:06 larryjava 阅读(387) |
评论 (0) |
编辑 收藏
在这里希望和大家分享 iRedMail 开源邮件方案的详细安装、配置文档,希望对大家有所帮助。
基于 Postfix + MySQL 的文档:
http://www.iredmail.org/wiki/index.php/IRedMail-doc-MySQL-0.2
基于 Postfix + OpenLDAP 的文档:
http://www.iredmail.org/wiki/index.php/IRedMail-doc-OpenLDAP-0.2
Extmail webmail系统
http://www.extmail.org/
Postfix
http://www.postfix.org.cn
posted @
2008-09-19 20:41 larryjava 阅读(215) |
评论 (0) |
编辑 收藏
使用Hibernate的sum函数进行数据的统计时,出现一个错误:
String sql = "select SUM(nf.fee) from CFee as nf where nf.adminAccount='testaccount' ";
public long getListSqlCountsLong(String sql) {
beginTransaction();
List li = getSession().createQuery(sql).list();
if (li == null || li.isEmpty()) {
return 0;
} else { return ((Integer) li.get(0)).longValue();
}
}
这样使用报null错误.
List的size明明等于1,但li.get(0)还是为空.(数据库中查询的账号sum本来就为null??可能是.)
解决方法:
String sql = "select SUM(nf.fee) from CFee as nf where nf.adminAccount='testaccount' ";
public long getListSqlCountsLong(String sql) {
beginTransaction();
List li = getSession().createQuery(sql).list();
if (li == null || li.isEmpty()) {
return 0;
} else {
if (li.get(0) == null) {
return 0;
}
return ((Integer) li.get(0)).longValue();
}
}
解决方法很简单,就是增加一个判断就可以了,如果li.get(0)为空,则返回0,不为空,返回值.
posted @
2008-05-09 13:17 larryjava 阅读(3999) |
评论 (0) |
编辑 收藏
传统的JS压缩(删除注释,删除多余空格等)提供的压缩率有时还是不尽不意,幸亏现在的浏览器都支持压缩传输(通过设置http header的Content-Encoding=gzip),可以通过服务器的配置(如apache)为你的js提供压缩传输,或是appfuse中使用的GZipFilter使tomcat也提供这种能力
现在的问题是这种动态的压缩会导致服务器CPU占用率过高,现在我想到的解决辨法是通过提供静态压缩(就是将js预先通过gzip.exe压缩好)
一.下面描述在tomcat中的应用
1.将prototype.js通过gzip.exe压缩保存成prototype.gzjs
2.设置header,我编写了一个简单的AddHeadersFilter来将所有以gzjs结尾的文件增加设置header Content-Encoding=gzip
web.xml中的配置
<filter>
<filter-name>AddHeaderFilter</filter-name>
<filter-class>
badqiu.web.filter.AddHeaderFilter
</filter-class>
<init-param>
<param-name>headers</param-name>
<param-value>Content-Encoding=gzip</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AddHeaderFilter</filter-name>
<url-pattern>*.gzjs</url-pattern>
</filter-mapping>
测试prototype.js是否正常的代码
<html>
<head>
<!-- type="text/javascript"不可少,有些浏览器缺少这个不能运行,具体已经忘记了 -->
<script src="prototype.gzjs" type="text/javascript"></script>
</head>
<body>
<input id="username" name="username" value="badqiu"/><br />
<input id="email" value="badqiu@gmail.com"/>
<script>
<!-- 测试prototype的方法是否正常-->
alert($F('username'))
</script>
</body>
</html>
在Apache httpd中可以直接通过在httpd.conf增加AddEncoding x-gzip .gzjs来映射.gzjs文件的header
二.相关压缩率数据
1. prototype.js 1.5.0_rc0原始大小56KB,未经任何处理直接使用gzip压缩为12KB,总压缩率79%
2. 通过js压缩工具压缩过的protytype.js为20KB,使用gzip压缩为10KB,总压缩率为83%
3. 实际项目中的多个js合并成的文件 439KB,直接通过gzip压缩为85KB,总压缩率81%
4. 439KB经过js压缩为165KB,再经过gzip压缩为65KB,总压缩率86%
基本上你都可以忽略js压缩工具的压缩率,直接使用gzip压缩
gzip下载地址
http://www.gzip.org
tomcat的压缩配置示例下载地址:
http://www.blogjava.net/Files/badqiu/gziptest.rar
posted @
2008-02-22 11:32 larryjava 阅读(366) |
评论 (0) |
编辑 收藏
经过我的多次测试实践,在linux和window下测试通过,win的比较简单,因为在默认的情况下,组播就已经打开。
1 在linux 下同一台服务器上测试通过(注意端口不要冲突。特别要注意那个tcpListenPort=”4001″ )
2 在window下的同一台服务器上测试通过,注意要点同上
3 window下多台不同主机上测试通过,没有什么好注意点,可以完全备份一个tomcat
4.linux多台不同tomcat服务器上测试通过。这个也是最后实现,最实际的方案。(一般不用在win下部署tomcatweb应用。呵呵。我觉得)
这里讲下第四中情况
先讲一下环境:都是在Red Hat Linux AS4上测试通过
Apache:ip:192.168.1.11 (一台) 版本:2.0/2.2 (做均换负载服务器)
Tomcat:ip1:192.168.1.12 ip2:192.168.1.13 (两台) 版本:5.0、5.5 (web应用的Real Server)
Jdk:版本1.5 安装在 192.168.1.12 和192.168.1.13上的tomcat服务器上
安装tomcat和apache ,在这里就不讲了。
软件都可以在www.apache.org 的网站上找到(apache,tomcat,mod_jk)
1.下载mod_jk.so 文件放到apache 下的modules下
地址:http://apache.mirror.phpchina.com/tomcat/tomcat-connectors/jk/binaries/ 请选择正确的操作系统和Apache的版本。
2.在apache的conf下建立文件workers.properties
添加内容:
#
# workers.properties
#
# list the workers by name
worker.list=tomcatlb, status
# localhost server 1
# ------------------------
worker.tomcat12.port=8009
worker.tomcat12.host=192.168.1.12
worker.tomcat12.type=ajp13
worker.tomcat12.lbfactor=1
worker.tomcat12.connection_pool_timeout=750
worker.tomcat12.socket_keepalive=0
worker.tomcat12.socket_timeout=300000
worker.tomcat12.connect_timeout=10000
worker.tomcat12.reply_timeout=330000
# localhost server 3
# ------------------------
worker.tomcat13.port=8009
worker.tomcat13.host=192.168.1.13
worker.tomcat13.type=ajp13
worker.tomcat13.lbfactor=1
worker.tomcat13.connection_pool_timeout=750
worker.tomcat13.socket_keepalive=0
worker.tomcat13.socket_timeout=300000
worker.tomcat13.connect_timeout=10000
worker.tomcat13.reply_timeout=330000
worker.tomcatlb.type=lb
worker.retries=3
worker.tomcatlb.balanced_workers=tomcat12,tomcat13
worker.tomcatlb.sticky_session=1
worker.status.type=status
3.在conf下添加一个mod_jk.conf文件
LoadModule jk_module modules/mod_jk.so
#configure mod_jk
JkWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel debug
4.增加一个虚拟机配置文件vhosts.conf (这个不是必须的,可以将JkMount 的写其他的配置文件中)
<VirtualHost *:80>
ServerAdmin webmaster@dummy-host.example.com
DocumentRoot /data/google
ServerName my.linuxcoffee.org
ErrorLog logs/my.linuxcoffee.org-error_log
CustomLog logs/my.linuxcoffee.org-access_log common
DirectoryIndex index.htm index.html
JkMount /*.jsp tomcatlb
JkMount /*.action tomcatlb
JKMount /jkstatus status
</VirtualHost>
4.修改conf下的httpd.conf文件加上
Include conf/vhosts.conf
Include conf/mod_jk.conf
5.配置tomcat
讲Cluster 前的注释去掉,启用tomcat集群功能。
一般不需要修改什么东西,但在我这边,两台linux的tomcat就是死活找不到node,而同样的配置文件在window下的跑得很是正常。
有个东西要说明下,因为tomcat的session同步功能需要用到组播,windows默认情况下是开通组播服务的,但是linux默认情况下并没有开通,可以通过指令打开route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0,如果需要服务器启动时即开通组播需在/etc/sysconfig/static-routes文件内加入eht0 net 224.0.0.0 netmask 240.0.0.0。具体组播概念请查阅CCNP相关内容。
可以通过netstate -g 来查看组播状态,也可以在route -e 命令中看到
原来一直提示这个问题
信息: Manager [/clusterapp]: skipping state transfer. No members active in cluster group.
最后测试只要修改在Cluster之间的一段代码
<Receiver
className="
org.apache.catalina.cluster.tcp.ReplicationListener"
tcpListenAddress="auto"
tcpListenPort="4001"
tcpSelectorTimeout="100"
tcpThreadCount="6"/>
改为
<Receiver
className="
org.apache.catalina.cluster.tcp.ReplicationListener"
tcpListenAddress="192.168.1.12"
tcpListenPort="4001"
tcpSelectorTimeout="100"
tcpThreadCount="6"/>
在13的服务器上也将这一段修改为自己的ip
然后打开jvmRoute ,跟workers.properties 的两个tomcat名字相匹配
ip1
<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat12"/>
ip2
<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat13"/>
好了,apache和tomcat的配置好了,可以访问了。
这里,再提供一个链接,可以监控和配置负载均衡的各种信息
http://192.168.1.11/jkstatus或http://my.linuxcoffee.org、jkstatus来访问网站。
注:需要在web.xml的display-name后面 下加上一段。 这个跟顺序有关的,不然,xml文件会变红哦。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>TomcatDemo</display-name>
<distributable/>
</web-app>
参考文章:
Tomcat 5 Home Page
Clustering Home Page on Tomcat site
Load Balancer Home Page on Tomcat site
posted @
2008-01-13 20:48 larryjava 阅读(1289) |
评论 (0) |
编辑 收藏
一
<script language="javascript">
g_blnCheckUnload = true;
function RunOnBeforeUnload() {
if (g_blnCheckUnload) {window.event.returnValue = 'You will lose any unsaved content';
}
}
</script>
<body onbeforeunload="RunOnBeforeUnload()">
</body>
二、
<script>
function window.onbeforeunload()
{
if(event.clientX>document.body.clientWidth&&event.clientY<0||event.altKey)
{
window.event.returnValue="确定上传完成吗?";
}else
{
alert("你在刷新");
}
}
</script>
posted @
2007-12-11 13:35 larryjava 阅读(308) |
评论 (0) |
编辑 收藏
父窗口代码:
<a href="javascript:void(0)" onclick="window.open('child.html','child','width=400,height=300,left=200,top=200');">打开子窗口</a>
子窗口代码:
<script language="JavaScript" type="text/javascript"> <!-- function refreshParent() { window.opener.location.href = window.opener.location.href; if (window.opener.progressWindow) { window.opener.progressWindow.close(); } window.close(); } //--> </script> <a href="javascript:void(0)" onclick="refreshParent()">刷新父窗口并关闭当前窗口</a>
点此查看示例
posted @
2007-12-05 15:04 larryjava 阅读(789) |
评论 (0) |
编辑 收藏
<script language=javascript>
function gbcount(message,total,used,remain)
{
var max;
max = total.value;
if (message.value.length > max) {
message.value = message.value.substring(0,max);
used.value = max;
remain.value = 0;
alert("内容不允许超过 1000 个字!");
}
else {
var iLength = 0;
for(var i = 0;i<message.value.length;i++)
{
if(message.value.charCodeAt(i) >255)//字母数字的ascii编码都小于255而汉字的编码肯定大于255
{
iLength += 2;
}else if(message.value.charCodeAt(i)==32)//空格
{
iLength += 0;
}else//字母或数字
{
iLength += 1;
}
}
used.value=iLength;
//used.value = message.value.length;
remain.value = max - used.value;
}
}
</script>
<form>
<textarea onkeydown=gbcount(this.form.contents,this.form.total,this.form.used,this.form.remain); onkeyup=gbcount(this.form.contents,this.form.total,this.form.used,this.form.remain); cols="80" rows="8" wrap="VIRTUAL" id="contents">
</textarea>
最多字数:<INPUT disabled maxLength=4 name=total size=3 value=1000>
已用字数:<INPUT disabled maxLength=4 name=used size=3 value=0>
可用字数:<INPUT disabled maxLength=4 name=remain size=3 value=1000>
</form>
<htmel><title>字数</title>
<script language="javascript">
function gbcount(message,total,used,remain)
{
var max;
max=total.value;
if(message.value.length > max){
message.value = message.value.substring(0,max);
used.value = max;
remain.value = 0;
alert('不能超过300个字!');
}
else{
used.value = message.value.length;
remain.value = max - used.value;
}
}
</script>
<body><form>
<textarea cols="60" rows="8" id="memo1" onkeydown="gbcount(this.form.memo1,this.form.total1,this.form.used1,this.form.remain1);" onkeyup="gbcount(this.form.memo1,this.form.total1,this.form.used1,this.form.remain1);"></textarea>
<br>
最多字数:<INPUT disabled maxLength=4 name=total1 size=3 value=500>
已用字数:<INPUT disabled maxLength=4 name=used1 size=3 value=0>
剩余字数:<INPUT disabled maxLength=4 name=remain1 size=3 value=500> </form>
</body></html>
posted @
2007-09-17 13:09 larryjava 阅读(832) |
评论 (0) |
编辑 收藏
http://www.dhtmlgoodies.com/
http://webfx.eae.net/
http://www.miniajax.com/
http://www.dynamicdrive.com
posted @
2007-09-13 09:24 larryjava 阅读(108) |
评论 (0) |
编辑 收藏
<directory /some/where/dir>
<FilesMatch "\.(mov|avi|bov|rm)">
Order Deny,Allow
Deny from all
</FilesMatch>
</directory>
限制指定的文件后缀…
posted @
2007-08-23 10:23 larryjava 阅读(308) |
评论 (0) |
编辑 收藏
项目中Struts/Spring/Hibernate的基本流程
Struts+Spring+Hibernate develepment process:
1.Write your business class : DTO,FormBean,Action,Service Interface,Service Implementation.
2.Write JSP pages.
3.struts-config.xml Configuration : FormBean,Action,Forward pages.
4.applicationContext-service.xml Configuration: add your Service Interface and Service Implementation.
5.Add your service factory Get method to ServiceFactory.java
6.Build project and Generate the Description file(*.hbm.xml) of DTO.
7.applicationContext.xml Configuration: add *.hbm.xml file to applicationContext for O/R mapping.
Spring+hibernate的单元测试Junit
spring提供的单元测试是强大的,spring的单元测试很简单,封装的很好。我们要用spring的单元测试测试我们写的add,delete等方法时候需要spring提供的一个额外包spring-mock.jar,我已经传上来了。你只要熟悉单元测试,编写一个测试案例,然后把继承改为org.springframework.test.AbstractTransactionalDataSourceSpringContextTests就可以了,此时编译器会提示你要实现
/**
* 必须实现的方法
*/
public String[] getConfigLocations(){
String[] config = new String[]{"applicationContext.xml","applicationContext-dao.xml","applicationContext-hibernate.xml","applicationContext-service.xml"};
return config;
}
看了大家应该明白,就是把你配置好的xml赋值给它,
然后大家就可以通过下面方法:
下面的applicationContext这个变量是你只要继承了刚才那个抽象类就可以得到的一个恒量。
FriendService friendService = (FriendService)applicationContext.getBean("friendService");
得到你的实例来进行业务逻辑测试了,是不是很简单,大家试试吧,它在此时完成以后会把数据库回滚一次,不会影响你的数据库记录,非常好。
spring中提供 ContextLoaderListenter类,用来加载context的xml文件。
spring为struts提供ContextLoaderPlugIn类,此类也可以加载context的xml文件。
区别在于,两种方式加载的WebApplicationContext,以不同的Key存放在ServletContext中。而如果你定义了HibernateFilter的话,spring会利用WebApplicationContextUtils来获取WebApplicationContext,而此类并不识别ContextLoaderPlugIn类所加载的上下文,此时便会抛出异常: No WebApplicationContext found: no ContextLoaderListener registered?
利用ContextLoaderListenter来加载dao、service级别的context,而对于struts的action,用ContextLoaderPlugIn加载。
2005年漂泊的一年,先后求职于南京,上海和北京三地,因此惨遭京沪宁三地java高手蹂躏。
这些都是面试java架构师的比较变态的题目:
1。变态指数 4
int x=4;
System.out.println("value is " +((x>4)?99.9:9));
答案 9.0 问号表达式的后面两个条件有要求,因为前面的是float,所以后面转为float.
估计出题者才通过SCJP的考试。
2.变态指数 5
public class Test {
public static void main(String[] args) {
int x = 4;
java.util.Date date = (x > 4) ? new A() : new B();
}
}
class A extends java.util.Date {}
class B extends java.util.Date {}
答案 jdk1.4编译不通过,1.5可以
不知道出题人的意图
3.变态指数 6
String s=new String("abc");
创建了几个String对象?
答案 2个
这样的公司最好不要去
4.变态指数 7
const是不是java的关键字?
答案 const是java的关键字,但是java没有实现它
一般人绝对用不到它
5.变态指数 8
,short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
答案 1错2对,1因为向上转型了,最后导致类型不匹配错误 ,
因为s1的+=是一个操作符,能够自动转型,
short s1 = 1;
s1 = s1+1;这句话在c++里面可以的
不知道出题人的意图
6.变态指数 9
上海贝尔的面试题:你认为效率最高的方法,实现从1加到100.
答案 1-100的累加相当于加50次101,这样循环次数从100次降为50次:
int sun = 0
for(int i = 1,j = 100 ; i <= 50 ; i++,j--){
sun = sun + i + j;
}
出题人脑子有问题,直接(1+100)*50不是最快...其实类似这样的优化应该不是程序员考虑的范畴吧
7.变态指数 10
System.out.println(5.0942*1000);
System.out.println(5.0943*1000);
System.out.println(5.0944*1000);的结果
答案 :5094.2 5094.299999999999 5094.400000000001
原理和浮点数的计算机表示方式有关 ,你不用上机,就答对了,你最好去微软,接替安德尔森.
posted @
2007-07-05 17:33 larryjava 阅读(895) |
评论 (0) |
编辑 收藏
<script language="javascript">
function test(){
if(escape(document.all.type.value).indexOf("%u")!=-1) alert("含有汉字");
else alert("全是字符");
}
</script>
<input type="text" name="type"><input type="button" name="test" onclick="test();">
posted @
2007-07-05 17:31 larryjava 阅读(215) |
评论 (0) |
编辑 收藏
应用系统架构设计
我们在做着表面上看似是对于各种不同应用的开发,其实背后所对应的架构设计都是相对稳定的。在一个好的架构下编程,不仅对于开发人员是一件赏心悦目的事情,更重要的是软件能够表现出一个健康的姿态;而架构设计的不合理,不仅让开发人员受苦受难,软件本身的生命周期更是受到严重威胁。这里我将针对在微软dotNet平台上做应用开发系统的一般架构流程设计做一个粗浅的讨论。
总体设计图
表示层
表示层由UI(User Interface)和UI控制逻辑组成。
l UI(User Interface)
UI是客户端的用户界面,负责从用户方接收命令,请求,数据,传递给业务层处理,然后将结果呈现出来。根据客户端的不同我们大体将应用程序分为BS(Browser-Server) 浏览器结构,CS(Client-Server)桌面客户端结构。
BS的优点是无需操心客户端,只需要部署维护好服务器即可。CS的优点在于强大的界面交互表达能力。RIA(Rich Internet Application)是为了融合这两种结构优点的一种技术,它依赖在客户端一次性安装一个通用解释器之后即获得强大的界面交互表达能力和无需部署具体客户端的方便性。具体的实现技术很多,例如微软的SmartClient, Avalon; Macromedia的Flex;以JS为基础的Bindows;Ajax等等很多。
l UI控制逻辑
UI控制逻辑负责处理UI和业务层之间的数据交互,UI之间状态流程的控制,同时负责简单的数据验证和格式化等功能。具体的说在dotNet事件驱动的编程模型下,UI控制逻辑被自然的实现在了事件函数中,例如PageLoad事件函数,ButtonClick事件函数。在这些事件函数中,主要任务就是做UI控件与业务实体的数据交换与业务调用,但面对大量的数据交换工作量与维护量就成了最大的问题。而在复杂应用的系统中,状态与流程的管理是必须要考虑的因素,它们同样是业务逻辑的一部分,如果不加以封装的直接写在事件函数中将导致业务依赖表示层。下面分别讨论这两个问题。
1. 1.UI与业务实体之间的数据交互
此阶段负责数据交换的业务实体称为DTO(Data Transfer Object),处理输入时我们从UI控件的获得数据填入DTO再向下传播,处理输出时用户发出请求业务层会将数据以DTO的形式返出再赋给UI控件展现。因此需要一种方式来自动解决这样的来回赋值问题。遗憾的是dotNet下的不少控件虽然支持数据绑定但仍然没有一个现成完整的解决办法。我们可以自己设计一个Adapter按照某种映射关系来自动处理这样的绑定,这样的映射关系最好是UI控件与DTO属性的事先命名约定,以此种方式的约定作为映射关系无需增加任何配置文件和配置工作即可实现。
2. 2.状态与流程的管理
既然是业务逻辑的一部分就不应该耦合再表示层当中。MVC(Model-View-Controller)模式提供了实现这一目标的方法。Controller是整个方案的核心,它是一个流程管理器,来自UI所有的命令与数据经过Controller分发给业务层或其他UI,这样我们可以把流程,权限等逻辑单独封装,例如配置文件中,达到最大化的业务重用。dotNet下MVC的方案并不像Java下有那么多选择,目前有以下几种选择:
微软的UIPAB,它可以处理bs,cs下的流程跳转,可以使得相同的业务系统有webform和winform不同的展现方式。
开源的Mavrick.Net,它只适用于Asp.Net应用程序,它对流程,国际化,页面包装,xslt页面转换提供了很好的支持。
开源的Lattis,同样只适用于Asp.Net应用程序。
业务层
业务层封装了实际业务逻辑,包含数据验证,事物处理,权限处理等业务相关操作,是整个应用系统的核心。因此设计一个能够真实反映实际需要的业务层是非常必要的,我们将实际业务具体分为业务数据与业务操作两部分。
l 业务数据
业务数据又是业务逻辑的核心,最终业务数据将以一种固定的格式表现于内存中,在系统的各个层次间传输,充当DTO角色。表达业务数据的方式一般分为两种Table Model和Domain Model。
Table Model是将数据库中的表直接映射成为业务数据对象,这样的优点是适合于机器操作,ADO.NET直接提供了这种操作的便利,但对于复杂业务关系的表达就很不直观。只适合于业务需求与数据表对应关系很直接的需要快速开发的情况。通常我们选用Dataset或者强类型Dataset(Strong Typed Dataset),强类型Dataset支持编译时的类型检查,效率上要略高于普通Dataset。Dataset有很多方便的特性:无需自己编写维护类,支持序列化,数据副本保存,支持数据集合,对控件绑定支持效果好,微软提供了相应的生成工具以及持久方案。但缺点也是明显,复杂数据表现不直观,做为DTO在各个层次间传输,尤其是分布式环境,庞大的体积,相对缓慢的实例化对于性能造成很大压力。
Domain Model则是根据实际业务按照现实方式用OO思想建模,这样很适合业务复杂的系统。通常采用自定义数据实体(Custom Data Entity)方式表达。自定义数据实体,有着良好的性能,编译时的类型检查,数据表现方式非常直观符合实际业务的操作方式等优点,但需要自己定义维护类,在分布式环境下需要自己编写序列化方法。
综合各种因素考虑,虽然业务简单对应直接的系统我们以Table Model建模开发效率很高但难免保证系统日后不会变的复杂,因此出于复用性,扩展性,性能等方面选用Domain Model建模为佳。
l 业务操作
业务操作负责对业务数据进行各种业务相关的处理,例如验证,流向,整合,事物,权限等,但它不负责有关对数据源的操作。它与业务数据的关系设计有2种方式。
分离业务数据与业务操作,将业务数据单独封装到只有数据get,set的数据类中,这个数据类只充当DTO。将业务操作封装到独立的service类中与业务数据一起充当业务层。这样当系统不复杂的时候显的简单直观,而随着系统日益复杂,service类会变的杂乱,而将本身耦合紧密的数据与操作分离对于复用也是不利的因素。具体可参考Martin Fowler 的贫血的Domain Model一文,但我并不倾向于业务层直接访问数据源。
整合业务数据与业务操作,将业务数据与相关的业务操作封装在一起称为业务实体,业务实体作为统一的业务层为表示层提供服务,同时也负责作为DTO在各个层次间传输,我倾向于这样完整的Domain Model设计方式,每个业务实体都可以做为一个单独组件形式存在,对于组件化复用有着莫大的好处。
l 业务模块间的依赖
各个业务模块之间的依赖,有时候会是难以解决的问题,尤其是一些可以重复利用的业务组件,例如权限管理,邮件发送等等。管理好这些各种不同的业务组件是我们的目标,IoC容器为我们提供了最完美的方案,通过它将不同的模块注入到系统中我们可以在不知道这个组件存在的情况下调用它。但目前只有不成熟的Spring.Net一个选择,我们只有一声叹息,因此也就不多讨论了。
业务数据访问层
业务数据访问层是一个针对具体应用系统的专属层,它为业务层提供与数据源交互的最小操作方式,仅仅是业务层需要的数据访问接口,业务层完全依赖业务数据访问层所提供的服务。这些服务负责从业务层接收数据或返回业务实体,它屏蔽了实际业务数据与机器存储方式的差别。当然,数据层选用抽象的解决方案同样可以达到这个效果,但业务数据访问层最大的特点就是针对具体业务做抽象,而抽象的数据层访问方案是针对通用做抽象。往往业务中针对具体的设计生命力会变的更强,这样我们可以最大限度的保持了上层代码的复用性,当需要更换存储策略如果数据层访问差别太大,通过更换数据层无法解决问题的时候我们最多只需要更换业务数据访问层,而无需改变业务层。
业务数据访问层由DAO(Data Access Object)层和系统服务层两部分组成。DAO层为每个业务实体提供最基本的数据访问服务,系统服务层为系统全局提供与业务关系不大的通用数据访问服务,这两层处于系统中的同一个层次位置。
业务层与业务数据访问层关系图
数据层
数据层的宗旨就是为数据源提供一个可供外界访问的接口,我们应该选用一种能够提供数据源无关的抽象数据访问接口并通过在其下挂接各种不同的DataProviador来访问数据源的数据层组件,这样做便于移植到不同的数据源上。目前有以下3种数据层方案:
1. 1. 封装ADO.Net
这些数据访问组件都是基于ADO.Net的浅封装,它的优点在于封装层次低所以速度最快,我们可以手动组织sql语句用来适应复杂的操作以及个性的优化等。缺点是无法直接处理自定义数据实体方式的业务实体对象,需要将业务实体中的数据属性以参数形式传入传出。这样的方式虽然最为保险,但随着系统规模增大,开发效率,质量,,后期的维护,二次开发都变成尤为突出的问题,对开发人员的要求会变的越来越高。另外对于事物操作封装不是很好,无法提供声明性事物,经常会在业务层出现访问数据层的需要。这样的组件目前应用的很广泛,例如微软在EnterpriseLibrary中提供的DAAB(Data Access Application Block),还有以前的DAAB3.1。EnterpriseLibrary是个成熟的产品,包括了数据访问,异常,日志,缓存,加密,配置,安全等组件做为通用服务非常适合。
2. 2. OR-Mapping组件
ORM是最好的数据持久解决方案,它的优点在于能够以面向对象的方式操纵数据,因此可以直接处理自定义数据实体的业务对象,我们根本不用操心sql语句以及底层存储方式,这样极大的简化的代码提高了开发效率,对于日后维护扩展都带来极大的便利。缺点在于屏蔽了底层使得我们无法针对具体数据源做优化,而且对于复杂关联的sql操作有些力不从心,同时性能也差一些但辅助以缓存情况会好很多,而在dotNet下最大的问题就是没有一个成熟便宜的ORM产品供我们使用,全部都是beta版本和商业版本。这些版本或多或少都存在一些问题,以至于真正应用中需要经过仔细考察。例如NHibernate,Gentle.Net,XPO,Grove.Net等等非常多。
3. 3. DataMapper(SqlMapper)
SqlMapper为以上两种方式提供了一个折中的选择,它可以以面向对象的方式直接处理自定义数据实体的业务对象,同时可以根据与数据源与业务实体的映射关系执行手写的sql语句,这样完全使得我们可以针对具体数据源做优化,对于复杂操作同样可以胜任。目前只有iBatis.Net一个产品,它是一个java移至的开源项目,已经比较成熟,可以在无需编译的情况下随意替换DAO。
至此,整个架构方案的讨论已经完成,我们可以看出dotNet下可供选择的解决方案是那么的有限,反看Java世界,有那么多成熟可供利用的组件框架,流口水中...不过dotNet也正在走向成熟,我们需要时间等待。这个架构设计的思路只代表了我个人的理解,而且也并不是说所有的开发都是这么一套方案,在具体环境中需要做具体的调整。希望能起到一个抛砖引玉的作用。我的邮箱是i-simon AT msn.com,由于我经验尚浅,有不正确或不足的地方欢迎指正讨论,另外本文将根据技术的最新进展持续更新。
posted @
2007-05-29 13:11 larryjava 阅读(188) |
评论 (0) |
编辑 收藏