打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Struts Validator验证器使用指南
Java视线论坛首页  -> 所有的Blog  

  主题: 1
(2005-8-19 周五)
 作者: kingapex
链接地址:
1111111111111
留言 (0 留言)
  主题: Struts Validator验证器使用指南
(2005-8-18 周四)
 作者: heweiya
链接地址:Struts Validator验证器使用指南
Struts Validator验证器使用指南

(根据Struts Validator Guide)

作者:

David Winterfeldt大卫

James Turner詹姆斯

Rob Leland罗伯特

翻译:

侯思超
验证器:

从0.5版,验证器在一些form中就已经实现了,他最初包含在开发人员包中,后来核心代码挪到Jakarta Commons包中和Struts特别扩展中作为 Struts 1.1的一部分。许多开发者为方便一直使用struts验证器,这篇文档首先概述验证器的核心功能性,然后大概介绍在 struts1.1中的变化和新增功能。

如果你配置好验证器插件,你应该扩展ValidatorForm而不是ActionForm,以便它能加载你的Validator资源。他根据struts-config.xml文件中的action的name属性为当前form的调用相应的验证器,因此在validator-rules.xml中的form元素的名称属性应该与action的name属性值相匹配。

另外一种选择是扩展ValidatorActionForm 而不是ValidatorForm,ValidatorActionForm使用struts-config.xml中action的path属性,所以path属性的值相应的应该与validator-rules.xml中的Form的name属性匹配。

一个分离的action可以定义给多页form的每个页面,而且验证规则可以与action关联而不是与页码,就像验证范例中的多页form范例那样。
国际化

在validator-rules.xml 文件中form的验证规则可以组织为FormSet。FormSet 有与java.util.Locale 类相应的属性:如语言, 国家以及变量型属性,如果他们未定义,FormSet 将把它设置为默认值。一个FormSet 也可以有关联的常量。另外还可以定义与FormSet 同一级别的全局global元素,他与FormSet同样也有常量。

注意:你必须在国际化的FormSet前声明一个没有国际化的默认FormSet。这样如果Validator没有找到locale时可以有一个默认版本。

可插入验证器的默认错误信息值可以被msg元素覆盖。所以为mask验证器生成错误信息的替代方法就是使用msg属性,如果字段的name属性与验证器的name属性匹配,那末将使用字段的msg属性。

error messages的可以设置arg0-arg3 等参数元素。如果没有设置arg0-arg3的name属性, error messages将使用他们作为默认的构建参数值。如果设置了name属性,你就可以把参数指定给一特定的可插入验证器,然后这些参数将在构造错误信息时被使用。

<field

property="lastName"

depends="required,mask">

<msg

name="mask"

key="registrationForm.lastname.maskmsg"/>

<arg0 key="registrationForm.lastname.displayname"/>

<var>

<var-name>mask</var-name>

<var-value>^[a-zA-Z]*$</var-value>

</var>

</field>

默认的arg0-arg3元素将在消息资源中查找相应的key,如果资源属性设为false,她将把值直接传进去,而不从消息资源中查找。注意1.1版本中,你必须为每个模块中明确地定义在验证中用到的消息资源,否则将使用top-level资源。

<field

property="integer"

depends="required,integer,intRange">

<arg0 key="typeForm.integer.displayname"/>

<arg1

name="range"

key="${var:min}"

resource="false"/>

<arg2

name="range"

key="${var:max}"

resource="false"/>

<var>

<var-name>min</var-name>

<var-value>10</var-value>

</var>

<var>

<var-name>max</var-name>

<var-value>20</var-value>

</var>

</field>
常量/变量

全局的常量可以在全局标签中定义,FormSet/本地常量能在formset 标签中创建。常量当前仅仅是代替字段的property属性,字段的var 元素的 value属性,字段的msg 元素的 key属性,字段的arg0-arg3 元素的 key属性。字段的变量也可以在arg0-arg3 元素中被代替(例如:${var:min}))。替换的顺序是FormSet/Locale常量第一,全局的常量第二,

arg elements 变量最后。

<global>

<constant>

<constant-name>zip</constant-name>

<constant-value>^\d{5}(-\d{4})?$</constant-value>

</constant>

</global>



<field

property="zip"

depends="required,mask">

<arg0 key="registrationForm.zippostal.displayname"/>

<var>

<var-name>mask</var-name>

<var-value>${zip}</var-value>

</var>

</field>

