|
利用Thread-Specific Storage撰寫一個HibernateUtil
import java.io.Serializable;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.Transaction;
public class HibernateSessionUtil implements Serializable { publicstaticfinal ThreadLocal tLocalsess = new ThreadLocal();
publicstaticfinal ThreadLocal tLocaltx = new ThreadLocal();
/* * getting the thread-safe session for using */ publicstatic Session currentSession(){ Session session = (Session) tLocalsess.get();
try{ if (session == null){ session = openSession(); tLocalsess.set(session); } }catch (HibernateException e){ thrownew InfrastructureException(e); } return session; }
/* * closing the thread-safe session */ publicstatic void closeSession(){
Session session = (Session) tLocalsess.get(); tLocalsess.set(null); try{ if (session != null && session.isOpen()){ session.close(); }
}catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * begin the transaction */ publicstatic void beginTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ if (tx == null){ tx = currentSession().beginTransaction(); tLocaltx.set(tx); } }catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * close the transaction */ publicstatic void commitTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) tx.commit(); tLocaltx.set(null); }catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * for rollbacking */ publicstatic void rollbackTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ tLocaltx.set(null); if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()){ tx.rollback(); } }catch (HibernateException e){ thrownew InfrastructureException(e); } }
privatestatic Session openSession() throws HibernateException{ return getSessionFactory().openSession(); }
privatestatic SessionFactory getSessionFactory() throws HibernateException{ return SingletonSessionFactory.getInstance(); } }
filter中的程式碼如下
public class HibernateSessionCloser implements Filter{
protected FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)throws ServletException{ this.filterConfig = filterConfig; }
public void destroy(){ this.filterConfig = null; }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try{ chain.doFilter(request, response); } finally{ try{ HibernateSessionUtil.commitTransaction(); }catch (InfrastructureException e){ HibernateSessionUtil.rollbackTransaction(); }finally{ HibernateSessionUtil.closeSession(); } }
} }
然後在操作資料庫之前加上
HibernateSessionUtil.beginTransaction(); HibernateSessionUtil.currentSession();
|
Java 5的泛型语法已经有太多书讲了,这里不再打字贴书。GP一定有用,不然Java和C#不会约好了似的同时开始支持GP。但大家也清楚,GP和Ruby式的动态OO语言属于不同的意识形态,如果是一人一票,我想大部分的平民程序员更热衷动态OO语言的平白自然。但如果不准备跳槽到支持JSR223的动态语言,那还是看看GP吧。 胡乱总结泛型的四点作用:
第一是泛化,可以拿个T代表任意类型。 但GP是被C++严苛的静态性逼出来的,落到Java、C#这样的花语平原里----所有对象除几个原始类型外都派生于Object,再加上Java的反射功能,Java的Collection库没有范型一样过得好好的。 第二是泛型 + 反射,原本因为Java的泛型拿不到T.class而觉得泛型没用,最近才刚刚学到通过反射的API来获取T的Class,后述。 第三是收敛,就是增加了类型安全,减少了强制类型转换的代码。这点倒是Java Collection历来的弱项。 第四是可以在编译期搞很多东西,比如MetaProgramming。但除非能完全封闭于框架内部,框架的使用者和扩展者都不用学习这些东西的用法,否则那就是自绝于人民的票房毒药。C++的MetaProgramming好厉害吧,但对比一下Python拿Meta Programming生造一个Class出来的简便语法,就明白什么才是真正的叫好又叫座。 所以,作为一个架构设计师,应该使用上述的第2,3项用法,在框架类里配合使用反射和泛型,使得框架的能力更强; 同时采用收敛特性,本着对人民负责的精神,用泛型使框架更加类型安全,更少强制类型转换。 擦拭法避免了Java的流血分裂 : 大家经常骂Java GP的擦拭法实现,但我觉得多亏于它的中庸特性---如果你用就是范型,不用就是普通Object,避免了Java阵营又要经历一场to be or not to be的分裂。 最大的例子莫过Java 5的Collection 框架, 比如有些同学坚持认为自己不会白痴到类型出错,而且难以忍受每个定义的地方都要带一个泛型定义List〈Book〉 ,不用强制类型转换所省下的代码还不够N处定义花的(对了,java里面还没有tyepdef.....),因此对范型十分不感冒,这时就要齐齐感谢这个搽拭法让你依然可以对一个泛型框架保持非泛型的用法了...
通过反射获得 T.class: 不知为何书上不怎么讲这个,是差沙告诉我才知道的,最经典的应用见Hibernate wiki的Generic Data Access Objects, 代码如下:
abstract public class BaseHibernateEntityDao<T> extends HibernateDaoSupport { private Class<T> entityClass; public BaseHibernateEntityDao() { entityClass =(Class<T>) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; } public T get(Serializable id) { T o = (T) getHibernateTemplate().get(entityClass, id); } } 精华就是这句了:
Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 泛型之后,所有BaseHibernateEntityDao的子类只要定义了泛型,就无需再重载getEnttityClass(),get()函数和find()函数,销益挺明显的,所以SpringSide的Dao基类毫不犹豫就泛型了。
不过擦拭法的大棒仍在,所以子类的泛型语法可不能乱写,最正确的用法只有:
public class BookDao extends BaseHibernateEntityDao<Book>
1:排序类
package com.tixa.bad.customer.util;
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator;
import com.tixa.bad.customer.data.AdstatGraphArea;
/** * 对地区数据进行排序 * * @StatAreaSort * * @author * * TODO */ public class StatAreaSort { /** * 点击排序规则,倒序 */ private static Comparator orderClick = new Comparator() { public int compare(Object o1, Object o2) { AdstatGraphArea b1 = (AdstatGraphArea) o1; AdstatGraphArea b2 = (AdstatGraphArea) o2; return (b2.getClickcount() - b1.getClickcount()); } };
/** * ip排序规则,倒序 */ private static Comparator orderIP = new Comparator() { public int compare(Object o1, Object o2) { AdstatGraphArea b1 = (AdstatGraphArea) o1; AdstatGraphArea b2 = (AdstatGraphArea) o2; return (b2.getIpcount() - b1.getIpcount()); } };
/** * 根据点击量进行排序 * * @param list */ public static void getSortClick(ArrayList list) { Collections.sort(list, orderClick); // Collections.reverse(list); }
/** * 根据ip量进行排序 * * @param list */ public static void getSortIp(ArrayList list) { Collections.sort(list, orderIP); // Collections.reverse(list); }
} 2:对排序进行测试 package com.tixa.bad.customer.util;
import java.util.ArrayList;
import junit.framework.TestCase;
import com.tixa.bad.customer.data.AdstatGraphArea;
public class StatAreaSortTest extends TestCase {
protected void setUp() throws Exception { super.setUp(); }
protected void tearDown() throws Exception { super.tearDown(); }
/* * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortClick(ArrayList)' */ public void testGetSortClick() {
ArrayList list = new ArrayList(); AdstatGraphArea graAre = new AdstatGraphArea(); graAre.setAdid(1); graAre.setClickcount(786); graAre.setIpcount(43453); list.add(graAre); AdstatGraphArea graAre1 = new AdstatGraphArea(); graAre1.setAdid(2); graAre1.setClickcount(987876); graAre1.setIpcount(545); list.add(graAre1); AdstatGraphArea graAre2 = new AdstatGraphArea(); graAre2.setAdid(3); graAre2.setClickcount(877887); graAre2.setIpcount(4534534); list.add(graAre2); AdstatGraphArea graAre3 = new AdstatGraphArea(); graAre3.setAdid(4); graAre3.setClickcount(97998); graAre3.setIpcount(34534); list.add(graAre3); AdstatGraphArea graAre4 = new AdstatGraphArea(); graAre4.setAdid(5); graAre4.setClickcount(500); graAre4.setIpcount(2000); list.add(graAre4); System.out.print("sore before "); for(int i = 0;i<list.size();i++) { AdstatGraphArea a = (AdstatGraphArea)list.get(i); System.out.println(a.getAdid()); } StatAreaSort.getSortClick(list); System.out.print("sore after"); for(int i = 0;i<list.size();i++) { AdstatGraphArea a = (AdstatGraphArea)list.get(i); System.out.println(a.getAdid()); } System.out.println("----------------------"); StatAreaSort.getSortIp(list); for(int i = 0;i<list.size();i++) { AdstatGraphArea a = (AdstatGraphArea)list.get(i); System.out.println(a.getAdid()); } }
/* * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortIp(ArrayList)' */ public void testGetSortIp() {
} }
一、避免在循环条件中使用复杂表达式
在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
例子: import java.util.Vector; class CEL { void method (Vector vector) { for (int i = 0; i < vector.size (); i++) // Violation ; // ... } }
更正: class CEL_fixed { void method (Vector vector) { int size = vector.size () for (int i = 0; i < size; i++) ; // ... } }
二、为'Vectors' 和 'Hashtables'定义初始大小 JVM为Vector扩充大小的时候需要重新创建一个更大的数组,将原原先数组中的内容复制过来,最后,原先的数组再被回收。可见Vector容量的扩大是一个颇费时间的事。 通常,默认的10个元素大小是不够的。你最好能准确的估计你所需要的最佳大小。
例子: import java.util.Vector; public class DIC { public void addObjects (Object[] o) { // if length > 10, Vector needs to expand for (int i = 0; i< o.length;i++) { v.add(o); // capacity before it can add more elements. } } public Vector v = new Vector(); // no initialCapacity. }
更正: 自己设定初始大小。 public Vector v = new Vector(20); public Hashtable hash = new Hashtable(10);
参考资料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques" Addison Wesley, ISBN: 0-201-70429-3 pp.55 – 57
三、在finally块中关闭Stream 程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。 例子: import java.io.*; public class CS { public static void main (String args[]) { CS cs = new CS (); cs.method (); } public void method () { try { FileInputStream fis = new FileInputStream ("CS.java"); int count = 0; while (fis.read () != -1) count++; System.out.println (count); fis.close (); } catch (FileNotFoundException e1) { } catch (IOException e2) { } } } 更正: 在最后一个catch后添加一个finally块
参考资料: Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.77-79
四、使用'System.arraycopy ()'代替通过来循环复制数组 'System.arraycopy ()' 要比通过循环来复制数组快的多。 例子: public class IRB { void method () { int[] array1 = new int [100]; for (int i = 0; i < array1.length; i++) { array1 [i] = i; } int[] array2 = new int [100]; for (int i = 0; i < array2.length; i++) { array2 [i] = array1 [i]; // Violation } } } 更正: public class IRB { void method () { int[] array1 = new int [100]; for (int i = 0; i < array1.length; i++) { array1 [i] = i; } int[] array2 = new int [100]; System.arraycopy(array1, 0, array2, 0, 100); } } 参考资料: http://www.cs.cmu.edu/~jch/java/speed.html
五、让访问实例内变量的getter/setter方法变成”final” 简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会被重载,所以,可以变成”inlined”
例子: class MAF { public void setSize (int size) { _size = size; } private int _size; }
更正: class DAF_fixed { final public void setSize (int size) { _size = size; } private int _size; }
参考资料: Warren N. and Bishop P. (1999), "Java in Practice", p. 4-5 Addison-Wesley, ISBN 0-201-36065-9
六、避免不需要的instanceof操作 如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。 例子: public class UISO { public UISO () {} } class Dog extends UISO { void method (Dog dog, UISO u) { Dog d = dog; if (d instanceof UISO) // always true. System.out.println("Dog is a UISO"); UISO uiso = u; if (uiso instanceof Object) // always true. System.out.println("uiso is an Object"); } } 更正: 删掉不需要的instanceof操作。 class Dog extends UISO { void method () { Dog d; System.out.println ("Dog is an UISO"); System.out.println ("UISO is an UISO"); } }
七、避免不需要的造型操作 所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的“等于”其父类。那么,由子类造型至父类的操作就是不必要的了。 例子: class UNC { String _id = "UNC"; } class Dog extends UNC { void method () { Dog dog = new Dog (); UNC animal = (UNC)dog; // not necessary. Object o = (Object)dog; // not necessary. } } 更正: class Dog extends UNC { void method () { Dog dog = new Dog(); UNC animal = dog; Object o = dog; } } 参考资料: Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms for Effective Java". Addison-Wesley, 1999. pp.22-23
八、如果只是查找单个字符的话,用charAt()代替startsWith() 用一个字符作为参数调用startsWith()也会工作的很好,但从性能角度上来看,调用用String API无疑是错误的! 例子: public class PCTS { private void method(String s) { if (s.startsWith("a")) { // violation // ... } } } 更正 将'startsWith()' 替换成'charAt()'. public class PCTS { private void method(String s) { if ('a' == s.charAt(0)) { // ... } } } 参考资料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques" Addison Wesley, ISBN: 0-201-70429-3
九、使用移位操作来代替'a / b'操作 "/"是一个很“昂贵”的操作,使用移位操作将会更快更有效。
例子: public class SDIV { public static final int NUM = 16; public void calculate(int a) { int div = a / 4; // should be replaced with "a >> 2". int div2 = a / 8; // should be replaced with "a >> 3". int temp = a / 3; } }
更正: public class SDIV { public static final int NUM = 16; public void calculate(int a) { int div = a >> 2; int div2 = a >> 3; int temp = a / 3; // 不能转换成位移操作 } }
十、使用移位操作代替'a * b' 同上。 [i]但我个人认为,除非是在一个非常大的循环内,性能非常重要,而且你很清楚你自己在做什么,方可使用这种方法。否则提高性能所带来的程序晚读性的降低将是不合算的。
例子: public class SMUL { public void calculate(int a) { int mul = a * 4; // should be replaced with "a << 2". int mul2 = 8 * a; // should be replaced with "a << 3". int temp = a * 3; } }
更正: package OPT; public class SMUL { public void calculate(int a) { int mul = a << 2; int mul2 = a << 3; int temp = a * 3; // 不能转换 } }
十一、在字符串相加的时候,使用 ' ' 代替 " ",如果该字符串只有一个字符的话
例子: public class STR { public void method(String s) { String string = s + "d" // violation. string = "abc" + "d" // violation. } }
更正: 将一个字符的字符串替换成' ' public class STR { public void method(String s) { String string = s + 'd' string = "abc" + 'd' } }
十二、不要在循环中调用synchronized(同步)方法 方法的同步需要消耗相当大的资料,在一个循环中调用它绝对不是一个好主意。
例子: import java.util.Vector; public class SYN { public synchronized void method (Object o) { } private void test () { for (int i = 0; i < vector.size(); i++) { method (vector.elementAt(i)); // violation } } private Vector vector = new Vector (5, 5); }
更正: 不要在循环体中调用同步方法,如果必须同步的话,推荐以下方式: import java.util.Vector; public class SYN { public void method (Object o) { } private void test () { synchronized{//在一个同步块中执行非同步方法 for (int i = 0; i < vector.size(); i++) { method (vector.elementAt(i)); } } } private Vector vector = new Vector (5, 5); }
十三、将try/catch块移出循环 把try/catch块放入循环体内,会极大的影响性能,如果编译JIT被关闭或者你所使用的是一个不带JIT的JVM,性能会将下降21%之多! 例子: import java.io.FileInputStream; public class TRY { void method (FileInputStream fis) { for (int i = 0; i < size; i++) { try { // violation _sum += fis.read(); } catch (Exception e) {} } } private int _sum; } 更正: 将try/catch块移出循环 void method (FileInputStream fis) { try { for (int i = 0; i < size; i++) { _sum += fis.read(); } } catch (Exception e) {} } 参考资料: Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.81 – 83
十四、对于boolean值,避免不必要的等式判断 将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值). 移走对于boolean的不必要操作至少会带来2个好处: 1)代码执行的更快 (生成的字节码少了5个字节); 2)代码也会更加干净 。
例子: public class UEQ { boolean method (String string) { return string.endsWith ("a") == true; // Violation } }
更正: class UEQ_fixed { boolean method (String string) { return string.endsWith ("a"); } }
十五、对于常量字符串,用'String' 代替 'StringBuffer' 常量字符串并不需要动态改变长度。 例子: public class USC { String method () { StringBuffer s = new StringBuffer ("Hello"); String t = s + "World!"; return t; } }
更正: 把StringBuffer换成String,如果确定这个String不会再变的话,这将会减少运行开销提高性能。
十六、用'StringTokenizer' 代替 'indexOf()' 和'substring()' 字符串的分析在很多应用中都是常见的。使用indexOf()和substring()来分析字符串容易导致StringIndexOutOfBoundsException。而使用StringTokenizer类来分析字符串则会容易一些,效率也会高一些。
例子: public class UST { void parseString(String string) { int index = 0; while ((index = string.indexOf(".", index)) != -1) { System.out.println (string.substring(index, string.length())); } } }
参考资料: Graig Larman, Rhett Guthrie: "Java 2 Performance and Idiom Guide" Prentice Hall PTR, ISBN: 0-13-014260-3 pp. 282 – 283
十七、使用条件操作符替代"if (cond) return; else return;" 结构 条件操作符更加的简捷 例子: public class IF { public int method(boolean isDone) { if (isDone) { return 0; } else { return 10; } } }
更正: public class IF { public int method(boolean isDone) { return (isDone ? 0 : 10); } }
十八、使用条件操作符代替"if (cond) a = b; else a = c;" 结构 例子: public class IFAS { void method(boolean isTrue) { if (isTrue) { _value = 0; } else { _value = 1; } } private int _value = 0; }
更正: public class IFAS { void method(boolean isTrue) { _value = (isTrue ? 0 : 1); // compact expression. } private int _value = 0; }
十九、不要在循环体中实例化变量 在循环体中实例化临时变量将会增加内存消耗
例子: import java.util.Vector; public class LOOP { void method (Vector v) { for (int i=0;i < v.size();i++) { Object o = new Object(); o = v.elementAt(i); } } } 更正: 在循环体外定义变量,并反复使用 import java.util.Vector; public class LOOP { void method (Vector v) { Object o; for (int i=0;i<v.size();i++) { o = v.elementAt(i); } } }
二十、确定 StringBuffer的容量 StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。
例子: public class RSBC { void method () { StringBuffer buffer = new StringBuffer(); // violation buffer.append ("hello"); } } 更正: 为StringBuffer提供寝大小。 public class RSBC { void method () { StringBuffer buffer = new StringBuffer(MAX); buffer.append ("hello"); } private final int MAX = 100; } 参考资料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques" Addison Wesley, ISBN: 0-201-70429-3 p.30 – 31
二十一、尽可能的使用栈变量 如果一个变量需要经常访问,那么你就需要考虑这个变量的作用域了。static? local?还是实例变量?访问静态变量和实例变量将会比访问局部变量多耗费2-3个时钟周期。 例子: public class USV { void getSum (int[] values) { for (int i=0; i < value.length; i++) { _sum += value[i]; // violation. } } void getSum2 (int[] values) { for (int i=0; i < value.length; i++) { _staticSum += value[i]; } } private int _sum; private static int _staticSum; } 更正: 如果可能,请使用局部变量作为你经常访问的变量。 你可以按下面的方法来修改getSum()方法: void getSum (int[] values) { int sum = _sum; // temporary local variable. for (int i=0; i < value.length; i++) { sum += value[i]; } _sum = sum; } 参考资料: Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.122 – 125
二十二、不要总是使用取反操作符(!) 取反操作符(!)降低程序的可读性,所以不要总是使用。
例子: public class DUN { boolean method (boolean a, boolean b) { if (!a) return !a; else return !b; } }
更正: 如果可能不要使用取反操作符(!)
二十三、与一个接口 进行instanceof操作 基于接口的设计通常是件好事,因为它允许有不同的实现,而又保持灵活。只要可能,对一个对象进行instanceof操作,以判断它是否某一接口要比是否某一个类要快。
例子: public class INSOF { private void method (Object o) { if (o instanceof InterfaceBase) { } // better if (o instanceof ClassBase) { } // worse. } }
class ClassBase {} interface InterfaceBase {}
UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。
在Java中生成UUID主要有以下几种方式:
1. JDK1.5 如果使用的JDK1.5的话,那么生成UUID变成了一件简单的事,以为JDK实现了UUID: java.util.UUID,直接调用即可. UUID uuid = UUID.randomUUID();
2. 第三方开源类库(推荐使用): 最著名的是 JUG .特点上是: 纯Java实现,开源,LGPL协议。采用了Native的方式产生真正的Uuid.而且提供了不同平台的实现,包括:
* Linux / x86 * Windows (98, ME, NT, 2K, XP?) / x86 * Solaris / Sparc * Mac OS X * FreeBSD / x86
import org.doomdark.uuid.UUID; import org.doomdark.uuid.UUIDGenerator;
UUIDGenerator generator = UUIDGenerator.getInstance(); UUID uuid = generator.generateRandomBasedUUID();
3. Java代码实现: 如果你使用JDK1.4或以前版本,又不想加入第三方的类库的话,下面提供了一个纯Java的UUID实现. 不过需要注意的是:这里产生的可能不是真正的UUID,只不过重复的机会少一些而已。 import java.net.InetAddress; import java.net.UnknownHostException;
/** */ /** * UUID(Univeral Unique Identity)类 *
* Title: 生成唯一编码 *
*
* Description: 源自于w3c.org <</font> http://源自于w3c.org > * *
* Copyright: Copyright (c) 2001-2004 *
* * @version 1.0 */ public final class UUID {
/** */ /** * @serial Integer that helps create a unique UID. */ private int unique;
/** */ /** * @serial Long used to record the time. The time will be * used to create a unique UID. */ private long time;
/** */ /** * InetAddress to make the UID globally unique */ private static String address;
/** */ /** * a random number */ private static int hostUnique;
/** */ /** * Used for synchronization */ private static Object mutex;
private static long lastTime;
private static long DELAY;
private static String generateNoNetworkID() { Thread current = Thread.currentThread(); String nid = current.activeCount() + System.getProperty( " os.version " ) + System.getProperty( " user.name " ) + System.getProperty( " java.version " ); System.out.println( " netWorkId = " + nid); MD5 md5 = new MD5(nid); md5.processString(); return md5.getStringDigest(); }
static { hostUnique = ( new Object()).hashCode(); mutex = new Object(); lastTime = System.currentTimeMillis(); DELAY = 10 ; // in milliseconds try { String s = InetAddress.getLocalHost().getHostAddress(); MD5 md5 = new MD5(s); md5.processString(); address = md5.getStringDigest(); } catch (UnknownHostException ex) { address = generateNoNetworkID(); } }
public UUID() { synchronized (mutex) { boolean done = false ; while ( ! done) { time = System.currentTimeMillis(); if (time lastTime + DELAY) { // pause for a second to wait for time to change try { Thread.currentThread().sleep(DELAY); } catch (java.lang.InterruptedException e) { } // ignore exception continue ; } else { lastTime = time; done = true ; } } unique = hostUnique; } }
public String toString() { return Integer.toString(unique, 16 ) + Long.toString(time, 16 ) + address; }
public boolean equals(Object obj) { if ((obj != null ) && (obj instanceof UUID)) { UUID uuid = (UUID) obj; return (unique == uuid.unique && time == uuid.time && address .equals(uuid.address)); } else { return false ; } }
public static void main(String args[]) { System.out.println( new UUID()); System.out.println( new UUID()); System.out.println( new UUID()); long start = System.currentTimeMillis(); System.out.println( new UUID()); long end = System.currentTimeMillis(); System.out.println((end - start)); System.out.println( new UUID().toString().length()); }
/** */ /** * 返回最新的UUID号码 * * @return String UUID,长50位 * */ public final static String getUUID() { UUID uid = new UUID(); return uid.toString(); } }
其中使用到MD5加密算法,实现代码如下:
import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException;
/** */ /** * MD5 加密算法类 * *
* Description: 源自于w3c.org <</font> http://源自于w3c.org > * *
* Copyright: Copyright (c) 2001-2004 *
* * @version 1.0 */ public class MD5 { private static final int BUFFER_SIZE = 1024 ;
private static final int S11 = 7 ;
private static final int S12 = 12 ;
private static final int S13 = 17 ;
private static final int S14 = 22 ;
private static final int S21 = 5 ;
private static final int S22 = 9 ;
private static final int S23 = 14 ;
private static final int S24 = 20 ;
private static final int S31 = 4 ;
private static final int S32 = 11 ;
private static final int S33 = 16 ;
private static final int S34 = 23 ;
private static final int S41 = 6 ;
private static final int S42 = 10 ;
private static final int S43 = 15 ;
private static final int S44 = 21 ;
private static byte padding[] = { ( byte ) 0x80 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 , ( byte ) 0 } ;
private InputStream in = null ;
private boolean stringp = false ;
private int state[] = null ;
private long count = 0 ;
private byte buffer[] = null ;
private byte digest[] = null ;
private static String stringify( byte buf[]) { StringBuffer sb = new StringBuffer( 2 * buf.length); for ( int i = 0 ; i buf.length; i ++ ) { int h = (buf[i] & 0xf0 ) >> 4 ; int l = (buf[i] & 0x0f ); sb.append( new Character(( char ) ((h > 9 ) ? ' a ' + h - 10 : ' 0 ' + h))); sb.append( new Character(( char ) ((l > 9 ) ? ' a ' + l - 10 : ' 0 ' + l))); } return sb.toString(); }
private final int F( int x, int y, int z) { return ((x & y) | (( ~ x) & z)); }
private final int G( int x, int y, int z) { return ((x & z) | (y & ( ~ z))); }
private final int H( int x, int y, int z) { return (x ^ y ^ z); }
private final int I( int x, int y, int z) { return (y ^ (x | ( ~ z))); }
private final int rotate_left( int x, int n) { return ((x <<</span> n) | (x >>> (32 - n))); }
private final int FF( int a, int b, int c, int d, int x, int s, int ac) { a += (F(b, c, d) + x + ac); a = rotate_left(a, s); a += b; return a; }
private final int GG( int a, int b, int c, int d, int x, int s, int ac) { a += (G(b, c, d) + x + ac); a = rotate_left(a, s); a += b; return a; }
private final int HH( int a, int b, int c, int d, int x, int s, int ac) { a += (H(b, c, d) + x + ac); a = rotate_left(a, s); a += b; return a; }
private final int II( int a, int b, int c, int d, int x, int s, int ac) { a += (I(b, c, d) + x + ac); a = rotate_left(a, s); a += b; return a; }
private final void decode( int output[], byte input[], int off, int len) { int i = 0 ; int j = 0 ; for (; j len; i ++ , j += 4 ) { output[i] = ((( int ) (input[off + j] & 0xff )) | ((( int ) (input[off + j + 1 ] & 0xff )) <<</span> 8 ) | ((( int ) (input[off + j + 2 ] & 0xff )) <<</span> 16) | (((int ) (input[off + j + 3 ] & 0xff )) <<</span> 24 )); } }
private final void transform( byte block[], int offset) { int a = state[ 0 ]; int b = state[ 1 ]; int c = state[ 2 ]; int d = state[ 3 ]; int x[] = new int [ 16 ];
decode(x, block, offset, 64 ); /**/ /* Round 1 */ a = FF(a, b, c, d, x[ 0 ], S11, 0xd76aa478 ); /**/ /* 1 */ d = FF(d, a, b, c, x[ 1 ], S12, 0xe8c7b756 ); /**/ /* 2 */ c = FF(c, d, a, b, x[ 2 ], S13, 0x242070db ); /**/ /* 3 */ b = FF(b, c, d, a, x[ 3 ], S14, 0xc1bdceee ); /**/ /* 4 */ a = FF(a, b, c, d, x[ 4 ], S11, 0xf57c0faf ); /**/ /* 5 */ d = FF(d, a, b, c, x[ 5 ], S12, 0x4787c62a ); /**/ /* 6 */ c = FF(c, d, a, b, x[ 6 ], S13, 0xa8304613 ); /**/ /* 7 */ b = FF(b, c, d, a, x[ 7 ], S14, 0xfd469501 ); /**/ /* 8 */ a = FF(a, b, c, d, x[ 8 ], S11, 0x698098d8 ); /**/ /* 9 */ d = FF(d, a, b, c, x[ 9 ], S12, 0x8b44f7af ); /**/ /* 10 */ c = FF(c, d, a, b, x[ 10 ], S13, 0xffff5bb1 ); /**/ /* 11 */ b = FF(b, c, d, a, x[ 11 ], S14, 0x895cd7be ); /**/ /* 12 */ a = FF(a, b, c, d, x[ 12 ], S11, 0x6b901122 ); /**/ /* 13 */ d = FF(d, a, b, c, x[ 13 ], S12, 0xfd987193 ); /**/ /* 14 */ c = FF(c, d, a, b, x[ 14 ], S13, 0xa679438e ); /**/ /* 15 */ b = FF(b, c, d, a, x[ 15 ], S14, 0x49b40821 ); /**/ /* 16 */ /**/ /* Round 2 */ a = GG(a, b, c, d, x[ 1 ], S21, 0xf61e2562 ); /**/ /* 17 */ d = GG(d, a, b, c, x[ 6 ], S22, 0xc040b340 ); /**/ /* 18 */ c = GG(c, d, a, b, x[ 11 ], S23, 0x265e5a51 ); /**/ /* 19 */ b = GG(b, c, d, a, x[ 0 ], S24, 0xe9b6c7aa ); /**/ /* 20 */ a = GG(a, b, c, d, x[ 5 ], S21, 0xd62f105d ); /**/ /* 21 */ d = GG(d, a, b, c, x[ 10 ], S22, 0x2441453 ); /**/ /* 22 */ c = GG(c, d, a, b, x[ 15 ], S23, 0xd8a1e681 ); /**/ /* 23 */ b = GG(b, c, d, a, x[ 4 ], S24, 0xe7d3fbc8 ); /**/ /* 24 */ a = GG(a, b, c, d, x[ 9 ], S21, 0x21e1cde6 ); /**/ /* 25 */ d = GG(d, a, b, c, x[ 14 ], S22, 0xc33707d6 ); /**/ /* 26 */ c = GG(c, d, a, b, x[ 3 ], S23, 0xf4d50d87 ); /**/ /* 27 */ b = GG(b, c, d, a, x[ 8 ], S24, 0x455a14ed ); /**/ /* 28 */ a = GG(a, b, c, d, x[ 13 ], S21, 0xa9e3e905 ); /**/ /* 29 */ d = GG(d, a, b, c, x[ 2 ], S22, 0xfcefa3f8 ); /**/ /* 30 */ c = GG(c, d, a, b, x[ 7 ], S23, 0x676f02d9 ); /**/ /* 31 */ b = GG(b, c, d, a, x[ 12 ], S24, 0x8d2a4c8a ); /**/ /* 32 */
/**/ /* Round 3 */ a = HH(a, b, c, d, x[ 5 ], S31, 0xfffa3942 ); /**/ /* 33 */ d = HH(d, a, b, c, x[ 8 ], S32, 0x8771f681 ); /**/ /* 34 */ c = HH(c, d, a, b, x[ 11 ], S33, 0x6d9d6122 ); /**/ /* 35 */ b = HH(b, c, d, a, x[ 14 ], S34, 0xfde5380c ); /**/ /* 36 */ a = HH(a, b, c, d, x[ 1 ], S31, 0xa4beea44 ); /**/ /* 37 */ d = HH(d, a, b, c, x[ 4 ], S32, 0x4bdecfa9 ); /**/ /* 38 */ c = HH(c, d, a, b, x[ 7 ], S33, 0xf6bb4b60 ); /**/ /* 39 */ b = HH(b, c, d, a, x[ 10 ], S34, 0xbebfbc70 ); /**/ /* 40 */ a = HH(a, b, c, d, x[ 13 ], S31, 0x289b7ec6 ); /**/ /* 41 */ d = HH(d, a, b, c, x[ 0 ], S32, 0xeaa127fa ); /**/ /* 42 */ c = HH(c, d, a, b, x[ 3 ], S33, 0xd4ef3085 ); /**/ /* 43 */ b = HH(b, c, d, a, x[ 6 ], S34, 0x4881d05 ); /**/ /* 44 */ a = HH(a, b, c, d, x[ 9 ], S31, 0xd9d4d039 ); /**/ /* 45 */ d = HH(d, a, b, c, x[ 12 ], S32, 0xe6db99e5 ); /**/ /* 46 */ c = HH(c, d, a, b, x[ 15 ], S33, 0x1fa27cf8 ); /**/ /* 47 */ b = HH(b, c, d, a, x[ 2 ], S34, 0xc4ac5665 ); /**/ /* 48 */
/**/ /* Round 4 */ a = II(a, b, c, d, x[ 0 ], S41, 0xf4292244 ); /**/ /* 49 */ d = II(d, a, b, c, x[ 7 ], S42, 0x432aff97 ); /**/ /* 50 */ c = II(c, d, a, b, x[ 14 ], S43, 0xab9423a7 ); /**/ /* 51 */ b = II(b, c, d, a, x[ 5 ], S44, 0xfc93a039 ); /**/ /* 52 */ a = II(a, b, c, d, x[ 12 ], S41, 0x655b59c3 ); /**/ /* 53 */ d = II(d, a, b, c, x[ 3 ], S42, 0x8f0ccc92 ); /**/ /* 54 */ c = II(c, d, a, b, x[ 10 ], S43, 0xffeff47d ); /**/ /* 55 */ b = II(b, c, d, a, x[ 1 ], S44, 0x85845dd1 ); /**/ /* 56 */ a = II(a, b, c, d, x[ 8 ], S41, 0x6fa87e4f ); /**/ /* 57 */ d = II(d, a, b, c, x[ 15 ], S42, 0xfe2ce6e0 ); /**/ /* 58 */ c = II(c, d, a, b, x[ 6 ], S43, 0xa3014314 ); /**/ /* 59 */ b = II(b, c, d, a, x[ 13 ], S44, 0x4e0811a1 ); /**/ /* 60 */ a = II(a, b, c, d, x[ 4 ], S41, 0xf7537e82 ); /**/ /* 61 */ d = II(d, a, b, c, x[ 11 ], S42, 0xbd3af235 ); /**/ /* 62 */ c = II(c, d, a, b, x[ 2 ], S43, 0x2ad7d2bb ); /**/ /* 63 */ b = II(b, c, d, a, x[ 9 ], S44, 0xeb86d391 ); /**/ /* 64 */
state[ 0 ] += a; state[ 1 ] += b; state[ 2 ] += c; state[ 3 ] += d; }
private final void update( byte input[], int len) { int index = (( int ) (count >> 3 )) & 0x3f ; count += (len <<</span> 3 ); int partLen = 64 - index; int i = 0 ; if (len >= partLen) { System.arraycopy(input, 0 , buffer, index, partLen); transform(buffer, 0 ); for (i = partLen; i + 63 len; i += 64 ) transform(input, i); index = 0 ; } else { i = 0 ; } System.arraycopy(input, i, buffer, index, len - i); }
private byte [] end() { byte bits[] = new byte [ 8 ]; for ( int i = 0 ; i 8 ; i ++ ) bits[i] = ( byte ) ((count >>> (i * 8 )) & 0xff ); int index = (( int ) (count >> 3 )) & 0x3f ; int padlen = (index 56 ) ? ( 56 - index) : ( 120 - index); update(padding, padlen); update(bits, 8 ); return encode(state, 16 ); }
// Encode the content.state array into 16 bytes array private byte [] encode( int input[], int len) { byte output[] = new byte [len]; int i = 0 ; int j = 0 ; for (; j len; i ++ , j += 4 ) { output[j] = ( byte ) ((input[i]) & 0xff ); output[j + 1 ] = ( byte ) ((input[i] >> 8 ) & 0xff ); output[j + 2 ] = ( byte ) ((input[i] >> 16 ) & 0xff ); output[j + 3 ] = ( byte ) ((input[i] >> 24 ) & 0xff ); } return output; }
/** */ /** * Get the digest for our input stream. This method constructs the input * stream digest, and return it, as a a String, following the MD5 (rfc1321) * algorithm, * * @return An instance of String, giving the message digest. * @exception IOException * Thrown if the digestifier was unable to read the input * stream. */
public byte [] getDigest() throws IOException { byte buffer[] = new byte [BUFFER_SIZE]; int got = - 1 ;
if (digest != null ) return digest; while ((got = in.read(buffer)) > 0 ) update(buffer, got); this .digest = end(); return digest; }
/** */ /** * Get the digest, for this string digestifier. This method doesn't throw * any IOException, since it knows that the underlying stream ws built from * a String. */
public byte [] processString() { if ( ! stringp) throw new RuntimeException( this .getClass().getName() + " [processString] " + " not a string. " ); try { return getDigest(); } catch (IOException ex) { } throw new RuntimeException( this .getClass().getName() + " [processString] " + " : implementation error. " ); }
/** */ /** * Get the digest, as a proper string. */
public String getStringDigest() { if (digest == null ) throw new RuntimeException( this .getClass().getName() + " [getStringDigest] " + " : called before processing. " ); return stringify(digest); }
/** */ /** * Construct a digestifier for the given string. * * @param input * The string to be digestified. * @param encoding * the encoding name used (such as UTF8) */
public MD5(String input, String enc) { byte bytes[] = null ; try { bytes = input.getBytes(enc); } catch (UnsupportedEncodingException e) { throw new RuntimeException( " no " + enc + " encoding!!! " ); } this .stringp = true ; this .in = new ByteArrayInputStream(bytes); this .state = new int [ 4 ]; this .buffer = new byte [ 64 ]; this .count = 0 ; state[ 0 ] = 0x67452301 ; state[ 1 ] = 0xefcdab89 ; state[ 2 ] = 0x98badcfe ; state[ 3 ] = 0x10325476 ; }
/** */ /** * Construct a digestifier for the given string. * * @param input * The string to be digestified. */
public MD5(String input) { this (input, " UTF8 " ); }
/** */ /** * Construct a digestifier for the given input stream. * * @param in * The input stream to be digestified. */
public MD5(InputStream in) { this .stringp = false ; this .in = in; this .state = new int [ 4 ]; this .buffer = new byte [ 64 ]; this .count = 0 ; state[ 0 ] = 0x67452301 ; state[ 1 ] = 0xefcdab89 ; state[ 2 ] = 0x98badcfe ; state[ 3 ] = 0x10325476 ; }
public static void main(String args[]) throws IOException { if (args.length != 1 ) { System.out.println( " Md5 " ); System.exit( 1 ); } MD5 md5 = new MD5( new FileInputStream( new File(args[ 0 ]))); byte b[] = md5.getDigest(); System.out.println(stringify(b)); }
}
这是一个非常好的Socket服务器样板程序,这个socket服务器可以为你建立指定的监听端口、客户端请求响应机制等一些服务器所具备的基本框架
/*
* Copyright (c) 2000 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 2nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book (recommended),
* visit http://www.davidflanagan.com/javaexamples2.
*/
import java.io.*;
import java.net.*;
import java.util.*;
/**
* This class is a generic framework for a flexible, multi-threaded server.
* It listens on any number of specified ports, and, when it receives a
* connection on a port, passes input and output streams to a specified Service
* object which provides the actual service. It can limit the number of
* concurrent connections, and logs activity to a specified stream.
**/
public class Server {
/**
* A main() method for running the server as a standalone program. The
* command-line arguments to the program should be pairs of servicenames
* and port numbers. For each pair, the program will dynamically load the
* named Service class, instantiate it, and tell the server to provide
* that Service on the specified port. The special -control argument
* should be followed by a password and port, and will start special
* server control service running on the specified port, protected by the
* specified password.
**/
public static void main(String[] args) {
try {
if (args.length < 2) // Check number of arguments
throw new IllegalArgumentException("Must specify a service");
// Create a Server object that uses standard out as its log and
// has a limit of ten concurrent connections at once.
Server s = new Server(System.out, 10);
// Parse the argument list
int i = 0;
while(i < args.length) {
if (args[i].equals("-control")) { // Handle the -control arg
i++;
String password = args[i++];
int port = Integer.parseInt(args[i++]);
// add control service
s.addService(new Control(s, password), port);
}
else {
// Otherwise start a named service on the specified port.
// Dynamically load and instantiate a Service class
String serviceName = args[i++];
Class serviceClass = Class.forName(serviceName);
Service service = (Service)serviceClass.newInstance();
int port = Integer.parseInt(args[i++]);
s.addService(service, port);
}
}
}
catch (Exception e) { // Display a message if anything goes wrong
System.err.println("Server: " + e);
System.err.println("Usage: java Server " +
"[-control ] " +
"[ ... ]");
System.exit(1);
}
}
// This is the state for the server
Map services; // Hashtable mapping ports to Listeners
Set connections; // The set of current connections
int maxConnections; // The concurrent connection limit
ThreadGroup threadGroup; // The threadgroup for all our threads
PrintWriter logStream; // Where we send our logging output to
/**
* This is the Server() constructor. It must be passed a stream
* to send log output to (may be null), and the limit on the number of
* concurrent connections.
**/
public Server(OutputStream logStream, int maxConnections) {
setLogStream(logStream);
log("Starting server");
threadGroup = new ThreadGroup(Server.class.getName());
this.maxConnections = maxConnections;
services = new HashMap();
connections = new HashSet(maxConnections);
}
/**
* A public method to set the current logging stream. Pass null
* to turn logging off
**/
public synchronized void setLogStream(OutputStream out) {
if (out != null) logStream = new PrintWriter(out);
else logStream = null;
}
/** Write the specified string to the log */
protected synchronized void log(String s) {
if (logStream != null) {
logStream.println("[" + new Date() + "] " + s);
logStream.flush();
}
}
/** Write the specified object to the log */
protected void log(Object o) { log(o.toString()); }
/**
* This method makes the server start providing a new service.
* It runs the specified Service object on the specified port.
**/
public synchronized void addService(Service service, int port)
throws IOException
{
Integer key = new Integer(port); // the hashtable key
// Check whether a service is already on that port
if (services.get(key) != null)
throw new IllegalArgumentException("Port " + port +
" already in use.");
// Create a Listener object to listen for connections on the port
Listener listener = new Listener(threadGroup, port, service);
// Store it in the hashtable
services.put(key, listener);
// Log it
log("Starting service " + service.getClass().getName() +
" on port " + port);
// Start the listener running.
listener.start();
}
/**
* This method makes the server stop providing a service on a port.
* It does not terminate any pending connections to that service, merely
* causes the server to stop accepting new connections
**/
public synchronized void removeService(int port) {
Integer key = new Integer(port); // hashtable key
// Look up the Listener object for the port in the hashtable
final Listener listener = (Listener) services.get(key);
if (listener == null) return;
// Ask the listener to stop
listener.pleaseStop();
// Remove it from the hashtable
services.remove(key);
// And log it.
log("Stopping service " + listener.service.getClass().getName() +
" on port " + port);
}
/**
* This nested Thread subclass is a "listener". It listens for
* connections on a specified port (using a ServerSocket) and when it gets
* a connection request, it calls the servers addConnection() method to
* accept (or reject) the connection. There is one Listener for each
* Service being provided by the Server.
**/
public class Listener extends Thread {
ServerSocket listen_socket; // The socket to listen for connections
int port; // The port we're listening on
Service service; // The service to provide on that port
volatile boolean stop = false; // Whether we've been asked to stop
/**
* The Listener constructor creates a thread for itself in the
* threadgroup. It creates a ServerSocket to listen for connections
* on the specified port. It arranges for the ServerSocket to be
* interruptible, so that services can be removed from the server.
**/
public Listener(ThreadGroup group, int port, Service service)
throws IOException
{
super(group, "Listener:" + port);
listen_socket = new ServerSocket(port);
// give it a non-zero timeout so accept() can be interrupted
listen_socket.setSoTimeout(600000);
this.port = port;
this.service = service;
}
/**
* This is the polite way to get a Listener to stop accepting
* connections
***/
public void pleaseStop() {
this.stop = true; // Set the stop flag
this.interrupt(); // Stop blocking in accept()
try { listen_socket.close(); } // Stop listening.
catch(IOException e) {}
}
/**
* A Listener is a Thread, and this is its body.
* Wait for connection requests, accept them, and pass the socket on
* to the addConnection method of the server.
**/
public void run() {
while(!stop) { // loop until we're asked to stop.
try {
Socket client = listen_socket.accept();
addConnection(client, service);
}
catch (InterruptedIOException e) {}
catch (IOException e) {log(e);}
}
}
}
/**
* This is the method that Listener objects call when they accept a
* connection from a client. It either creates a Connection object
* for the connection and adds it to the list of current connections,
* or, if the limit on connections has been reached, it closes the
* connection.
**/
protected synchronized void addConnection(Socket s, Service service) {
// If the connection limit has been reached
if (connections.size() >= maxConnections) {
try {
// Then tell the client it is being rejected.
PrintWriter out = new PrintWriter(s.getOutputStream());
out.print("Connection refused; " +
"the server is busy; please try again later.\n");
out.flush();
// And close the connection to the rejected client.
s.close();
// And log it, of course
log("Connection refused to " +
s.getInetAddress().getHostAddress() +
":" + s.getPort() + ": max connections reached.");
} catch (IOException e) {log(e);}
}
else { // Otherwise, if the limit has not been reached
// Create a Connection thread to handle this connection
Connection c = new Connection(s, service);
// Add it to the list of current connections
connections.add(c);
// Log this new connection
log("Connected to " + s.getInetAddress().getHostAddress() +
":" + s.getPort() + " on port " + s.getLocalPort() +
" for service " + service.getClass().getName());
// And start the Connection thread to provide the service
c.start();
}
}
/**
* A Connection thread calls this method just before it exits. It removes
* the specified Connection from the set of connections.
**/
protected synchronized void endConnection(Connection c) {
connections.remove(c);
log("Connection to " + c.client.getInetAddress().getHostAddress() +
":" + c.client.getPort() + " closed.");
}
/** Change the current connection limit */
public synchronized void setMaxConnections(int max) {
maxConnections = max;
}
/**
* This method displays status information about the server on the
* specified stream. It can be used for debugging, and is used by the
* Control service later in this example.
**/
public synchronized void displayStatus(PrintWriter out) {
// Display a list of all Services that are being provided
Iterator keys = services.keySet().iterator();
while(keys.hasNext()) {
Integer port = (Integer) keys.next();
Listener listener = (Listener) services.get(port);
out.print("SERVICE " + listener.service.getClass().getName()
+ " ON PORT " + port + "\n");
}
// Display the current connection limit
out.print("MAX CONNECTIONS: " + maxConnections + "\n");
// Display a list of all current connections
Iterator conns = connections.iterator();
while(conns.hasNext()) {
Connection c = (Connection)conns.next();
out.print("CONNECTED TO " +
c.client.getInetAddress().getHostAddress() +
":" + c.client.getPort() + " ON PORT " +
c.client.getLocalPort() + " FOR SERVICE " +
c.service.getClass().getName() + "\n");
}
}
/**
* This class is a subclass of Thread that handles an individual
* connection between a client and a Service provided by this server.
* Because each such connection has a thread of its own, each Service can
* have multiple connections pending at once. Despite all the other
* threads in use, this is the key feature that makes this a
* multi-threaded server implementation.
**/
public class Connection extends Thread {
Socket client; // The socket to talk to the client through
Service service; // The service being provided to that client
/**
* This constructor just saves some state and calls the superclass
* constructor to create a thread to handle the connection. Connection
* objects are created by Listener threads. These threads are part of
* the server's ThreadGroup, so all Connection threads are part of that
* group, too.
**/
public Connection(Socket client, Service service) {
super("Server.Connection:" +
client.getInetAddress().getHostAddress() +
":" + client.getPort());
this.client = client;
this.service = service;
}
/**
* This is the body of each and every Connection thread.
* All it does is pass the client input and output streams to the
* serve() method of the specified Service object. That method is
* responsible for reading from and writing to those streams to
* provide the actual service. Recall that the Service object has
* been passed from the Server.addService() method to a Listener
* object to the addConnection() method to this Connection object, and
* is now finally being used to provide the service. Note that just
* before this thread exits it always calls the endConnection() method
* to remove itself from the set of connections
**/
public void run() {
try {
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
service.serve(in, out);
}
catch (IOException e) {log(e);}
finally { endConnection(this); }
}
}
/**
* Here is the Service interface that we have seen so much of. It defines
* only a single method which is invoked to provide the service. serve()
* will be passed an input stream and an output stream to the client. It
* should do whatever it wants with them, and should close them before
* returning.
*
* All connections through the same port to this service share a single
* Service object. Thus, any state local to an individual connection must
* be stored in local variables within the serve() method. State that
* should be global to all connections on the same port should be stored
* in instance variables of the Service class. If the same Service is
* running on more than one port, there will typically be different
* Service instances for each port. Data that should be global to all
* connections on any port should be stored in static variables.
*
* Note that implementations of this interface must have a no-argument
* constructor if they are to be dynamically instantiated by the main()
* method of the Server class.
**/
public interface Service {
public void serve(InputStream in, OutputStream out) throws IOException;
}
/**
* A very simple service. It displays the current time on the server
* to the client, and closes the connection.
**/
public static class Time implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
PrintWriter out = new PrintWriter(o);
out.print(new Date() + "\n");
out.close();
i.close();
}
}
/**
* This is another example service. It reads lines of input from the
* client, and sends them back, reversed. It also displays a welcome
* message and instructions, and closes the connection when the user
* enters a '.' on a line by itself.
**/
public static class Reverse implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(o)));
out.print("Welcome to the line reversal server.\n");
out.print("Enter lines. End with a '.' on a line by itself.\n");
for(;;) {
out.print("> ");
out.flush();
String line = in.readLine();
if ((line == null) || line.equals(".")) break;
for(int j = line.length()-1; j >= 0; j--)
out.print(line.charAt(j));
out.print("\n");
}
out.close();
in.close();
}
}
/**
* This service is an HTTP mirror, just like the HttpMirror class
* implemented earlier in this chapter. It echos back the client's
* HTTP request
**/
public static class HTTPMirror implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out = new PrintWriter(o);
out.print("HTTP/1.0 200 \n");
out.print("Content-Type: text/plain\n\n");
String line;
while((line = in.readLine()) != null) {
if (line.length() == 0) break;
out.print(line + "\n");
}
out.close();
in.close();
}
}
/**
* This service demonstrates how to maintain state across connections by
* saving it in instance variables and using synchronized access to those
* variables. It maintains a count of how many clients have connected and
* tells each client what number it is
**/
public static class UniqueID implements Service {
public int id=0;
public synchronized int nextId() { return id++; }
public void serve(InputStream i, OutputStream o) throws IOException {
PrintWriter out = new PrintWriter(o);
out.print("You are client #: " + nextId() + "\n");
out.close();
i.close();
}
}
/**
* This is a non-trivial service. It implements a command-based protocol
* that gives password-protected runtime control over the operation of the
* server. See the main() method of the Server class to see how this
* service is started.
*
* The recognized commands are:
* password: give password; authorization is required for most commands
* add: dynamically add a named service on a specified port
* remove: dynamically remove the service running on a specified port
* max: change the current maximum connection limit.
* status: display current services, connections, and connection limit
* help: display a help message
* quit: disconnect
*
* This service displays a prompt, and sends all of its output to the user
* in capital letters. Only one client is allowed to connect to this
* service at a time.
**/
public static class Control implements Service {
Server server; // The server we control
String password; // The password we require
boolean connected = false; // Whether a client is already connected
/**
* Create a new Control service. It will control the specified Server
* object, and will require the specified password for authorization
* Note that this Service does not have a no argument constructor,
* which means that it cannot be dynamically instantiated and added as
* the other, generic services above can be.
**/
public Control(Server server, String password) {
this.server = server;
this.password = password;
}
/**
* This is the serve method that provides the service. It reads a
* line the client, and uses java.util.StringTokenizer to parse it
* into commands and arguments. It does various things depending on
* the command.
**/
public void serve(InputStream i, OutputStream o) throws IOException {
// Setup the streams
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out = new PrintWriter(o);
String line; // For reading client input lines
// Has the user has given the password yet?
boolean authorized = false;
// If there is already a client connected to this service, display
// a message to this client and close the connection. We use a
// synchronized block to prevent a race condition.
synchronized(this) {
if (connected) {
out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n");
out.close();
return;
}
else connected = true;
}
// This is the main loop: read a command, parse it, and handle it
for(;;) { // infinite loop
out.print("> "); // Display a prompt
out.flush(); // Make it appear right away
line = in.readLine(); // Get the user's input
if (line == null) break; // Quit if we get EOF.
try {
// Use a StringTokenizer to parse the user's command
StringTokenizer t = new StringTokenizer(line);
if (!t.hasMoreTokens()) continue; // if input was empty
// Get first word of the input and convert to lower case
String command = t.nextToken().toLowerCase();
// Now compare to each of the possible commands, doing the
// appropriate thing for each command
if (command.equals("password")) { // Password command
String p = t.nextToken(); // Get the next word
if (p.equals(this.password)) { // Is it the password?
out.print("OK\n"); // Say so
authorized = true; // Grant authorization
}
else out.print("INVALID PASSWORD\n"); // Otherwise fail
}
else if (command.equals("add")) { // Add Service command
// Check whether password has been given
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
// Get the name of the service and try to
// dynamically load and instantiate it.
// Exceptions will be handled below
String serviceName = t.nextToken();
Class serviceClass = Class.forName(serviceName);
Service service;
try {
service = (Service)serviceClass.newInstance();
}
catch (NoSuchMethodError e) {
throw new IllegalArgumentException(
"Service must have a " +
"no-argument constructor");
}
int port = Integer.parseInt(t.nextToken());
// If no exceptions occurred, add the service
server.addService(service, port);
out.print("SERVICE ADDED\n"); // acknowledge
}
}
else if (command.equals("remove")) { // Remove service
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
int port = Integer.parseInt(t.nextToken());
server.removeService(port); // remove the service
out.print("SERVICE REMOVED\n"); // acknowledge
}
}
else if (command.equals("max")) { // Set connection limit
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
int max = Integer.parseInt(t.nextToken());
server.setMaxConnections(max);
out.print("MAX CONNECTIONS CHANGED\n");
}
}
else if (command.equals("status")) { // Status Display
if (!authorized) out.print("PASSWORD REQUIRED\n");
else server.displayStatus(out);
}
else if (command.equals("help")) { // Help command
// Display command syntax. Password not required
out.print("COMMANDS:\n" +
"\tpassword \n" +
"\tadd \n" +
"\tremove \n" +
"\tmax \n" +
"\tstatus\n" +
"\thelp\n" +
"\tquit\n");
}
else if (command.equals("quit")) break; // Quit command.
else out.print("UNRECOGNIZED COMMAND\n"); // Error
}
catch (Exception e) {
// If an exception occurred during the command, print an
// error message, then output details of the exception.
out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" +
e + "\n");
}
}
// Finally, when the loop command loop ends, close the streams
// and set our connected flag to false so that other clients can
// now connect.
connected = false;
out.close();
in.close();
}
}
}
|
一、软件开发技术
1)服务器端
在最近5年内,Java还是主流,不光是因为当前的普及程度和遗留系统问题,而且除Microsoft几乎所有大公司都投资到Java上面的原因,此外开源也是一股无法忽略的力量:除了Java方面的开源框架在推动Java,也有Linux在带动java企业应用在普及(别忘记dotnet只能在 Windows Server上面运行)
dotnet有自己的优势,但是在五年内无法和Java取得均势,不光是因为Java普及带来的优势,也不光因为开源界对java的推动,也不光因为其他大公司在java上面的投资,而是很多公司的行业性质决定了dotnet的出局,例如电信行业,金融行业,电子政务行业等等,是根本没有可能采用 dotnet的。
Python和Ruby算不上后起,但是很有竞争实力,不过基于上面的原因,仍然不能成为主流。
在Java服务器端技术中,清晰的分为两条路线:高端的商业路线,这条路线是EJB3,J2EE5.0;低端的开源路线,这条路线是Hibernate, Spring。这两条路线也有重叠的地方,例如开源的Struts几乎成为J2EE Web层的标准,开源的Hibernate奠定了EJB3的基础。但是划分路线不是基于技术上的区别,而是基于商业运作上的区别。注重技术支持和商业服务的公司会选择前者,注重成本控制和选择自由的公司会选择后者。
商业路线的技术方案是:EJB3+Struts; 开源路线的技术方案是:Spring+Hibernate+Struts/Webwork
Struts是一个很成功的开源框架,它的地位短期内还无法动摇,JavaEye有一项使命,就是动摇Struts在Java Web领域的地位,把它赶下王座,把Webwork扶上位!
商业的Web层技术,JSTL算是一个不错的东西,但是和灵活的模板语言如FreeMarker相比,却有很大的差距。JSF基本上是一个没有前途的东西。商业Web层技术因为一直没有出现好的应用,这样也导致了Struts的上位。
服务器端业务层和持久层框架,我非常看好EJB3,原因也不用多谈了,从商业上来说,需要这样一个东西,跨国公司们也需要这样一个产品来卖,来取代糟糕的 EJB2。开源的方案里面,Spring+Hibenrate是一个很好的商业方案的开源替代,他们不存在很直接的竞争,而是一个互补的关系。这里比较尴尬的反而是JDO:JDO是商业产品(目前没有好的开源实现),造成开源应用不会对它感兴趣,JDO没有一个像EJB容器那样的脱管环境,造成商业方案对它不感兴趣。不过有了JDO,我觉得是对EJB3,对Hibernate形成一个良好的竞争环境,这一点是非常有利的。
2)客户端技术
准确的说是RIA应用。虽然我前面对XAML进行了正面的评价,但是我认为我前面有些结论给错了。经过这段时间,我觉得,XAML即时在多年之后,也未必能够成为一个非常成功的解决方案。道理很二:
1、XAML会带来比ActiveX更严重的安全性问题。 XAML本质上就是一个本地应用程序,虽然号称可以在IE浏览器里面运行,但IE就是一个皮而已,XAML应用具备对本地资源完全的访问能力(就算IE限制也没有用,IE限制就丧失功能,那样的话,功能并不会比Javascript来得更多;不限制的话,就为所欲为了),因此只要IE具备了运行XAML的能力,黑客将可以非常轻易的通过IE进行入侵,这仅仅需要引导用户在不知不觉中访问一个恶意的网页就搞定了!用户必须面临选择:要么禁止IE对XAML的运行能力,要么接受随时被攻击的危险。
2、XAML应用本质上也是RIA应用,因此必须进行大量的RPC调用 当前XAML采用XML Web Services进行通讯,这是一种低效的RPC。当前的XAML案例中并没有注意到RPC领域,实际上根据我现在做RIA的体验来说,RPC绝对不是一个简单的事情,要考虑的问题非常多。
从当前的阶段来说,最实际可用的方案有两个:
1、AJAX 实际上就是基于XMLHTTP的JS异步交互,这个东西已经出现很多年了,最近随着Google应用和Sun Blueprint的推出开始火热。我原来对这个东西持否定态度,但是后来转变了。我原来否定态度的一个前提就是:XMLHTTP缺乏成熟的组件库!但是没有想到的是,现在XMLHTTP从去年下半年开始,如雨后春笋般冒出来。AJAX应用最大的好处就是充分利用现有资源,我认为应成为RIA应用的首选。
2、Flash Flash的优势也很明显,强大的AS支持,强大的组件可视化设计,强大的交互能力和很炫的用户体验,并且Flash Remoting也已经非常成熟了。Flash的缺点就是Flash虽然嵌入网页,但是和网页没有数据交互能力,Flash另一个缺点就是不适合处理大量文本内容(HTML最适合)。现在有些人开始滥用Flash了。
因此比较好的方式可能是两种混用,一般不过度复杂的交互交给AJAX,非常复杂,甚至需要托拽操作的,交给Flash。
总结一下:
软件开发领域服务器端技术Java是主流,两个技术路线,一个是EJB3,一个是Spring+Hibernate,此外iBATIS也有一席之地;客户端技术就是AJAX和Flash。
二、数据库技术
基本上格局不会发生多大变化,Oracle还是高高在上,SQL Server进一步蚕食NT平台其他数据库的领地。开源方面,MySQL将一枝独秀,但是开源数据库在很多方面还是和商业数据库有无法拉近的巨大差距。这也使得商业数据库的地位不可替代。我会比较关注Oracle,MySQL这两个数据库。面向对象数据库仍然不会有什么起色。
三、桌面编程技术
我还是相信一点,对于桌面应用来说,本地代码的位置永远无法被取代,所以我总觉得XAML那样的东西效率实在很成问题。Longhorn要像成熟,也不是第一个版本就可以达到的。当前桌面应用开发技术,还是首推Delphi,不过我觉得Python是后起之秀,非常有可能在未来取代Delphi。
初探在下一代 Windows 中编写和部署应用程序 http://www.microsoft.com/china/MSDN/library/windev/longhorn/DevelopAppLonghorn.mspx
首先,以Microsoft公司的实力和Windows操作系统的占有率来说,Longhorn迟早会被普及,而XAML的开发方式也有可能普及的。记得当初WindowsXP刚出来的时候,因为资源占用率和新的激活制度招致一片骂声,但是慢慢的,现在也都接受了下来。由此可以推断,Longhorn以其更加丰富的桌面功能和诱人的外观,会在将来成为主流。
但是Longhorn什么时候才会全面普及,这是很值得琢磨的问题。WindowsXP是2001年推出的,在随后的几年,Microsoft采用了一些商业手段来迫使用户升级,例如企图取消Windows98的技术支持,不再提供WindowsNT技术支持,不再销售 WindowsNT/Windows98,将Windows2000保持在一个比较高的售价的同时,对WindowsXP推出优惠价格,让 WindowsXP的售价低于Windows2000等等手段。但是直到现在,Windows2000仍然占据了非常高的份额,据我个人的观察是比 WindowsXP略高。按照这种情况来推断,Longhorn要普及,恐怕难度更大,非常多的用户现在仍然是Windows2000的死忠派, WindowsXP推广了四年还未能超过Windows2000,那么Longhorn究竟要几年才能超过WindowsXP呢?我估计四年以上是起码的。
XAML应用程序不同以往,它只能跑在Longhorn上面,甚至比Java和dotnet要求更严格,后者仅仅下载安装一个运行环境就可以了,但是前者要求你必须更新操作系统。XAML在IE浏览器中运行虽然肯定是下一代RIA的主流,但是不可忽视的问题是,只要Longhorn没有彻底淘汰 Windows2000/XP,软件开发商和网站开发商就不敢大面积采用XAML。而根据我的观察,现在企业中,Windows98仍有少部分市场份额。因此Longhorn必须要等待到彻底的,毫不残留的淘汰Windows98,Windows2000,WindowsXP之后,才会全面普及,而在此之前,不得不经历一个漫长的过渡期。
就好像现在,假设你开发桌面应用程序,你敢只针对WindowsXP开发吗?而彻底不支持98和2000吗?我想,没有哪个软件开发商敢这样做。除非 Windows2000几乎被彻底淘汰了,你才敢这样做,但是WindowsXP已经推出四年了,还没有Windows2000占用率高,哪全面淘汰究竟要几年呢?再看看现在dotnet winforms应用,推出也已经五年时间了,但是到现在仍然没有普及开来,根本的原因就是Windows2000/WindowsXP没有预装 dotnet framework。仅仅是需要打包安装一个运行环境就使得winforms五年都推广不了,更何况要求你升级操作系统呢?
我个人的估计是,假设2006年Longhorn如期上市,那么将需要7-9年时间来彻底淘汰Windows2000/WindowsXP。 Longhorm上面XAML应用的初步普及也至少需要4-5年时间以后才会有软件开发商大量去做(想向dotnet是2000年开始宣传和推广的,到 2004年开始普及,今年和明年才会全面普及)。因此,基于XAML应用的普及可能是在2010年以后!上面的估计中还没有包括MacOS 和Linux在桌面会否有什么表现。
先说说服务器端吧:
从可预见的未来来看,服务器和客户端TCP通讯的主流方式一定是HTTP协议(即时通讯软件走UDP端口,不在讨论范围)。在基于HTTP协议之上,又分为两类:一类是SOAP协议,异构系统支持良好,但是性能很差,目前Microsoft很喜欢用这种方式;一类是轻量级二进制协议,例如Flash的 AMF协议,Resin的Hessian协议。值得一提的是,不管哪种方式,他们都支持异构的系统,所以完全可用在客户端采用dotnet,在服务器端采用Java或者Python。因此,XAML的流行不会对服务器端技术产生致命的影响(肯定会提高dotnet的服务器的市场份额)。所以我们可用抛开客户端影响,单独来看服务器端技术:
1、Java Java是当前服务器端技术当之无愧的王者,在未来五年内,也不会有任何动摇(受到dotnet和python的影响,市场份额会下降一些)。Java特别有利的一点是,现在有太多的现存系统基于Java,这些系统都不会轻易迁移到其他平台上。另外还有一个决定因素是除了Microsoft之外的几乎全部 IT大公司都在Java方面的投资巨大,放弃Java对他们来说也意味着沉重的打击,甚至毁灭性的打击。这些公司可以列很长很长,IBM,HP, Oracle,SAP,Sun,BEA,Macromedia等等。
2、dotnet 由于Microsoft的影响力,dotnet会成为为仅次于Java的第二大服务器端技术,但是Microsoft有一个隐忧,就是Linux操作系统在服务器端的高速成长。虽然现在Linux在整个服务器端市场的出货量只有13%左右,但是成长率惊人,根据我看到的资料显示,到2008年,将占据 25%以上的市场份额。考虑到很多公司是自己安装Linux,因此不会被硬件服务器厂商统计进来,因此Linux的服务器端的市场份额应该比25%高一些。并且现在主要的服务器厂商都对Linux有非常巨大的投入和支持,这些公司包括IBM,HP,Dell(只有Sun不支持),因此Linux在未来会对Windows在服务器端的市场构成最严重的威胁。
不要忘记dotnet只能在Windows平台上面跑,虽然有mono,但是你不可能移植MTS,COM+,SQL Server etc。所以只要Linux在服务器市场对Windows构成持续的威胁,dotnet就不可能超过Java,Java的地位还是稳稳的老大。从某种程度上来说,Java的命运是和Linux联系在一起的,只要Linux在服务器端不输于Windows,Java就稳稳压制dotnet。
BTW:从未来来看,Linux和Windows会在低端和中端服务器市场成为主要竞争对手,由于各自都有其不可替代性,所以双方都不可能彻底消灭对方,最大的可能性是Linux和Windows平分市场,或者Windows市场份额略高一点。
3、Python 我个人认为Python会成长为第三大服务器端技术,Python成长于开源,但是又有商业公司来商业运作,并且背后还有大公司的支持,在欧洲普及的非常好。当然最重要的原因是我觉得Python在技术上非常先进,并且技术发展方向上比较统一,不会出现Java那种吵架的事情。
4、PHP PHP这东西是不错,Yahoo也在用,IBM现在也对他感兴趣,但是我还是要说PHP没有太广阔的前途,原因很简单,PHP没有服务端中间件,例如 Java有App Server,dotnet有IIS/MTS,Python有Zope,但是PHP他就是一个脚本,没有自己的中间件就是致命问题。Yahoo用PHP有其特定的原因,主要是从原先自己的技术迁移到PHP很方便,而IBM支持PHP,显然醉翁之意不在酒,IBM意不在推广PHP,而在于争取到那些使用 PHP的商业大客户们,向他们卖服务。
BTW:感觉欧洲用Python/PHP的很多,似乎开源在欧洲非常深入人心。
从服务器端技术来说,Java还是我们最需要下功夫去学习和掌握的,此外,我会比较倾向于钻研和应用Python,而不是dotnet。原因也很简单,跟随Micorsoft的技术会很辛苦,Microsoft产生的新概念多,他总是会猛的推出n多种技术,然后让他们在市场上自己生存,最后根据市场反馈,无情的抛弃某些东西,大力推进有市场前景的东西,这样的例子太多了,举不胜举了。我的感觉就是这种方式会让Microsft经过市场尝试在技术竞争中筛选最优秀的技术,但是对于Microsoft技术的跟随者来说,未免有点太不公平,整天吭哧吭哧被Microsoft拿来当免费的试验品来用。我特别不理解的是MSDN宇宙版,Microsoft总是把无穷无尽的文档灌给你,让你永远学不完,但实际上我真的不需要那么多概念,我只需要能够很好的完成我工作的技术,并且这个技术可以持续的完善就好了。而不是今天给我这样一个东西,明天灌给我无穷的文档,后天当我用顺手以后,又告诉我这东西作废了,你给我重新学习新东西,然后又是无穷的文档,总之很恼火。
所以就是:重点学习Java,有时间去学习Python,保持对dotnet的关注即可。
客户端:
前面说了那么多XAML的东西,都是和这有关,七年以后肯定是XAML的天下,但是五到七年之内还不是:
1、Java Java在客户端真的是扶不起的阿斗,这都怪Sun。Sun造就了Java的成功,又一手毁了Java在客户端的市场。那些个Swing和SWT的死忠团也不要和我争什么,我也懒得和你们争,你们觉得好就好吧,道不同不相与谋,你觉得好你就用你的,我觉得不好我就用别的。用不着缠着我非逼我说Java做客户端好,没必要,况且就算你逼我承认又怎样?我就是玉皇大帝金口玉言了?得到我的承认,Java就有前途了?我好像还没有那么大本领吧?就是IBM, Sun也没有那么大本领,所以好不好也不是我说了算,用不着逼我。
2、dotnet winforms 由于Windows2000/WindowsXP不带dotnet CLR,所以winforms一直没有能够普及得很好,等Longhorn一出来,又变成了XAML了,winforms又被淘汰了,所以 winforms的地位特别尴尬,但是在这5-7年中,你想开发既能够在Windows2000/WindowsXP,又能够在Longhorn上面跑的桌面程序,winforms好像又是Microsoft技术中最好的选择。所以只好一直尴尬下去。
3、VC,VB dotnet出来以后就开始尴尬了,说用吧,好像很落伍了,都dotnet时代了,说不用吧,又没有好的替代品,现阶段开发桌面程序,还真得不得不用,而且还挺好用的。所以VC6SP5,VB6的死忠团也比较多。
4、Delphi dotnet出来以后Borland就开始跟风了,这一跟风,连老本都跟没有了。未来的XAML时代,我也不知道Borland怎样找自己的定位,但不管怎么说,从历史来看,本地代码的应用程序永远有它一席之地!就算XAML又如何如何做得漂亮了,关键的地方,和特定资源处理相关的部分,还是本地代码的程序管用。你看VB出来多少年了,用VB开发的都是一些上层的项目级别的应用软件,一旦涉及产品领域,还是VC和Delphi管用。所以现在大家还是不得不用Delphi7阿。
BTW:XAML应用致力于快速开发项目级别的应用,特别是可以跑在IE浏览器里面的,因此是RIA的首选。但是毕竟也有很多不适合用RIA的场所,特别是例如我要备份某些文件,你用XAML?那性能就不用提了。所以Delphi如果好好发展VCL,封装Windows32 API,我觉得也是一条路,未必比现在跟随dotnet差。
5、Flash RIA 其实我觉得Flash不适合做RIA的,但是Flash普及率太高,XAML又离普及太遥远,而Flash现在就可以用了,所以是当前RIA的首选。不过我对Macromedia公司比较失望,如果Macromedia能够公布Flash实现细节,作为一个公开的标准向ISO提交,同时免费开源Flex,我敢说,Flash RIA会迅速普及的。等5-7年XAML的时代,由于Flash的市场占有率,XAML就未必能拼得过Flash。可惜的是Macromedia公司目光过于短浅,只知道赚眼前的小钱。
6、Python 这5-7年内,RIA应用和RCP应用不会统一,XAML才具备将RIA和RCP统一的实力。从这5-7年来看,Flash是RIA的首选,而RCP的首选,我要推荐Python。原因前面已经提过,简单总结一下: 1)wxWidgets是一个比MFC优雅的库,TortoiseCVS用wxWidges而不用MFC,就是因为wxWidgets好用,而不是为了可以移植。 2)Python的面向对象脚本语言编程适合快速界面开发 3)Python在服务器端和客户端都非常有前途,可以形成一个统一的解决方案,这一点明显比Java有优势 4)Python桌面应用程序可以完全编译为本地代码,脱离Python运行环境,这一点比dotnet winforms都有优势 5)Python可以不受限制的任意调用Windows32 API,所以凡是VC6可以做的事情,Python就可以做
试想一下,现在我们开发桌面应用程序有什么要求? 一、不要附带一个JRE或者CLR的累赘 二、可以快速开发 三、性能要有保证 四、方便的远程方法调用支持 此外如果能够跨平台就最好了
Java前三点都不符合;dotnet winforms不符合一;VC6不符合二和四,VB6不符合三和四;Delphi7符合前四点;Flash RIA不符合三;Python全部都符合!并且请记住Python是一个完全开源免费的方案!
客户端技术在这5-7年中,在RIA领域我会学习一下Flash,在RCP领域我会重点学习Python,此外会观望一下XAML。
人们总是偏爱“大词”。一个表达方式,如果听起来足够响亮,写在纸上能够吸引眼球,那就会变成很多人的新宠。但同样是这些大词,经过太多人的传递、消费之后,原本的含义反而像硬币上的图案一样被磨损殆尽:几乎没有人知道这些说法到底是指什么了。在IT业界,“平台(platform)”、“框架(framework)”、“构架(architecture)”等等就是这种人见人爱的大词。几乎每个厂商都愿意请来其中的一位、甚至多位为自己推销。久而久之,这些说法似乎适用于各个领域、各个层面:所有的软件系统都是“平台”,所有的开发者都在自矜于独有的“框架”。原本有确切意义的“好词”,经过这一番争夺和滥用,也只能衰减为所谓的“buzzwords”,供市场营销人士们玩味了。 我想让这些词中的一个——“框架”——荡污涤垢,重现青春。要完成这样的任务,必须动用重典才行。软件业圣经《设计模式》对框架有如下定义:“A framework is a set of cooperating classes that make up a reusable design for a specific class of software(一个框架,就是一组相互协作的类,对于特定的一类软件,框架构成了一种可重用的设计)”。这个定义虽然主要着眼于面向对象的软件开发,但已经基本上给出了这个词的核心含义:框架是软件系统的设计、开发过程中的一个概念,它强调对以完成的设计、代码的重复使用,并且,一个框架主要适用于实现某一特定类型的软件系统。 为了更好地说明框架是什么,也许还应该看看框架不是什么。 框架不是现成可用的应用系统。它仍是一个半成品,等待后来者做“二次开发”,实现为具体的应用系统。 框架不是“平台”。后者的概念更加浮泛和模糊——人们说的一个平台,可以是一种操作系统,一种应用服务器,一种数据库软件,一种通信中间件等等,因此“平台”几乎成了所有系统软件的统称。在平台的大家族中,框架的概念可能与近来人们常说的“应用平台”最为接近,但平台主要指提供特定服务的系统软件,而框架则更侧重于设计、开发过程,或者可以说,框架通过调用平台提供的服务而起作用。 框架不是工具包(toolkit)/类库(library) /API。目前流行的很多框架中,就包括了大量的类库和API,但是调用API并不就是在使用框架开发。仅仅使用API时,开发者完成系统的主体部分,并不时地调用类库实现特定任务。而框架构成了通用的、具有一般性的系统主体部分,“二次开发者”只是像做填空题一样,根据具体业务,完成特定应用系统中与众不同特殊的部分。 框架不是构架(architecture)。构架确定了系统整体结构、层次划分、不同部分之间的协作等设计考虑。框架比构架更具体,更偏重于技术实现。确定框架后,构架也随之确定,而对于同一种构架(比如web开发中的MVC),可以通过多种框架(比如Apache Struts或Apache Velocity)实现。
2
那么,在企业应用系统开发中,框架具有什么样的意义?要阐明这一点,大概要看一看在这个领域里软件开发方式的演变。在计算机应用普及之前,只有少数大企业才负担得起企业信息系统软件,这一类的软件开发也已委托定制(custom-made software)为主。在企业信息化基础设施逐步完备之后,多数中、小企业也要在预算不高的前提下实施企业应用系统,按照以前的方式逐个定制开发,是这种类型的企业难以承受的。因此,对于一些需求简明的系统,往往会购买现成软件(shrink-wrapped software)解决问题。但是各个企业具体业务不同,需求很难统一,现成软件只能满足最通用的情况和最一致的操作(比如财会系统、网站内容发布系统等),对于头绪众多的业务处理就难以胜任了。 如何最大程度地萃取不同企业应用系统的共性,重复使用已经完成的设计和代码,对企业应用系统中典型场景给出最佳解决方案——这是一个“一般性”的问题;如何让一个早先完成的软件产品贴切地适应极为多变、复杂的企业需求——这是一个“特殊性”的问题。作为对这一组冲突的一种解决方案,不少厂商推出了自己的企业应用框架。这些框架往往是从大量的委托项目开发中精选出的系统“不变项”,因此具有很强的普适性和实用性。 目前,主流企业应用框架中大都包含对以下问题的现成解决方案: * 持久性(persistence):实现数据存储、处理,数据与对象映射,数据缓存(caching)。 * 事务(transaction):确保一组关联操作正常、完整的执行。 * 安全性(security):保证系统的通信安全、数据安全。 * 负载均衡(load balance):在大量并发访问时,保持系统可用。 * 监控(system monitoring/management):监控系统运行状况,设置系统参数。 * 日志(logging):记录系统运行情况和异常,记录特定用户操作。 * 应用集成 (application integration):与其他系统、应用程序集成。 * 认证/权限/组织角色管理(authentication/authorization):管理系统用户、组织职权结构,限制特定用户对特定功能、特定数据的访问。 * 业务模型(domain model):管理系统中业务对象的属性、字段。 * 业务逻辑(business logic/rules):实现业务规则和业务逻辑。 * 工作流(work flow):实现多用户、多环节之间的业务处理流程。 * 文件管理(file management):管理文档,实现系统内部的文件传递。 * 报表/打印 (reporting/printing):实现数据打印,实现报表的定制和输出。 * 门户/信息发布 (portal solution):发布企业相关的信息、新闻,提供企业客户的访问入口。 * 通信(communication/messaging):系统内部的消息、通知;系统与外部角色(比如企业客户)之间通过不同通信媒介(电话、网站、邮件等)的互动。 * 特定行业/领域模块 (business modules):实现特定行业、流域相关的业务模块。 以上诸方面中,除了前四项目前主要由应用服务器解决之外,其他的部分本身都是专门的软件开发领域。框架的作用,在于确定上述每种因素的具体技术实现,并规定它们在系统中的组织方式和协作方式,从而给出完整的企业应用解决方案。 企业应用框架的特点首先是,当应用框架确定之后,系统的整个构架,也就是主体结构就已经固定。因此框架的选取往往是方案选型的首要问题。 其次,人们常常听信“组件式开发”的一面之词,认为系统搭建的过程类似于搭积木,好像是用胶水代码(glue code)拼合现成的组件或模块。其实采用框架开发时,系统的构建过程更类似于填空——系统骨架早已完成,开发者填写特定的代码,由系统来调用。《设计模式》中提到的“好莱坞原则(the Hollywood principle——Don't call us, we'll call you)”,非常符合我们谈的这种情况。很多框架还允许下游厂商开发系统插件(plug-ins),以满足特定需要——这是另一种形式的“填空”。 另外,对于实现具体应用系统的二次开发者来说,不少任务都无需通过编程实现。比如要给一个业务模型增添一个新字段,或是要设置一种新的工作流程,这些工作都可以通过简单的图形用户界面(GUI)操作,或是修改部署描述符(DD),或是编写脚本来完成。也就是说,相当多(而不是全部)的开发任务是通过声明/配置的(declarative),而不是编程的(programmatic)的方式实现的。系统往往会在启动或运行时载入相关的配置,据此实现特定的功能。
企业应用框架是菜场里的半成品。当我们面对要么自己下厨、要么去饭馆吃饭的选择时,我们往往会采取这种省时省力的折衷方案。但是选择之所以为选择,就因为其中肯定包含对收益和代价的权衡,都隐含着复杂的利弊关系(pros and cons)。下面我们也来检讨一下企业应用框架的情况: Pros: * 缩短开发周期 毫无疑问,采用框架的开发,要比一切从头做起快速、高效得多。通过一般化(generalization)和重用(reuse)机制,框架能最大限度地加快一个特定应用系统的实现。 * 客户化 如上所述,基于框架的系统有很多功能通过配置而不是编程实现,这样也给用户带来了一定便利。比如,企业内部的IT人员经过一定培训,就能够自己完成一种新的工作流程的设置。这对于不断变化的业务需求是一个很理想的解决方案。 * 不重新发明轮子 框架对于大量典型场景给出了最优的实践。在具体开发时,与其无视前人的成果,重新构思答案,不如套用这些成熟、稳定的做法。这不仅能加快开发进度,更能够提升系统的质量和健壮性。 * 可维护性/知识共享 完全通过委托开发完成的系统很难由其他厂商维护。框架往往是多个企业、大量开发者实践的成果,因此能在一定程度上打破上述壁垒,增进系统的可维护性。当框架使用者形成社区之后,还能实现更高层次上的知识共享。 Cons: * 太多 半成品总有其代价。超市配好的一包菜里,老是又我们用不到的调料——但是我们却不得不为之付费。同样,为了达到一般性和普适性,框架总比紧凑、贴切的特定应用多出不少内容。二次开发完成后,企业获得的只是一种特定的实现,却要为所有的客户化可能性付费,为所有用不上的功能付费。这是一个相当让人尴尬的事实。 * 太少 框架总是一种限制。就像半成品菜限制了我们的烹调方法,框架也限制了我们实际应用的可能性。当框架本身设计的足够普适时,我们不太会感到类似的限制。但是,情况往往正好相反——面对一个足够特殊的需求,二次开发者总有一种冲破框架的渴望。最后的解决办法,往往是狡计、妥协和框架补丁的结合体。 * 效率 上面说过,基于框架的系统中,具体功能经常是通过配置实现的。与硬编码(hard-coded)的方式相比较,这虽然能提供很大的灵活性,但也往往牺牲了运行时的效率。 * 锁定 一个采用特定框架的系统几乎肯定被锁定在这个厂商的产品上。换言之,框架意味着all or nothing式的态度,很难调和两种不同的框架,各取所长,更难把应用系统从一个框架迁移到另一个——这往往要求系统的全部改写。 * 学习曲线 一种框架也就是一种方言。要精通特定框架的开发,你要熟悉其中的所有的用法、思路和弱点。对于开发者,这也意味着比较陡峭的学习曲线。
3
上面谈到的种种弊端,还属于一般开发框架共有的缺陷。对于市面上流行的很多企业应用框架来说,更大的问题是框架产品自身的价格过高。我在别处也讲过,企业应用系统项目往往不能靠运行安装程序,再作简单的设置就完成,而是一个复杂、漫长、不断尝试/修改的过程,或者说,更近似于一种服务而不是简单的产品销售。但是框架厂商的产品(或者说是半成品)价格过高,经常就蚕食了整个系统的大部分开发预算,使方案总价偏重于框架本身而不是后期开发。对于需求不甚符合原有框架,需要大量开发的项目,或是需求本身不够清晰的项目,这都几乎肯定会导致整个项目的失败。
软件工程宗师F. Brooks曾经表述过这样一个道理:没有银弹(No Silver Bullet/NSB)。那意思是说,没有一种万应药能够戏剧性地提升软件开发的效率和质量。最近的很多舆论好像是专门和这个经典论述抬杠,动不动就把一些特殊的解决方案奉为银弹。对于企业应用开发而言,基于框架的开发模式是多种选择中的一种。在不少情况下,这种选择是不错的,但同时应该留意随之而来的风险,更不该以为,选定了框架就一定能保证项目成功。 很多企业应用项目的难点,在于客户自身缺乏规范的企业管理制度和合格的计算机应用水平。客户不具备成型的业务流程,也无法明晰表达需求,不知道怎样的应用形式对自身业务更合理:这种需求不清、或是需求剧烈变更的困境是困扰大量企业应用开发项目的症结。简言之,企业应用项目的成败经常是“业务”、“技术”、“管理”三种因素共同作用的结果,而单纯引入框架,只能解决部分“技术问题”。如果过于乐观地估计框架在其中的作用,甚至认为它能解决任何项目的任何问题,这对于本领域的各个环节(厂商、项目开发商、企业客户)都只能起到消极作用。 我个人的建议是:在搭建企业应用系统时,针对应用情况不同、预算/时限不同、对系统指标要求不同,有多种替代方案可以从中选择。当需求明确、固定,又有现成产品完全满足需要时,或者当企业想要以极低预算消除某个业务瓶颈时,应该优先考虑现成产品;在需求明确、固定,但很难被现成产品完全覆盖时,可以选择应用框架,并由合格开发商完成实施;在需求不够明确,或者预感到需求会发生剧烈变更时,采用开发源码的应用框架,从而避免高昂的初期投资,并“软化”框架带来的种种限制,是另一种可供选择的思路。还是那个比方,一顿饭怎么吃,究竟是下馆子、买半成品或者全由自己动手,还要视具体情形而定——我也希望,每个企业都能吃上可口顺心的应用大餐。
Extract Method
如果方法中含有过多特定的操作,方法太长,或者其中的某段代码被多次使用,这时,可以用提炼方法重构将这部分代码提取到单独的方法中。在Eclipse中应用此重构方便快捷。
选中要提炼的代码段,从重构菜单中选择提炼方法项,或者使用快捷键Alt + Shift + M。
在提炼方法对话框中,输入新方法的名字,选择修饰词,选择是否让新方法抛出运行时异常。在底部提供了新方法的预览。
Extract Local Variable
使用一个变量来代替一个表达式有很多好处。如果表达式在多处被使用,这样能够提高性能,而且也提高了代码的可读性。要把一个表达式提炼为局部变量,选择要提炼的表达式,从重构菜单中选择提炼局部变量项,或者使用快捷键Alt + Shift + L。
在提炼局部变量对话框中输入新变量的名字,选择是否要替换所有的表达式,是否使此变量为final。在对话框的底部提供变量的预览。
Extract Constant
提炼常量与提炼局部变量很相似,唯一的区别是提炼常量重构可以选择提炼出的常量的修饰词,而且此常量将作为类的成员变量。
Introduce Parameter
介绍参数重构在方法中创建新的参数,然后用此新参数取代局部变量或者成员变量的实例。要是用此重构,选中方法中一个成员变量或局部变量的引用,然后从重构菜单中选择介绍参数项。
Introduce Factory
工厂是用来创建新对象,返回新创建对象的方法。你可以选择一个类的构造方法,从重构菜单中选择介绍工厂项,应用此重构,为此类创建工厂方法。
在介绍工厂对话框,输入工厂方法的名字和需要工厂方法创建的对象的名字。选择构造方法的修饰词是否为私有。
点击OK按钮后,在指定的类中会出现此指定工厂方法。此方法创建一个当前类的实例,然后返回此实例。
Convert Local Variable to Field
转换局部变量为成员变量重构,将方法内的变量声明移动到方法所在类中,使该变量对整个类可见。选择一个局部变量,从重构菜单中选择转换局部变量为成员变量项,随后打开配置的对话框。
在此对话框中,添入成员变量的名字,选择修饰词,选择在哪里实例化此成员变量。随后的声明为静态,声明为final 选择项是否可以使用,取决于实例化位置的选择情况。
Encapsulate Field
要正确的实践面向对象编程,应该将成员变量的修饰词置为私有,提供相应的访问器来访问这些成员变量。但是这些操作很烦琐。如果使用了封装成员变量重构,则十分方便。选择一个成员变量,从重构菜单中选择封装成员变量项。
在封装局部变量对话框中,添入Getter, Setter方法的名字,选择新方法在哪个方法后出现。选择合适的修饰词。应用了此重构会创建两个新方法,将此成员变量的修饰词置为私有,将对此成员变量的引用改变为对新方法的引用。
重构项列表:
下表从Eclipse帮助中提取,列出了各种重构支持的Java资源类型,对应的快捷键。
名字
|
可应用的Java元素
|
快捷键
|
Undo
|
在一次重构后可执行
|
Alt + Shift + Z
|
Redo
|
在一次撤销重构后可执行
|
Alt + Shift + Y
|
Rename
|
对方法,成员变量,局部变量,方法参数,对象,类,包,源代码目录,工程可用。
|
Alt + Shift + R
|
Move
|
对方法,成员变量,局部变量,方法参数,对象,类,包,源代码目录,工程可用。
|
Alt + Shift + V
|
Change Method Signature
|
对方法可用。
|
Alt + Shift + C
|
Convert Anonymous Class to Nested
|
对匿名内部类可用。
|
|
Move Member Type to New File
|
对嵌套类可用。
|
|
Push Down
|
对同一个类中成员变量和方法可用。
|
|
Pull Up
|
对同一个类中成员变量和方法,嵌套类可用。
|
|
Extract Interface
|
对类可用。
|
|
Generalize Type
|
对对象的声明可用。
|
|
Use Supertype Where Possible
|
对类可用。
|
|
Inline
|
对方法,静态final类,局部变量可用。
|
Alt + Shift + I
|
Extract Method
|
对方法中的一段代码可用。
|
Alt + Shift + M
|
Extract Local Variable
|
对选中的与局部变量相关的代码可用。
|
Alt + Shift + L
|
Extract Constant
|
对静态final类变量,选中的与静态final类变量相关的代码可用。
|
|
Introduce Parameter
|
对方法中对成员变量和局部变量的引用可用。
|
|
Introduce Factory
|
对构造方法可用。
|
|
Convert Local Variable to Field
|
对局部变量可用。
|
Alt + Shift + F
|
Encapsulate Field
|
对成员变量可用。
|
|
本文介绍了Eclipse提供的各种重构。这些重构易于使用,可以确保代码重构更加方便安全。而且可以自动生成代码以提高生产率。
某些重构改变了某些类的结构,但没有改变项目中其他类的结构,如下推,上移重构。这时,就要确保项目中所有对改变元素的引用都要被更新。这也是为什么要有一个好的测试套。同时,你也要更新测试套中的对改变元素的引用。所以说,重构和单元测试的有机结合对于软件开发是多么的重要。
重构和单元测试是程序员的两大法宝,他们的作用就像空气和水对于人一样,平凡,不起眼,但是意义深重。预善事,必先利器,本文就介绍怎样在Eclipse中进行重构。
本文介绍了Eclipse支持的重构种类,它们的含义,以及怎样重构。本文同时也可以作为学习重构知识的快速手册。
什么是重构
重构是指在保持程序的全部功能的基础上改变程序结构的过程。重构的类型有很多,如更改类名,改变方法名,或者提取代码到方法中。每一次重构,都要执行一系列的步骤,这些步骤要保证代码和原代码相一致。
为什么重构很重要
手工重构时,很容易在代码中引入错误,例如拼写错误或者漏掉了重构的某一步。为了防止引入错误,在每次重构前后,都要执行充分的测试。你可能会好奇重构是否是值得的。
重构的理由很多。你可能想要更新一段代码很烂的程序。或者最初的设计队伍都不在了,现在队伍中每人了解这些程序。为了更新,你必须要重新设计构建程序来满足你的需求。另一个原因是原来的设计无法使你将新的特性添加进去。为了添加进去,你要重构这些代码。第三个原因是一个自动重构的工具可以为你自动生成代码,例如Eclipse中的重构功能。使用重构,你可以在重写尽量少的代码和仍保持软件功能的同时,使代码的逻辑性更好。
测试
在重构时,测试是十分重要的。应为重构改变了代码的结构,你要保证重构后代码的功能没有被改变。手工重构时,一个好的测试套是必须的。使用自动重构工具是,测试也是必要的,但不需要很频繁,应为自动重构工具不会产生手工重构时的那些错误,如拼写错误。
在Eclipse中可以使用JUnit方便的为程序创建测试代码,具体方法不在本文描述。
Eclipse中的重构
JDT,Eclipse中的Java插件,能够对Java项目,类,或成员进行多种类型的自动重构。可以采取多种方法快速的为Java项目中的某个元素进行重构。
为某些元素进行重构的前提是你必须选中他们。你可以在多个视图中选择这些元素,像大纲视图或包浏览视图。可以按住Ctrl或Shift键,在视图中选择多个元素。另外一种选择的方法是使该元素的编辑区高亮显示,或者把鼠标定位到源程序文件。在选中希望重构的元素后,可以从重构菜单的下拉项选择重构,也可以从右键单击后弹出菜单中选择重构子菜单。同时,Eclipse还提供了重构的快捷键操作。
某些重构可以应用在任意元素上,有些则只能用在特定类型的元素上,如类或方法。在本文的最后的表格中,列出了重构能够应用的元素类型,以及重构的快捷键。
在Eclipse中,所有的重构都能够在正式执行之前预览一下。在重构对话框中点击“预览”按钮,可以查看所有将要被改变的地方。唯一没有预览按钮的的重构是Pull Up,在它的重构向导中,到最后,预览面板总会出现。可以将其中的个别变化反选掉,这样这些改变就不会生效。
撤销和重做
在重构菜单中有撤销和重做项。他们和编辑菜单中的撤销重做不同。即使重构改变了很多文件,编辑菜单中的撤销重做只会更改当前文件。重构菜单中的撤销和重做则会对一次重构的所有文件进行撤销和重做操作。但是在使用时,它们有一定的限制。
重构后,无论重构改变了文件与否,如果任一个文件被另外改变而且保存了,你就无法撤销或重做这个重构。假如一个文件在重构中被修改了,然后又被编辑了,但是还没有保存,这时就会有错误信息提示,如果你想要撤销或重做该重构,必须撤销未保存的文件。
只要注意到以上的限制条件,你就可以随心所欲的对重构进行撤销或重做。你甚至能够编译,运行你的程序测试一下,然后再撤销该重构,只要你没有改变并保存任何文件。
Eclipse中的重构类型
如果你看一下Eclipse的重构菜单,可以看到四部分。第一部分是撤销和重做。其他的三部分包含Eclipse提供的三种类型的重构。
第一种类型的重构改变代码的物理结构,像Rename和Move。第二种是在类层次上改变代码结构,例如Pull Up和Push Down。第三种是改变类内部的代码,像Extract Method和Encapsulate Field。这三部分的重构列表如下。
类型1 物理结构
l Rename
l Move
l Change Method signature
l Convert Anonymous Class to Nested
l Convert Member Type to New File
类型2 类层次结构
l Push Down
l Push Up
l Extract Interface
l Generalize Type (Eclipse 3)
l User Supertype Where Possible
类型3 类内部结构
l Inline
l Extract Method
l Extract Local Variable
l Extract Constant
l Introduce Parameter
l Introduce Factory
l Encapsulate Field
Rename:
Rename用来改变一个Java元素的名字。虽然你可以手工改变Java文件Java元素的名字,但是这样不能自动更新所有引用它们的文件或Java元素。你必须在项目中搜索文件然后手工替换这些引用。很可能你就会漏掉一个或者改错一个。Rename 重构会智能的更新所有有此引用的地方。
有时候,Java元素的名字不是很明了,或者它的功能已经改变了。为了保持代码的可读性,该元素的名字也要更新。使用Rename重构,能够十分快捷的更新元素的名字和所有引用它的地方。
要为一个Java元素改名,在包浏览视图或大纲视图选中该元素,从重构菜单中选择Rename项,或者使用快捷键Alt+Shift+R。Rename对话框会出现。在这里添入新的名字,选择是否更新该元素的引用。点击预览按钮,会打开预览窗口,在这里,你可以看到那些内容会被改变。点击OK按钮,重构结束。
Move
Move和Rename很相似。它用来把元素从一个位置移动到另一个位置。它主要用来将类从一个包移动到另一个包。选中要移动的元素,从重构菜单中选择Move,或者使用快捷键,Alt+Shift+V,在弹出窗口中选择要移动的目的地。你仍然可以用预览功能检查一下有什么改变,也可以按OK按钮直接让其生效。
Change Method Signature
更改方法签名能够改变参数名,参数类型,参数顺序,返回类型,以及方法的可见性。也可以添加,删除参数。
要执行此重构,选择要重构的方法,选中重构菜单的更改方法签名项,会出现更改方法签名对话框。
在此对话框中选择方法的修饰词,返回类型,参数。参数的添加,修改,移动,删除可以通过右边的按钮控制。当添加新的参数时,会自动赋予默认值。凡是调用此方法的地方都会用此默认值作为参数输入。
改变方法签名可能在方法中导致问题,如果有问题,当你点击预览或OK时,会被标记出来。
Move Members Type to New File
此重构将嵌套类转为一个单独类。将会创建一个新的Java文件包含此嵌套类。选中要重构的类,在重构菜单上选择Move Member Type to New File项,在弹出的对话框中添入要创建的实例的名字。
Push Down
此重构将算中的方法和成员从父类中移动到它的直接子类中,所有下推的方法都可选作为一个抽象方法留在父类中。下推重构对于重新构建项目设计十分有用。
选择若干方法或成员,从重构菜单中选择下推项,弹出下推对话框。
在此对话框中,可以分别选择方法或成员,所有选中元素都会移动到当前类的子类中。当点击Add Required按钮时,所有已选择元素所必需的元素也会自动选上,此行为并不能保证所有必须的元素都能自动选中,还是需要人工确认。当有方法被选中时,编辑按钮就会可用,点击编辑按钮,弹出编辑对话框。在其中可以选择为选中方法在当前类中遗留抽象方法,还是在当前类中删除这些方法。双击一天选中的方法,也可以打开编辑对话框。在方法的Action列点击,会出现一个下拉列表,可以在其中选择遗留抽象方法还是在当前类中删除方法。按回车键确认编辑结果。
Pull Up
上移与下推类似,也是在类之间移动方法和成员。上移将方法或成员从一个类移动到它的一个父类中。选中若干个方法或成员,在重构菜单中选择上移项,上移向导马上会出现。
在选择目标类多选框中,列出了当前类继承的所有父类。你只能将方法或成员移动到它们其中的一个里面。
如果在选中方法的Action列,被设置成在目标类中声明抽象方法,那么在目标类的非抽象子类中创建必须的方法选项变为可选。当它选中时,目标类的所有子类,如果它们中没有选中的方法,则会为它们创建选中的方法。
和在下推中一样,选择多个方法,点击编辑按钮,或者双击一个方法,都会打开编辑成员对话框。其中有两个选项,上移和在目标类中声明抽象方法。上移只是简单的复制方法到到父类中,并提供选择是否在当前类中删除该方法。在目标类中声明抽象方法会在父类中创建一个选中方法的抽象方法,如果父类不是抽象类则置为抽象类,最后选中方法留在当前类中。和在下推中一样,也可以点击Action列,可以在出现的下拉列表中选择。
如果方法的Action列选为上移,在下一步的向导中,将会要求你选择是否在当前类中删除这些方法,选中的方法会在当前类中被删除。
在向导的任意一步都可以按完成按钮,结束重构操作,此时按照默认规则进行重构。
Extract Interface
提炼接口可以从一个存在的类中创建一个接口。你可以选择在接口中包含着个类的那些方法。选中一个类,从重构菜单选择提炼接口项,就可以打开提炼接口对话框。
这此对话框中添入接口的名字,选择希望包含的方法,在这个列表里面只列出了公共方法。选中改变对类[当前类名]的应用为对接口的引用选择框,将把所有对当前类的引用更新为对此接口的引用。
Generalize Type
泛化类型重构可以将一个声明对象的类型改变为它的超类,选择变量,参数,对象成员,方法返回类型,然后选择重构菜单的泛化类型项。在打开的泛化类型对话框,选择希望的新类型,然后点击完成按钮,结束重构。
Use Supertype Where Possible
使用超类会将对一个特定类型的引用改变为对它的超类的引用。选择一个类,选中重构菜单的使用超类项,会打开使用超类对话框。选中希望的超类类型,点击完成按钮完成重构。重构后,instanceof 表达式也会做相应的替换。
Inline
内联是用代码或值来取代调用方法的地方,静态final对象成员,或局部变量。比如说,如果你内联一个方法调用,这个调用的地方就会被替换为该方法体。要内联一个方法,静态final对象成员,局部变量,选中这些元素,在重构菜单中选择内联项,或者使用快捷键Alt + Ctrl + I。在随后打开的内联对话框,你可以选择是否要内联所有的调用,或者是选择的调用。如果选择所有调用,你还可以选择是否删除声明本身。
|