第六条 在改写equals的时候请遵守通用规定
有一种“值类”可以不要求改写equals方法,类型安全枚举类型,因为类型安全枚举类型保证每一个值之多只存在一个对象,所有对于这样的类而言,Object的queals方法等同于逻辑意义上的equals方法。
在改写equals方法的时候,要遵循的规范:
1,自反性。对任意的引用值x,x.equals(x)一定是true
2,对称性。对于任意的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)也一定返回true.
3,传递性。对于任意的引用值x,y和z,如果x.equals(y)==true and y.equals(z)==true,so x.equals(z)==true.
4,一致性。对于任意的引用值x和y,如果用于equals比较的对象信息没有被修改的话,那么,多次调用x.equals(y)要么一致地返回true,要么一致地返回false.
5,非空性。对于任意的非空引用值x,x.equals(null)一定返回false.
自反性:要求一个对象必须等于其自身。一个例子:你把该类的实例加入到一个集合中,则该集合的contains方法
将果断地告诉你,该集合不包含你刚刚加入的实例.
对称性:
例如:
public final class CaseInsensitiveString{
private String s;
public CaseInsensitiveString(String s){
if(s==null) throw new NullPointerException();
this.s=s;
}
public boolean equals(Object o){
if(o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
if(o instanceof String)
return s.equalsIgnoreCase((String)o);
return false;
}
}
调用:
CaseInsensitiveString cis=new CaseInsensitiveString("Polish");
String s="polish";
正如我们期望的:cis.equals(s)==true but s.equals(cis)==false
这就违反了对称性的原则.为了解决这个问题,只需把企图与String互操作的这段代码从equals方法中去掉旧可以了.这样做之后,你可以重构代码,使他变成一条返回语句:
public boolean equals(Object o){
return o instanceof CaseInsensitiveString && ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
传递性---即如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
例如:
public class Point{
private final int x;
private final int y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
public boolean equals(Object o){
if(!(o instanceof Point))
return false;
Point p=(Point)o;
return p.x==x&&p.y==y;
}
}
现在我们来扩展这个类,为一个点增加颜色信息:
public class ColorPoint extends Point{
private Color color;
public ColorPoint(int x,int y,Color color){
super(x,y);
this.color=color;
}
}
分析: equals方法会怎么样呢?如果你完全不提供equals方法,而是直接从Point继承过来,那么在equals座比较的时候颜色信息会被忽略掉。如果你编写一个equals方法,只有当实参是另一个有色点,并且具有同样的位置和颜色的时候,它才返回true:
public boolean equals(Object o){
if(!(o instanceof ColorPoint)) return false;
ColorPoint cp=(ColorPoint)o;
return super.equals(o) && cp.color==color;
}
分析:这个方法的问题在于,我们在比较一个普通点和一个有色点,以及反过来的情形的时候,可能会得到不同的结果。前一种比较忽略了颜色信息,而后一种比较总是返回false,因为实参的类型不正确。例如:
Point p=new Point(1,2);
ColorPoint cp=new ColorPoint(1,2,Color.RED);
然后:p.equals(cp)==true but cp.equals(p)==false
修正:让ColorPoint.equals在进行“混合比较”的时候忽略颜色信息:
public boolean equals(Object o){
if(!(o instanceof Point)) return false;
if(!(o instanceof ColorPoint)) return o.equals(this);
ColorPoint cp=(ColorPoint)o;
return super.equals(o) && cp.color==color;
}
这种方法确实提供了对称性,但是却牺牲了传递性:
ColorPoint p1=new ColorPoint(1,2,Color.RED);
Point p2=new Point(1,2);
ColorPoint p3=new ColorPoint(1,2,Color.BLUE);
此时:p1.equals(p2)==true and p2.equals(p3)==true but p1.equals(p3)==false很显然违反了传递性。前两个比较不考虑颜色信息,而第三个比较考虑了颜色信息。
结论:要想在扩展一个可实例华的类的同时,既要增加新的特征,同时还要保留equals约定,没有一个简单的办法可以做到这一点。根据"复合优于继承",这个问题还是有好的解决办法:我们不让ColorPoint扩展Point,而是在ColorPoint中加入一个私有的Point域,以及一个公有的视图方法,此方法返回一个与该有色 点在同一位置上的普通Point对象:
public class ColorPoint{
private Point point;
private Color color;
public ColorPoint(int x,int y,Color color){
point=new Point(x,y);
this.color=color;
}
public Point asPoint(){
return point;
}
public boolean equals(Object o){
if(!(o instanceof ColorPoint)) return false;
ColorPoint cp=(ColorPoint)o;
return cp.point.equals.(point) && cp.color.equals(color);
}
}
注意,你可以在一个抽象类的子类中增加新的特征,而不会违反equals约定。
一致性:
如果两个对象相等的话,那么它们必须始终保持相等,除非有一个对象被修改了。由于可变对象在不同的时候可以与不同的对象相等,而非可变对象不会这样,这个约定没有严格界定。
非空性:没什么好说的。
1,使用==操作符检查“实参是否为指向对象的一个应用”。如果是的话,则返回true。
2,使用instanceof操作符检查“实参是否为正确的类型”。如果不是的话,则返回false。
3,把实参转换到正确的类型。
4,对于该类中每一个“关键(significant)”域,检查实参中的域与当前对象中对应的域值是否匹配
if (!(this.field == null ? o.field == null : this.equals(o.field)))
//或者写成 if(!(this.field == o.field || (this.field != null && this.field.equals(o.field)))) 对于this.field和o.field通常是相同的对象引用,会更快一些。
return false;
//比较下一个field
//都比较完了
return true;
5.最后还要确认以下事情
5.1)改写equals的同时,总是(必须)要改写hashCode方法(见【第8条】),这是极容易被忽略的,有极为重要的
5.2)不要企图让equals方法过于聪明
5.3)不要使用不可靠的资源。如依赖网络连接
5.4)不要将equals声明中的Object对象替换为其他类型。
public boolean equals(MyClass) 这样的声明并不鲜见,往外使程序员数小时搞不清楚为什么不能正常工作
原因是这里是重载(overload)而并不是改写(override)(或称为覆盖、重写)
相当于给equals又增加了一个实参类型为MyClass的重载,而实参为Object的那个equals并没有被改写,依然还是从Object继承来的最初的那个equals,所总是看不到程序员想要的效果。因为类库或其他人写的代码都是调用实参为Object型的那个equals方法的(别人又如何在事前知晓你今天写的MyClass呢?)
ExtJS可以用来开发RIA也即富客户端的AJAX应用,是一个用javascript写 的,主要用于创建前端用户界面,是一个与后台技术无关的前端ajax框架。因此,可以把ExtJS用在.Net、Java、Php等各种开发语言开发的应 用中。
ExtJs最开始基于YUI技术,由开发人员Jack Slocum开发,通过参考Java Swing等机制来组织可视化组件,无论从UI界面上CSS样式的应用,到数据解析上的异常处理,都可算是一款不可多得的JavaScript客户端技术 的精品。
ExtJS: http://www.spket.com/demos/js.html spket插件(eclipse)
· Start Aptana and navigate the application menu to: Help → Software Updates → Find and Install… → Search for new features to install → New remote site…
· Name: “Spket”, Url: “http://www.spket.com/update/”
· Restart Eclipse
· Watch this Spket IDE Tutorial to see how to easily add Ext code assist (you can point it at the latest /src/ext.jsb to keep code assist up to date with the latest Ext version). The steps are basically:
o Window → Preferences → Spket → JavaScript Profiles → New
o Enter “ExtJS” and click OK
o Select “ExtJS” and click “Add Library”, then choose “ExtJS” from the dropdown
o Select “ExtJS” and click “Add File”, then choose the “ext.jsb” file in your “./ext-2.x/source” directory
o Set the new ExtJS profile as the default by selecting it an clicking the “Default” button on the right-hand side of the “JavaScript Profiles” dialog.
o Restart Eclipse
o Create a new JS file and type: Ext. and you should get the Ext Code completion options.
一个简单的例子:
http://extjs.org.cn/node/83
java程序设计风格:(类的说明介绍)
Java文件注释头
类中开头处插入如下 注释
/******************************************************************
*该类功能及特点的描述(例如:该类是用来.....)
*
*
*该类未被编译测试过。
*
*@see(与该类相关联的类):(AnotherClass.java)
*
*
*
*开发公司或单位:XXX软件有限公司研发中心
*
*版权:本文件版权归属XXX公司研发中心
*
*
*@author(作者):XXX
*
*@since(该文件所支持的JDK版本):Jdk1.3或Jdk1.4
*
*@version(版本):1.0
*
*
*@date(开发日期): 1999-01-29
*
*最后更改日期: 2003-01-02
*
*
*修改人: XXXX
*
*复审人: 张三李四 王五
*
*/
内存管理
伊甸园用来保存新的对象,它就像一个堆栈,新的对象被创建,就像指向该栈的指针在不断的增长一样,当伊甸园区域中的对象满了之后,JVM系统将要做可到达性测试,主要任务是检测有哪些对象由根集合出发是不可到达的,这些对象就可以被JVM回收,并且将所有的活动对象从伊甸园区域拷到TO区域,此时一些对象将发生状态交换,有的对象就从TO区域被转移到From区域,此时From区域就有了对象,这个过程都是JVM控制完成的。
Java 中的析构方法 finalize
对象是使用完了 尽量都赋 为 null
共享静态变量存储空间
不要提前创建对象
........
void f(){
int i;
A a = new A();
//类A的对象a被创建
//在判断语句之中没有
//应用过a对象
.....
if(....){
//类A的对象a仅在此处被应用
a.showMessage();
....
}
.....
}
..........
正确的书写方式为:
void f(){
int i;
.....
if(...){
A a = new A();
//类A的对象a被创建
//应用过a对象
a.showMessage();
}
......
}
JVM内存参数调优
表达式、语句和保留字
非静态方法中可引用静态变量
静态方法不可以引用非静态变量
静态方法中可 创建 非静态变量
调用父类的构造方法必须将其放置子类构造方法的第一行
JAVA核心类与性能优化
线程同步:Vector Hashtable
非线程同步: ArrayList HashMap
字符串累加 尽量使用 StringBuffer +=
方法length() 和 length属性 区别
IO缓存,读写文件优化。
类与接口
内部类(Inner Class)是Java语言中特有的类型,内部类只能被主类以外的其他内部类继承,主类是不能继承其内部类的,因为这样就引起了类循环继承的错误,下面的代码就是错误的。
public class A extends x {
public A(){}
……
Classs x{
…..
}
}
上面的代码将引发类循环继承的错误,这种错误在编译时就会被发现,比较容易发现和排除。
但是下面例子中的内部类的继承方式却是正确的:
class A{
….
public A(){}
……
class X extends Y {
……….
}
calss Y {
……
}
}
什么时候使用继承,什么样的继承是合理的:
1. 现实世界的事物继承关系,可以作为软件系统中类继承关系的依据。
2. 包含关系的类之间不存在继承关系。如:主机,外设 ,电脑。 把主机类和外设类作为电脑类的成员就可以了。
3. 如果在逻辑上类B是类A的一种,并且类的所有属性和行为对类而言都有意义,则允许B继承A的行为和属性(私有属性与行为除外)。
原帖javaeye上的:
http://yongtech.javaeye.com/blog/428671 ,觉得写得挺不错的!不知道您到什么阶段了。。。。hoho
本来我想把这篇文章的名字命名为: <怎样成为一个优秀的Java程序员>的, 但是自己还不够优秀, 而本篇所涉及的都是自己学习和工作中的一些经验, 后来一想, 叫<怎样进阶Java>可能更为合适吧. 能给初学Java的人一个参考, 也就是我本来的心愿. 如果有大牛看到不妥之处, 敬请指正. 我一定会修正的 :).
Java目前是最流行的语言之一, 是很多公司和程序员喜爱的一门程序语言. 而且, Java的入门比C++相对来说要简单一些, 所以有很大一部分程序员都选择Java作为自己的开发语言. 我也是其中之一, 就是因为觉得学C++太难, 当初在学校学了将近一个学期的C++, 啥进步都没有, 哈哈, 天资太差, 所以才选择自学Java(当时学校并没有开设Java的课程), 才走上了程序开发这条路.
Java虽然入门要容易, 然而要精通它, 要成为专家却很难. 主要原因是Java所涉及的技术面比较宽, 人的精力总是有限的. 有些Java方面的技术是必须要要掌握的, 钻研得越深入越好, 比如多线程技术.
1. 基础阶段
基础阶段, 可能需要经历1-2年吧. 这个时段, 应该多写一些基础的小程序(自己动手写的越多越好). 计算机是一门实践性很强的学科, 自己动手的东西, 记忆非常深刻, 效果要胜过读好多书. 当然, 学Java基础的时候, 书籍的选择也非常重要, 好的书籍事半功倍, 能让你打个非常好的基础. 而差的书籍, 很容易将你带入歧途, 多走很多弯路. 书籍不在多, 而在乎读得精(有些书, 你读十遍都不为过). 我记得我学Java的第一本书是<Thinking in Java>的中文版, 网上有很多人都建议不要把这本书作为第一本的入门教程来看, 太难. 我却想在此极力推荐它, 这本书确实是本经典之作. 而且书中确实讲的也是Java中的一些基础技术, 没有什么太难的东西, 只不过比较厚, 学习周期比较长, 所以很多人中途会选择放弃. 其实, 这本书是一本难得的入门教程, 对Java一些基础的东西, 讲得很全, 而且也很清晰, 更重要的是, 这本书能让你养成很多好的编程习惯, 例子也很多. 建议你把大部分的例子自己去实现一遍. 我的亲身经历, 我记得当时认真的看了2遍, 花了大概7个月的时间, 不过真的有很好的效果. 另外一个教程, 就是<Java核心技术>卷一, 卷二的话可以不必要买. 卷一看完, 自己再钻研一下, 就已经能达到卷二的高度了:). 到那时, 你就会觉得看卷二没啥意思, 感觉浪费钱了. 还有一个, 就是张孝祥的Java视频, 看视频有个好处, 就是比看书的记忆要深刻, 还有很多你可以跟着视频的演示同步操作. 张孝祥的Java视频对初学者来说, 确实很有作用. 总结起来: 看这些资料的时候, 一定要多写例子, 写的越多越好!
2. 中级阶段
中级阶段, 是一个更漫长的时期, 能否突破此阶段, 跟个人的努力和天资有着很大的关系. 你不得不承认, 同样一门新技术, 有些人一个月领悟到的东西, 比你一年的都多. 这就是天资, 程序员是一个需要天才的工作. 我想, 很多人听说李一男吧, 此君就是这样的人物, 三个月的时间就能解决好大一帮人几年解决不了的问题, 给华为某部门带来了很多的收益. 哦, 这是题外话了, 与此篇的主题无关, 只是本人偶尔的感慨而已:). 这个阶段, 就需要研究很多专题性的东西了, 比如: IO的实现原理, 多线程和Java的线程模型, 网络编程, swing, RMI, reflect, EJB, JDBC等等很多很多的专题技术, 钻研得越深越好. 为了更好的提高, 研究的更深入, 你需要经常到网络上搜索资料, 这个时候往往一本书起不来很大的作用. 选一个JDK版本吧, 目前建议选用1.6, 多多研究它, 尤其是源代码(尽量! 就是尽自己最大的努力, 虽然研究透是不可能滴). 比如说: util, collection, io, nio, concurrent等等包. 可能有人会反对我说, 不是有API文档吗, 为什么还要研究这么多的源代码? 错了, 有API文档, 你仅仅只是知道怎么用而已, 而认真仔细的研读这些大牛的源码, 你就会深入更高的一个阶层, 自己的编码, 设计都会有很大的提高. 如果有能力和精力, 我建议你把JDK的每一行代码都熟悉一遍, 绝对只有好处, 没有坏处! 而且你会有些意外的收获, 比如, 当你仔细地读完concurrent包的时候(不多, 好像总共是86个类吧), 你就会对Doug Lea佩服得五体投地. 这个时候最忌碰到难题就去寻找帮助, 去网上找答案! 先把自己的脑袋想破吧, 或者等你的老板拿着砍刀冲过来要把你杀了, 再去寻求帮助吧. 对于专题的学习, 英文原版的阅读是非常必要的, 看的越多越好, 多上上IBM的developer, SUN的网站吧, 当然Javaeye也很不错:), 有很多大牛, 呵呵.
这个时候, 你应该建立自己的代码库了, 你应该自己去研究很多有意思的东西了. 从一个200多M的文件中寻找一个字段, 最坏情况(在文件的末尾咯)也只需要1秒左右的时间, 你知道吗? 这个阶段, 有很多很多类似的有趣的东西可以供你去研究, 你需要更多地关注性能, 规范性, 多解决一些疑难问题. 需要学会所有的调试技术, 运用各种性能工具, 还有JDK附带的很多工具, 这些你都要熟练得跟屠夫操刀一样. 也可以看看<Effective Java>, 这本书总结的也不错, 对写高效稳定的Java程序有些帮助. 也可以看看模式方面的东西, 但是我建议模式不要滥用, 非得要用的时候才用, 模式往往会把问题搞复杂:). 总结起来: 这个阶段是一个由点延伸到面的过程, 经过不断的学习, 演变成全面的深入! Java技术中你没什么盲点了, 还能解决很多性能问题和疑难问题, 你就成了一个合格的程序员了! :) [要想成为优秀程序员, 还得对数据库和操作系统很精通.]
3. 高级阶段
高级阶段, 我就不敢妄言了. 呵呵, 我感觉自己也是处于中级阶段吧. 也是根据自己的一些经验, 谈谈自己的理解吧:
这个阶段, 需要研究各种框架, Spring, struts, Junit, Hibernate, iBatis, Jboss, Tomcat, snmp4j等等, 我觉得这个时候, 只要是用Java实现的经典框架, 你都可以去研究. ------在此申明一下, 我的意思不是说会用. 光会用其实是远远不够的, 你可以选择自己喜欢钻研的框架, 去好好研究一下, 兴趣是最好的老师嘛.(2009.07.21)
建议开始的时候, 研究Junit和Struts吧, 小一点, 里面都采用了很多的模式, 呵呵, 可以熟悉一下, 尽量想想人家为什么这么做. 我建议主要的精力可以花在spring和jboss上, 尤其是jboss, 经典中的经典, 设计, 性能, 多线程, 资源管理等等, 你从中可以学到的东西简直是太多了. 而且它还有一本写得很好的参考书, 叫<Jboss管理与开发核心技术>, 英文方面的资料也是非常的多. 在工作中如果有机会参与架构的设计, 业务问题的讨论, 一定想方设法杀进去! 这对自己的设计能力, 以及对设计如何运用在业务上有很大的帮助. 毕竟, 程序都是为了更好地实现用户的业务的. 这个时候, 需要更多看看软件工程和UML方面的资料, 或者自己主持一个项目玩玩, 不一定非得出去拉项目赚钱(能赚钱当然更好), 不管成功或失败, 都是很宝贵的经验, 都能提高很多!
该书介绍了在Java编程中极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。
每天下班花点时间学习下吧,尽量在一个星期内把它看完,总结出来,大多数内容都来自书上,个人觉得该书不错的地方摘出来。
第一条:考虑用静态工厂方法代替构造函数
静态工厂方法(优点):
1.每次调用的时候,不一定要创建一个新的对象,这个可以自由控制。
2.它可以返回一个原返回类型的子类型的对象。
第二条:使用私有构造函数强化singleton属性
第一种:提供共有的静态final域
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
}
第二种:提供一个共有的静态工厂方法
1public class Elvis{
2 private static final Elvis INSTANCE = new Elvis();
3 private Elvis(){
4
5 }
6
7 public static Elvis getInstance(){
8 return INSTANCE;
9 }
10
11}
第一种性能上会稍微好些
第二种提供了灵活性,在不改变API的前提下,允许我们改变想法,把该类做成singleton,或者不做,容易被修改。
注意点:为了使一个singleton类变成克序列花的(Serializable),仅仅在声明中加上implements Serializable是不够的,
为了维护singleton性,必须也要提供一个
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
第三条:通过私有构造函数强化不可实例化的能力
只要让这个类包含单个显式的私有构造函数,则它就不可被实例化;
1 public class UtilityClass{
2 private UtilityClass(){
3
4 }
5
6 }
企图通过将一个类做成抽象类来强制该类不可被实例化,这是行不通的。该类可以被子类化,并且该子类也可以被实例化。
更进一步,这样做会误导用户,以为这种类是专门为了继承而设计的。
第四条:避免创建重复的对象
String s = new Sting("silly");//这么恶心的代码就不要写啦。。。
1.静态工厂方法可几乎总是优先于构造方法;Boolean.valueOf(...) > Boolean(...),构造函数每次被调用的时候都会创建一个新的对象,
而静态工厂方法从来不要求这样做。
2.
public class Person {
private final Date birthDate;
public Person(Date date){
this.birthDate = date;
}
//don't do this
public boolean isBabyBoomer(){
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >=0 && birthDate.compareTo(boomEnd) <0;
}
}
isBabyBoomer每次被调用的时候,都会创建一个新的Calendar,一个新的TimeZone和两个新的Date实例。。。
下面的版本避免了这种低效率的动作,代之以一个static 块初始化Calendar对象,而且最体现效率的是,他的生命周期只实例化一次Calendar并且把
80年,90年的出生的值赋值给类静态变量BOOM_START和BOOM_END
class Person {
private final Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1980, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0
&& birthDate.compareTo(BOOM_END) < 0;
}
最近在翻《首先,打破一切常规》,书不只翻一遍了,每次都会有一些新的收获.书中经常提到的6个问题越想越觉得有道理,列出跟大家分享:
1.你知道对你工作的要求吗?
第一优先级的问题,如果不知道对你的工作要求,那么你如何衡量你工作的好坏?其他人如何衡量你的工作成果?首当其冲需要考虑的问题.
2.你有做好你当前工作的材料和设备或者资源吗?
当然明确工作要求的时候需要明确你达到你目标的途径,此时需要衡量你能掌控的资源,或者你能够使用到的潜在资源.
3.在工作中你每天都有机会做你最擅长的事情吗?
是否能够发挥自己的潜力,是否是自己的兴趣爱好,是否能做的更好.
4.过去的7天内,你有因你的工作出色而受到表扬吗?
周围同事或者你的主管关注你的个人价值,为什么会受到表扬?是否可以在这件工作上受到一贯的认可.
5.你觉得你的主管或者同事关心你的个人情况吗?
恒定自己在团队中的作用,如果不关心,为什么?你是否了解过其他人的感受?如果关心?关心那点?如何满足期望.
6.工作单位里面有人会鼓励你的发展吗?
职业发展的考虑,企业是否值得呆下去,是否能够共同进步.
定期拿这些问题进行自勉,会有很多收获.包括你的同事,你的主管,你的合作伙伴,甚至会改变你的人生.
问题:
下面一个简单的类:
public class MyTest {
private static String
className = String.class.getName(); //红色部分是下面问题的关键
public static void
main(String[] args){
System.out.println(className);
}
}
经eclipse3.1.1编译后(指定类兼容性版本为1.4),再反编译的结果是:
public class MyTest
{
public MyTest()
{
}
public static void main(String args[])
{
System.out.println(className);
}
private static String className;
static
{
className =
java.lang.String.class.getName();
}
}
而经过sun javac(或者ant, antx)编译后(JDK版本1.4,或者JDK1.5,但是编译结果指定版本为1.4),再反编译的结果是:
public class MyTest
{
public MyTest()
{
}
public static void main(String args[])
{
System.out.println(className);
}
static Class _mthclass$(String x0)
{
return Class.forName(x0);
ClassNotFoundException
x1;
x1;
throw (new
NoClassDefFoundError()).initCause(x1);
}
private static String className;
static
{
className =
(java.lang.String.class).getName();
}
}
也就是说sun javac编译出来的结果里面多了一个_mthclass$方法,这个通常不会有什么问题,不过在使用hot swap技术(例如Antx
eclipse plugin中的快速部署插件利用hot swap来实现类的热替换,或者某些类序列化的地方,这个就成为问题了。
用_mthclass$在google上搜一把,结果不多,比较有价值的是这一篇:http://coding.derkeiler.com/Archive/Java/comp.lang.java.softwaretools/2004-01/0138.html
按照这个说法,这个问题是由于Sun本身没有遵循规范,而eclipse compiler遵循规范导致的,而且eclipse
compiler是没有办法替换的。
尝试将JDK版本换成1.5,sun
javac生成出来的_mthclass$是不见了,不过,这回奇怪的是eclipse的编译结果中多了一个成员变量class$0,
下面是经eclipse3.1.1编译后(指定类兼容性版本为5.0),再反编译的结果:
public
class MyTest
{
public MyTest()
{
}
public static void main(String args[])
{
System.out.println(className);
}
private static String className = java/lang/String.getName();
static Class class$0;
}
而经过sun javac(或者ant, antx)编译后(JDK版本1.5),再反编译的结果是:
public class
MyTest
{
public MyTest()
{
}
public static void main(String args[])
{
System.out.println(className);
}
private static String className = java/lang/String.getName();
}
再在goole上搜了一把,发现了Eclipse3.2有两个比较重要的特征:
1.与javac的兼容性更好。
2.提供了单独的编译器,可以在eclipse外部使用,包括ant中使用。
于是下载eclipse3.2版本,首先验证一下其与sun javac的兼容性如何,
使用JDK1.4版本的时候,还是老样子,sun
javac编译出来的_mthclass$方法在eclipse3.2的编译结果中还是不存在,所以还是不兼容的。
不过使用JDK1.5版本的时候,这会eclipse
3.2的编译结果总算和sun javac一致了。
虽然,用JDK1.5加上eclipse
3.2已经保证了这个类在两种编译器下的兼容性,不过总觉得不踏实:
1.谁知道这两个编译器还有没有其它不兼容的地方呢?
2.版本要求太严格,很多由于各种原因没有使用这些版本的很麻烦。
因此,还是从根本上解决这个问题比较合适:根本上解决最好就是不要使用两种不同的编译器,而使用同一种。
由于eclipse环境下的编译器是不可替换的,所以企图都使用sun
javac的方式不太可行,那么统一使用eclipse自带的编译器如何呢?
刚才提到的eclipse3.2的第二个比较重要的特性就派上用场了。
独立的eclipse编译器(1M大小而已)可以在如下地址下载:http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops/R-3.2-200606291905/ecj.jar
这个独立的编译器在antx下的使用也很简单:(关于该编译器的独立使用或者ant下面的使用可以参看this help section: JDT
Plug-in Developer Guide>Programmer's Guide>JDT Core>Compiling Java
code)
1.将下载下来的编译器放在ANTX_HOME/lib目录下面。
2.在总控项目文件的project.xml增加这么一行即可:<property
name="build.compiler"
value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
这样就保证了通过antx打包的类也是用eclipse的编译器编译出来的,当然就不应该存在类不兼容的情况了。
实际上,eclipse3.1.1版本也已经提供了独立的eclipse编译器,不过当时并没有单独提供独立的包下载,如果希望使用3.1.1版本的eclipse编译器,可以使用org.eclipse.jdt.core_3.1.1.jar以及其中包含的jdtCompilerAdapter.jar。(eclipse3.1.1环境的编译器我没有独立验证过)
Unicode是一种字符编码规范 。
先从ASCII说起。ASCII是用来表示英文字符的一种编码规范,每个ASCII字符占用1个字节(8bits)
因此,ASCII编码可以表示的最大字符数是256,其实英文字符并没有那么多,一般只用前128个(最高位为0),其中包括了控制字符、数字、大小写字母和其他一些符号 。
而最高位为1的另128个字符被成为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其他符号
这种字符编码规范显然用来处理英文没有什么问题 。(实际上也可以用来处理法文、德文等一些其他的西欧字符,但是不能和英文通用),但是面对中文、阿拉伯文之类复杂的文字,255个字符显然不够用
于是,各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312-80”,它是和ASCII兼容的一种编码规范,其实就是利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。
但是这个方法有问题,最大的问题就是,中文文字没有真正属于自己的编码,因为扩展ASCII码虽然没有真正的标准化,但是PC里的ASCII码还是有一个事实标准的(存放着英文制表符),所以很多软件利用这些符号来画表格。这样的软件用到中文系统中,这些表格符就会被误认作中文字,破坏版面。而且,统计中英文混合字符串中的字数,也是比较复杂的,我们必须判断一个ASCII码是否扩展,以及它的下一个ASCII是否扩展,然后才“猜”那可能是一个中文字 。
总之当时处理中文是很痛苦的。而更痛苦的是GB2312是国家标准,台湾当时有一个Big5编码标准,很多编码和GB是相同的,所以……,嘿嘿。
这时候,我们就知道,要真正解决中文问题,不能从扩展ASCII的角度入手,也不能仅靠中国一家来解决。而必须有一个全新的编码系统,这个系统要可以将中文、英文、法文、德文……等等所有的文字统一起来考虑,为每个文字都分配一个单独的编码,这样才不会有上面那种现象出现。
于是,Unicode诞生了。
Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。
以目前常用的UCS-2为例,它可以表示的字符数为2^16=65535,基本上可以容纳所有的欧美字符和绝大部分的亚洲字符 。
UTF-8的问题后面会提到 。
在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的解决了 。
但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。
我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了 。
另一个更加严重的问题是,C语言使用'\0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉 。
于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF 。
UTF= UCS Transformation Format UCS转换格式
它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16 。
其中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容 。
UTF-8有点类似于Haffman编码,它将Unicode编码为00000000-0000007F的字符,用单个字节来表示;
00000080-000007FF的字符用两个字节表示
00000800-0000FFFF的字符用3字节表示
因为目前为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多需要用6字节表示一个字符。
在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类可以对UTF编码进行处理。
下面说说中文的问题。
由于历史的原因,在Unicode之前,一共存在过3套中文编码标准。
GB2312-80,是中国大陆使用的国家标准,其中一共编码了6763个常用简体汉字。Big5,是台湾使用的编码标准,编码了台湾使用的繁体汉字,大概有8千多个。HKSCS,是中国香港使用的编码标准,字体也是繁体,但跟Big5有所不同。
这3套编码标准都采用了两个扩展ASCII的方法,因此,几套编码互不兼容,而且编码区间也各有不同
因为其不兼容性,在同一个系统中同时显示GB和Big5基本上是不可能的。当时的南极星、RichWin等等软件,在自动识别中文编码、自动显示正确编码方面都做了很多努力 。
他们用了怎样的技术我就不得而知了,我知道好像南极星曾经以同屏显示繁简中文为卖点。
后来,由于各方面的原因,国际上又制定了针对中文的统一字符集GBK和GB18030,其中GBK已经在Windows、Linux等多种操作系统中被实现。
GBK兼容GB2312,并增加了大量不常用汉字,还加入了几乎所有的Big5中的繁体汉字。但是GBK中的繁体汉字和Big5中的几乎不兼容。
GB18030相当于是GBK的超集,比GBK包含的字符更多。据我所知目前还没有操作系统直接支持GB18030。
谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:
问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。
根据维基百科全书(
http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。
UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的编码是否正确。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (
http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:
"Understanding Unicode A general introduction to the Unicode Standard" (
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and legacy encodings" (
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)
下面列举10个攻击:
1.AJAX中的跨站点脚本
前几个月,人们发现了多种跨站点的脚本攻击。在此类攻击中,受害者的包含信息的浏览器上会运行来自特定网站的恶意JAVA脚本代码。
Yamanner蠕虫就是一个最近的范例,它利用Yahoo邮件的AJAX调用中的跨站点脚本机会来攻击受害者。另一个近期的范例就是Samy蠕虫,它利
用MySpace.com的跨站点脚本漏洞来攻击。AJAX在客户端上运行,它允许错误书写的脚本被攻击者利用。攻击者能够编写恶意链接来哄骗那些没有准
备的用户,让他们用浏览器去访问特定的网页。传统应用中也存在这样的弱点,但AJAX给它添加了更多可能的漏洞。例子, Yamanner蠕虫利用了Yahoo
Mail的AJAX的跨站脚本漏洞,Samy蠕虫利用了MySpace.com的跨站脚本漏洞。
2.XML中毒
很多Web2.0应用中,XML传输在服务器和浏览器之间往复。网络应用接收来自AJAX客户端的XML块。这XML块很可能染毒。多次将递归
负载应用到产出相似的XML节点,这样的技术还并不普遍。如果机器的处理能力较弱,这将导致服务器拒绝服务。很多攻击者还制作结构错误的XML文档,这些
文档会扰乱服务器上所使用的依赖剖析机制的逻辑。服务器端的剖析机制有两种类型,它们是SAX和DOM。网络服务也使用相同的攻击向量,这是因为网络服务
接收SOAP消息,而SOAP就是XML消息。在应用层大范围地使用XMLs使攻击者有更多的机会利用这个新的攻击向量。
XML外部实体参照是能被攻击者伪造的一个XML的属性。这会使攻击者能够利用人意的文件或者TCP连接的缺陷。XML
schema中毒是另一个XML中毒的攻击向量,它能够改变执行的流程。这个漏洞能帮助攻击者获得机密信息。攻击者可以通过复制节点进行DOS攻击,或者生成不合法的XML导致服务器端逻辑的中断。攻击者也可以操纵外部实体,导致打开任何文件或TCP连接端口。XML数据定义的中毒也可以导致运行流程的改变,助攻击者获取机密信息。
3.恶意AJAX代码的执行
AJAX调用非常不易察觉,终端用户无法确定浏览器是否正在用XMLHTTP请求对象发出无记载的调用。浏览器发出AJAX调用给任意网站的时
候,该网站会对每个请求回应以cookies。这将导致出现泄漏的潜在可能性。例如,约翰已经登陆了他的银行,并且通过了服务器认证。完成认证过程后,他
会得到一个会话
cookie。银行的页面中包含了很多关键信息。现在,他去浏览器他网页,并同时仍然保持银行账户的登陆状态。他可能会刚好访问一个攻击者的网页,在这个
网页上攻击者写了不易被察觉的AJAX
代码,这个代码不用经过约翰的同意,就能够发出后台调用给约翰的银行网页,因而能够从银行页面取得关键信息并且把这些信息发送到攻击者的网站。这将导致机
密信息的泄漏甚至引发安全突破。AJAX
编码可以在不为用户所知的情形下运行,假如用户先登录一个机密网站,机密网站返回一个会话cookie,然后用户在没有退出机密网站的情形下,访问攻击者
的网站,攻击者网页上的AJAX编码可以(通过这个会话cookie?)去访问机密网站上的网页,从而窃取用户的机密信息。(注:这里的解释有点含糊,理
论上讲,浏览器不会把一个网站的会话cookie传给另外一个网站的,即文中的这句“When the browser makes an AJAX
call to any Web site it replays cookies for each request. ”,不完全对)
4.RSS/Atom 注入
这是一项新的web2.0攻击。RSS反馈是人们在门户网站或者网络应用中共享信息的常用手段。网络应用接受这些反馈然后发送给客户端的浏览器。人们可
以在该RSS反馈中插入文本的JavaScript来产生对用户浏览器的攻击。访问特定网站的终端用户加载了带有该RSS反馈的网页,这个脚本
就会运行起来——它能够往用户的电脑中安装软件或者窃取cookies信息。这就是一个致命的客户端攻击。更糟糕的是,它可以变异。随着RSS和ATOM
反馈成为网络应用中整合的组件,在服务器端将数据发布给终端用户之前,过滤特定字符是非常必要的。攻击者可以在RSS
feeds里注入Javascript脚本,如果服务器端没有过滤掉这些脚本的话,在浏览器端会造成问题。
5.WSDL扫描和enumeration
WSDL(网络服务界定语言)是网络服务的一个接口。该文件提供了技术,开放方法,创新形式等等的关键信息。这是非常敏感信息,而且能够帮助人
们决定利用什么弱点来攻击。如果将不必要的功能或者方法一直开着,这会为网络服务造成潜在的灾难。保护WSDL文件或者限定对其的访问是非常重要的。在实
际情况中,很有可能找到一些使用WSDL扫描的一些漏洞。WSDL提供了Web服务所用的技术,以及外露的方法,调用的模式等信息。假如Web服务对不必要的方法没有禁止的话,攻击者可以通过WSDL扫描找到潜在的攻击点。
6.AJAX常规程序中客户端的确认
基于web2.0的应用使用AJAX常规程序来在客户端上进行很多操作,比如客户端数据类型的确认,内容检查,数据域等等。正常情况下,服务端
也应该备份这些客户端检查信息。大部分开发者都没有这么做;他们这样做的理由是,他们假设这样的确认是由AJAX常规程序来负责的。避开基于AJAX的确
认和直接发送POST或者GET请求给那些应用——这些应用是诸如SQL注入,LDAP注入等类随确认进入的攻击主要来源,它们能够攻击网络应用的关键资
源——都是可以做到的。这都增加了能够为攻击者所利用的潜在攻击向量的数量。假如开发人员只依赖客户端验证,不在服务器端重新验证的话,会导致SQL注入,LDAP注入等等。
7.网络服务路由问题
网络服务安全协议包括WS-Routing服务。WS-Routing允许SOAP消息在互联网上各种各样不同的节点中的特别序列中传输。通常
加密的信息在这些节点来回传送。交互的节点中的任意一个被攻击都将致使攻击者能够访问到在两个端点之间传输的SOAP消息。这将造成SOAP消息的严重的
安全泄漏。随着网络应用开始被网络服务框架所采用,攻击者们开始转而利用这些新协议和新的攻击向量。Web服务安全协议使用WS-Routing服务,假如任何中转站被攻占,SOAP消息可以被截获。
8.SOAP消息的参数操作
网络服务接收信息和来自SOAP消息的变量。修改这些变量是很可能的。例如,“10”是SOAP消息中多个节点中的一个。攻击者可以修改点,并
且尝试不同种的注入攻击——比如,SQL,LDAP,XPATH,命令行解释器——并且探索能被他用来掌握及其内部信息的攻击向量。网络服务代码中错误的
或者不够完备的输入确认使网络服务应用易于发生泄漏.这是一个目标指向网络服务所带的网络应用的一项新的攻击向量。类似于SQL注入,假如对SOAP消息里节点的数据不做验证的话。
9.SOAP消息中的XPATH注入
XPATH是一种用来查询XML文档的语言,它跟SQL语句很类似:我们提供某些信息(参数)然后从数据库中得到查询结果。很多语言都支持
XPATH
解析的功能。网络应用接收大型XML文档,很多时候这些应用从终端用户和XPATH语句中取得输入量。这些代码的段落对XPATH注入没有什么防御能力。
如果XPATH执行成功,攻击者能够绕过认证机制或者造成机密信息的一些损失。现在人们只知道很少部分的能够被攻击者利用的XPATH的漏洞。阻止这个攻
击向量的唯一方法就是在给XPATH语句传递变量值的时候提供适当的输入确认。类似于SQL注入,假如对数据不做验证而直接做XPATH查询的话。
10. RIA瘦客户端二进制的伪造
丰富网络应用(RIA)使用非常丰富的UI要素比如Flash,ActiveX控件或者Applets,它使用这些要素作为网络应用的基本接
口。这个框架存在几个安全问题。其中最主要的一个就是关于会话管理。它是在浏览器中运行的,并且共享相同的会话。同时,由于客户端将下载整个二进制元件到
自己的主机,攻击者就可以颠倒工程的那个二进制文件并且反编译代码。把这些二进制串打包并绕过一些包含在代码中的认证逻辑是有可能实现的。这是
WEB2.0框架下的另一个有趣的攻击向量。因为Rich Internet
Applications的组件是下载到浏览器本地的,攻击者可以对二进制文件进行逆向工程,反编译编码,通过改动文件,跳过认证逻辑
。
结论
AJAX,RIA以及网络服务是WEB2.0应用空间的三项重要的技术向量。这些技术很有前景,它们带给桌面新的程式,加强了网络应用的整体效
率和效用。随着这些新技术而来的是新的安全问题,忽略这些问题将会导致整个世界发生巨大的灾难。本文中,我们只讨论了10种攻击。但是实际上还有很多其他
的攻击向量。对这些新的攻击向量的最好的防御方法是增加WEB2.0的安全意识,提高代码操作的安全性以及配置的安全性
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
匹配双字节字符(包括汉字在内):[^\x00-\xff]
应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}
匹配空行的正则表达式:\n[\s| ]*\r
匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/
匹配首尾空格的正则表达式:(^\s*)|(\s*$)
应用:javascript中没有像vbscript那样的trim函数,我们就可以利用这个表达式来实现,如下:
String.prototype.trim = function()
{ return this.replace(/(^\s*)|(\s*$)/g, "");
} 利用正则表达式分解和转换IP地址:
下面是利用正则表达式匹配IP地址,并将IP地址转换成对应数值的Javascript程序:
function IP2V(ip)
{
re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g //匹配IP地址的正则表达式
if(re.test(ip))
{
return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1
}
else
{
throw new Error("Not a valid IP address!")
}
}
不过上面的程序如果不用正则表达式,而直接用split函数来分解可能更简单,程序如下:
var ip="10.100.20.168"
ip=ip.split(".")
alert("IP值是:"+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配网址URL的正则表达式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
利用正则表达式限制网页表单里的文本框输入内容:
用正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')"
onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"
用正则表达式限制只能输入全角字符: onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')"
onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"
用正则表达式限制只能输入数字:onkeyup="value=value.replace(/
[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"
用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace(/
[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"