验证器可以使用字段下面的变量部分来存储变量,这些变量通过字段的getVar((String key)方法取得。

<field

property="integer"

depends="required,integer,intRange">

<arg0 key="typeForm.integer.displayname"/>

<arg1

name="range"

key="${var:min}" resource="false"/>

<arg2

name="range"

key="${var:max}" resource="false"/>

<var>

<var-name>min</var-name>

<var-value>10</var-value>

</var>

<var>

<var-name>max</var-name>

<var-value>20</var-value>

</var>

</field>
使用validwhen设计复杂的验证

使用validwhen来设计复杂验证的一个经常的要求就是根据一个字段验证另外一个字段(比如, 如果你要用户两次输入口令来确认值口令一致),另外一个就是表单中的一个字段只有另外一个字段有确定值的时候才是必须输入的。新的validwhen验证规则将很快被包含在1.1后的STRUTS版本中,她就是用来处理这种情况的。

validwhen 规则处理单个的变量字段,叫测试。这变量的值是一个布尔的表达式,如果验证有效则它必须为真。可以包含这种变量的表达式有:

u 单引号或双引号字符串literals,

u 十进制、十六进制、八进制的Integer literals,

u null与null和空字符串匹配,

u 其它可以用属性名引用的form字段,例如customerAge,

u 可以在外部因用得索引字段, 例如childLastName[2],

u 可以默认implicit因用得索引字段, 例如childLastName[], 她将作为被索引的字段使用同样的索引到数组中,

The literal *这里指它包含当前测试字段的值,

作为例子,考虑一个包含通讯地址和邮箱字段的form。如果通讯地址不为空则邮箱字段是必须的required。你能这样定义validwhen 规则:

<field property="emailAddress" depends="validwhen">

<arg0 key="userinfo.emailAddress.label"/>

<var>

<var-name>test</var-name>

<var-value>((sendNewsletter == null) or (*this* != null))</var-value>

</var>

</field>

上面定义的意思是:如果通讯地址是空或不空时这个字段时有效的。

这里有个稍微复杂的例子,它使用了索引字段。假定有一个表单,允许用户输入他们希望定购的部件号和数量。类orderLine 的bean的一数组被用来在称为orderLines 的一属性保持输入项。

If you wished to verify that every line with part number also had a quantity entered, you could do it with:

如果你希望校验订单中有数量输入得每一行,你可以这样:

<field

property="quantity"

indexedListProperty="orderLines"

depends="validwhen">

<arg0 key="orderform.quantity.label"/>

<var>

<var-name>test</var-name>

<var-value>((orderLines[].partNumber == null) or (*this* != null))</var-value>

</var>

</field>

这里的意思是:如果相应的partNumber 字段是空, 或这字段是不空的,则这字段是有效的。

最后一个例子,想象一表单,用户必须输入他们的以英寸为单位的高度,如果他们在高度在60英寸以下,则出一错误。(it is an error to have checked off nbaPointGuard as a career.)

<field property="nbaPointGuard" depends="validwhen">

<arg0 key="careers.nbaPointGuard.label"/>

<var>

<var-name>test</var-name>

<var-value>((heightInInches >= 60) or (*this* == null))</var-value>

</var>

</field>



给程序员的简单说明:

所有的比较关系必须在parens 封装。All comparisons must be enclosed in parens.

只有两个itme时可以and或or链接。

如果比较的两item都可以转为整数,则使用numeric比较,否则使用字符串比较。
可插入验证器

验证是从validation.xml 文件中加载的,默认的验证规则定义在validation.xml 文件中,默认定义了required, mask ,byte, short, int, long, float, double, date (没有本地支持), and a numeric range。

" mask "方式依赖于默认值安装要求,那意味着"required "可以完成,在"‘mask "将运行以前"required "和" mask "方式被默认包含进框架中了。任何字段如果不是"required "而且是空或有零长度将跳过其他验证。

如果使用了Javascript 标签,客户端javascript在validator‘s javascript 属性中查找值而且产生一个有验证form方法的对象,要得到更多的关于Javascript Validator 标签工作细节的详细的解释,参阅html标签API参考。

"‘mask‘ "方式让你用一正则表达式掩码验证字段,它使用jakarta的正规表达式包,所有的有效性规则存储在validator-rules.xml 文件,使用的主类是org.apache.regexp.RE。

validation.xml文件中的验证器配置范例:

<validator name="required"

classname="org.apache.struts.validator.FieldChecks"

method="validateRequired"

methodParams="java.lang.Object,

org.apache.commons.validator.ValidatorAction,

org.apache.commons.validator.Field,

org.apache.struts.action.ActionErrors,

javax.servlet.http.HttpServletRequest"

msg="errors.required">

<validator name="mask"

classname="org.apache.struts.validator.FieldChecks"

method="validateMask"

methodParams="java.lang.Object,

org.apache.commons.validator.ValidatorAction,

org.apache.commons.validator.Field,

org.apache.struts.action.ActionErrors,

javax.servlet.http.HttpServletRequest"

msg="errors.invalid">


定义可插入验证器

方法的参数是用逗号分隔的一些类名称列表,方法属性需要有一个符合上面的列表的签名。列表由以下组合而成:

java.lang.Object – 要验证的Bean。

org.apache.commons.validator.ValidatorAction – 当前ValidatorAction。

org.apache.commons.validator.Field – 要验证的字段

org.apache.struts.action.ActionErrors – 如果验证错误将加入ActionError的错误对象javax.servlet.http.HttpServletRequest –当前request 对象。

javax.servlet.ServletContext – 应用的ServletContext。

org.apache.commons.validator.Validator–当前的org.apache.commons.validator.Validator实例。

java.util.Locale – 当前用户的Locale。
多页面form

字段部分有一可选的页面属性,它可以被设为整数,页上字段的所有验证小于或等于服务器端验证的当前页,页上字段的所有验证小于或等于客户端页上所有字段的验证小于或等于服务器端验证的当前页验证的当前页。一个mutli-part表单需要定义页面属性:

<html:hidden property="page" value="1"/>。
比较两个字段

这是一个展示你怎样才能比较两个字段是否有一样的值的例子。比如“用户改变他们的口令“一般会有口令字段和一确认字段。

<validator name="twofields"

classname="com.mysite.StrutsValidator"

method="validateTwoFields"

msg="errors.twofields"/>

<field property="password" depends="required,twofields">

<arg0 key="typeForm.password.displayname"/>

<var>

<var-name>secondProperty</var-name>

<var-value>password2</var-value>

</var>

</field>



public static boolean validateTwoFields(

Object bean, ValidatorAction va,

Field field, ActionErrors errors, HttpServletRequest request,

ServletContext application) {

String value = ValidatorUtils.getValueAsString( bean, field.getProperty());

String sProperty2 = field.getVarValue("secondProperty");

String value2 = ValidatorUtils.getValueAsString( bean, sProperty2);



if (!GenericValidator.isBlankOrNull(value)) {

try {

if (!value.equals(value2)) {

errors.add(field.getKey(),

Resources.getActionError( application, request, va, field));

return false;

}

} catch (Exception e) {

errors.add(field.getKey(), Resources.getActionError( application, request, va, field));

return false;

}

}

}
已知的bug

Struts Validator依赖于Commons Validator包,所以问题报告和增强需求可能在两个产品中列出。

· Struts Validator Bugzilla Reports

· Commons Validator Bugzilla Reports
变更和deprecations

新建的标记属性。

<html:javascript>标记有新的属性定义.

使用commons-validator.jar中的DTD验证。

当前使用的验证XML文件是根据commons-validator.jar中的DTD。Struts不在为validator-rules.xml and validator.xml.单独维护一个分离的DTD,另外,commons-validator 现在维护一个统一的validator.dtd。修改所有validator.xml文件的DTD引用为

<!DOCTYPE form-validation PUBLIC

"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"

"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">

空字段。

当前默认在所有得基础验证类型中忽略空白的字段,如果你要求一个字段必须输入,那末在你的应用的validator.xml 文件相应的字段定义的depends属性中添加 " required "。

新建的范围RANGE方法.

JavaScript 和JAVA中都添加了intRange & floatRange 方法。

有条件地REQUIRED字段.

最大的修改是添加了基于其她字段的值的有条件地require验证的能力。它允许你定义逻辑如:“只有X字段非空的时候Y字段为’male’才有效”,这是实现上述逻辑的推荐方法,这种方法在1.1版后的第一版将实现。在1.1版中添加的Requiredif验证规则,将在新版中去掉。不过,如果你正准备使用requiredif,这里有一个简短的教程。

让我们假定你有一个有3个字段的医药的信息表单,性别sex,怀孕测试pregnancyTest,测试结果testResult,如果性别为‘f‘ or ‘F‘,则怀孕测试pregnancyTest是required,如果pregnancyTest不是空,测试结果testResult是required。

你的validation.xml 文件的输入项应该是这样的:

<form name="medicalStatusForm">

<field property="pregnancyTest" depends="requiredif">

<arg0 key="medicalStatusForm.pregnancyTest.label"/>

<var>

<var-name>field[0]</var-name>

<var-value>sex</var-value>

</var>

<var>

<var-name>fieldTest[0]</var-name>

<var-value>EQUAL</var-value>

</var>

<var>

<var-name>fieldValue[0]</var-name>

<var-value>F</var-value>

</var>

<var>

<var-name>field[1]</var-name>

<var-value>sex</var-value>

</var>

<var>

<var-name>fieldTest[1]</var-name>

<var-value>EQUAL</var-value>

</var>

<var>

<var-name>fieldValue[1]</var-name>

<var-value>f</var-value>

</var>

<var>

<var-name>fieldJoin</var-name>

<var-value>OR</var-value>

</var>

</field>

<field property="testResult" depends="requiredif">

<arg0 key="medicalStatusForm.testResult.label"/>

<var>

<var-name>field[0]</var-name>

<var-value>pregnancyTest</var-value>

</var>

<var>

<var-name>fieldTest[0]</var-name>

<var-value>NOTNULL</var-value>

</var>

</field>

</form>



这里有一个使用索引的属性更复杂的例子,如果你的struts-config.xml 有这下面:

<form-bean name="dependentlistForm"

type="org.apache.struts.webapp.validator.forms.ValidatorForm">

<form-property

name="dependents"

type="org.apache.struts.webapp.validator.Dependent[]" size="10"/>

<form-property name="insureDependents" type="java.lang.Boolean" initial="false"/>

</form-bean>

这里dependentlistForm bean有lastName,firstName,dob,coverageType四个属性,你可以这样定义一验证规则:

<form name="dependentlistForm">

<field

property="firstName" indexedListProperty="dependents" depends="requiredif">

<arg0 key="dependentlistForm.firstName.label"/>

<var>

<var-name>field[0]</var-name>

<var-value>lastName</var-value>

</var>

<var>

<var-name>fieldIndexed[0]</var-name>

<var-value>true</var-value>

</var>

<var>

<var-name>fieldTest[0]</var-name>

<var-value>NOTNULL</var-value>

</var>

</field>



<field

property="dob" indexedListProperty="dependents" depends="requiredif,date">

<arg0 key="dependentlistForm.dob.label"/>

<var>

<var-name>field[0]</var-name>

<var-value>lastName</var-value>

</var>

<var>

<var-name>fieldIndexed[0]</var-name>

<var-value>true</var-value>

</var>

<var>

<var-name>fieldTest[0]</var-name>

<var-value>NOTNULL</var-value>

</var>

</field>



<field

property="coverageType" indexedListProperty="dependents" depends="requiredif">

<arg0 key="dependentlistForm.coverageType.label"/>

<var>

<var-name>field[0]</var-name>

<var-value>lastName</var-value>

</var>

<var>

<var-name>fieldIndexed[0]</var-name>

<var-value>true</var-value>

</var>

<var>

<var-name>fieldTest[0]</var-name>

<var-value>NOTNULL</var-value>

</var>

<var>

<var-name>field[1]</var-name>

<var-value>insureDependents</var-value>

</var>

<var>

<var-name>fieldTest[1]</var-name>

<var-value>EQUAL</var-value>

</var>

<var>

<var-name>fieldValue[1]</var-name>

<var-value>true</var-value>

</var>

<var>

<var-name>fieldJoin</var-name>

<var-value>AND</var-value>

</var>

</field>

</form>

这里的意思是:

如果lastName 字段是非空的,firstName 字段required。因为字段Indexed 为真,这它意味着lastName的indexed 必须与firstName 的索引的一样,dob同理,除非date不为空。

如果lastName 用样索引时的值不空, 而且非索引字段insureDependents为真,则coverageType 是only require。

你可以对字段在[n]中使用任意数字,唯一的限制是他们必须都是AND或OR,你无法混合使用。

Deprecation:

u JavaScript 和Java的range方法.

u StrutsValidator &StrutsValidatorUtil 类中的Deprecation方法
验证器api指南

一个简明的Struts验证器API指南 可以帮助你开始。
验证器资源

Struts Validator: Validating Two Fields Match 作者Matt Raible。(两个字段匹配验证)关于使用方法的文章。(范例部分为翻译此文内容)

DynaForms and the Validator 作者James Turner and Kevin Bedell。Struts Kickstart的其中一章(动态form和验证器),可以自由下载PDF).

Validating user input 作者 David Winterfeldt and Ted Husted。Struts in Action的其中一章,可以自由下载(PDF)。

使用方法

作者:

丑陋 && Snowtears:经过2周的不懈努力,阅读了大量的资料,终于对Validator有了个初步的认识,整理了一下,浅浅的谈了谈写法,希望能有一定的帮助,其中肯定有许多说的不对不准确的地方,还请多指教
real_herozx@163.net

王艺:

根据以上两位的文章正理而成
配置ruts-config.xml:
1、 添加ApplicationResources配置文件。

如:

<!-- ========== Message Resources Definitions =========================== -->

<message-resources parameter="com.dc.sibss.om.struts.ApplicationResources" />

