??xml version="1.0" encoding="utf-8" standalone="yes"?>
1. ?/span>
AspectJ是AOP的Java语言的实玎ͼ获得了JavaE序员的q泛x? 关于AspectJ和AOP的具体资料,请从下列链接中查找:
http://www.eclipse.org/aspectj/
|上出现了很多讲解AspectJ的资料,但大多是从讲解AspectJ语法开始,然后讲解如何应用AspectJQ如何分Y件开发过E的不同斚wQAspectQ?-LogQSessionQAuthentication and AuthorizationQTransactionQ等{? 初次接触AspectJ的读者看到这些资料(或者语法手册)Q会感到AspectJ有些秘。他们想知道QAspectJ是如何做到这些的QAspectJ是怎样工作的?AspectJ需要特D的q行环境吗? 本文从另一个角度讲解AspectJQ本文从讲解AspectJ的设计思\、运行原理入手,回答上述问题? 本文讲解的主要内容,按照概念的重要程度,排列如下Q? 本文的原则是Q只l讲其他资料没有讲到的东西,其他资料讲过的东西,不讲或略讌Ӏ以节省|络资源Q更Z节省大家宝贵的时间。J
2QAspect Oriented Programming (AOP)
AOP是Object Oriented ProgrammingQOOPQ的补充?/p>
OOP能够很好地解军_象的数据和封装的问题Q却不能很好的解决AspectQ?斚w"Q分ȝ问题。下面D例具体说明?/p>
比如Q我们有一个BankQ银行)cRBank有两个方法,depositQ存钱)和withdrawQ取钱)? cdҎ(gu)的定义如下:
q两个方法涉及到用户的̎戯金等重要信息Q必要非常心Q所以编写完上面的商业逻辑之后Q项目负责h又提Z新的要求--lBankcȝ每个重要Ҏ(gu)加上安全认证Ҏ(gu)? 于是Q我们不得不分别在上面的两个Ҏ(gu)中加入安全认证的代码? cdҎ(gu)的定义如下:Q新增加的代码用不同的背景标出)
q两个方法都需要操作数据库Qؓ了保持数据完整性,目负责人又提出了新的要?-lBankcȝ每个操作数据库的Ҏ(gu)加上事务控制? 于是Q我们不得不分别在上面的两个Ҏ(gu)中加入安全认证的代码? cdҎ(gu)的定义如下:Q新增加的代码用不同的背景标出)
我们看到Q这些与商业逻辑无关的重复代码遍布在整个E序中。实际的工程目中涉及到的类和函敎ͼq远不止两个。如何解册U问题? 我们首先来看看OOP能否解决q个问题? 我们利用Design Pattern的Template PatternQ可以抽Z个框Ӟ改变上面的例子的整个设计l构? cdҎ(gu)的定义如下:
q里我们用一U很勉强的方法实C认证和事务代码的重用。而且Q有心的读者可能会注意刎ͼq种Ҏ(gu)的前提是Q强制所有的Ҏ(gu)都遵守同L(fng)signature? 如果有一个{账方法transfer(AccountInfo giver, AccountInfo receiver, float money)Q由于transferҎ(gu)的signature不同于yourBusiness的signatureQ这个方法无法用上面的框架? q个例子中提到的认证Q事务等斚wQ就是AOP所兛_的Aspect? AOP是Z解决q种问题而出现的。AOP的目的就?-Separation of Aspects (or Separation of Concerns). 下面的章节,解释EJB DescriptorQAspectJQxDoclet{工具如何解决Separation of Aspects的问题?
3QEJB Descriptor
在Bank的代码中只用考虑商业逻辑Q不用考虑认证和事务等斚w? 认证和事务等斚w在EJB Descriptor中定义,由EJB Container提供q些斚w的实现? 我们来看一下,如何使用EJB Descriptor描述上面的例子? EJB Descriptor包括一个ejb-jar.xml文g。ejb-jar.xml文g包含两大部分Qenterprise-beans?assembly-descriptor部分。enterprise-beans部分包含EJB的定?-JNDI NameQEJB Home, Interface, Bean Class Path{;assembly-descriptor部分包括配置信息的定?-安全角色Q事务控制等{? 下面l出上面例子对应的模拟EJB Descriptor?
本文后面会讲到如何用AspectJ实现上例中的Separation of Aspects? 读者可以比较一下AspectJ语法和EJB Descriptor定义之间的对应关pR? 两者都提供了类名、方法名的匹配规则,能够把类的方法映到认证Q事务等AspectQ方面)?
4QAspectJ
使用AspectJQ我们不用对原有的代码做M修改Q就可以Z码提供不同的AspectQ方面)--比如Q认证,事务{? 我们只需要提供两个不同的Aspect--认证Aspect和事务Aspect?
如果(zhn)暂时不能理解这D代码,没有关系Q后面会讲到Q这些aspect的定义,不过是定义了一些代码生成规则? 我们用AspectJ~译器编译B(ti)ank文g和含有aspect的这个文Ӟ出来的结果就是带有安全认证和事务处理的BankcR编译出来的q个 Bankc调用了AspectJ Runtime LibQ所以,如果你要q行q个Bankc,你需要把AspectJ Runtime Lib讄在你的classpath里面? 我们来看看,AspectJ~译器ؓ我们做了什么事情? 如何验证q一点?(zhn)可以到http://www.eclipse.org/aspectj/下蝲安装AspectJQ编译里面的SampleQ把~译l果反编译一下,可以看到AspetJ自动生成的代码? 我们看到QAspectJ是一U代码自动生成工兗你~写一D通用的代码,比如认证斚w的代码,事务斚w的代码,然后Ҏ(gu)AspectJ语法定义一套代码生成规则(aspect定义Q,AspectJ׃帮助你自动把q段通用代码分布到对应的代码里面去,单快P无遗策? 无独有偶Q一个著名的~译器生成工?-Java Compiler Compiler (JavaCC)Q也采用了非常相似的代码生成机制。JavaCC允许你在语法定义规则文g中,加入你自qJava代码Q用来处理读入的各种语法元素? AspectJ令你的代码更_Q结构更良好。AspectJ的好处,我就不多说了Q网上很多精彩的文章探讨AspectJ的各U用途? 下面介绍一个著名的代码自动生成?-xDocletQ和EJB DescriptorQAspectJ之间的联pd比较?
5QxDoclet
Doclet和xDoclet的工作原理,是处理源代码中的注释中的tagQ生成相应的信息。这些tag都以@开_你可以自己定义tag和对tag的处理,生成自定义的信息? Q这里提一下Apache Maven Project。Maven是一UProject Build工具。用Mavenq行理的项目,能够同时生成Javadoc和XRef。XRef是Source Code Cross Reference? JBoss利用xDoclet为EJB自动生成EJB Home和EJB Object Interface源文Ӟ和EJB Descriptor文g? 在Sourceforge.net上看C个叫做Barter的开源项目,利用xDoclet为类Ҏ(gu)生成AspectJ代码? h意,EJB Descriptor和AspectJ都是把方斚w面的Aspects集中在一处进行管理,而xDoclet的思想是处理散布在源代码中的各Utag? xDoclet在生成EJB Descriptor和AspectJ{方面的应用Q正应了中国的一句古?-分久必合Q合久必分?
6Qȝ
同时Q一些新名词Q新概念也层ZIP令hD~ؕQ无所适从。其实,很多东西都是换汤不换药,我们理解应用q些新技术的时候,要抓住本质,要破除迷信,破除MZؓ的神U感? 举个例子Q现在炒作的很热的一些概念,"Web Service"Q还?Grid Computation"Q网D)Q都是基于原有的各种技术发展出来的。媒体和技术文章不应该Zؓ地制造Q何神U感? 互联|时代的权威Q不是说出来的,而是做出来的? 另外Q围l着一些有前途的新技术,M出现大量?快速入门手?Q有些简直就是对该技术帮助文档的译Q而且Q有隑ֺ的地Ҏ(gu)有翻译出来,大家都明白的地方译得非常详,详尽C没有必要的地步。这U因为市场需求而生的应景时文Q大量地出现在技术文章领域? W者对本文的期望是Q决不迷信,决不重复。ƈ试图引入一U洁净的,毫无废话的文风。笔者期待一针见血的驳斥和批评?
摘要
Java 反射机制Z用XML-RPCQXML-based Remote Procedure Call,ZXML的远E过E调用)q程q程调用提供了一U简便又高效的实现方法,q种Ҏ(gu)隐蔽掉了一些远E过E调用过E中的复杂操作。在q篇文章里, Stephan Maier展示l你如何从反包中用一些类d装XML-RPC去调用远E接口:Proxyc,Arrayc? 和BeanInfocR这文章也要讨论q种Ҏ(gu)的多重实现和在RMIQRemote Method InvocationQ远E方法调用)中可反射Ҏ(gu)的用?br />
因ؓ它作E方法调用的一个简单协议,Z偶尔xZXML的远E过E调用(XML-RPCQ。它易于使用Q又着可以q用的实?Apache XML-RPC?br />
如果是一个小应用E序或应用程序中使用有限数量的远E过E,那么不应该趋向于正式地定义远E过E的名字和方法声明,取而代之,应该直接使用XML-RPC。甚臻I应用E序规模增大和远E接口数量增加,可发现对远E方法和数据对象必要的约定必定是一U莫名的一致。在q篇文章里,展CJava提供的所有需要去实现的定义远E接口和讉Kq程Ҏ(gu)Q过E,使用Java接口定义的过E签名,和用XML-RPC的远E过E调?q种调用把一个通信信道的两侧封装成仅仅是接口和相对的数据对象?br />
本文也展C当l定的描q远E过E和数据的Java接口构徏?JavaBean规范一_可以使用Java反射机制Q把反射机制和JavaBean整合使用可以透明地调用远E方法,L地在XML-RPC数据cd和Java数据cd之间q行转换?br />
实际中隐藏复杂性是个好的事情。无需_不是所有的复杂性能或者应该被隐藏。针对分布式计算Q这个观点已l在“分布式计算W记”有了个著名的论著。(Sun MicrosystemsQ?994.11Q。本文中展现的框架不Ҏ(gu)去隐藏分布式计算的复杂性,但这个框架帮助用者减在调用q程q程时的J琐。简单点_本文只讨论ƈ发远E过E调用,热心的读者可以自己去研究觳皆d痰饔谩?
XML-RPC可以被看作RPC跨SOAP协议的一个简化。扩展点_本文所讨论的这个简易框架必被认ؓSOAP引擎的简化版本,像Axis。本文主要以教学位目标:希望展示在目前的XML-RPC框架上层是怎样通过反射来徏立一个简化的XML-RPC引擎。这些帮助读者理解相g更复杂的其他协议的引擎的内部实现机理Q或怎样应用反射去解军_杂问题。一个RPC引擎必须在SOAP引擎可实现的环境中用,好比中间g控g不可用,那么应用E序׃能通过Web服务器发布给q大用户。Roy Miller’s “XML-RPC Java ~程”对q个作了很好的解释?br />
在本文中Q用XML-RPC的Apachelgd装这个框架。读者不需奥区知道XML-RPCQ也不需要理解Apache XML-RPC框架Q就是只有一个基本的了解都能使你理解下面的讲q。本文重Ҏ(gu)在框架内部精的q作机理Q但不涉及协议的l节?br />
版权声明QQ何获得Matrix授权的网站,转蝲时请务必保留以下作者信息和链接
作?steven_guo(作者的blog:http://blog.matrix.org.cn/page/steven_guo)
原文:http://www.matrix.org.cn/resource/article/44/44437_XML-RPC.html
关键?XML-RPC;Reflective
消除?fn)?/span>
有时候,我更喜欢非传l的~程。谈到这点,我必M你确信,我不是一个L破旧?fn)俗的hQ我不反对好的编E习(fn)惯;恰恰与此完全相反。“非传统”在q里主要表明我喜Ƣ去避免E序中到处的定义字符Ԍ而且q些代码可以在可~程的API里定义。考虑下面代码片断?br />
代码1. 调用q程q程Vector paras = new Vector();
paras.add("Herbert");
Object result = client.execute("app.PersonHome.getName", paras);
代码1展示了通过Apzche XML-RPC的实现如何调用远E过E。可以注意到Q用者需要去指导q程q程和传递到q程Ҏ(gu)中参数的名字。也必须知道q程q程q回的对象类型。除非你已经实现了一个类去验证你使用的这些名字(app.PersonHome ?getNameQ是否正,那么你局需要去查找q些名字和声明,通常q些信息保存在文本文件或帔R接口中(一个接口提供所有参数名字)。也可能攄?Javadoc中适当的地斏V大家可以发玎ͼq种U定有造成q行旉误的隐?zhn)Q因些错误只在运行时才能昄出来Q而在~译时不能显现?br />
现在Q对比考虑下面一D代码:
代码2调用q程q程Person person = ((PersonHome)Invocator.getProxy(PersonHome.class)).getPerson("Herbert");
在这Q我们调用Invocatorcȝ静态方法getProxy()L索PersonHome接口的实现。在q个接口里,可以调用getPerson()Q得到Person对象?br />
代码2相对于代?是一个较z的方式。在代码2U,使用z的定义在接口中的方法,在接口里可以把可用的Ҏ(gu)Q方法声明,q回cd都一起定义。因Z需要强制{换对象,所以也是类型安全。因Z需要额外的像Vectorcȝ构造函敎ͼ代码可读性也较好?br />
而且Q如果你使用了一个功能强大的IDEQ代码助手将|列所有可用的Ҏ(gu)和他们的实现。因此,在类型安全远E方法调用上从IDE获得支持?br />
我必L认,没有规范我们不能作Q何事。我们必d持(除非我们准备接受高昂的成本和复杂化)的一个规则就是:假设所有的数据对象都遵从JavaBean规范。简单地_对象的属性操作必通过getter/setterҎ(gu)。在我们讨论把XML-RPC数据l构转换成Java对象Ӟq个假设的重要性将昄出来?br />
把所有数据对象遵从JavaBean规约是比在XML-RPC应用E序中的规约更高U一些,因ؓ后者是一个通用规范。它也是所有Java E序员的的通用规范。在本文l尾部分Q我讨论XML-RPC的限Ӟq要其他一些有用的规范Q而且可以更好的让你理解那些限制?br />
随后的部分,一h览Invocatorcȝ实现和一个提供框枉信信道的一个本地服务器?br />
实现调用
让我们首先看看提供一个接口实现的Ҏ(gu)?br />
代码3 创徏代理public static Object getProxy(Class ifType) {
if (!ifType.isInterface()) {
throw new AssertionError("Type must be an interface");
}
return Proxy.newProxyInstance(Invocator.class.getClassLoader(),
new Class[]{ifType}, new XMLRPCInvocationHandler(ifType));
}
所有的实现逻辑隐藏在了Proxy.newProxyInstance()Ҏ(gu)中。ProxycMJava1.3开始已l是Java Reflection包的一部分。经由newProxyInstance()Ҏ(gu)Q一些列的接口可以自动被实现。当Ӟ一个proxycM知道怎样处理Ҏ(gu)调用。因此,它必L调用传送给一个合适的处理??一个实?java.lang.reflect.InvocationHandlercȝ作业中。在q里Q我已经选择取调用这个实现类 XMLRPCInvocationHandler。InvocationHandler接口定义单一的方法,如代?所C?br />
代码4 InvocationHandlerpublic interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
当一个proxy实例调用了一个方法,proxy实例传递这个方法和他的参数叨处理器cȝinvokeQ)Ҏ(gu)Q同时要识别它。让我们看看处理器的实现Q?br />
代码5 InvocationHandlerprivate static class XMLRPCInvocationHandler implements InvocationHandler {
private Class type;
public XMLRPCInvocationHandler(Class ifType) {
this.type = ifType;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
XmlRpcClient client = getClient(); // Get a reference to the client
Vector paras = null; // This will hold the list of parameters
if (args != null){
paras = new Vector();
for (int i = 0; i < args.length; i++) {
paras.add(ValueConverter.convertFromType(args[i]));
}
}
else{
paras = new Vector(); // The vector holding the parameters must not be null
}
Class retType = method.getReturnType();
Object ret = client.execute(type.getName() + '.' + method.getName(), paras);
return ValueConverter.convertToType(ret, retType);
}
}
在创E中QXMLRPCInvocationHandler是有远E接口的实现cR我们用这个类仅仅是ؓ了获得远E接口的名字和可供调用的Ҏ(gu)名。可以观察到q程Ҏ(gu)的调用L动态的Q我们既不需要在桩类里调用方法,也不需要从外部获得M接口的信息?br />
ClientcMgetClient()Ҏ(gu)获得:
代码6. 获得clientc?br />protected static XmlRpcClient getClient() throws MalformedURLException {
return new XmlRpcClient("localhost", 8080);
}
q里Q我们能使用Apache XML-RPC去获得处理远E调用的ClientcR可以看到我们返回一个Clientc而没有考虑我们所有调用的Ҏ(gu)的接口?br />
更重要的代码是在ValueConvertercM用静态方法调用。在那个Ҏ(gu)里反提供了q种方式的魅力所在。在下面部分我们分析q些代码Q?br />
XML-RPC 和Java之间的{?/span>
q部分解释XML-RPC架构的核心。这个框枉要作两g事:转换Java对对象成能被XML-RPC所理解的数据结构和一个反向的转换处理?br />
我开始展C如何把一个Java对象转换被XML-RPC理解的数据结构:
代码7. Java ?XML-RPC转换public static Object convertFromType(Object obj) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException, IntrospectionException {
if (obj == null) {
return null;
}
Class type = obj.getClass();
if (type.equals(Integer.class)
|| type.equals(Double.class)
|| type.equals(Boolean.class)
|| type.equals(String.class)
|| type.equals(Date.class)) {
return obj;
else if (type.isArray() && type.getComponentType().equals(byte.class)) {
return obj;
}
else if (type.isArray()) {
int length = Array.getLength(obj);
Vector res = new Vector();
for (int i = 0; i < length; i++) {
res.add(convertFromType(Array.get(obj, i)));
}
return res;
}
else {
Hashtable res = new Hashtable();
BeanInfo info = Introspector.getBeanInfo(type, Object.class);
PropertyDescriptor[] props = info.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
String propName = props[i].getName();
Object value = null;
value = convertFromType(props[i].getReadMethod().invoke(obj, null));
if (value != null) res.put(propName, value);
}
return res;
}
}
转换Java对对象成能被XML-RPC所理解的数据结构,需要考虑如上面代码展C的5U情况:
1. NullQ如果要转换的对象是Null|我们必须q回一个Null?br />2. 原始cdQ如果要转换的对象是原始cdQ或q些原始cȝ包装c)-- int、double、Boolean、string或dateQ?那么p回该对象卛_?br />3. base64Q如果对象是一个字节数l,那么可以认ؓq个数组是base64cd数据。只需把数l直接返回即可?br />4. ArrayQ如果对象不是一个字节数l,我们能从Java Reflection包中使用Array工具c获得数l长度。用这个长度|在一个@环中Ll的每个数据单元。把每个数据单元的数据传入ValueConverter装q入一个Vector中?br />5Q复杂类型:如果对象不是上面所叙述的类型,我们可以假定它是一个JavaBean ?在程序开始我们可以设立这么一个共同遵守的假定原则。我们把属性插入HashTable内。要讉Kq些属性,可以使用JavaBean框架自D的机Ӟ使用工具cIntrospector去获得封装在BeanInfo对象里的信息。特别,我们可以循环讉KPropertyDescriptor对象数组获得 Bean的属性。从q样一个属性描q器里,可以索到属性的名字Q这些名字也是访问HashTable的键。我们通过d性描q器获得q些键|例如属性倹{?br />
看到如此Ҏ(gu)C用JavaBean框架从Bean里提取信息。我不需要知道所惌{换的cdQ只需知道q个Bean卛_。这个假设是我们的框架完地q行其俩的先x件?br />
现在Q让我们来看看相反的转换 ?把XML-RPCl构数据转换为Java对象Q?br />
代码8. 从XML-RPC转换到Java对象public static Object convertToType(Object object, Class type) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException, IntrospectionException, InstantiationException {
if (type.equals(int.class)
|| type.equals(double.class)
|| type.equals(boolean.class)
|| type.equals(String.class)
|| type.equals(Date.class)) {
return object;
}
else if (type.isArray() && type.getComponentType().equals(byte.class)) {
return object;
}
else if (type.isArray()) {
int length = ((Vector) object).size();
Class compType = type.getComponentType();
Object res = Array.newInstance(compType, length);
for (int i = 0; i < length; i++) {
Object value = ((Vector) object).get(i);
Array.set(res, i, convertToType(value, compType));
}
return res;
}
else {
Object res = type.newInstance();
BeanInfo info = Introspector.getBeanInfo(type, Object.class);
PropertyDescriptor[] props = info.getPropertyDescriptors();
for (int i = 0; i < props.length; i++) {
String propName = props[i].getName();
if (((Hashtable) object).containsKey(propName)) {
Class propType = props[i].getPropertyType();
props[i].getWriteMethod().
invoke(res, new Object[]
{ convertToType(((Hashtable) object).get(propName), propType)});
}
}
return res;
}
}
转换成一个Javacd需要更多的了解q个我们所要{换的|我们也必M解那个D转换到这个Javacd。这个解释了在代??convertToType()Ҏ(gu)的第二个参数的存在性。知道了cdQ我们用Java的自举机Ӟ把XML-RPC数据cd转换成Javacd。下面的列表展示了完成多U{换的U定Q?br />1QNullQXML-RPC不传递空|q个限制在稍后又更详l说明。我们不需要考虑q种情况?br />2Q原始类型:如果对象是原始类型(或原始类型的包装c)- int, double, Boolean, string, ?date Q那么我们可以把对象本社q回Q作为XML-RPC可以识别的原始类型?br />3Qbase64Q如果对象是一个二q制数组Q可以认Z表一个base64cd的实例。我们可以再ơ把数组本书q回?br />4Q数l:如果对象是一个数l,但不是一个二q制数组Q我们首先要在数l类定所存项目的cd。我们可以根据对象断定类型。可以?getComponentTypeQ)。下一步,我们使用Array工具cd于给定的lgcd创徏一个新的数l。我们可以用Array工具cd@环遍历数l,讄各个域,使用ValueConveter从每个数l项中获得正的倹{在XML-RPC框架中,可以发现我们所期望的数l数据结构是一?Vector?br />5.复杂cdQ如果一个对象不是上面所q的cdQ我们假讑֮是一个JavaBeanQ依照我们的基本U定Q。再ơ,我们使用 Introspector查找Bean的属性描q器Q用属性描q器讄实际的属性通过讉KwriteQ)Ҏ(gu)。需要注意的是,框架l了我们一个储存在 HashTable的属性。当Ӟ属性类型可能是复杂cdQ我们必M用ValueConverter去获得正的Java对象?br />
理解了数据{换的U定Q我们可以看看处理服务是如何实现的?br />
实现服务处理
已经解释了一个远E服务是如何被调用和在XML-RPC和Java对象之间转换时包含什么。我了解了所qh不解的问题的剩下部分Q在一个服务端如何处理h
q里是一个ؓq篇文章所实现单的服务器实C码:
代码9. 服务?br />public class Server {
private WebServer webserver = null;
public void start() {
webserver = new WebServer(8080);
webserver.addHandler
(PersonHome.class.getName(),
new Handler(PersonHome.class,
new PersonHomeImpl()));
webserver.setParanoid(false);
webserver.start();
}
public void stop() {
webserver.shutdown();
webserver = null;
}
private static class Handler implements XmlRpcHandler {
private Object instance;
private Class type;
public Handler(Class ifType, Object impl) {
if (!ifType.isInterface()) {
throw new AssertionError("Type must be an interface");
}
if (!ifType.isAssignableFrom(impl.getClass())) {
throw new AssertionError("Handler must implement interface");
}
this.type = ifType;
this.instance = impl;
}
public Object execute(String method, Vector arguments) throws Exception {
String mName = method.substring(method.lastIndexOf('.') + 1);
Method[] methods = type.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(mName)){
try {
Object[] args = new Object[arguments.size()];
for (int j = 0; j < args.length; j++) {
args[j] = ValueConverter.convertToType
(arguments.get(j), methods[i].getParameterTypes()[j]);
}
return ValueConverter.convertFromType(methods[i].invoke(instance,args));
}
catch (Exception e) {
if (e.getCause() instanceof XmlRpcException){
throw (XmlRpcException)e.getCause();
}
else{
throw new XmlRpcException(-1, e.getMessage());
}
}
}
}
throw new NoSuchMethodException(mName);
}
}
public static void main(String[] args){
Server server = new Server();
System.out.println("Starting server...");
server.start();
try {
Thread.sleep(30000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Stopping server...");
server.stop();
}
}
关键cLApacheXML-RPC包中的WebServercR黑体字代码展示了我们主要的需求:我们必须注册一个服务句柄。这个句柄经?XmlRpcHandler接口定义Q这个接口就像代理机制中InvocationHandler接口Q有个方法对应方法调用的委派。在q里Q叫?executeQ)Ҏ(gu)Q有着和InvocationHandler接口相同的实现精髓。最大的不同是,我们L册联pL口和他的实现的一个句柄,不需要提供服务接口的实现Q一桩程序Ş式)。然而,在服务器里,我们需要定义那块代码负责处理到来的h。最后,可以看到Q通过循环遍历接口Ҏ(gu)调用服务Ҏ(gu)Q我们用普通的方式dC争取的方法。这里,我们不依靠标准的自DJavaBeanQ因为服务方法不是仅仅有setter和getterҎ(gu)?br />
后记
在这个部分,主要讨论在先前讨Z所处想的以下问题。我看到XML-RPC协议和这个文章所q的框架的局限性,但我也考虑q些方式的一定先q性?br />
局限?/b>
XML-RPC是一个简单协议,很明昑֮不能Z表面向对象系l特色的q程q程调用实现可编EAPI。特别地Q这样一个API不支持以下的一些实玎ͼ
•l承QXML-RPC没能携带充的信息决定那U类型可以沿着l承的层U结构传递。在q程q程调用和对象传递参C都存在这U情c因此,x所有的cMؓfinalcd是一个好的编E习(fn)?br />& #8226;重蝲QXML-RPC不允许方法重载。依据这条规则,可以重蝲那些有原始类型声明的Ҏ(gu)Q但实际q个选择是不能满的。当我们需要从Ҏ(gu)的声明去推断l构cd是,我们不允?dng)R载。我仅仅允许同一个方法有不同参数个数q种情况除向Q因为所有的Ҏ(gu)在远E过E调用期间是可用的。我么有以这个方式实玎ͼ而是使用了不同的Ҏ(gu)名。注意:Web服务在这斚w也不提供更多的灵zL。即使灵zL个那个靠的框架Axis也对重蝲有限制?br />• 集合QXML-RPC不允许结合类型出现。和重蝲相同的原因,我们必须从被l定的集合类型推断集合中目的类型,q是不可能的。(JDK1.5之前版本Q。取而代之,我们使用数组Q可以查询组件类型。虽ӞW(xu)eb服务在远E方法调用方面比XML-RPC更强大,但更多的意见是反对用集合类型。参?“Web Services Programming Tips and Tricks: Use Collection Types with SOAP and JAX-RPC?Russell Butek and Richard Scheuerle, JrQ?002.4Q?br />& #8226;Null|XML-RPC不支持空倹{这可能是这个协议中最令h尬的瑕疵,因ؓq个意味着在数l里不能保存I倹{在XML-RPC有过关于Null值得提议Q但大多数的实现不支持Null倹{无需去说Q如果通信q接的两边均是与JavaE序交互Q通过手动方式在消息里加入一些元数据可以克服q个~点。而且Q这意味着滥用协议Q不是一个好的徏议?br />
序列化控?/b>
序列化在下面的场景中出现。特别地Q文中所提议的框架在发现属性去自动序列化。有Ӟ你可以在序列化中L一些属性值得传递?br />
设想一个Person对象引用多个不同cd的Address对象。特别地Q这些Address对象之一是邮件地址Q而其他对象在其他的上下文环境中有意义。你可能希望通过PersoncȝPerson.getMailingAddress()Ҏ(gu)可以获得邮g地址Q这样增Z的PersoncR标准的自D机制看到的是一个新的属?mailingAddressQ这个属性在序列化的时候可使用众多的地址列表中初始化。在q种情况下,一个对应的 Person.setMailingAddress()Ҏ(gu)执行这L(fng)操作Q不地址序列化的序如何Q反序列化将q回一个对应的AddresscR当Ӟ你的Ҏ(gu)应该如何序列化是无关竟要的,但即使你写的Ҏ(gu)是正的Q在其程序接口编E的有些人可能不清楚你想的是什么,增加了发生问题的可能性。在M情况下,你应该容忍两ơ序列化邮g地址?br />
但是Q这里有个帮助,Introspector可能告诉你,要查找一个类的属性时要去使用反射Q而要使用l定的信息。这些信息可以在BeanInfoc里扑ֈQ如果你的类名是MyClassQ那么你的BeanInfocd该叫?MyClassBeanInfo。BeanInfocd该在MyClasscȝ相同包里Q或者在BeanInfo的搜索\径里。搜索\径可以在 Introspector里设|。作Z个BeanInfoc,应该提供如下的属性:
代码10. BeanInfo 例子 1public class MyClassBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
BeanInfo superInfo = Introspector.getBeanInfo(MyClass.class.getSuperclass());
List list = new ArrayList();
for (int i = 0; i < superInfo.getPropertyDescriptors().length; i++) {
list.add(superInfo.getPropertyDescriptors()[i]);
}
//
list.add(new PropertyDescriptor("myProperty", MyClass.class));
//
return (PropertyDescriptor[])list.toArray(new PropertyDescriptor[list.size()]);
} catch (IntrospectionException e) {
return null;
}
}
}
getPropertyDescriptors()Ҏ(gu)必须q回属性描q器所代表的属性。首先,在你的超c里增加q个属性,增加q个你希望发布的q个属性到你的c里Q如_体部分昄?br />
q是一个严重的~陷Q上面的提议包含了很多固定代码,而这些是~程的时候应量避免的。正好,增加的这些被序列化的属性是比罗列显C些属性能更好的运作。当Ӟ一个方式是使用Introspector通过反射机制调用Introspector.getBeanInfo(MyClass.class, Introspector.IGNORE_ALL_BEANINFO)获得所有的属性。你能增加一个过滤器在你的返回结果时。这U方式看h象如下展C:
代码11. BeanInfo例子 2public class MyClassBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
BeanInfo infoByReflection = Introspector.getBeanInfo(MyClass.class,
Introspector.IGNORE_ALL_BEANINFO); PropetyDescriptor allProperies =
infoByReflection.getPropertyDescriptors();
return filter(allProperies);
} catch (IntrospectionException e) {
return null;
}
}
protected PropertyDescriptor[] filter(PropertyDescriptor[] props){
// Remove properties which must not be exposed
}
}
一个好的方法是使用接口定义语言QIDLQ构造一个框Ӟq样允许你手动去产生Bean和扩阿占属性和Ҏ(gu)。这个生器负责提供通过IDLqo属性的BeanInfocRl看一个这样实现的例子?br />
增加?/b>
当我们隐藏了实际的传送机Ӟ很容易在收到和发送的消息中增加信息。假若我们需要在每个q程Ҏ(gu)调用中传送Session信息。这个信息就可以在调用者那里增加上Q处理者把它作为第一个参敎ͼ包装所有必需的信息成一个适当德BeanQ。在其他的调用里Q这些信息能从参数Vector里删除,在方法调用里分别处理。在文后的资源引用中扑ֈ更多的可用代码,可以更好的用这个框架?br />
其他语言
如果你正视弱点,它可能变成支炏VXML-RPC的简易导致了上面所描述的限制。然而,XML-RPC已有了多U语a的实玎ͼ例如RubyQPythonQ或函数性怨言 Haskell。不是所有的语言支持面向对象pȝ中所支持的承,不是所有的语言支持重蝲。有些语aQ例如HaskellQ有灉|的列表类型,从Java 语言的角度看Q它的这U类型介于数l和列表之间。因此,XML-RPC内在的限制它适宜于跨语言通信?br />
当选择XML-RPC作ؓ跨越 Java和其他语a的桥梁时Q你仍可以用这个框Ӟ但你仅仅能在与Java通信的一侧用。而且Q可以扩展这个框架去覆盖其他语言。例如,你可以用其他语言重写q个框架Q增加对Java接口和数据对象与其他语言对应对象之间转换的支持。另外的方式Q我已在上面暗示q,是写一个编译器把IDL的适当形式转换到其他多U语a的Ş式,Java是其中之一。在下面Q我l出一个例子?br />
无需M诉说Q这U方式扩展文中所提的框架是比框架更手的事Q但它们协同运行?br />
删除或替换XML-RPC实现
一个有效率的系l更愿意避免使用XML-RPC中间框架Q反而通过把XML-RPC的XML数据直接转换成适当的对象。你可能认ؓQ在潜藏在后面的接口里的抽象Ҏ(gu)调用能用多种XML-RPC实现。当我认Z需d什么事Ӟ那就不能实现q些功能。再者,你是被吸引,而去适应q个框架Q以满你的需要?br />
q程Ҏ(gu)调用
伴随着J2SE1.5QRMI用代理机制。将不再需要用RMI~译器生桩c(除非你要与一些旧的系l协作)。因此,如果不能夹在一个桩c,那么q程对象的桩p认ؓ是一个java.lang.reflect.Proxy实例?br />
接口定义语言
d要查看大两Bean实现规约和XML-RPC限制的麻烦,正如上面所qͼ是避免写接口和Bean。取而代之,适用适当的IDLd建它们。这L(fng)语言看v来就像下面的代码Q?br />
代码12 . IDLmodule partner;
exception NoPartnerException < 123 : "No partner found" >;
struct Partner {
int id;
string name;
int age;
date birthday;
};
interface PartnerHome {
Partner getPartner(int id) throws NoPartnerException;
Partner[] findPartner(string name, date bday) throws NoPartnerException;
};
ZIDL~写一个解析器和代码生器Q得交叉语a通信更加易?br />
ȝ
在这片文章里Q展CZ如何使用Java反射机制透明地包装经由XML-RPC实现q程Ҏ(gu)调用。已l重点展CZ已经整合在Proxyc,Arraycd IntrospectorcM的实现机制。基于这些工L(fng)Q一个可适用于多U用途的q程Ҏ(gu)调用的中间g框架已经实构造出来?br />
关于作?/b>
Stephan Maier 拥有数学博士学位Q超q五q的软g开发经验。他也是一位职业生涯中艺术U的导师。除了编E,他喜Ƣ唱歌和q动。现在,他在~写一个编译器Q这个编译器可以单地转换IDL定义到其他适当的数据结构和其他语言的远E接口,例如JavaQRubyQ或PythonQ在q些接口里潜在的调用协议都是XML-RPC?br />
参考资?/b>
Matrix:http://www.matrix.org.cn
Javaworld:http://www.Javaworld.com
"Web Services Programming Tips and Tricks: Roundtrip Issues in Java Coding Conventions," Russell Butek, Richard Scheuerle, Jr. (developerWorks, April 2004):
http://www-106.ibm.com/developerworks/xml/library/ws-tip-roundtrip2.html
"XML-RPC in Java Programming," Roy Miller (developerWorks, January 2004):
http://www-106.ibm.com/developerworks/library/j-xmlrpc.html
"Web Services Programming Tips and Tricks: Use Collection Types with SOAP and JAX-RPC" by Andre Tost and Tony Cowan (developerWorks, May 2004):
http://www-106.ibm.com/developerworks/library/ws-tip-coding.html?ca=dnx-420
"A Note on Distributed Computing," Jim Waldo, Geoff Wyant, Ann Wollrath, and Sam Kendall (Sun 1994):
http://research.sun.com/techrep/1994/smli_tr-94-29.pdf
XML-RPC homepage:
http://www.xmlrpc.com
Apache XML-RPC implementation:
http://ws.apache.org/xmlrpc
]]>
]]>|上出现了很多讲解AspectJ的资料,但大多是从讲解AspectJ语法开?本文从另一个角度讲解AspectJQ作者着重介l了AspectJ的设计思\和运行原理?
Aspect Oriented Programming (AOP)是近来一个比较热门的话题?
http://www.parc.com/research/csl/projects/aspectj/
http://aosd.net/
本节单介lAOP的概念,解释我们Z么需要AOP?
Code 2.1 Bank.java
class Bank{
public float deposit(AccountInfo account, float money){
// 增加account账户的钱敎ͼq回账户里当前的钱数
}
public float withdraw(AccountInfo account, float money){
// 减少account账户的钱敎ͼq回取出的钱?
}
};
Code 2.2 Bank.java
class Bank{
public float deposit(AccountInfo account, float money){
// 验证account是否为合法用?
// 增加account账户的钱敎ͼq回账户里当前的钱数
}
public float withdraw(AccountInfo account, float money){
// 验证account是否为合法用?
// 减少account账户的钱敎ͼq回取出的钱?
}
};
Code 2.3 Bank.java
class Bank{
public float deposit(AccountInfo account, float money){
// 验证account是否为合法用?
// Begin Transaction
// 增加account账户的钱敎ͼq回账户里当前的钱数
// End Transaction
}
public float withdraw(AccountInfo account, float money){
// 验证account是否为合法用?
// Begin Transaction
// 减少account账户的钱敎ͼq回取出的钱?
// End Transaction
}
};
Code 2.4 Base.java
abstract class Base{
public float importantMethod(AccountInfo account, float money){
// 验证account是否为合法用?
// Begin Transaction
float result = yourBusiness(account, money)
// End Transaction
return result;
}
protected abstract float yourBusiness(AccountInfo account, float money);
};
Code 2.5 BankDeposit.java
class BankDeposit extends Base{
protected float yourBusiness(AccountInfo account, float money){
// 增加account账户的钱敎ͼq回账户里当前的钱数
}
};
Code 2.6 BankWithdraw.java
class BankWithdraw extends Base{
protected float yourBusiness(AccountInfo account, float money){
// 减少account账户的钱敎ͼq回取出的钱?
}
};
如果我们使用EJB实现上面的例子,Bankcd以作Z个Stateless Session Bean实现?
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>Bank</ejb-name>
?
<ejb-class>example.Bank</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<security-role-ref>
<role-name>bank-account</role-name>
</security-role-ref>
</session>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<role-name>bank-account</role-name>
</security-role>
<method-permission>
<role-name>employee</role-name>
<method>
<ejb-name>Bank</ejb-name>
<method-name>deposit</method-name>
</method>
<method>
<ejb-name>Bank</ejb-name>
<method-name>withdraw</method-name>
</method>
</method-permission>
<container-transaction>
<method>
<ejb-name>Bank</ejb-name>
<method-name>deposit</method-name>
</method>
<method>
<ejb-name>Bank</ejb-name>
<method-name>withdraw</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
q一节我们来看看AspectJ如何实现上例中的Separation of Aspects?
Code 4.1 AuthAspect.java
aspect AuthAspect{
pointcut bankMethods() : execution (* Bank.deposit(?) || execution (* Bank. withdraw (?);
Object around(): bankMethods(){
// 验证account是否为合法用?
return proceed();
}
};
Code 4.2 TransactionAspect.java
aspect TransactionAspect{
pointcut bankMethods() : execution(* Bank.deposit(?) || execution (* Bank. withdraw (?);
Object around(): bankMethods(){
// Begin Transaction
Object result = proceed();
// End Transaction
return result;
}
};
我们知道QDoclet用来生成JavadocQxDoclet是Doclet的扩展,不仅仅能生成JavadocQ还能够生成源代码和配置信息{?
开源项目的出现Q打破了软g技术领域的众多壁垒Q推动Y件技术进E的日新月异?
]]>
var checkSubmitFlg = false;
function checkSubmit() {
if (checkSubmitFlg == true) {
return false;
}
checkSubmitFlg = true;
return true;
}
document.ondblclick = function docondblclick() {
window.event.returnValue = false;
}
document.onclick = function doconclick() {
if (checkSubmitFlg) {
window.event.returnValue = false;
}
}
</script>
<html:form action="myAction.do" method="post" onsubmit="return checkSubmit();">
2 q是javascriptQ将提交按钮或者image|ؓdisable
<html:form action="myAction.do" method="post" onsubmit="getElById('submitInput').disabled = true; return true;">
<html:image styleId="submitInput" src="images/ok_b.gif" border="0" />
</html:form>
3 利用struts的同步o牌机制
利用同步令牌QTokenQ机制来解决Web应用中重复提交的问题QStruts也给Z一个参考实现?
基本原理Q
服务器端在处理到辄h之前Q会请求中包含的o牌g保存在当前用户会话中的o牌D行比较,看是否匹配。在处理完该h后,且在{复发送给客户端之前,会产生一个新的o牌,该o牌除传给客户端以外,也会用户会话中保存的旧的o牌进行替换。这样如果用户回退到刚才的提交面q再ơ提交的话,客户端传q来的o牌就和服务器端的令牌不一_从而有效地防止了重复提交的发生。
if (isTokenValid(request, true)) {
// your code here
return mapping.findForward("success");
} else {
saveToken(request);
return mapping.findForward("submitagain");
}
StrutsҎ(gu)用户会话ID和当前系l时间来生成一个唯一Q对于每个会话)令牌的,具体实现可以参考TokenProcessorcM的generateToken()Ҏ(gu)。
1. //验证事务控制令牌,<html:form >会自动根据session中标识生成一个隐含input代表令牌Q防止两ơ提?
2. 在action中:
//<input type="hidden" name="org.apache.struts.taglib.html.TOKEN"
// value="6aa35341f25184fd996c4c918255c3ae">
if (!isTokenValid(request))
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError("error.transaction.token"));
resetToken(request); //删除session中的令牌
3. action有这L(fng)一个方法生成o牌
protected String generateToken(HttpServletRequest request) {
HttpSession session = request.getSession();
try {
byte id[] = session.getId().getBytes();
byte now[] =
new Long(System.currentTimeMillis()).toString().getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(id);
md.update(now);
return (toHex(md.digest()));
} catch (IllegalStateException e) {
return (null);
} catch (NoSuchAlgorithmException e) {
return (null);
}
}
Validator是基于JavaScript技术的伪静态类和对象的自定义属性,可以对网中的表单项输入q行相应的验证,允许同一面中同旉证多个表单,熟?zhn)接口之后也可以对特定的表单项甚至仅仅是某个字W串q行验证。因为是伪静态类Q所以在调用时不需要实例化Q直接以"cd+.语法+属性或Ҏ(gu)?来调用。此外,Validatorq提?U不同的错误提示模式Q以满不同的需要?
Validator目前可实现的验证cd有:
[JavaScript] ?br />Validator目前可实现的验证cd有:
1.是否为空Q?br />2.中文字符Q?br />3.双字节字W?br />4.英文Q?br />5.数字Q?br />6.整数Q?br />7.实数Q?br />8.Email地址Q?br />9.使用HTTP协议的网址Q?br />10.?sh)话L(fng)Q?br />11.货币Q?br />12.手机L(fng)Q?br />13.邮政~码Q?br />14.w䆾证号?1.05增强)Q?br />15.QQL(fng)Q?br />16.日期Q?br />17.W合安全规则的密码;
18.某项的重复|
19.两数的关pL较;
20.判断输入值是否在(n, m)区间Q?br />21.输入字符长度限制(可按字节比较)Q?br />22.对于h相同名称的单选按钮的选中判断Q?br />23.限制h相同名称的多选按钮的选中数目Q?br />24.自定义的正则表达式验证;
25.文g上传格式qo(1.04)
q行环境(客户?Q?
在Windows Server 2003下用IE6.0+SP1和Mozilla Firefox 1.0试通过Q?br />在Lunix RedHat 9下的Netscape试通过Q?
对于客户端的表单验证Q这个基于JavaScript~写的Validator基本上都可以满Q具体可以下载CHM文gQValidator.CHM下蝲
<title>表单验证c?Validator v1.05</title>
<style>
body,td{font:normal 12px Verdana;color:#333333}
input,textarea,select,td{font:normal 12px Verdana;color:#333333;border:1px solid #999999;background:#ffffff}
table{border-collapse:collapse;}
td{padding:3px}
input{height:20;}
textarea{width:80%;height:50px;overflow:auto;}
form{display:inline}
</style>
<table align="center">
<form name="theForm" id="demo" method="get" onSubmit="return Validator.Validate(this,2)">
<tr>
<td>w䆾证号Q?lt;/td><td><input name="Card" dataType="IdCard" msg="w䆾证号错误"></td>
</tr>
<tr>
<td>真实姓名Q?lt;/td><td><input name="Name" dataType="Chinese" msg="真实姓名只允怸?></td>
</tr>
<tr>
<td>IDQ?lt;/td><td><input name="username" dataType="Username" msg="ID名不W合规定"></td>
</tr>
<tr>
<td>英文名:</td><td><input name="Nick" dataType="English" require="false" msg="英文名只允许英文字母"></td>
</tr>
<tr>
<td>主页Q?lt;/td><td><input name="Homepage" require="false" dataType="Url" msg="非法的Url"></td>
</tr>
<tr>
<td>密码Q?lt;/td><td><input name="Password" dataType="SafeString" msg="密码不符合安全规? type="password"></td>
</tr>
<tr>
<td>重复Q?lt;/td><td><input name="Repeat" dataType="Repeat" to="Password" msg="两次输入的密码不一? type="password"></td>
</tr>
<tr>
<td>信箱Q?lt;/td><td><input name="Email" dataType="Email" msg="信箱格式不正?></td>
</tr>
<tr>
<td>信箱Q?lt;/td><td><input name="Email" dataType="Repeat" to="Email" msg="两次输入的信׃一?></td>
</tr>
<tr>
<td>QQQ?lt;/td><td><input name="QQ" require="false" dataType="QQ" msg="QQL(fng)不存?></td>
</tr>
<tr>
<td>w䆾证:</td><td><input name="Card" dataType="IdCard" msg="w䆾证号码不正确"></td>
</tr>
<tr>
<td>q龄Q?lt;/td><td><input name="Year" dataType="Range" msg="q龄必须?8~28之间" min="18" max="28"></td>
</tr>
<tr>
<td>q龄1Q?lt;/td><td><input name="Year1" require="false" dataType="Compare" msg="q龄必须?8以上" to="18" operator="GreaterThanEqual"></td>
</tr>
<tr>
<td>?sh)话Q?lt;/td><td><input name="Phone" require="false" dataType="Phone" msg="?sh)话L(fng)不正?></td>
</tr>
<tr>
<td>手机Q?lt;/td><td><input name="Mobile" require="false" dataType="Mobile" msg="手机L(fng)不正?></td>
</tr>
<tr>
<td>生日Q?lt;/td><td><input name="Birthday" dataType="Date" format="ymd" msg="生日日期不存?></td>
</tr>
<tr>
<td>邮政~码Q?lt;/td><td><input name="Zip" dataType="Custom" regexp="^[1-9]\d{5}$" msg="邮政~码不存?></td>
</tr>
<tr>
<td>邮政~码Q?lt;/td><td><input name="Zip1" dataType="Zip" msg="邮政~码不存?></td>
</tr>
<tr>
<td>操作pȝQ?lt;/td><td><select name="Operation" dataType="Require" msg="未选择所用操作系l? ><option value="">选择(zhn)所用的操作pȝ</option><option value="Win98">Win98</option><option value="Win2k">Win2k</option><option value="WinXP">WinXP</option></select></td>
</tr>
<tr>
<td>所在省份:</td><td>q东<input name="Province" value="1" type="radio">陕西<input name="Province" value="2" type="radio">江<input name="Province" value="3" type="radio">江西<input name="Province" value="4" type="radio" dataType="Group" msg="必须选定一个省? ></td>
</tr>
<tr>
<td>爱好Q?lt;/td><td>q动<input name="Favorite" value="1" type="checkbox">上网<input name="Favorite" value="2" type="checkbox">听音?lt;input name="Favorite" value="3" type="checkbox">看书<input name="Favorite" value="4" type="checkbox"" dataType="Group" min="2" max="3" msg="必须选择2~3U爱?></td>
</tr>
<td>自我介绍Q?lt;/td><td><textarea name="Description" dataType="Limit" max="10" msg="自我介绍内容必须?0个字之内">中文是一个字</textarea></td>
</tr>
<td>自传Q?lt;/td><td><textarea name="History" dataType="LimitB" min="3" max="10" msg="自传内容必须在[3,10]个字节之?>中文是两个字节t</textarea></td>
</tr>
<tr>
<td>相片上传Q?lt;/td><td><input name="up" dataType="Filter" msg="非法的文件格? type="file" accept="jpg, gif, png"></td>
</tr>
<tr>
<td colspan="2"><input name="Submit" type="submit" value="定提交"><input onClick="Validator.Validate(document.getElementById('demo'))" value="验模?" type="button"><input onClick="Validator.Validate(document.getElementById('demo'),2)" value="验模?" type="button"><input onClick="Validator.Validate(document.getElementById('demo'),3)" value="验模?" type="button"></td>
</tr>
</form>
</table>
<script>
/*************************************************
Validator v1.05
code by 我佛(jng)׃h
wfsr@msn.com
*************************************************/
Validator = {
Require : /.+/,
Email : /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
Phone : /^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/,
Mobile : /^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$/,
Url : /^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/,
IdCard : "this.IsIdCard(value)",
Currency : /^\d+(\.\d+)?$/,
Number : /^\d+$/,
Zip : /^[1-9]\d{5}$/,
QQ : /^[1-9]\d{4,8}$/,
Integer : /^[-\+]?\d+$/,
Double : /^[-\+]?\d+(\.\d+)?$/,
English : /^[A-Za-z]+$/,
Chinese : /^[\u0391-\uFFE5]+$/,
Username : /^[a-z]\w{3,}$/i,
UnSafe : /^(([A-Z]*|[a-z]*|\d*|[-_\~!@#\$%\^&\*\.\(\)\[\]\{\}<>\?\\\/\'\"]*)|.{0,5})$|\s/,
IsSafe : function(str){return !this.UnSafe.test(str);},
SafeString : "this.IsSafe(value)",
Filter : "this.DoFilter(value, getAttribute('accept'))",
Limit : "this.limit(value.length,getAttribute('min'), getAttribute('max'))",
LimitB : "this.limit(this.LenB(value), getAttribute('min'), getAttribute('max'))",
Date : "this.IsDate(value, getAttribute('min'), getAttribute('format'))",
Repeat : "value == document.getElementsByName(getAttribute('to'))[0].value",
Range : "getAttribute('min') < (value|0) && (value|0) < getAttribute('max')",
Compare : "this.compare(value,getAttribute('operator'),getAttribute('to'))",
Custom : "this.Exec(value, getAttribute('regexp'))",
Group : "this.MustChecked(getAttribute('name'), getAttribute('min'), getAttribute('max'))",
ErrorItem : [document.forms[0]],
ErrorMessage : ["以下原因D提交p|Q\t\t\t\t"],
Validate : function(theForm, mode){
var obj = theForm || event.srcElement;
var count = obj.elements.length;
this.ErrorMessage.length = 1;
this.ErrorItem.length = 1;
this.ErrorItem[0] = obj;
for(var i=0;i<count;i++){
with(obj.elements[i]){
var _dataType = getAttribute("dataType");
if(typeof(_dataType) == "object" || typeof(this[_dataType]) == "undefined") continue;
this.ClearState(obj.elements[i]);
if(getAttribute("require") == "false" && value == "") continue;
switch(_dataType){
case "IdCard" :
case "Date" :
case "Repeat" :
case "Range" :
case "Compare" :
case "Custom" :
case "Group" :
case "Limit" :
case "LimitB" :
case "SafeString" :
case "Filter" :
if(!eval(this[_dataType])) {
this.AddError(i, getAttribute("msg"));
}
break;
default :
if(!this[_dataType].test(value)){
this.AddError(i, getAttribute("msg"));
}
break;
}
}
}
if(this.ErrorMessage.length > 1){
mode = mode || 1;
var errCount = this.ErrorItem.length;
switch(mode){
case 2 :
for(var i=1;i<errCount;i++)
this.ErrorItem[i].style.color = "red";
case 1 :
alert(this.ErrorMessage.join("\n"));
this.ErrorItem[1].focus();
break;
case 3 :
for(var i=1;i<errCount;i++){
try{
var span = document.createElement("SPAN");
span.id = "__ErrorMessagePanel";
span.style.color = "red";
this.ErrorItem[i].parentNode.appendChild(span);
span.innerHTML = this.ErrorMessage[i].replace(/\d+:/,"*");
}
catch(e){alert(e.description);}
}
this.ErrorItem[1].focus();
break;
default :
alert(this.ErrorMessage.join("\n"));
break;
}
return false;
}
return true;
},
limit : function(len,min, max){
min = min || 0;
max = max || Number.MAX_VALUE;
return min <= len && len <= max;
},
LenB : function(str){
return str.replace(/[^\x00-\xff]/g,"**").length;
},
ClearState : function(elem){
with(elem){
if(style.color == "red")
style.color = "";
var lastNode = parentNode.childNodes[parentNode.childNodes.length-1];
if(lastNode.id == "__ErrorMessagePanel")
parentNode.removeChild(lastNode);
}
},
AddError : function(index, str){
this.ErrorItem[this.ErrorItem.length] = this.ErrorItem[0].elements[index];
this.ErrorMessage[this.ErrorMessage.length] = this.ErrorMessage.length + ":" + str;
},
Exec : function(op, reg){
return new RegExp(reg,"g").test(op);
},
compare : function(op1,operator,op2){
switch (operator) {
case "NotEqual":
return (op1 != op2);
case "GreaterThan":
return (op1 > op2);
case "GreaterThanEqual":
return (op1 >= op2);
case "LessThan":
return (op1 < op2);
case "LessThanEqual":
return (op1 <= op2);
default:
return (op1 == op2);
}
},
MustChecked : function(name, min, max){
var groups = document.getElementsByName(name);
var hasChecked = 0;
min = min || 1;
max = max || groups.length;
for(var i=groups.length-1;i>=0;i--)
if(groups[i].checked) hasChecked++;
return min <= hasChecked && hasChecked <= max;
},
DoFilter : function(input, filter){
return new RegExp("^.+\.(?=EXT)(EXT)$".replace(/EXT/g, filter.split(/\s*,\s*/).join("|")), "gi").test(input);
},
IsIdCard : function(number){
var date, Ai;
var verify = "10x98765432";
var Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
var area = ['','','','','','','','','','','','北京','天|','沛_','p','内蒙?,'','','','','','辽宁','吉林','黑龙?,'','','','','','','','上v','江苏','江','安微','徏','江西','׃','','','','沛_','湖北','湖南','q东','q西','南','','','','重庆','四川','贵州','云南','西藏','','','','','','','陕西','甘肃','青v','宁夏','新疆','','','','','','台湾','','','','','','','','','','香港','澳门','','','','','','','','','国外'];
var re = number.match(/^(\d{2})\d{4}(((\d{2})(\d{2})(\d{2})(\d{3}))|((\d{4})(\d{2})(\d{2})(\d{3}[x\d])))$/i);
if(re == null) return false;
if(re[1] >= area.length || area[re[1]] == "") return false;
if(re[2].length == 12){
Ai = number.substr(0, 17);
date = [re[9], re[10], re[11]].join("-");
}
else{
Ai = number.substr(0, 6) + "19" + number.substr(6);
date = ["19" + re[4], re[5], re[6]].join("-");
}
if(!this.IsDate(date, "ymd")) return false;
var sum = 0;
for(var i = 0;i<=16;i++){
sum += Ai.charAt(i) * Wi[i];
}
Ai += verify.charAt(sum%11);
return (number.length ==15 || number.length == 18 && number == Ai);
},
IsDate : function(op, formatString){
formatString = formatString || "ymd";
var m, year, month, day;
switch(formatString){
case "ymd" :
m = op.match(new RegExp("^((\\d{4})|(\\d{2}))([-./])(\\d{1,2})\\4(\\d{1,2})$"));
if(m == null ) return false;
day = m[6];
month = m[5]*1;
year = (m[2].length == 4) ? m[2] : GetFullYear(parseInt(m[3], 10));
break;
case "dmy" :
m = op.match(new RegExp("^(\\d{1,2})([-./])(\\d{1,2})\\2((\\d{4})|(\\d{2}))$"));
if(m == null ) return false;
day = m[1];
month = m[3]*1;
year = (m[5].length == 4) ? m[5] : GetFullYear(parseInt(m[6], 10));
break;
default :
break;
}
if(!parseInt(month)) return false;
month = month==0 ?12:month;
var date = new Date(year, month-1, day);
return (typeof(date) == "object" && year == date.getFullYear() && month == (date.getMonth()+1) && day == date.getDate());
function GetFullYear(y){return ((y<30 ? "20" : "19") + y)|0;}
}
}
</script>
PowerBand 是一个IE的插?同时也支持MyIE2/Maxthon)。提供了对HTML动态分析,跟踪Q编辑的功能。能够方便快L(fng)分析HTML面的结构,有助于网设计h?|站开发h员调试,分析晦ӆ的HTML代码。这是我最早用的HTML开发辅助工P现在?.1版了Q支持ASP.NET ViewState的解码,支持DebugViewQ类gVC中的WatchQ通过q个功能能够昄面中元素对象的详细内容。同Ӟ PowerBand也支持即时脚本交互功能?/p>
下蝲HttpWatch3.2.0.63< /A> 下蝲注册?/a>< /p>
W三个插件还是基于IE下的Q不q这个可?a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=e59c3964-672d -4511-bb3e-2d5e1db91038&displaylang=en">微Y发布的Web开发IE工具?lt; /A>Q实现的功能如下Q?/a>3、IE Developer Toolbar
4、ViewPage
< p>ViewPage是MyIE2/Maxthon览器插Ӟq个插g不算是纯_的开发辅助,它看h更像是资源提取,可以方便的提取网中的源代码Q包括所有框架的源代码,提取囑փ、Flash、媒体文件、链接,我最ƣ赏的就是能方便的查看页面中包含的所有js文g源代码和所有css源代码?
Firefox下的开发辅助工具和插g非常多,先说Firefox自带的吧?/p>
Firefox自带的开发辅助功能虽然已很丰富,但让我更加垂涎的是丰富的开发类插g
POJO ?PO?概念 |
作者:robbin (MSN:robbin_fan AT hotmail DOT com)整理人:smallduzi 版权声明Q本文严{载,如有转蝲hQ请和作者联p?/STRONG> POJO = pure old java object or plain ordinary java object or what ever. PO = persisent object 持久对象 是说在一些Object/Relation Mapping工具中,能够做到l护数据库表记录的persisent object完全是一个符合Java Bean规范的纯Java对象Q没有增加别的属性和Ҏ(gu)。全都是q样子的Q?/P> public class User { private long id; private String name; public void setId(long id) { this.id = id; } public void setName(String name) { this.name=name; } public long getId() { return id; } public String getName() { return name; } } 首先要区别持久对象和POJO?/P> 持久对象实际上必d应数据库中的entityQ所以和POJO有所区别。比如说POJO是由new创徏Q由GC回收。但是持久对象是insert数据库创建,由数据库delete删除的。基本上持久对象生命周期和数据库密切相关。另外持久对象往往只能存在一个数据库Connection之中QConnnection关闭以后Q持久对象就不存在了Q而POJO只要不被GC回收QL存在的?/P> ׃存在诸多差别Q因此持久对象PO(Persistent Object)在代码上肯定和POJO不同Qv码PO相对于POJO会增加一些用来管理数据库entity状态的属性和Ҏ(gu)。而ORMq求的目标就是要PO在用上量和POJO一_对于E序员来_他们可以把PO当做POJO来用Q而感觉不到PO的存在?/P> JDO的实现方法是q样的: 1、编写POJO 2、编译POJO 3、用JDO的一个专门工P叫做EnhancerQ一般是一个命令行E序Q手工运行,或者在ant脚本里面q行Q对POJO的class文g处理一下,把POJO替换成同名的PO?/P> 4、在q行期运行的实际上是POQ而不是POJO?/P> 该方法有点类gJSPQJSP也是在编译期被{换成Servlet来运行的Q在q行期实际上q行的是ServletQ而不是JSP?/P> Hibernate的实现方法比较先q: 1、编写POJO 2、编译POJO 3、直接运行,在运行期Q由Hibernate的CGLIB动态把POJO转换为PO?/P> 由此可以看出Hibernate是在q行期把POJO的字节码转换为PO的,而JDO是在~译期{换的。一般认为JDO的方式效率会E高Q毕竟是~译期{换嘛。但是Hibernate的作者Gavin King说CGLIB的效率非怹高,q行期的PO的字节码生成速度非常之快Q效率损失几乎可以忽略不计?/P> 实际上运行期生成PO的好处非常大Q这样对于程序员来说Q是无法接触到PO的,PO对他们来说完全透明。可以更加自q以POJO的概忉|UPO。另外由于是q行期生成POQ所以可以支持增量编译,增量调试。而JDO则无法做到这一炏V实际上已经有很多h在抱怨JDO的编译期Enhancer问题了,而据说JBossDO采用运行期生成PO字节码,而不采用~译期生成PO字节码?/P> 另外一个相关的问题是,不同的JDO产品的Enhancer生成的PO字节码可能会有所不同Q可能会影响在JDO产品之间的可UL性,q一Ҏ(gu)点类似EJB的可UL性难题?/P> p个问题另外引Z个JDO的缺陗?/P> ׃JDO的PO状态管理方式,所以当你在E序里面get/set的时候,实际上不是从PO的实例中取valuesQ而是从JDO State Manager?中取出来Q所以一旦PM关闭QPO׃能进行存取了?/P> 在JDO中,也可以通过一些办法得PO可以在PM外面使用Q比如说定义PO是transient的,但是该PO在PM关闭后就没有PO identity了。无法进行跨PM的状态管理?/P> 而Hibernate是从PO实例中取values的,所以即使Session关闭Q也一样可以get/setQ可以进行跨Session的状态管理?/P> 在分多层的应用中Q由于持久层和业务层和web层都是分开的,此时Hibernate的PO完全可以当做一个POJO来用Q也是当做一个VOQ在各层间自׃递,而不用去Session是开q是兟뀂如果你把这个POJO序列化的话,甚至可以用在分布式环境中。(不适合lazy loading的情况) 但是JDO的PO在PM关闭后就不能再用了,所以必dPM关闭前把PO拯一份VOQ把VO传递给业务层和web层用。在非分布式环境中,也可以用ThreadLocal模式保PM始终是打开状态,来避免每ơ必进行PO到VO的拷贝操作。但是不怎么_qL权宜之计Q不如Hibernate的功能强?/P> 评论re: POJO ?PO?概念potian 写道:辨别一些名词: 1。VOQ实际上很模p,通常指ValueObject和ViewObject 2. ViewObjectQ界面展现需要的对象Q如Struts的FormBean 3。Value ObjectQ早期被作ؓValueObject和Transfer Object的ȝ。实际上Value Object的真正意义在于它的内容,而不是n? 4。Transfer ObjectQ数据传输对象,在应用程序不同层ơ之间传书对象,在一个分布式应用E序中,通常可以提高整体的性能 5。POQ也许就是Persistent ObjectQ基本上是Entity? 在不同的体系l构和实现方式里面,q些对象有可能重复,也有可能不重叠。如果你要做一个对所有的体系都能够方便移植的框架Q那么每一U对象都需要严格区分。例如JDO的PO不能作ؓTOQ应为它不能qPMQ譬如你可以选择用ViewObjectQ如Struts的FOrmBean)直接作ؓTOQ但在tapestry和W(xu)ebwork里面׃合适了。但在很多时候,能够方便实用是最重要的,不要q度设计是了? robbin写道Q? POJO是这样一个对象,它是一个普通的Java对象Q它不同于EJBq样的带有繁重的容器控制功能的对象,它也不是那种被Enhancedq的对象Q例如JDO的静态EnhanceQ也不是cMHibernate那样被动态的byte code generationq? 也就是说POJO的概忉|相对于其他那U被人动q手脚的class而言的,它是没有被动q手脚的? Ҏ(gu)? 其实Qؓ什么要做DAO?无非是: 1Q?理connection/transaction (hibernate的话是session/transaction) 2, 便于q行l计/log操作Q? 3Q?便于q行权限控制Q? DAO模式中,有两cd象,一U是DAOQ一U是valueObject?在我们讨论的q个情况中,value object是hibernate对应的POJO. 那么Q按照我的理解,DAO是一个Transaction包装器,光辑l构是商业的具体事务。此处,数据库的transaction和商业的事务是统一的? q里有一不错的关于DAO的文章? http://www-106.ibm.com/developerworks/java/library/j-dao/ BO 包含business logic 留在q备忘? |
板桥里h http://www.jdon.com 2005/06/29
Java中文问题一直困扰着很多初学者,如果了解了Javapȝ的中文问题原理,我们可以对中文问题能够采取Ҏ(gu)的解决之道?/P>
最古老的解决Ҏ(gu)是用String的字节码转换Q这U方案问题是不方便,我们需要破坏对象封装性,q行字节码{换?/P>
q有一U方式是对J2EE容器q行~码讄Q如果J2EE应用pȝq该容器,则会发生qQ而且指定容器配置不符合J2EE应用和容器分ȝ原则?/P>
在Java内部q算中,涉及到的所有字W串都会被{化ؓUTF-8~码来进行运。那么,在被Java转化之前Q字W串是什么样的字W集Q?JavaLҎ(gu)操作pȝ的默认编码字W集来决定字W串的初始编码,而且Javapȝ的输入和输出的都是采取操作系l的默认~码?/P>
因此Q如果能l一Javapȝ的输入、输出和操作pȝ3者的~码字符集合Q将能够使Javapȝ正确处理和显C汉字。这是处理Javapȝ汉字的一个原则,但是在实际项目中Q能够正抓住和控制住Javapȝ的输入和输出部分是比较难的。J2EE中,׃涉及到外部浏览器和数据库{,所以中文问题ؕ码显得非常突出?/P>
J2EE应用E序是运行在J2EE容器中。在q个pȝ中,输入途径有很多种Q一U是通过面表单打包成请求(requestQ发往服务器的Q第二种是通过数据库读入;q有W?U输入比较复杂,JSP在第一ơ运行时L被编译成ServletQJSP中常常包含中文字W,那么~译使用javacӞJava根据默认的操作pȝ~码作ؓ初始~码。除非特别指定,如在Jbuilder/eclipse中可以指定默认的字符集?/P>
输出途径也有几种Q第一U是JSP面的输出。由于JSP面已经被编译成ServletQ那么在输出Ӟ也将Ҏ(gu)操作pȝ的默认编码来选择输出~码Q除非指定输出编码方式;q有输出途径是数据库Q将字符串输出到数据库?/P>
由此看来Q一个J2EEpȝ的输入输出是非常复杂Q而且是动态变化的Q而Java是跨q_q行的,在实际编译和q行中,都可能涉及到不同的操作系l,如果ȝJava自由Ҏ(gu)操作pȝ来决定输入输出的~码字符集,q将不可控制地出Cؕ码?/P>
正是׃Java的跨q_Ҏ(gu),使得字符集问题必ȝ具体pȝ来统一解决Q所以在一个Java应用pȝ中,解决中文q的根本办法是明确指定整个应用pȝl一字符集?/STRONG>
指定l一字符集时Q到底是指定ISO8859_1 、GBKq是UTF-8呢?
Q?Q如l一指定为ISO8859_1Q因为目前大多数软g都是西方人编制的Q他们默认的字符集就是ISO8859_1Q包括操作系lLinux和数据库MySQL{。这P如果指定Jivel一~码为ISO8859_1Q那么就有下?个环节必L握:
开发和~译代码时指定字W集为ISO8859_1?/P>
q行操作pȝ的默认编码必LISO8859_1Q如Linux?/P>
在JSP头部声明Q?lt;%@ page contentType="text/html;charset=ISO8859_1" %>?/P>
Q?Q如果统一指定为GBK中文字符集,上述3个环节同样需要做刎ͼ不同的是只能q行在默认编码ؓGBK的操作系l,如中文Windows?/P>
l一~码为ISO8859_1和GBK虽然带来~制代码的方便,但是各自只能在相应的操作pȝ上运行。但是也破坏了Java跨^台运行的优越性,只在一定范围内行得通。例如,Z使得GBK~码在linux上运行,讄Linux~码为GBK?/P>
那么有没有一U除了应用系l以外不需要进行Q何附加设|的中文~码Ҏ(gu)解决Ҏ(gu)呢?
Java/J2EEpȝ的统一~码定义为UTF-8。UTF-8~码是一U兼Ҏ(gu)有语a的编码方式,惟一比较ȝ的就是要扑ֈ应用pȝ的所有出入口Q然后用UTF-8厠Z结扎”它?/P>
一个J2EE应用pȝ需要做下列几步工作Q?/P>
AbstractQ本文深入分析了JavaE序设计中Java~译器对java源文件和JVM对classcL件的~码/解码q程Q通过此过E的解析透视ZJava~程中中文问题生的Ҏ(gu)原因Q最后给Z的最优化的解决Java中文问题的方法?
1、中文问题的来源
计算机最初的操作pȝ支持的编码是单字节的字符~码Q于是,在计机中一切处理程序最初都是以单字节编码的英文为准q行处理。随着计算机的发展Qؓ了适应世界其它民族的语aQ当然包括我们的汉字Q,Z提出了UNICODE~码Q它采用双字节编码,兼容英文字符和其它民族的双字节字W编码,所以,目前Q大多数国际性的软g内部均采用UNICODE~码Q在软gq行Ӟ它获得本地支持系l(多数旉是操作系l)默认支持的编码格式,然后再将软g内部的UNICODE转化为本地系l默认支持的格式昄出来。Java的JDK和JVMx如此Q我q里说的JDK是指国际版的JDKQ我们大多数E序员用的是国际化的JDK版本Q以下所有的JDK均指国际化的JDK版本。我们的汉字是双字节~码语言Qؓ了能让计机处理中文Q我们自己制定的gb2312、GBK、GBK2K{标准以适应计算机处理的需求。所以,大部分的操作pȝZ适应我们处理中文的需求,均定制有中文操作pȝQ它们采用的是GBK,GB2312~码格式以正显C我们的汉字。如Q中文Win2K默认采用的是GBK~码昄Q在中文WIN2k中保存文件时默认采用的保存文件的~码格式也是GBK的,卻I所有在中文WIN2K中保存的文g它的内部~码默认均采用GBK~码Q注意:GBK是在GB2312基础上扩充来的?/P>
׃Java语言内部采用UNICODE~码Q所以在JAVAE序q行Ӟ存在着一个从UNICODE~码和对应的操作pȝ及浏览器支持的编码格式{换输入、输出的问题Q这个{换过E有着一pd的步骤,如果其中M一步出错,则显C出来的汉字׃出是qQ这是我们常见的JAVA中文问题?/P>
同时QJava是一个跨q_的编E语aQ也x们编写的E序不仅能在中文windows上运行,也能在中文Linux{系l上q行Q同时也要求能在英文{系l上q行Q我们经常看到有人把在中文win2k上编写的JAVAE序Q移植到英文Linux上运行)。这U移植操作也会带来中文问题?/P>
q有Q有Z用英文的操作pȝ和英文的IE{浏览器Q来q行带中文字W的E序和浏览中文网,它们本n׃支持中文Q也会带来中文问题?/P>
几乎所有的览器默认在传递参数时都是以UTF-8~码格式来传递,而不是按中文~码传递,所以,传递中文参数时也会有问题,从而带来ؕ码现象?/P>
MQ以上几个方面是JAVA中的中文问题的主要来源,我们把以上原因造成的程序不能正运行而生的问题UCQJAVA中文问题?/P>
2、JAVA~码转换的详l过E?
我们常见的JAVAE序包括以下cdQ?BR>*直接在console上运行的c?包括可视化界面的c?
*JSP代码c(注:JSP是Servletscȝ变型Q?BR>*Serveletsc?BR>*EJBc?BR>*其它不可以直接运行的支持c?/P>
q些cL件中Q都有可能含有中文字W串Qƈ且我们常用前三类JAVAE序和用L(fng)接交互,用于输出和输入字W,如:我们在JSP和Servlet中得到客L(fng)送来的字W,q些字符也包括中文字W。无些JAVAcȝ作用如何Q这些JAVAE序的生命周期都是这L(fng)Q?/P>
*~程人员在一定的操作pȝ上选择一个合适的~辑软g来实现源E序代码q以.java扩展名保存在操作pȝ中,例如我们在中文win2k中用C本编辑一个java源程序;
*~程人员用JDK中的javac.exe来编译这些源代码QŞ?classc?JSP文g是由容器调用JDK来编译的)Q?BR>*直接q行q些cL这些类布v到WEB容器中去q行Qƈ输出l果?BR>那么Q在q些q程中,JDK和JVM是如何将q些文g如何~码和解码ƈq行的呢Q?BR>q里Q我们以中文win2k操作pȝZ说明JAVAcL如何来编码和被解码的?/P>
W一步,我们在中文win2k中用~辑软g如记事本~写一个Java源程序文?包括以上五类JAVAE序)Q程序文件在保存旉认采用了操作pȝ默认支持GBK~码格式(操作pȝ默认支持的格式ؓfile.encoding格式)形成了一?java文gQ也卻IjavaE序在被~译前,我们的JAVA源程序文件是采用操作pȝ默认支持的file.encoding~码格式保存的,java源程序中含有中文信息字符和英文程序代码;要查看系l的file.encoding参数Q可以用以下代码Q?BR> public class ShowSystemDefaultEncoding {
public static void main(String[] args) {
String encoding = System.getProperty("file.encoding");
System.out.println(encoding);
}}
W二步,我们用JDK的javac.exe文g~译我们的Java源程序,׃JDK是国际版的,在编译的时候,如果我们没有?encoding参数指定我们的JAVA源程序的~码格式Q则javac.exe首先获得我们操作pȝ默认采用的编码格式,也即在编译javaE序Ӟ若我们不指定源程序文件的~码格式QJDK首先获得操作pȝ的file.encoding参数(它保存的是操作pȝ默认的编码格式,如WIN2kQ它的gؓGBK)Q然后JDK把我们的java源程序从file.encoding~码格式转化为JAVA内部默认的UNICODE格式攑օ内存中。然后,javac把{换后的unicode格式的文件进行编译成.classcLӞ此时.class文g是UNICODE~码的,它暂攑֜内存中,紧接着QJDK此以UNICODE~码的编译后的class文g保存到我们的操作pȝ中Ş成我们见到的.class文g。对我们来说Q我们最l获得的.class文g是内容以UNICODE~码格式保存的类文gQ它内部包含我们源程序中的中文字W串Q只不过此时它己l由file.encoding格式转化为UNICODE格式了?/P>
q一步中Q对于JSP源程序文件是不同的,对于JSPQ这个过E是q样的:即WEB容器调用JSP~译器,JSP~译器先查看JSP文g中是否设|有文g~码格式Q如果JSP文g中没有设|JSP文g的编码格式,则JSP~译器调用JDK先把JSP文g用JVM默认的字W编码格?也即WEB容器所在的操作pȝ的默认的file.encoding)转化Z(f)时的Servletc,然后再把它编译成UNICODE格式的classc,q保存在临时文g夹中。如Q在中文win2k上,W(xu)EB容器把JSP文g从GBK~码格式转化为UNICODE格式Q然后编译成临时保存的Servletc,以响应用L(fng)h?/P>
W三步,q行W二步编译出来的c,分ؓ三种情况Q?/P>
A?直接在console上运行的c?BR>B?EJBcd不可以直接运行的支持c?如JavaBeanc?
C?JSP代码和Servletc?BR>D?JAVAE序和数据库之间
下面我们分这四种情况来看?BR>A、直接在console上运行的c?/P>
q种情况Q运行该c首先需要JVM支持Q即操作pȝ中必d装有JRE。运行过E是q样的:首先java启动JVMQ此时JVMd操作pȝ中保存的class文gq把内容d内存中,此时内存中ؓUNICODE格式的classc,然后JVMq行它,如果此时此类需要接收用戯入,则类会默认用file.encoding~码格式对用戯入的串进行编码ƈ转化为unicode保存入内存(用户可以讄输入的~码格式Q。程序运行后Q生的字符ԌUNICODE~码的)再回交给JVMQ最后JRE把此字符串再转化为file.encoding格式(用户可以讄输出的~码格式)传递给操作pȝ昄接口q输出到界面上?/P>
对于q种直接在console上运行的c,它的转化q程可用?更加明确的表C出来:
?
以上每一步的转化都需要正的~码格式转化Q才能最l不出现q现象?/P>
B、EJBcd不可以直接运行的支持c?如JavaBeanc?
׃EJBcd不可以直接运行的支持c,它们一般不与用L(fng)接交互输入和输出Q它们常怸其它的类q行交互输入和输出,所以它们在W二步被~译后,Ş成了内容是UNICODE~码的类保存在操作系l中了,以后只要它与其它的类之间的交互在参数传递过E中没有丢失Q则它就会正的q行?BR>q种EJBcd不可以直接运行的支持c? 它的转化q程可用?更加明确的表C出来:
?
C、JSP代码和Servletc?/P>
l过W二步后QJSP文g也被转化为ServletscLӞ只不q它不像标准的Servlets一校存在于classes目录中,它存在于WEB容器的(f)时目录中Q故q一步中我们也把它做为Servlets来看?/P>
对于ServletsQ客L(fng)h它时QW(xu)EB容器调用它的JVM来运行ServletQ首先,JVM把Servlet的classcMpȝ中读出ƈ装入内存中,内存中是以UNICODE~码的Servletcȝ代码Q然后JVM在内存中q行该Servletc,如果Servlet在运行的q程中,需要接受从客户端传来的字符如:表单输入的值和URL中传入的|此时如果E序中没有设定接受参数时采用的编码格式,则WEB容器会默认采用ISO-8859-1~码格式来接受传入的值ƈ在JVM中{化ؓUNICODE格式的保存在WEB容器的内存中。Servletq行后生成输出,输出的字W串是UNICODE格式的,紧接着Q容器将Servletq行产生的UNICODE格式的串Q如html语法Q用戯出的串等Q直接发送到客户端浏览器上ƈ输出l用P如果此时指定了发送时输出的编码格式,则按指定的编码格式输出到览器上Q如果没有指定,则默认按ISO-8859-1~码发送到客户的浏览器上。这UJSP代码和Servletc,它的转化q程可用?更加明确地表C出来:
?
D、JavaE序和数据库之间
对于几乎所有数据库的JDBC驱动E序Q默认的在JAVAE序和数据库之间传递数据都是以ISO-8859-1为默认编码格式的Q所以,我们的程序在向数据库内存储包含中文的数据ӞJDBC首先是把E序内部的UNICODE~码格式的数据{化ؓISO-8859-1的格式,然后传递到数据库中Q在数据库保存数据时Q它默认即以ISO-8859-1保存Q所以,q是Z么我们常常在数据库中d的中文数据是q?BR>对于JAVAE序和数据库之间的数据传递,我们可以用图4清晰地表C出?/P>
?
3、分析常见的JAVA中文问题几个必须清楚的原?/P>
首先Q经q上面的详细分析Q我们可以清晰地看到QQ何JAVAE序的生命期中,其编码{换的关键q程是在于:最初编译成class文g的{码和最l向用户输出的{码过E?BR>其次Q我们必M解JAVA在编译时支持的、常用的~码格式有以下几U:
*ISO-8859-1Q?-bit, ?859_1,ISO-8859-1,ISO_8859_1{编?BR>*Cp1252Q美国英语编码,同ANSI标准~码
*UTF-8Q同unicode~码
*GB2312Q同gb2312-80,gb2312-1980{编?BR>*GBK , 同MS936Q它是gb2312的扩?BR>及其它的~码Q如韩文、日文、繁体中文等。同Ӟ我们要注意这些编码间的兼容关体系如下Q?BR>unicode和UTF-8~码是一一对应的关pRGB2312可以认ؓ是GBK的子集,即GBK~码是在gb2312上扩展来的。同ӞGBK~码包含?0902个汉字,~码范围为:0x8140-0xfefeQ所有的字符可以一一对应到UNICODE2.0中来?/P>
再次Q对于放在操作系l中?java源程序文Ӟ在编译时Q我们可以指定它内容的编码格式,具体来说?encoding来指定。注意:如果源程序中含有中文字符Q而你?encoding指定为其它的~码字符Q显然是要出错的。用-encoding指定源文件的~码方式为GBK或gb2312Q无论我们在什么系l上~译含有中文字符的JAVA源程序都不会有问题,它都会正地中文{化ؓUNICODE存储在class文g中?BR>
然后Q我们必L楚,几乎所有的WEB容器在其内部默认的字W编码格式都是以ISO-8859-1为默认值的Q同Ӟ几乎所有的览器在传递参数时都是默认以UTF-8的方式来传递参数的。所以,虽然我们的Java源文件在出入口的地方指定了正的~码方式Q但其在容器内部q行时还是以ISO-8859-1来处理的?BR>
4、中文问题的分类及其最优解军_?
了解以上JAVA处理文g的原理之后,我们可以提Z一套徏议最优的解决汉字问题的办法?BR>我们的目标是Q我们在中文pȝ中编辑的含有中文字符串或q行中文处理的JAVA源程序经~译后可以移值到M其它的操作系l中正确q行Q或拿到其它操作pȝ中编译后能正运行,能正地传递中文和英文参数Q能正确地和数据库交中英文字符丌Ӏ?BR>我们的具体思\是:在JAVAE序转码的入口和出口及JAVAE序同用h输入输出转换的地斚w制编码方法之正即可?/P>
具体解决办法如下Q?/P>
1?针对直接在console上运行的c?BR>对于q种情况Q我们徏议在E序~写Ӟ如果需要从用户端接收用L(fng)可能含有中文的输入或含有中文的输出,E序中应该采用字W流来处理输入和输出Q具体来_应用以下面向字符型节Ҏ(gu)cdQ?BR>Ҏ(gu)ӞFileReaderQFileWrieter
其字节型节点类型ؓQFileInputStreamQFileOutputStream
对内存(数组Q:CharArrayReaderQCharArrayWriter
其字节型节点类型ؓQByteArrayInputStreamQByteArrayOutputStream
对内存(字符ԌQStringReaderQStringWriter
对管道:PipedReaderQPipedWriter
其字节型节点类型ؓQPipedInputStreamQPipedOutputStream
同时Q应该用以下面向字符型处理流来处理输入和输出Q?BR>BufferedWriterQBufferedReader
其字节型的处理流为:BufferedInputeStreamQBufferedOutputStream
InputStreamReaderQOutputStreamWriter
其字节型的处理流为:DataInputStreamQDataOutputStream
其中InputStreamReader和InputStreamWriter用于字节流按照指定的字W编码集转换到字W流Q如Q?BR>InputStreamReader in = new InputStreamReader(System.inQ?GB2312")Q?BR>OutputStreamWriter out = new OutputStreamWriter (System.outQ?GB2312")Q?BR>例如Q采用如下的CZJAVA~码pC要求Q?/P>
//Read.java
import java.io.*;
public class Read {
public static void main(String[] args) throws IOException {
String str = "n中文试Q这是内部硬~码的串"+"ntest english character";
String strin= "";
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in,"gb2312")); //讄输入接口按中文编?BR>BufferedWriter stdout = new BufferedWriter(new OutputStreamWriter(System.out,"gb2312")); //讄输出接口按中文编?BR>stdout.write("误?");
stdout.flush();
strin = stdin.readLine();
stdout.write("q是从用戯入的Ԍ"+strin);
stdout.write(str);
stdout.flush();
}}
同时Q在~译E序Ӟ我们用以下方式来q行Q?BR>javac -encoding gb2312 Read.java
其运行结果如?所C:
?
2?针对EJBcd不可以直接运行的支持c?如JavaBeanc?
׃q种cd们本w被其它的类调用Q不直接与用户交互,故对q种cL_我们的徏议的处理方式是内部程序中应该采用字符来处理E序内部的中文字W串Q具体如上面一节中一PQ同Ӟ在编译类时用-encoding gb2312参数指示源文件是中文格式~码的即可?BR>
3?针对Servletc?/P>
针对ServletQ我们徏议用以下Ҏ(gu)Q?/P>
在编译Servletcȝ源程序时Q用-encoding指定~码为GBK或GB2312Q且在向用户输出时的~码部分用response对象的setContentType("text/html;charset=GBK");或gb2312来设|输出编码格式,同样在接收用戯入时Q我们用request.setCharacterEncoding("GB2312")Q这h论我们的servletcȝ植到什么操作系l中Q只有客L(fng)的浏览器支持中文昄Q就可以正确昄。如下是一个正的CZQ?/P>
//HelloWorld.java
package hello;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld extends HttpServlet
{
public void init() throws ServletException { }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
request.setCharacterEncoding("GB2312"); //讄输入~码格式
response.setContentType("text/html;charset=GB2312"); //讄输出~码格式
PrintWriter out = response.getWriter(); //使用PrintWriter输出
out.println("<hr>");
out.println("Hello World! This is created by Servlet!试中文!");
out.println("<hr>");
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
request.setCharacterEncoding("GB2312"); //讄输入~码格式
response.setContentType("text/html;charset=GB2312"); //讄输出~码格式
String name = request.getParameter("name");
String id = request.getParameter("id");
if(name==null) name="";
if(id==null) id="";
PrintWriter out = response.getWriter(); //使用PrintWriter输出
out.println("<hr>");
out.println("你传入的中文字串是:" + name);
out.println("<hr>你输入的id是:" + id);
out.println("<hr>");
}
public void destroy() { }
}
L(fng)javac -encoding gb2312 HelloWorld.java来编译此E序?BR>试此Servlet的程序如下所C:
<%@page contentType="text/html; charset=gb2312"%>
<%request.setCharacterEncoding("GB2312");%>
<html><head><title></title>
<Script language="JavaScript">
function Submit() {
//通过URL传递中文字W串值给Servlet
document.base.action = "./HelloWorld?name=中文";
document.base.method = "POST";
document.base.submit();
}
</Script>
</head>
<body bgcolor="#FFFFFF" text="#000000" topmargin="5">
<form name="base" method = "POST" target="_self">
<input name="id" type="text" value="" size="30">
<a href = "JavaScript:Submit()">传给Servlet</a>
</form></body></html>
其运行结果如?所C:
?
4?JAVAE序和数据库之间
为避免JAVAE序和数据库之间数据传递出Cؕ码现象,我们采用以下最优方法来处理Q?BR>1?对于JAVAE序的处理方法按我们指定的方法处理?BR>2?把数据库默认支持的编码格式改为GBK或GB2312的?/P>
如:在mysql中,我们可以在配|文件my.ini中加入以下语句实玎ͼ
在[mysqld]区增加:
default-character-set=gbk
q增加:
[client]
default-character-set=gbk
在SQL Server2K中,我们可以数据库默认的语a讄为Simplified Chinese来达到目的?/P>
5?针对JSP代码
׃JSP是在q行Ӟ由WEB容器q行动态编译的Q如果我们没有指定JSP源文件的~码格式Q则JSP~译器会获得服务器操作系l的file.encoding值来对JSP文g~译的,它在UL时最Ҏ(gu)出问题,如在中文win2k中可以很好运行的jsp文g拿到英文linux中就不行Q尽客L(fng)都是一L(fng)Q那是因为容器在~译JSP文g时获取的操作pȝ的编码不同造成的(在中文wink中的file.encoding和在英文Linux中file.encoding是不同的Q且英文Linux的file.encoding对中文不支持Q所以编译出来的JSPcd会有问题Q。网l上讨论的大多数是此c问题,多是因ؓJSP文gULq_时不能正显C的问题Q对于这c问题,我们了解了JAVA中程序编码{换的原理Q解册v来就Ҏ(gu)多了。我们徏议的解决办法如下Q?/P>
1、我们要保证JSP向客L(fng)输出时是采用中文~码方式输出的,x论如何我们首先在我们的JSP源代~中加入以下一行:
<%@page contentType="text/html; charset=gb2312"%>
2、ؓ了让JSP能正获得传入的参数Q我们在JSP源文件头加入下面一句:
<%request.setCharacterEncoding("GB2312");%>
3、ؓ了让JSP~译器能正确地解码我们的含有中文字符的JSP文gQ我们需要在JSP源文件中指定我们的JSP源文件的~码格式Q具体来_我们在JSP源文件头上加入下面的一句即可:
<%@page pageEncoding="GB2312"%>?lt;%@page pageEncoding="GBK"%>
q是JSP规范2.0新增加的指o?BR>我们使用此方法来解JSP文g中的中文问题Q下面的代码是一个正做法的JSP文g的测试程序:
//testchinese.jsp
<%@page pageEncoding="GB2312"%>
<%@page contentType="text/html; charset=gb2312"%>
<%request.setCharacterEncoding("GB2312");%>
<%
String action = request.getParameter("ACTION");
String name = "";
String str = "";
if(action!=null && action.equals("SENT"))
{
name = request.getParameter("name");
str = request.getParameter("str");
}
%>
<html>
<head>
<title></title>
<Script language="JavaScript">
function Submit()
{
document.base.action = "?ACTION=SENT&str=传入的中?;
document.base.method = "POST";
document.base.submit();
}
</Script>
</head>
<body bgcolor="#FFFFFF" text="#000000" topmargin="5">
<form name="base" method = "POST" target="_self">
<input type="text" name="name" value="" size="30">
<a href = "JavaScript:Submit()">提交</a>
</form>
<%
if(action!=null && action.equals("SENT"))
{
out.println("<br>你输入的字符为:"+name);
out.println("<br>你通过URL传入的字WؓQ?+str);
}
%>
</body>
</html>
如图7是此E序q行的结果示意图Q?BR>
?
5、ȝ
在上面的详细分析中,我们清晰地给ZJAVA在处理源E序q程中的详细转换q程Qؓ我们正确解决JAVA~程中的中文问题提供了基。同Ӟ我们l出了认为是最优的解决JAVA中文问题的办法?/P>
6、参考资?BR>1、段明辉.Java ~程技术中汉字问题的分析及解决.
http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
2?周竞?关于Java中文问题的几条分析原?BR>http://www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
作者介l:abnerchaiQ高U程序员Q联pL法:josserchai@yahoo.com
1 |
package cc.ejb.examples; import java.io.IOException; import java.io.StringWriter; import java.io.PrintWriter; import java.util.Date; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class PageTimerFilter implements Filter { private FilterConfig config = null; public void init(FilterConfig config) throws ServletException { this.config = config; } public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { Date startTime, endTime; double duration; startTime = new Date(); // Forward the request to the next resource in the chain chain.doFilter(request, response); // Calculate the duration between the start time and end time endTime = new Date(); duration = (endTime.getTime() - startTime.getTime())/1000;//Convert from milliseconds to seconds StringWriter sw = new StringWriter(); PrintWriter writer = new PrintWriter(sw); writer.println(); writer.println("==============="); writer.println("Total elapsed time is: " + duration + " seconds."); writer.println("==============="); // Log the resulting string writer.flush(); config.getServletContext().log(sw.getBuffer().toString()); } public void destroy() { this.config=null; } } |
1 |
(((description*,display-name*,icon*)),filter-name,filter-class,init-param*) |
1 |
<init-param> <param-name>counter</param-name> <param-value>100</param-value> </init-param> |
1 |
<filter> <filter-name>Page Timers</filter-name> <filter-class>cc.ejb.examples.PageTimerFilter</filter-class> </filter> <filter-mapping> <filter-name>Page Timers</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
1 |
<%@ page language="java" %> <html> <body> <jsp:forward page="/Thank.html"/> </body> </html> |
1 |
<html> <body> Thank you for coming Filter worlds<br> </body> </html> |
1 |
11:06:57,045 INFO [Engine] StandardContext[/Test] =============== Total elapsed time is: 0.0 seconds. =============== |
1 |
<filter> <filter-name>Page Timers</filter-name> <filter-class>cc.ejb.examples.PageTimerFilter</filter-class> </filter> <filter-mapping> <filter-name>Page Timers</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> |
1 |
11:17:51,165 INFO [Engine] StandardContext[/Test] =============== Total elapsed time is: 0.0 seconds. =============== 11:17:51,165 INFO [Engine] StandardContext[/Test] =============== Total elapsed time is: 10.0 seconds. =============== |