其中com.sibss.om.struts.ApplicationResources"的部分是资源文件的路径,此文件的作用是提供错误信息的非编程定制化和多语言支持。如果我们使用中文平台操作系统,则默认情况下将首先查找ApplicationResource_zh_CN.properties文件,然后是ApplicationResources_zh.properties,如果前两个文件没有被找到则将查找ApplicationResources.properties文件。

为了能够在页面上显示错误提示信息,我们还需要将以下内容添加到ApplicationResources.properties文件的末尾:

errors.required={0} is required.

errors.minlength={0} cannot be less than {1} characters.

errors.maxlength={0} cannot be greater than {2} characters.

errors.invalid={0} is invalid.

errors.byte={0} must be an byte.

errors.short={0} must be an short.

errors.integer={0} must be an integer.

errors.long={0} must be an long.

errors.float={0} must be an float.

errors.double={0} must be an double.

errors.date={0} is not a date.

errors.range={0} is not in the range {1} through {2}.

errors.creditcard={0} is not a valid credit card number.

errors.email={0} is an invalid e-mail address.

以上仅是struts现在支持的错误类型的错误提示信息,如果你自定义了新类型的错误验证,则还需要在此加上你自己的内容。

以上内容中的{0}指的是错误提交的参数。比如:当你需要页面上的“用户名”不能为空时(也就是上面的errors.required),这个{0}就代表“用户名”,所以如果你没有填写用户名将抛出如下错误:

用户名 is required.(你可以根据需要修改称中文)

我们可能已经注意到了,既然错误提示信息需要配置,那么上例中“用户名”系统是如何得到的呢?没错!也是通过修改此配置文件,内容如下:

visitCust.error.name.required=<br>用户名

这样当“用户名”为空时,struts后台程序将联合以上两处定义显示错误信息。

另外,上面的“visitCust.error.name.required”是在Validation.xml配置验证内容时指定的。具体见以下介绍。

注意:一般情况下,你的系统只需要一个ApplicationResources文件,所以开发组的成员不要添加自己的resource文件。只有在你的项目分组开发时才需要使用多个ApplicationResources文件,但是,同时你的struts-config.xml文件也会有相同的数量对应。
2、 在struts-config.xml文件中加入validator插件:

加入这个插件后你的应用就具备使用Validator的环境,如:

<!-- ========== Plug Ins Configuration ================================== -->

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">

<set-property value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" property="pathnames" />

</plug-in>

这里如果是想使用多个***.xml文件的话,value部分写法如下value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml, /WEB-INF/validation1.xml , /WEB-INF/validation2.xml "

在<action-mappings>里,定义需要验证的画面对应的Action的时候要加上validate="true"
四种使用方法
1、 用Javascript在客户端进行验证

配置:在需要验证的JSP文件中写入

<html:form action="/XXX" onsubmit="return validateXXXX(this);">

这里的XXX 是与要进行验证的 forward name,validateXXXX (this);里面的XXXX是需要进行验证的ActionForm名。

<html:javascript formName="mytestForm"/>

在validation.xml文件中写入验证代码就可以进行基本的验证了。这种方法是在客户端进行验证,客户端可以看到JAVASCRIPT部分的全代码。安全性不高
2、 ValidatorForm的validate方法

1、validate()方法:使自己的ActionForm继承ValidatorForm类,在里面编写自己的方法:

public ActionErrors validate (ActionMapping mapping,HttpServletRequest request) {

ActionErrors errors = new ActionErrors();

。。。。。。

if ( mytext.equals("aaa") ) {

//my exampleerrors.add("mytext",new ActionError("mytext.error"));

}

。。。。。。

return errors;

}

此时,如果写了这个方法,就会屏蔽掉在Validation.xml中定义的验证部分,换句话说就是系统运行时,Validation.xml里对应此ActionForm的定义的错误验证部分不实行,如果不写这个方法的话,系统运行时会进行Validation.xml里对应此ActionForm的定义的错误验证部分的操作。此类方法是在服务器端进行验证,验证部分代码客户端不可见。

2、创建你的ActionForm并让它继承org.apache.struts.validator.ValidatorForm类。创建你的Action实现,并和上面定义的ActionForm关联。这里需要注意的是,在定义此Action时一定将validate属性设置为true,并且在你定义的ActionForm中不要实现它的validate方法――这就意味着你将使用ValidatorForm的validate方法,这样才能保证你的错误验证正常进行。配置validation.xml文件。基本内容如下:

<form-validation>

<!-- ========== Default Language Form Definitions ===================== -->

<formset>

<form name="custGNewForm">需要验证页面上form的名字

<field property="certifiCode"需要校验的属性

depends="required,maxlength">校验内容

<arg0 key="prompt.certifiCode"/>ApplicationResource文件中对应

<arg1 key="${var:maxlength}" name="maxlength" resouce="false"/>

<var>确定最长限制的长度

<var-name>maxlength</var-name>

<var-value>20</var-value>

</var>

</field>

注意:此处的arg0和arg1就代表了ApplicationResources文件中使用“{}”括起来的参数。比如:

errors.range={0} is not in the range {1} through {2}.

定义了三个参数,所以你这里也要定义<arg0>、<arg1>、<arg2>三个参数才能完整的显示错误信息。

errors.maxlength={0} cannot be greater than {2} characters.

定义了0、2两个参数,所以你就需要定义<arg0>和<arg2>两个参数。

<field property="userName"

depends="required,maxlength">

<arg0 key="prompt.userName"/>

<arg2 key="${var:maxlength}" name="maxlength" resouce="false"/>

<var>

<var-name>maxlength</var-name>

<var-value>80</var-value>

</var>

</field>

<field property="email"

depends="email">

<arg0 key="prompt.email"/>

</field>

</form>

<form name="custGNewCheckForm">

<field property="certifiCode"

depends="required">

<arg0 key="prompt.certifiCode"/>

</field>

</form>

</formset>

</form-validation>

在校验页面的<body>前添加如下内容:<html:errors/>
3、 DynaValidatorForm

不需要再写对应的ActionForm,只需在struts-config.xml里把自己的ActionForm进行配置:

<form-bean name="testForm" type="org.apache.struts.validator. DynaValidatorForm">

<form-property name="mytext" type="java.lang.String"/>

<form-property name="mytextarea" type="java.lang.String"/>

<form-property name="mydatetext" type="java.lang.String"/>

</form-bean>

在form-property里设置相应的项目,比如说mytext,mytextarea什么的,执行的时候会动态生成ActionForm,再在validation.xml里写入所希望的验证代码,就可以了。JSP文件里不需要写入任何东西,验证也是在服务器端进行,验证部分代码在JSP中不可见。
4、 组合验证

如果使用动态验证DynaValidatorForm的话,不许编写自己的对应的ActionForm,相应的特殊验证会受到相当程度的限制。这个时候,需要将特殊验证部分写入对应的Action,

if(mytext.equals("aaa")){//My Example

ActionErrors errors = new ActionErrors();

errors.add("***",new ActionError("***.error"));

saveErrors(request,errors);

return (mapping.findForward("false"));

}

就可以实现特殊验证了。



实际上你的FORM还可以继承ValidatorActionForm和DynaValidatorActionForm,这两种与他们对应的ValidatorForm和DynaValidatorForm的唯一区别正如开篇就讲到的:在struts-config.xml中查找对应的FORM类时,前者根据ACTION的PATH值,而后者使用NAME值。
范例:

Struts 验证器:验证两个字段匹配

在使用指南中,有一节讲述怎样创建验证器来验证两个字段匹配,我用这个服务器端验证器(象例子中显示的那样)做口令,确定口令验证。这个已经可以正常工作了;但我还想用客户端的javascript验证器来试一试。我写了自己的程序来比较两个字段,但他们和推荐给你的那个不同(from validator-rules.xml)。所以昨天,我补充了怎样添加JavaScript方法到validator-rules.xml。 这里就是怎样配置的的整个过程(大部分在使用指南中已经包含了,保存JavaScript)。

怎样添加两个字段的验证器

Step 1: 生成一个包含validateTwoFields方法的类。在我的代码重,我的类定义为ValidationUtil,他有下列方法:



public static boolean validateTwoFields(

Object bean,

ValidatorAction va,

Field field,

ActionErrors errors,

HttpServletRequest request) {



String value = ValidatorUtil.getValueAsString(bean, field.getProperty());

String sProperty2 = field.getVarValue("secondProperty");

String value2 = ValidatorUtil.getValueAsString(bean, sProperty2);



if (!GenericValidator.isBlankOrNull(value)) {

try {

if (!value.equals(value2)) {

errors.add(field.getKey(),

Resources.getActionError(request, va, field));



return false;

}

} catch (Exception e) {

errors.add(field.getKey(),

Resources.getActionError(request, va, field));



return false;

}

}



return true;

}

Step 2: 编辑 validator-rules.xml ,加入"twofields" 规则。

<validator name="twofields" classname="org.appfuse.webapp.util.ValidationUtil"

method="validateTwoFields"

methodParams="java.lang.Object,

org.apache.commons.validator.ValidatorAction,

org.apache.commons.validator.Field,

org.apache.struts.action.ActionErrors,

javax.servlet.http.HttpServletRequest"

depends="required" msg="errors.twofields">

<javascript><![CDATA[

function validateTwoFields(form) {

var bValid = true;

var focusField = null;

var i = 0;

var fields = new Array();

oTwoFields = new twofields();

for (x in oTwoFields) {

var field = form[oTwoFields[x][0]];

var secondField = form[oTwoFields[x][2]("secondProperty")];



if (field.type == ‘text‘ ||

field.type == ‘textarea‘ ||

field.type == ‘select-one‘ ||

field.type == ‘radio‘ ||

field.type == ‘password‘) {



var value;

var secondValue;

// get field‘s value

if (field.type == "select-one") {

var si = field.selectedIndex;

value = field.options[si].value;

secondValue = secondField.options[si].value;

} else {

value = field.value;

secondValue = secondField.value;

}



if (value != secondValue) {



if (i == 0) {

focusField = field;

}

fields[i++] = oTwoFields[x][1];

bValid = false;

}

}

}



if (fields.length > 0) {

focusField.focus();

alert(fields.join(‘\n‘));

}



return bValid;

}]]></javascript>

</validator>

Step 3: 在validation.xml中为你的表单配置验证:

<field property="password" depends="required,twofields">

<msg name="required" key="errors.required"/>

<msg name="twofields" key="errors.twofields"/>

<arg0 key="userForm.password"/>

<arg1 key="userForm.confirmPassword"/>

<var>

<var-name>secondProperty</var-name>

<var-value>confirmPassword</var-value>

</var>

</field>

这里errors.twofields的字段 ‘{0}‘必须与字段‘{1}‘ 的值相同。第三步的一个可选的工作就时使用 XDoclet 来生成validation.xml。requires (1) 配置XDoclet (当然)和(2) 在你的表单中添加添加一些@struts 标签setPassword方法。

/**

* Returns the password.

* @return String

*

* @struts.validator type="required" msgkey="errors.required"

* @struts.validator type="twofields" msgkey="errors.twofields"

* @struts.validator-args arg1resource="userForm.password"

* @struts.validator-args arg1resource="userForm.confirmPassword"

* @struts.validator-var name="secondProperty" value="confirmPassword"

*/

public String setPassword() {

return password;

}

我昨天已经把这个作为建议发送给struts-dev邮件列表, 但还没有收到任何消息。
留言 (0 留言)
  主题: 工作的规范
(2005-8-17 周三)
 作者: scud
链接地址:当老板喜欢研发过程规范化,该怎么办
工作的规范 留言 (0 留言)
  主题: http://forum.javaeye.com/viewtopic.php?t=15346&highlight=
(2005-8-16 周二)
 作者: xiaoyu
链接地址:
http://forum.javaeye.com/viewtopic.php?t=15346&highlight= 留言 (0 留言)
  主题: 开源实现将为我淘得第一桶金。
(2005-8-16 周二)
 作者: heweiya
链接地址:
冥冥中有一种感觉,呵呵,还是呀:天行键,君子以自强不息@
Lucane Groupware是一个用Java编写的免费的群件,设计具有高度的可扩展性。绑定的应用程序有即时消息,文件共享,聊天,论坛,个人注释,共享的日历...这个平台是开发网络应用程序的一种简单方法。
这两天用了一次lucane这个协同管理软件,感觉很不错,应该在项目管理当中是一个很值得推荐的实现方案,因为他是支持插件式的开发,我想假如把CITIA、PTC等3D制造软件的控件也加入,肯定是一个很好用的协同制造平台。

世界之大,我还需要拼命的学习多种东西才能够达到自己想要的高峰。
留言 (0 留言)
  主题: 我的收藏夹(J2EE部分)
(2005-8-16 周二)
 作者: ben_nb
链接地址:我的收藏夹(J2EE部分)
我的收藏夹(J2EE部分) 留言 (0 留言)
  主题: spring 的第一个例子,运行成功
(2005-8-15 周一)
 作者: zouhongwu
链接地址:
加油
留言 (0 留言)
  主题: 一些常用的Eclipse 3.0的插件
(2005-8-15 周一)
 作者: sctom123
链接地址:
一些常用的Eclipse 3.0的插件
1.
2.Properties Editor 编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html

3.Colorer Take 为上百种类型的文件按语法着色
http://colorer.sourceforge.net/

4.XMLBuddy 编辑xml文件
www.xmlbuddy.com

5.Code Folding 加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport

6.Easy Explorer 从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/

7.Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/

8.RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php

9.JasperAssistant 报表插件(强,要钱的)
http://www.jasperassistant.com/

10.Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/

11.Profiler 性能跟踪、测量工具,能跟踪、测量BS程序
http://sourceforge.net/projects/eclipsecolorer/

12.AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html

13.Log4j Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page

14.VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin

15.Implementors 提供跳转到一个方法的实现类,而不是接中的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/

16.Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html

17.EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/

18.Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm

19.Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/

20.VeloEclipse Velocity插件
http://propsorter.sourceforge.net/

21.EditorList 方便的列出所有打开的Editor
http://editorlist.sourceforge.net/

22.MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/

补充:
1. Easy Struts支持Struts的插件 (0.64版只支持Eclipse2.X)
是开放源代码组织sourceforge.net上的一个项目,目前最新的版本是0.64,

http://sourceforge.net/project/showfiles.php?group_id=54542&package_id=49230
http://easystruts.sourceforge.net/

2.TomcatPlugin 支持Tomcat插件
http://www.sysdeo.com/eclipse/tomcatPlugin.html



本文引用通告地址:
http://blog.csdn.net/edusaj/services/trackbacks/451183.aspx
留言 (0 留言)
  主题: 最漂亮的XML输出
(2005-8-15 周一)
 作者: 凝血神抓
链接地址:最漂亮的XML输出
XMLOutputter类比org.jdom.output工具包里的其他类更为复杂。仅output()这个方法就有18个不同的签名存在。这个类里的绝大多数方法是为了使得输出更为漂亮。
public XMLOutputter()
public XMLOutputter(String indent)
public XMLOutputter(String indent,boolean newlines)
public XMLOutputter(String indent,boolean newlines,String encoding)
public XMLOutputter(XMLOutputter referenceOutputter)
使用3个参数的构造函数方法public XMLOutputter(String indent,boolean newlines,String encoding)可以缩排String。通常这仅仅是一些空格。默认的构造方法里是2个空格。第二个参数指示是否将打印新的一行。在默认的构造方法中是false。最后一个参数是设置编码方式。
通过实践设置,发现最漂亮的XML输出设置是:
XMLOutputter xmlOutputter=new XMLOutputter(" ",true);
xmlOutputter.setTextNormalize(true);
//剥离任何不需要的空格。如果不佳这句,xml文件输出行间距离大概有两行,非常难看
xmlOutputter.oupput(document,new FileWriter("xxx.xml");
留言 (0 留言)
  主题: 已经很长时间没有写BLOG了
(2005-8-15 周一)
 作者: heweiya
链接地址:
已经有很长时间没有写BLOG了。只是因为这一段时间在写文档。
BLOG,感觉和自己的日记一样,只是和日记有一个不一样的地方,就是要改变自己成为一个开放的心态。
现在这一段时间可能又要开始做技术方面的开发的探索。可能BLOG也会多起来。但愿自己成为一个技术上的领袖,因为我觉得管理太空洞,好象是一个骗人的把戏。管理者是一个虚荣的职位,而只有掌握了技术,自己才可能脚踏实地的出来自己做事。
留言 (0 留言)
  主题: 论面向组合子程序设计方法 之 monad
(2005-8-13 周六)
 作者: ajoo
链接地址:论面向组合子程序设计方法 之 monad
仍然是先用oo把轮廓划出来,我们需要建模一个接口来围绕它进行组合。

因为本文是关于co的论述,那么这个接口怎样分析出来的就暂时忽略掉了:

java代码: 

interface Dependency{
  Object getArgument(int i, Class type);
  Class verifyArgument(int i, Class type);
  Object getProperty(Object key, Class type);
  Class verifyProperty(Object key, Class type);
}


这个Dependency接口由每个不同的组件调用,来解决依赖。如果解析失败,则抛出异常。此处,我们暂时忽略异常这个细节。

getArgument负责解析一个函数参数,组件告诉Dependency对象,我需要给第3个参数,类型为String的解析依赖。于是就调用
getArgument(2, String.class)。

getProperty负责解析一个用某个key来标识的属性。比如一个javabean的property。

那两个verify是只取得解析到的那个符合要求的组件类型,但是并不实际创建对象。


然后是Component接口。这里,为了名字简短,我们不用ComponentAdapter这么恶长的名字,直接就是Component好了。


java代码: 

interface Component{
  Class getType();
  Object create(Dependency dep);
  Class verify(Dependency dep);
}




getType()用来返回这个Component生成的对象的类型。
create用来创建这个对象。
verify用来保证这个对象可以被创建。

至于容器接口,再简单不过了。我们都知道pico不过是个hash table,yan的容器也差不多,虽然多几个getComponentOfType()的方法,但是大体上就是一个hash table。

java代码: 

interface Container{
  Component getComponent(Object key);
  Component getComponentOfType(Class type);
}




好了。oo完毕。下面来co。


首先,最简单的Component是什么?什么也不干,直接返回一个值。
java代码: 


class ValueComponent implements Component{
  private final Object v;
  public Class getType(){
    return v==null?null:v.getClass();
  }
  public Object create(Dependency dep){
    return v;
  }
  public Class verify(Dependency dep){
    return getType();
  }
}



稍微难啃点的,是构造函数和工厂方法。这两个都会调用Dependency的getArgument()来取得自己需要的参数实例。
实际上,java的reflection api里面的Method和Constructor还是有很多相似点的。
为了抽取共性,我们定义一个新的接口,叫做Function:

java代码: 

interface Function{
  Class getReturnType();
  Class[] getParameterTypes();
  Object call(Object[] args);
}



这里,我就不展现把Method和Constructor匹配为Function的代码了,因为应该一目了然。
我们只要知道我们现在可以有三个函数产生Function对象:

java代码: 

class Functions{
  static Function ctor(Constructor ctor);
  static Function method(Object obj, Method mtd);
  static Function static_method(Class type, Method mtd);
}


当然,还有一些辅助函数,
比如:
java代码: 

static Function ctor(Class type);




然后是FunctionComponent。
java代码: 


class FunctionComponent implements Component{
  private final Function f;
  public Class getType(){
    return f.getReturnType();
  }
  public Object create(Dependency dep){
    final Class[] types = f.getParameterTypes();
    final Object[] args = new Object[types.length];
    foreach(t:types){
      args[i] = dep.getArgument(i, t);
    }
    return f.call(args);
  }
  public Class verify(Dependency dep){
    final Class[] types = f.getParameterTypes();
    foreach(t:types){
      Class arg_type = dep.verifyArgument(i, t);
      checkTypeMatch(types[i], arg_type);
    }
    return f.getReturnType();
  }
}



然后一个基本的component应该是java bean的setter了,对应pico的SetterInjectionComponentAdapter,也对应spring的bean。
java代码: 

class BeanComponent  implements Component{
  private final Class type;
  public Class getType(){
    return type;
  }
  public Object create(Dependency dep){
    Object r = createInstance();
    setJavaBeans(r,dep);
  }
  public Class verify(Dependency dep){
    ...
  }
}


具体的实现我省略了很多。因为会调用java.beans的api,并且会有一些caching优化的考虑,但是思路上很清楚,就是对每个property调用getProperty()就是了。


好,最基本的就这么几个了(其实,bean component并不是最基本的,后面我们会看到)。

下面看看都有些什么组合规则。

1。手工指定某个参数。
java代码: 

class WithArgument implements Component{
  private final Component parent;
  private final int pos;
  private final Component arg;
  public Class getType(){
    return parent.getType();
  }
  public Object create(Dependency dep){
    return parent.create(withArg(dep));
  }
  public Class verify(Dependency dep){
    return parent.verify(withArg(dep));
  }
  private Dependency withArg(final Dependency dep){
    return new Dependency(){
      public Object getArgument(int i, Class type){
        if(i==pos){
          checkTypeMatch(type, arg);
          return arg.create(dep);
        }
        else return dep.getArgument(i, type);
      }
    }
    ...
  }
}



好,通过decorate这个Dependency对象,我们得到了手工制定某个参数的能力。
这里,我们对参数仍然用Component,而不是一个简单的Object作为这个参数的值,是因为参数本身也可能需要创建,它的依赖关系也可能需要在Dependency对象中解析。如果参数不需要创建,那么,你尽可以用ValueComponent来包装一下。


2。手工指定property的值。跟上面的代码非常类似,就是重载了getProperty()和verifyProperty()。
java代码: 

class WithProperty implements Component{
  private final Component parent;
  private final Object key;
  private final Component prop;
  public Class getType(){
    return parent.getType();
  }
  public Object create(Dependency dep){
    return parent.create(withProp(dep));
  }
  public Class verify(Dependency dep){
    return parent.verify(withProp(dep));
  }
  private Dependency withProp(final Dependency dep){
    return new Dependency(){
      public Object getProperty(Object k, Class type){
        if(k.equals(key)){
          checkTypeMatch(type, prop);
          return prop.create(dep);
        }
        else return dep.getProperty(k, type);
      }
    }
    ...
  }
}




3。和很多组合子一样,map是一个相当有用的组合规则。它负责把一个Component返回的对象作一下额外的处理,transform成另外一个对象。
java代码: 

interface Map{
  Object map(Object obj);
}


java代码: 

class MapComponent implements Component{
  private final Component c;
  private final Map map;
  public Class getType(){
    return null;
  }
  public Object create(Dependency dep){
    return map.map(c.create(dep));
  }
  public Class verify(Dependency dep){
    c.verify(dep);
    return Object.class;
  }
    ...
}



注意,这里,因为我们无法预先知道Map这个接口返回的对象会是什么类型,所以,我们让getType()返回null来标示这是一个动态决定的组件类型。

4。比map更一般化一点的,是bind动作。所谓bind,也是根据一个Component创建的对象来决定接下来返回什么动作。不同的是,它用这个对象来产生另外一个Component,让这个Component来生成一个新对象。多说无益,让我们看代码:
java代码: 

interface Binder{
  Component bind(Object obj);
}


java代码: 

class BoundComponent implements Component{
  private final Component c;
  private final Binder binder;
  public Class getType(){
    return null;
  }
  public Object create(Dependency dep){
    return binder.bind(c.create(dep)).create(dep);
  }
  public Class verify(Dependency dep){
    c.verify(dep);
    return Object.class;
  }
    ...
}


这个Binder接口看似简单,但是它的存在对整个co都是生死攸关的大事。可以说,如果没有这个Binder, co就基本可以不存在了。
为什么这么说呢?因为这个binder再加上前面的那个ValueComponent代表了一种非常一般性的计算模型:monad。有一个专门的数学分支:组论,就是研究monad的。
它虽然不是放之四海皆准的计算模型,比如,有比它更为一般性的Arrow模型。但是,用它几乎可以描述我们一般所遇到的大量问题。

除了前面的几个基本组合子之外,几乎所有的组合子,如果我们愿意,都可以从这个bind推衍出来。比如上面的map,如果用简洁点的函数式语法来表述的话(原谅我还是忍不住用函数式,java的语法就象一砣一砣屎一样压得我喘不过气来)
java代码: 

map(a, f) = bind (a, \x->value(f(x)));


这个代码的意思是说,你可以很轻易地把一个Map对象adapt到Binder对象,只要在bind函数里面调用:
java代码: 

return new ValueComponent(map.map(v));


就行了。

后面的很多组合子,比如对一个组件生成的对象调用某个方法,设置一些java bean setter,都是从这个bind组合子衍生出来的。


好了,今天时间紧迫,到此告一段落吧。
留言 (0 留言)
  主题: maven资料收集
(2005-8-12 周五)
 作者: monkeyhero
链接地址:
1、执行时提示为???的问题
这个问题其实也就是中文问题,在Maven 1.0中其实已经将黄东的汉化版融入进去了,只是在融入中文的时候有一个小的失误。大家到%HAVEN_HOME%\lib目录下着到maven.jar文件,在这个压缩文件中的“org\apache\maven\messages”下有一个messages_zh_CN.properties中文消息文件,解压出来,用java自带的工具native2ascii messages_zh_CN.properties messages_zh_CN1.properties转化编码,再将新的文件放回原处。

2、关于无法连接到远程repository库进行下载和编译速度慢的问题。
在默认的情况下Maven的远程库为ibibio这个网站,不知道为什么这个网站在中国被封掉了!所有无法下载,不过我们可以通过修改每个项目中的project.properties文件,在其中加入这样一行maven.repo.remote =
http://public.planetmirror.com/pub/maven,这样就代表这个项目中的远程库可以到public.planetmirror.com/pub/maven这个地方下载。当然如果我们不想每个项目都更改的话,可以直接修改maven.jar中的defaults.properties文件,将其修改为上面这样。
同时对于在一个公司内的开发,我们没有必要每个开发人员都到外网上下载,我们建立一个repository远程库网站就可以了,比如如果我们用Window IIS的话,我们只需要在我们的将建立一个web目录,将我们下载好的Repository文件夹下的所有的目录拷贝到这个web目录就好了。如我们建立的这个web目录对应的IIS虚拟目录为http://192.168.1.1/Maven/Reppository,那么我们只需要将开发人员的maven.repo.remote设置为http://192.168.1.1/Maven/Reppository就好了,这样我们一来是统一了我们的jar版本,二来我们开发人员在下载所需要的文件也就快多了。
同时对应在编译速度慢还有一个原因是,在第一次编译时需要下载,同时,我们在编译的过程中需要检查jar的版本,对于下载jar,这个动作只是在第一次的时候会执行,在后面的时候Maven只会检查是否有最新版本的jar,如果有才会下载。我想检查jar版本对于一个团队的开发是必要的吧!
留言 (0 留言)
  主题: JNDI命名和目录服务器
(2005-8-12 周五)
 作者: Tracylau
链接地址:
命名服务器,它把名称和对象关联(绑定)起来;它提供根据一个命名查找对象的工具。
目录服务器,是扩展和提高的命名服务器;它为使用属性提供了目录对象的操作。
JNDI由两部分组成:客户端API和服务技术接口(Service Provider Interfae SPI)。客户端API允许JAVA代码执行目录操作;SPI是一个命名和目录服务器供应商能够插入插件的接口。

JNDI的命名类型:1,原子命名,"/etc/fstab",etc和fstab是原子命名。
2,复合命名,"etc/fstab"就是一个复合命名。
留言 (0 留言)
  主题: 论面向组合子程序设计方法 之 重构
(2005-8-12 周五)
 作者: ajoo
链接地址:论面向组合子程序设计方法 之 重构
迄今,发现典型的几种疑问是:
1。组合子的设计要求正交,要求最基本,这是不是太难达到呢?
2。面对一些现实中更复杂的需求,组合子怎样scale up呢?

其实,这两者都指向一个答案:重构。


要设计一个完全正交,原子到不可再分的组合子,也许不是总是那么容易。但是,我们并不需要一开始就设计出来完美的组合子设计。

比如,我前面的logging例子,TimestampLogger负责给在一行的开头打印当前时间。
然后readonly提出了一个新的需要:打印调用这个logger的那个java文件的类名字和行号。

分析这个需求,可以发现,两者都要求在一行的开始打印一些东西。似乎有些共性.
这个"在行首打印一些前缀"就成了一个可以抽象出来的共性.于是重构:

java代码: 

interface Factory{
  String create();
}
class PrefixLogger implements Logger{
  private final Logger logger;
  private final Factory factory;
  private boolean freshline = true;

  private void prefix(int lvl){
    if(freshline){
      Object r = factory.create();
      if(r!=null)
        logger.print(lvl, r);
      freshline = false;
    }
  }
  public void print(int lvl, String s){
    prefix(lvl);
    logger.print(lvl, s);
  }
  public void println(int lvl, String s){
    prefix(lvl);
    logger.println(lvl, s);
    freshline = true;
  }
  public void printException(int lvl, Throwable e){
    prefix(lvl);
    logger.printException(lvl, e);
    freshline = true;
  }
}


这里,Factory接口用来抽象往行首打印的前缀。这个地方之所以不是一个String,是因为考虑到生成这个前缀可能是比较昂贵的(比如打印行号,这需要创建一个临时异常对象)

另外,真正的Logger接口,会负责打印所有的原始类型和Object类型,例子中我们简化了这个接口,为了演示方便。


然后,先重构timestamp:

java代码: 

class TimestampFactory implements Factory{
  private final DateFormat fmt;
  public String create(){
    return fmt.format(new Date());
  }
}



这样,就把timestamp和“行首打印”解耦了出来。


下面添加TraceBackFactory,负责打印当前行号等源代码相关信息。
java代码: 

interface SourceLocationFormat{
  String format(StackTraceElement frame);
}
class TraceBackFactory implements Factory{
  private final SourceLocationFormat fmt;
  public String create(){
    final StackTraceElement frame = getNearestUserFrame();
    if(frame!=null)
      return fmt.format(frame);
    else return null;
  }
  private StackTraceElement getNearestUserFrame(){
    final StackTraceElement[] frames = new Throwable().getStackTrace();
    foreach(frame: frames){
      if(!frame.getClassName().startsWith("org.mylogging")){
        //user frame
        return frame;
      }
    }
    return null;
  }
}



具体的SourceLocationFormat的实现我就不写了。

注意,到现在为止,这个重构都是经典的oo的思路,划分责任,按照责任定义Factory, SourceLocationFormat等等接口,依赖注入等。完全没有co的影子。

这也说明,在co里面,我们不是不能采用oo,就象在oo里面,我们也可以围绕某个接口按照co来提供一整套的实现一样,就象在oo里面,我们也可以在函数内部用po的方法来实现某个具体功能一样。


下面开始对factory做一些co的勾当:
先是最简单的:

java代码: 

class ReturnFactory implements Factory{
  private final String s;
  public String create(){return s;}
}



然后是两个factory的串联,

java代码: 

class ConcatFactory implements Factory{
  private final Factory[] fs;
  public String create(){
    StringBuffer buf = new StringBuffer();
    foreach(f: fs){
      buf.append(f.create());
    }
    return buf.toString();
  }
}





最后,我们把这几个零件组合在一起:

java代码: 

Logger myprefix(Logger l){
  Factory timestamp = new TimestampFactory(some_date_format);
  Factory traceback = new TraceBackFactory(some_location_format);
  Factory both = new ConcatFactory(
    timestamp,
    new ReturnFactory(" - "),
    traceback,
    new ReturnFactory(" : ")
  );
  return new PrefixLogger(both, l);
}



如此,基本上,在行首添加东西的需求就差不多了,我们甚至也可以在行尾添加东西,还可以重用这些factory的组合子。

另一点我想说明的是:这种重构是相当局部的,仅仅影响几个组合子,而并不影响整个组合子框架。


真正影响组合子框架的,是Logger接口本身的变化。假设,readonly提出了一个非常好的意见:printException应该也接受level,因为我们应该也可以选择一个exception的重要程度。


那么,如果需要做这个变化,很不幸的是,所有的实现这个接口的类都要改变。

这是不是co的一个缺陷呢?


我说不是。
即使是oo,如果你需要改动接口,所有的实现类也都要改动。co对这种情况,其实还是做了很大的贡献来避免的:
只有原子组合子需要实现这个接口,而派生的组合子和客户代码,根本就不会被波及到。
而co相比于oo,同样面对相同复杂的需求,往往原子组合子的数目远远小于实际上要实现的语义数,大量的需求要求的语义,被通过组合基本粒子来实现。也因此会减少直接实现这个接口的类的数目,降低了接口变化的波及范围。


那么,这个Logger接口是怎么来的呢?

它的形成来自两方面:

1。需求。通过oo的手段分配责任,最后分析出来的一个接口。这个接口不一定是最简化的,因为它完全是外部需求驱动的。

2。组合子自身接口简单性和完备性的需要。有些时候,我们发现,一个组合子里面如果没有某个方法,或者某个方法如果没有某个参数,一些组合就无法成立。这很可能说明我们的接口不是完备的。(比如那个print函数)。
此时,就需要改动接口,并且修改原子组合子的实现。
因为这个变化完全是基于组合需求的完备性的,所以是co方法本身带来的问题,而不能推诿于oo设计出来的接口。
也因为如此,基本组合子个数的尽量精简就是一个目标。能够通过基本组合子组合而成的,就可以考虑不要直接实现这个接口。
当然,这里面仍然有个权衡:
通过组合出来的不如直接实现的直接,可理解性,甚至可调试性,性能都会有所下降。
而如果选择直接实现接口,那么就要做好接口一旦变化,就多出一个类要改动这个类的心理准备。

如何抉择,没有一定之规。

而因为1和2的目标并不完全一致,很多时候,我们还需要在1和2之间架一个adapter以避免两个目标的冲突。

比如说,实际使用中,我可能希望Logger接口提供不要求level的println函数,让它的缺省值取INFO就好了。

但是,这对组合子的实现来说却是不利的。这时,我们也许就要把这个实现要求的Logger接口和组合子的Logger接口分离开来。(比如把组合子单独挪到一个package中)。



Logger这个例子是非常简单的,它虽然来自于实际项目,但是项目对logging的需求并不是太多,所以一些朋友提出了一些基于实际使用的一些问题,我只能给一个怎么做的大致轮廓,手边却没有可以运行的程序。


那么,下面一个例子,我们来看看一个我经过了很多思考比较完善了的ioc容器的设计。这个设计来源于yan container。


先说一下ioc容器的背景知识。

所谓ioc容器,是一种用来组装用ioc模式(或者叫依赖注射)设计出来的类的工具。
一个用ioc设计出来的类,本身对ioc容器是一无所知的。使用它的时候,可以根据实际情况选择直接new,直接调用setter等等比较直接的方法,但是,当这样的组件非常非常多的时候,用一个ioc容器来统一管理这些对象的组装就可以被考虑。


拿pico作为例子,对应这样一个类:

java代码: 

class Boy{
  private final Girl girl;
  public Boy(Girl g){
    this.girl = g;
  }
...
}



我们自然可以new Boy(new Girl());

没什么不好的。

但是,如果这种需要组装的类太多,那么这个组装就变成一件累人的活了。

于是,pico container提供了一个统一管理组建的方法:
java代码: 


picocontainer container = new DefaultContainer();
container.registerComponentImplementation(Boy.class);
container.registerComponentImplementation(Girl.class);




这个代码,很可能不是直接写在程序里面,而是先读取配置文件或者什么东西,然后动态地调用这段代码。

最后,使用下面的方法来取得对象:

java代码: 

Object obj = container.getComponentInstance(Boy.class);



注意,这个container.getXXX,本身是违反ioc的设计模式的,它主动地去寻找某个组件了。所以,组件本身是忌讳调用这种api的。如果你在组件级别的代码直接依赖ioc容器的api,那么,恭喜你,你终于成功地化神奇为腐朽了。



这段代码,实际上应该出现在系统的最外围的组装程序中。

当然,这是题外话。


那么,我们来评估一下pico先,

1。让容器自动寻找符合某个类型的组件,叫做auto-wiring。这个功能方便,但是不能scale up。一旦系统复杂起来,就会造成一团乱麻,尤其是有两个组件都符合这个要求的时候,就会出现二义性。所以,必须提供让配置者或者程序员显示指定使用哪个组件的能力。所谓manual-wire。
当然,pico实际上是提供了这个能力的,它允许你使用组件key或者组件类型来显示地给某个组件的某个参数或者某个property指定它的那个girl。

但是,pico的灵活性就到这里了,它要求你的这个girl必须被直接登记在这个容器中,占用一个宝贵的全局key,即使这个girl只是专门为这个body临时制造的夏娃。

在java中,遇到这种情况:

java代码: 

void A createA(){
  B b = new B();
  return new A(b,b);
}



我们只需要把b作为一个局部变量,构造完A,b就扔掉了。然而,pico里面这不成,b必须被登记在这个容器中。这就相当于你必须要把b定义成一个全局变量一样。
pico的对应代码:

java代码: 

container.registerComponent("b" new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(B.class)));
container.registerComponent("a", new ConstructorInjectionComponentAdapter(A.class));



这里,为了对应上面java代码中的两个参数公用一个b的实例的要求,必须把a登记成一个singleton。CachingComponentAdapter负责singleton化某个组件,而ConstructorInjectionComponentAdapter就是一个调用构造函数的组建匹配器。


当然,这样做其实还是有麻烦的,当container不把a登记成singleton的时候(pico缺省都登记成singleton,但是你可以换缺省不用singleton的container。),麻烦就来了。

大家可以看到,上面的createA()函数如果调用两次,会创建两个A对象,两个B对象,而用这段pico代码,调用两次getComponentInstance("a"),会生成两个A对象,但是却只有一个B对象!因为b被被迫登记为singleton了。





2。pico除了支持constructor injection,也支持setter injection甚至factory method injection。(对最后一点我有点含糊,不过就假设它支持)。所以,跟spring对比,除了没有一个配置文件,life-cycle不太优雅之外,什么都有了。

但是,这就够了吗?如果我们把上面的那个createA函数稍微变一下:

java代码: 

A createA(){
  B b = new B();
  return new A(b, b.createC(x_component));
}




现在,我们要在b组件上面调用createC()来生成一个C对象。完了,我们要的既不是构造函数,也不是工厂方法,而是在某个临时组件的基础上调用一个函数。

缺省提供的几个ComponentAdapter这时就不够用了,我们被告知要自己实现ComponentAdapter。

实际上,pico对很多灵活性的要求的回答都是:自己实现ComponentAdapter。


这是可行的。没什么是ComponentAdapter干不了的,如果不计工作量的话。

一个麻烦是:我们要直接调用pico的api来自己解析依赖了。我们要自己知道是调用container.getComponentInstance("x_component")还是container.getComponentInstance(X.class)。
第二个麻烦是:降低了代码重用。自己实现ComponentAdapter就得自己老老实实地写,如果自己的component adapter也要动态设置java bean setter的话,甭想直接用SetterInjectionComponentAdapter,好好看java bean的api吧。


其实,我们可以看出,pico的各种ComponentAdapter正是正宗的decorator pattern。什么CachingComponentAdapter,什么SynchronizedComponentAdapter,都是decorator。

但是,这也就是decorator而已了。因为没有围绕组合子的思路开展设计,这些decorator显得非常随意,没有什么章法,没办法支撑起整个的ComponentAdapter的架构。

下一章,我们会介绍yan container对上面提出的问题以及很多其他问题的解决方法。

yan container的口号是:只要你直接组装能够做到的,容器就能做到。
不管你是不是用构造函数,静态方法,java bean,构造函数然后再调用某个方法,等等等等。
而且yan container的目标是,你几乎不用自己实现component adapter,所有的需求,都通过组合各种已经存在的组合子来完成。


对我们前面那个很不厚道地用来刁难pico的例子,yan的解决方法是:


java代码: 

b_component = Components.ctor(B.class).singleton();
a_component = Components.ctor(A.class)
  .withArgument(0, b_component)
  .withArgument(1, b_component.method("createC"));



b_component不需要登记在容器中,它作为局部component存在。
是不是非常declarative呢?


下一节,你会发现,用面向组合子的方法,ioc容器这种东西真的不难。我们不需要仔细分析各种需求,精心分配责任。让我们再次体验一下吊儿郎当不知不觉间就天下大治的感觉吧。

待续。
留言 (0 留言)
  主题: 通过XML描述下拉框的级连选择处理
(2005-8-11 周四)
 作者: dingyd
链接地址:
java代码: 

<xs:element name="onchange" minOccurs="0">
          <xs:complexType>
            <xs:sequence>
        <xs:element name="onchange-func" type="xs:string"/>
        <xs:element name="onchange-process" type="xs:string"/>
        <xs:element name="param-name" type="xs:string"/>
        <xs:element name="dest-name" type="xs:string"/>
        <xs:element name="dest-desc" type="xs:string"/>
        <xs:element name="dest-value" type="xs:string"/>
        <xs:element name="delete-name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>



onchange-func: 要触发的JS函数
onchange-process: 通过XMLHTTP调用的ACTION
param-name: 调用ACTION要用到的参数(目前只用一个)
dest-name: 目的选择框名称
dest-desc: 目的下拉框的描述
dest-value: 目的下拉框的值
delete-name: 对应该CHANGE事件要删除的其他相关下拉框的内容.

呵呵: 好久没更新了,今天把以往做的不好的级连下拉框处理做的优化,深感一个人的力量太小,找个时间把代码整理后开源出来,也算是对自己的一个交代.
留言 (0 留言)
  主题: DBCP连接池测试用例(8月修正版)
(2005-8-11 周四)
 作者: cnsdl
链接地址:
[color=red系统:[/color]
WIN2000>>APACHE TOMCAT5.0.28(要求5.0及以上版本)>>SQL SERVER 2000
系统用的是SQL SERVER 库中的Northwind。采用第四类驱动,驱动类放到D:\testpool\WEB-INF\lib中。
保证TOMCAT和SQL SERVER正常运行。
[1]在%TOMCAT_HOME%\conf\Catalina\localhost\目录下建一个testPool.xml文件:
<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<Context docBase="D:/testpool" path="/testpool" privileged="true" workDir="work\Catalina\localhost\testpool">
<Resource type="javax.sql.DataSource" auth="Container" name="jdbc/northwind"/>
<ResourceParams name="jdbc/northwind">
<parameter>
<name>maxWait</name>
<value>5000</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>4</value>
</parameter>
<parameter>
<name>password</name>
<value>12345</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:microsoft:sqlserver://10.0.0.168:1433;databaseName=Northwind</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>com.microsoft.jdbc.sqlserver.SQLServerDriver</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>2</value>
</parameter>
<parameter>
<name>username</name>
<value>sa</value>
</parameter>
</ResourceParams>
</Context>
[2]在D:\testpool\WEB-INF\下面建立一个web.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>

<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">
<!--ConnectionPool-->
<resource-ref>
<res-ref-name>jdbc/northwind</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
[3]在D:\testpool\下面建立测试文件index.jsp
<%@ page contentType="text/html;charset=GB2312"%>
<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*"%>
<%@ page import="javax.naming.*"%>
<%@ page session="false" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>tetst connection pool</title>
<%
out.println("我的测试开始");
DataSource ds = null;

try{
InitialContext ctx=new InitialContext();
ds=(DataSource)ctx.lookup("java:comp/env/jdbc/northwind");
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();

String strSql = " select * from Categories";
ResultSet rs = stmt.executeQuery(strSql);
while(rs.next()){
out.println(rs.getString(1));
}
out.println("我的测试结束");
}
catch(Exception ex){
out.print("出现例外,信息是:"+ex.getMessage());
ex.printStackTrace();
}
%>
</head>
<body>
</body>
</html>
[4]补充:做配置时大体要搞清楚类似的几个问题,就是考虑WWW原则,要建或改什么文件(WHO),在那里做(WHERE),做什么(WHAT).与之对应的是:
WHO WHERE WHAT
testPool.xml %TOMCAT_HOME%\conf\Catalina\localhost\ 见第一步
web.xml(名字固定) D:\testpool\WEB-INF\web.xml 见第二步
index.jsp D:\testpool\index.jsp 见第三步
[5]如果使用hibernate,除作上面的外还需要更改hibernate.cfg.xml文件:
<?xml version=‘1.0‘ encoding=‘UTF-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<!-- DO NOT EDIT: This is a generated file that is synchronized -->
<!-- by MyEclipse Hibernate tool integration. -->
<hibernate-configuration>

<session-factory>
<!-- properties
<property name="connection.username">sa</property>
<property name="connection.url">
jdbc:microsoft:sqlserver://10.0.0.168:1433;DataBaseName=cw_scene
</property>
<property name="dialect">
net.sf.hibernate.dialect.SQLServerDialect
</property>
<property name="connection.password">12345</property>
<property name="connection.driver_class">
com.microsoft.jdbc.sqlserver.SQLServerDriver
</property>
<property name="hibernate.jdbc.fetch_size">50</property>
<property name="hibernate.jdbc.batch_size">25</property>
-->
<!-- properties -->

<property name="connection.datasource">
java:comp/env/jdbc/testpool
</property>
<property name="dialect">
net.sf.hibernate.dialect.SQLServerDialect
</property>
<property name="hibernate.connection.provider_class">
net.sf.hibernate.connection.DatasourceConnectionProvider
</property>
<property name="hibernate.jdbc.fetch_size">50</property>
<property name="hibernate.jdbc.batch_size">25</property>

<!-- mapping files -->
<mapping resource="com/scenechina/table/Imgpos.hbm.xml" />

</session-factory>

</hibernate-configuration>
留言 (0 留言)
  主题: 序列化
(2005-8-10 周三)
 作者: Tracylau
链接地址:
序列化是将一个JAVA对象转化为一个描述该对象的位块(bit-blob);对象一旦变成了bit-blob后,就可以发送到任何硬盘/网络上;准备再使用该对象时,要将bit-blob解序列化为JAVA对象方可使用。
变为可序列化的,只要实现java.lang.Serializable接口。
用静态关键字标识的对象不能序列化,并且在解序列是无效的。

tracylau 2005.8.10
留言 (0 留言)
  主题: RMI值传递
(2005-8-10 周三)
 作者: Tracylau
链接地址:
用RMI请求一个方法时,传递远程对象的所有参数是通过值传递的。当调用目标方法时,将所有参数从一台机器传到另一台机器。
如果要将对象在网络上传递的话,则要将对象序列化。
see next==>

tracylau 2005.8.10
留言 (0 留言)
  主题: 开头之二
(2005-8-10 周三)
 作者: floating
链接地址:
谁说谁是谁的鱼
谁说把秘密藏在天空里
有没有前提
有没有意义
失眠会不会弄丢了记忆
会不会想起
会不会涕泣

从荧幕上看着巨大的飞机
从左边看着你右边的鼻息
从脑海里看着对你微弱的回忆
从镜子里看着自己

35mm的摄像头
35cm的距离
三分钟的休息
夹杂着所谓从前的痕迹
留言 (0 留言)
  主题: 论面向组合子程序设计方法 之 oracle
(2005-8-10 周三)
 作者: ajoo
链接地址:论面向组合子程序设计方法 之 oracle
不少朋友说我的阐述很苍白无力。这让我很苦恼。我确实是拚了命地想把问题说清楚,我也有实际non-trivial的项目经验,怎么就说不明白呢?哎!

所以,还是不能不多罗嗦一下,希望能够再阐述得明白一点。


其实,所谓co,有心的朋友也许能够感觉到,它很象是设计一门语言。
它有顺序/分支,有函数调用,异常处理,基本上一个程序设计语言有的东西它都有了。这些顺序/分支作为语言的基础设施,而一些应对具体需求的原子操作,(比如WriterLogger,比如NeptuneExceptionLogger)则可以看作是语言的扩展或者库。

只不过,c/c++/java是有编译器来把源代码转化成目标代码。而co的组合子则是利用了宿主语言(比如java)本身的能力来实现各个组合子的语义。可以说,co是在设计一门语言中的语言。


最近这段时间,流行过一阵LOP(language oriented programming),就是用domain-specific-language来处理一些特定的domain需求。
为什么会有这种说法呢?还是因为domain的需求多变灵活,不容易琢磨,所以人们发现用一个灵活的语言比用一些功能相对死板的库更容易对付。
但是dsl的实现不是那么容易的。而co是不是可以说给我们提供了一个相对廉价的创建自己的domain specific language的途径呢?
我们虽然不能说
java代码: 

if level > 1 then logger1 else logger2


至少可以退而求其次,写出
java代码: 

ignore(1, logger1, logger2);







当然,这种子语言相比于宿主语言,缺乏调试器的帮助,缺乏编译器的优化,缺乏各种ide的支持,可用性程度是大大不如的。

但是,它也有它的好处,那就是,不用额外进行一次编译;写出来的东西和宿主语言无缝集成;可以自动利用宿主语言里面的一切设施。等等。

那么如果把co作为一个语言来考虑,是不是会给我们一些别的启示呢?

oz曾经质疑,所谓的co,怎样决定基本的组合子?怎么样才知道组合子够用了?

那么,设计语言的时候,怎么设计基本的语言设施?怎么样知道这个设施是不足,还是太多呢?
这是个大话题,语言设计非常有争议性和主观性。但是,也仍然是有些共性的。比如,几乎所有的语言都支持顺序,if/else,递归/循环,函数定义等。相当多我们熟悉的语言还支持异常处理,字符串,整数等。

我们难道不能从这里借鉴一些东西?如果我们把基本的nop, 顺序,分支作为启动co的基本要求,这算不算是一个可以遵循的guidline呢?

另外,不同的语言,根据它所面向的领域,还有一些不同的内建操作,比如perl,面向文本处理,所以内建了regexp,而c/c++因为直接面对机器硬件模型,所以提供了指针。
那么,根据我们的组合子所要面对的应用类型,我们也可以预先定义一些内建的原始组合子,比如那个WriterLogger。


一般来说,对general purpose的语言,设计的基础设施都是非常”基础“的,基础到几乎不会有什么设施是多余的。同时,也基础到一般人都可以没有什么困难地掌握基本的语法语义。
那么这些语言用这么有限的聊聊几个设施怎么对应它不可能预见的各种现实中的需求呢?

我们平时对可以用简单的if-else,顺序,循环就可以做几乎任何我们能想到的事情感到惊讶了吗?对某个语言能否处理复杂的需求担忧了吗?

为什么没有呢?就在于”组合“的威力。就是那么几个简单的设施,组合起来却可以具有巨大的力量。

同样,在设计组合子的时候,如果我们遵循这些共性,同时在和具体需求磨合的时候积极改进组合子本身,应该就可以达到一个可以满意的组合子系统。


说到这里,可能有人问:你不是说组合子系统是脱离需求自己发展的吗?


这个,怎么说呢?还是说语言。设计一个语言,原则上是设计一些和具体需求没有耦合的基础设施,然后让写具体应用的人组合这些基础设施达到我们语言设计者不可能都预见到的某些具体效果。
但是,在语言设计和逐渐发展过程中,仍然会从用户(就是程序员)那里得到反馈,什么样的需求是他们需要的,什么样的设施不好用,然后设计者再进行改进。

一个语言,一般不会是某个具体需求通过划分责任,自顶向下的设计,最后作为这个需求的附属模块被开发出来。更多的情况,是语言的设计者对可能面对的需求有一个大致了解,并且知道这种可能的需求是变化的,不确定的,于是才会从基本设施开始设计这个语言,最后交付给开发具体应用的人来使用。


组合子系统也遵循一样的规律。组合子的设计需要来自具体需求的灵感和纠正。但是,基本组合子本身却不能耦合于这些具体需求。
组合子的设计者应该对可能面对的需求有大致的了解,但是并不需要完整地预测这种多变的需求。


当然,组合子这种语言中的语言,还有不容易理解的弱点。当组合层次增多,逻辑变得复杂,这个复杂的组合子可不象写在一般的程序设计语言里面的代码那样简明,java繁琐的语法让这种语言中的语言对一些程序员也许就象是神庙里面的神谕一样难以理解,甚至让人望而生畏。

这一半是因为java本身没有提供对这种高阶逻辑的直接支持,组合的语法相对繁琐。如果用haskell,使用语言提供的do-notation,形式上的复杂程度就没了。

另一方面,高阶逻辑都是有这种弱点的。
其实,就算是oo,相比于po,它的多态也是降低可理解性的。

因此,就象我们也不能在oo中到处override虚函数一样,组合子的使用也要有一定的度,过犹不及。



好了,没有实际例子的论述已经太长了。肯定已经有人昏昏欲睡了。


做个调查,我下面的例子有几个选择:

    1。简单的predicate。就是一个具有bool is(Object v);方法的接口。
    2。ant的FileSelector。一个predicate的变体。
    3。swing提到的jdbc的action。负责数据库操作的组合子。
    4。yan container的component。负责从任意的构造函数,工厂方法,java bean以及它们的任意组合来产生和注射组件。比pico/spring灵活很多。
    5。neptune的command,负责调用任何ant task以及其它的可能返回一个java对象的函数。是一个一般化了的ant task。



这些东西都是基于组合子的。大家觉得对哪一个更有兴趣呢?我有些拿不定主意哪个更合适。
留言 (0 留言)
  主题: 用Java程序获取绝对路径
(2005-8-09 周二)
 作者: 凝血神抓
链接地址:用Java程序获取绝对路径
http://www.softhouse.com.cn/html/200409/2004092114331500000778.html 留言 (0 留言)
  主题: 路径相关
(2005-8-09 周二)
 作者: 凝血神抓
链接地址:路径相关
http://www.javaresearch.org/article/showarticle.jsp?column=2&thread=7499 留言 (0 留言)
  主题: 在JSP中处理虚拟路径
(2005-8-09 周二)
 作者: 凝血神抓
链接地址:在JSP中处理虚拟路径
http://www.programfan.com/article/showarticle.asp?id=1083 留言 (0 留言)
  主题: 防止ACCESS数据库被下载的9种方法 [整理版]
(2005-8-09 周二)
 作者: 凝血神抓
链接地址:防止ACCESS数据库被下载的9种方法 [整理版]
http://oldblog.blogchina.com/article_47389.178225.html 留言 (0 留言)
  主题: veloeclipse
(2005-8-09 周二)
 作者: chjdxiao
链接地址:veloeclipse
veloeclipseveloeclipse 留言 (0 留言)
第37页,共105页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Validator验证框架使用教程
Struts源代码阅读(Commons-Validator) - [Matrix - 与 Java 共舞]
Struts的验证--Validator
Struts第4天
hk编写扩展Struts Validator校验密码输入
Struts Validator 开发指南
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服