分类 Java 下的文章

struts1.2和struts2区别

struts1.2和struts2区别
struts1.2和struts2最大的不同是,struts2集成了Webwork。
区别相当大,2.0改进很多,整合webwork,去掉了一些不常用得标签,
struts2.x是在webwork2的基础上构建的,集成了大量的东西,像什么拦截器,校验框架等,相当复杂,struts1.x相对来说就显得小得多了,东西就那么几个。
特性
Struts 1。2
Struts 2 .0
Action类
Struts 1要求Action类要扩展自一个抽象基类。Struts 1的一个共有的问题是面向抽象类编程而不是面向接口编程。
Struts 2的Action类实现了一个Action接口,连同其他接口一起来实现可选择和自定义的服务。Struts 2提供一个名叫ActionSupport的基类来实现一般使用的接口。虽然,Action接口不是必须的。任何使用execute方法的POJO对象可以被当作Struts 2的Action对象来使用。
线程模型
Struts 1 Action类是单例类,因为只有一个示例来控制所有的请求。单例类策略造成了一定的限制幷且给开发带来了额外的烦恼。Action资源必须是线程安全或者同步的。
Struts 2 Action对象为每一个请求都实例化对象,所以没有线程安全的问题。(实践中,servlet容器产生许多丢弃的对象对于每一个请求,多于一个的对象并不影响垃圾收集)
Servlet 依赖
Struts 1的Action类依赖于servlet API以为HttpServletRequest和HttpServletResponse作为参数传给execute方法当Action被调用时。
Struts 2的Action不和容器有关。Servlet上下文被表现为简单的Maps,允许Action被独立的测试。Struts 2的Action可以访问最初的请求和相应,如果需要的话。然而,其他的架构元素减少或者排除直接访问HttpServletRequest或者HttpServletResponse的需要。
易测性
测试Struts 1的主要障碍是execute方法暴露了Servlet API。第三方的扩展,Struts测试用例,提供Struts 1的集合对象。
Struts 2的Action可以通过实例化Action来测试,设置属性,然后调用方法。依赖注入的支持也是测试变得更简单。
接受输入
Struts 1使用ActionForm对象来捕获输入。象Action一样,所有的ActionForm必须扩展基类。因为其他的JavaBean不能作为ActionForm使用,开发者经常创建多余的类来捕获输入。DynaBeans可以被用来作为替代ActionForm的类来创建。但是开发者可以重新描述已经存在的JavaBean。
Struts 2 Action属性作为输入属性,排除第二个输入对象的需要。输入属性可能有丰富的对象类型这些类型有他们自己的属性。Action的属性可以通过标签库来访问。Struts 2也支持ActionForm形式。丰富的对象类型,包含业务或者域对象,可以被当作输入或者输出对象来使用。糢型驱动特性简化标签对POJO输入对象的引用。
表达式语言
Struts 1整和JSTL,所以它使用JSTL的表达式语言。表达式语言有基本的图形对象移动,但是相对很弱的集合和被索引的属性支持。
Struts 2使用JSTL,但是框架也支持更强大和更灵活的表达式,叫做“对象图形符号语言”(OGNL)。
将值绑定要视图上
Struts 1使用标準JSP机製来绑定对象到页面上下文。
Struts 2使用“ValueStack”技术为了标签库可以不用链接你的视图到对象的表现类型来访问值。ValueStack策略允许重用视图。
类型转换
Struts 1的ActionForm属性经常都是String的。Struts 1使用Commons-Beanutils来类型转换。转换每一个类,幷不是为每一个实例配置。
Struts 2使用OGNL来类型转换。框架包含转换器来为基本的和共同的对象类型和原始类型。
验证
Struts 1支持手动验证凭借ActionForm的validate方法,或者通过扩展的公用验证器。类可以有不同的验证上下文来未相同的类,但是不能不能包括验证子对象。
Struts 2支持手动验证凭藉validate方法和XWork验证框架。Xwork验证框架支持一连串的验证子属性使用的验证为了属性类的类型和严正上下文而定义。
Action执行的控制
Struts 1支持独立的请求处理器对于每一个模型,但是所有在模型中的Action必须共享同一个生命周期。
Struts 2支持在每一个Action基础上凭借拦截栈创建不同的生命周期。自定义栈可以被创建幷且使用不同的所需 的Action。

js函数定义详解

javascript中有以下这些方法可以定义一个函数。所有这些都是有效的,但是它们在后台如何
实现的则有一些差别。
常用的写法
一般大家都用这个写法来定义一个函数:
CODE:

functionName([parameters]){functionBody};

Example D1:
CODE:

function add(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

当我们这么定义函数的时候,函数内容会被编译(但不会立即执行,除非我们去
调用它)。而且,也许你不知道,当这个函数创建的时候有一个同名的对象也被
创建。就我们的例子来说,我们现在有一个对象叫做“add”(要更深入了解,看
底下函数:对象节。)
匿名函数
我们也可以通过指派一个变量名给匿名函数的方式来定义它
Example D2
CODE:

var add=function(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

这个代码和前一个例子做了同样的事情。也许语法看起来比较奇怪,但它应该更
能让你感觉到函数是一个对象,而且我们只是为这个对指派了一个名称。可以把
它看做和 var myVar=[1,2,3]一样的语句。以这种方式声明的函数内容也一样会
被编译。
当我们指派一个这样的函数的时候,我们并不一定要求必须是匿名函数。在这里
,我作了和ExampleD2一样的事情,但我加了函数名“theAdd”,而且我可以通过
调用函数名或者是那个变量来引用函数
Example D2A
CODE:

var add=function theAdd(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3
alert(theAdd(1,2)); // 结果也是 3

使用这种方式来定义函数在面向对象编程中是很有用的,因为我们能像底下这样
使一个函数成为一个对象的属性。
CODE:

var myObject=new Object();
myObject.add=function(a,b){return a+b};
// myObject 现在有一个叫做“add”的属性(或方法)
// 而且我能够象下面这样使用它
myObject.add(1, 2);

我们也能够通过使用运算符new来定义一个函数。这是一个最少见的定义函数的方
式并且并不推荐使用这种方式除非有特殊的理由(可能的理由见下)。语法如下

CODE:

varName=new Function([param1Name, param2Name,...paramNName],
functionBody);

Example D3:
CODE:

var add=new Function("a", "b", "return a+b;");
alert(add(3,4)); // 结果 7

我在这里有两个参数叫做a和b,而函数体返回a和b的和。请注意new Function
(…)使用了大写F,而不是小写f。 这就告诉javascript,我们将要创建一个类
型是Function的对象。 还要注意到,参数名和函数体都是作为字符串而被传递。
我们可以随心所欲的增加参数,javascript知道函数体会是右括号前的最后一个
字符串(如果没有参数,你能够只写函数体)。你没必要将所有东西都写在一行
里(使用或者使用字符串连接符+来分隔长代码)。标记告诉JavaScript在下一
行查找字符串的其余部分。例子如下:
Example D4
CODE:

var add=new Function("a", "b",
"alert" + // 注意 "+"
"('adding '+a+' and ' +b); // 和 ""的不同用法
return a+b;");
alert(add(3,4)); // 结果 7

采用这种方式定义函数会导致函数并没被编译,而且它有可能会比用其它方式定
义的函数要慢。至于为什么,看一下这个代码:
Example D5
CODE:

function createMyFunction(myOperator)
{
return new Function("a", "b", "return a" + myOperator + "b;");
}
var add=createMyFunction("+"); // 创建函数 "add"
var subtract=createMyFunction("-"); // 创建函数 "subtract"
var multiply=createMyFunction("*"); // 创建函数 "multiply"
// test the functions
alert("加的结果="+add(10,2)); // 结果是 12
alert("减的结果="+subtract(10,2)); // 结果是 8
alert("乘的结果="+multiply(10,2)); // 结果是 20
alert(add);

这个有趣的例子创建了三个不同的function,通过实时传递不同的参数来创建一
个新Function。因为编译器没法知道最终代码会是什么样子的,所以new
Function(…)的内容不会被编译。那这有什么好处呢?嗯,举个例子,如果你需
要用户能够创建他们自己的函数的时候这个功能也许很有用,比如在游戏里。我
们也许需要允许用户添加“行为”给一个“player”。但是,再说一次,一般情
况下,我们应该避免使用这种形式,除非有一个特殊的目的。
函数:对象
函数是javascript中的一种特殊形式的对象。它是第一个[b〕类数据类型(class
data type)。这意味着我们能够给它增加属性。这里有一些需要注意的有趣观点

对象的创建
就像刚才提及的,当我们定义一个函数时,javascript实际上在后台为你创建了
一个对象。这个对象的名称就是函数名本身。这个对象的类型是function。在下
面的例子,我们也许不会意识到这一点,但我们实际上已经创建了一个对象:它
叫做Ball。
Example 1
CODE:

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象
i=1;
}
alert(typeof Ball); // 结果 "function"

我们甚至能将这个对象的内容打印出来而且它会输出这个函数的实际代码,
Example2: 点击 alert(Ball);来看看Ball的内容。
属性的添加
我们能够添加给Object添加属性,包括对象function。因为定义一个函数的实质
是创建一个对象。我们能够“暗地里”给函数添加属性。比如,我们这里定义了
函数Ball,并添加属性callsign。
CODE:

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象,而且你能够
} // 引用它或者象下面那样给它增加属性
Ball.callsign="The Ball"; // 给Ball增加属性
alert(Ball.callsign); // 输出 "The Ball"

指针
因为function是一个对象,我们能够为一个function分配一个指针。如下例,变
量ptr指向了对象myFunction。
CODE:

function myFunction(message)
{
alert(message);
}
var ptr=myFunction; // ptr指向了myFunction
ptr("hello"); // 这句会执行myFunction:输出"hello"

我们能够运行这个函数,就好像这个函数名已经被指针名代替了一样。所以在上
面,这行ptr(“hello”); 和myFunction(“hello”);的意义是一样的。
指向函数的指针在面向对象编程中相当有用。例如:当我们有多个对象指向同一
个函数的时候(如下):
Example 4A
CODE:

function sayName(name)
{
alert(name);
}
var object1=new Object(); // 创建三个对象
var object2=new Object();
var object3=new Object();
object1.sayMyName=sayName; // 将这个函数指派给所有对象
object2.sayMyName=sayName;
object3.sayMyName=sayName;
object1.sayMyName("object1"); // 输出 "object1"
object2.sayMyName("object2"); // 输出 "object2"
object3.sayMyName("object3"); // 输出 "object3"

因为只有指针被保存(而不是函数本身),当我们改变函数对象自身的时候,所
有指向那个函数的指针都会发生变化。我们能够在底下看到:
Example 5:
CODE:

function myFunction()
{
alert(myFunction.message);
}
myFunction.message="old";
var ptr1=myFunction; // ptr1 指向 myFunction
var ptr2=myFunction; // ptr2 也指向 myFunction
ptr1(); // 输出 "old"
ptr2(); // 输出 "old"
myFunction.message="new";
ptr1(); // 输出 "new"
ptr2(); // 输出 "new"

指针的指向
我们能够在一个函数创建之后重新分配它,但是我们需要指向函数对象本身,而
不是指向它的指针。在下例中,我将改变myfunction()的内容。
Example 6:
CODE:

function myFunction()
{
alert("Old");
}
myFunction(); // 输出 "Old"
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"

旧函数哪里去了??被抛弃了。
如果我们需要保留它,我们可以在改变它之前给它分配一个指针。
Example 6A:
CODE:

function myFunction()
{
alert("Old");
}
var savedFuncion=myFunction;
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"
savedFuncion(); // 输出 "Old"

不过要小心,象下面这样的例子并不会有作用,因为是创建了另一个叫做
myFunctionPtr的函数而不是修改它。
Example 6B:
CODE:

function myFunction()
{
alert("Old");
}
var savedFunc=myFunction;
savedFunc=function()
{
alert("New");
};
myFunction(); // 输出 "Old"
savedFunc(); // 输出 "New"

内嵌函数
我们还能够在一个函数中嵌套一个函数。下例,我有一个叫做getHalfOf的函数,
而在它里面,我有另一个叫做calculate的函数。
Example 7
CODE:

function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}
var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

你只能在内部调用嵌套的函数。就是说,你不能这么调用:
getHalfOf.calculate(10),因为calculate只有当外部函数(getHalfOf())在运行
的时候才会存在。这和我们前面的讨论一致(函数会被编译,但只有当你去调用
它的时候才会执行)。
调用哪个函数?
你也许正在想命名冲突的问题。比如,下面哪一个叫做calculate的函数会被调用

Example 8
CODE:

function calculate(number)
{
return number/3;
}
function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}
var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

在这个例子中,编译器会首先搜索局部内存地址,所以它会使用内嵌的calculate
函数。如果我们删除了这个内嵌(局部)的calculate函数,这个代码会使用全局
的calculate函数。
函数:数据类型及构造函数
让我们来看看函数的另一个特殊功能--这让它和其它对象类型截然不同。一个
函数能够用来作为一个数据类型的蓝图。这个特性通常被用在面向对象编程中来
模拟用户自定义数据类型(user defined data type)。使用用户自定义数据类型
创建的对象通常被成为用户自定义对象(user defined object)。
数据类型
在定义了一个函数之后,我们也同时创建了一个新的数据类型。这个数据类型能
够用来创建一个新对象。下例,我创建了一个叫做Ball的新数据类型。
Example DT1
CODE:

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向一个新对象
alert(ball0); // 输出 "Object",因为 ball0 现在是一个对象

这样看来,ball0=new Ball()作了什么?new关键字创建了一个类型是Object的新
对象(叫做ball0)。然后它会执行Ball(),并将这个引用传给ball0(用于调用
对象)。下面,你会看到这条消息:“creating new Ball”,如果Ball()实际上
被运行的话。
Example DT2
CODE:

function Ball(message)
{
alert(message);
}
var ball0=new Ball("creating new Ball"); // 创建对象并输出消息
ball0.name="ball-0"; // ball0现在有一个属性:name
alert(ball0.name); // 输出 "ball-0"

我们可以把上面这段代码的第6行看做是底下的代码6-8行的一个简写:
CODE:

function Ball(message)
{
alert(message);
}
var ball0=new Object();
ball0.construct=Ball;
ball0.construct("creating new ball"); // 执行 ball0.Ball
("creating..");
ball0.name="ball-0";
alert(ball0.name);

这行代码ball0.construct=Ball和Example 4中的ptr=myFunction语法一致。
如果你还是不明白这行的含义那就回过头再复习一下Example 4。注意:你也许考
虑直接运行ball0.Ball(“…”),但是它不会起作用的,因为ball0并没有一个叫
做Ball(“…”)的属性,并且它也不知道你究竟想作些什么。
添加属性
当我们象上面那样使用关键字new创建一个对象的时候,一个新的Object被创建了
。我们可以在创建之后给这个对象添加属性(就好像我在上面那样添加属性name
。而接下来的问题就是如果我们创建了这个对象的另外一个实例,我们得象下面
那样再次给这个新对象添加这个属性。)
Example DT3 (creates 3 ball objects)
CODE:

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向了类型Ball的一个新实例
ball0.name="ball-0"; // ball0 现在有一个属性"name"
var ball1=new Ball();
ball1.name="ball-1";
var ball2=new Ball();
alert(ball0.name); // 输出 "ball-0"
alert(ball1.name); // 输出 "ball-1"
alert(ball2.name); // 哦,我忘记给ball2添加“name”了!

我忘记给ball2添加属性name了,如果在正式的程序中这也许会引发问题。有什么
好办法可以自动增加属性呢?嗯,有一个:使用this关键字。this这个词在
function中有特别的意义。它指向了调用函数的那个对象。让我们看看下面的另
一个示例,这时候我们在构造函数中添加上这些属性:
Example DT4
CODE:

function Ball(message, specifiedName)
{
alert(message);
this.name=specifiedName;
}
var ball0=new Ball("creating new Ball", "Soccer Ball");
alert(ball0.name); // prints "Soccer Ball"

请记住:是new关键字最终使得构造函数被执行。在这个例子中,它将会运行
Ball(“creating new Ball”, “Soccer Ball”);而关键字this将指向ball0。
因此,这行:this.name=specifiedName变成了ball0.name=”Soccer Ball”。
它主要是说:给ball0添加属性name,属性值是Soccer Ball。
我们现在只是添加了一个name属性给ball0,看起来和上一个例子中所做的很象,
但却是一个更好更具扩展性的方法。现在,我们可以随心所欲的创建许多带有属
性的ball而无需我们手动添加它们。而且,人们也希望创建的Ball对象能够清晰
的看懂它的构造函数并且能够轻松找出Ball的所有属性。让我们添加更多属性到
Ball里。
Example DT5
CODE:

function Ball(color, specifiedName, owner, weight)
{
this.name=specifiedName;
this.color=color;
this.owner=owner;
this.weight=weigth;
}
var ball0=new Ball("black/white", "Soccer Ball", "John", 20);
var ball1=new Ball("gray", "Bowling Ball", "John", 30);
var ball2=new Ball("yellow", "Golf Ball", "John", 55);
var balloon=new Ball("red", "Balloon", "Pete", 10);
alert(ball0.name); // 输出 "Soccer Ball"
alert(balloon.name); // 输出 "Balloon"
alert(ball2.weight); // 输出 "55"

嘿!使用面向对象术语,你能够说Ball是一个拥有如下属性的对象类型:name,
color, owner, weight。
将对象赋给属性
我们并没被限制只能添加形如字符串或者数字之类的简单数据类型作为属性。我
们也能够将对象赋给属性。下面,supervisor是Employee的一个属性.
Example DT6
CODE:

function Employee(name, salary, mySupervisor)
{
this.name=name;
this.salary=salary;
this.supervisor=mySupervisor;
}
var boss=new Employee("John", 200);
var manager=new Employee("Joan", 50, boss);
var teamLeader=new Employee("Rose", 50, boss);
alert(manager.supervisor.name+" is the supervisor of "+manager.name);
alert(manager.name+"'s supervisor is "+manager.supervisor.name);

会输出什么呢?
就像你在上面这个例子中看到的那样,manager和teamLeader都有一个supervisor
属性,而这个属性是类型Employee的一个对象。
将函数作为属性
任何类型的对象都可以作为一个属性,回忆一下前面的Example 4(不是Example
DT4),函数也是一个对象。所以你可以让一个函数作为一个对象的一个属性。下
面,我将添加两个函数getSalary和addSalary。
Example DT7
CODE:

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
boss.addSalary(10000); // boss 长了 10K 工资……为什
么老板工资可以长这么多:'(
alert(boss.getSalary()); // 输出 210K……为什么默认工
资也那么高……:'(

addSalary和getSalary演示了几种将函数赋给属性的不同方法。如果你记得我们
最开始的讨论;我讨论了三种声明函数的不同方式。所有那些在这里都是适用的
,但是上面展示的两个最常用。
让我们看看有什么不同。下面,注意一下9-12行的代码。当这部分代码执行的时
候,函数getSalary被声明。如前面数次提到的,一个函数声明的结果是一个对象
被创建。所以这时候boss被创建(接下来的第19行),而boss里有一个getSalary
属性。
CODE:

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
var boss3=new Employee("Kim", 200000);

当你创建这个对象的更多实例时(boss2和boss3),每一个实例都有一份getSalary
代码的单独拷贝;而与此相反,addSalary则指向了同一个地方(即
addSalaryFunction)。
看看下面的代码来理解一下上面所描述的内容。
Example DT8
CODE:

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}
var boss1=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
// 给getSalary函数对象添加属性
boss1.getSalary.owner="boss1";
boss2.getSalary.owner="boss2";
alert(boss1.getSalary.owner); // 输出 "boss1"
alert(boss2.getSalary.owner); // 输出 "boss2"
// 如果两个对象指向同一个函数对象,那么
// 上面两个输出都应该是“boss2”。
// 给addSalary函数对象添加属性
boss1.addSalary.owner="boss1";
boss1.addSalary.owner="boss2";
alert(boss1.addSalary.owner); // 输出 "boss2"
alert(boss2.addSalary.owner); // 输出 "boss2"
// 因为两个对象都指向同一个函数,(子乌注:原文写are not pointing to
the same function,疑为笔误)
// 当修改其中一个的时候,会影响所有的实例(所以两个都输出“boss2”).
也许不是重要的事情,但这里有一些关于运行类似上面的getSalary的内嵌函数的
结论: 1) 需要更多的存储空间来存储对象(因为每一个对象实例都会有它自己
的getSalary代码拷贝);2) javascript需要更多时间来构造这个对象。
让我们重新写这个示例来让它更有效率些。

Example DT9
CODE:

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
this.addSalary=addSalaryFunction;
this.getSalary=getSalaryFunction;
}
function getSalaryFunction()
{
return this.salary;
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

看这儿,两个函数都指向同一个地方,这将会节约空间和缩短构造时间(特别是
当你有一大堆内嵌函数在一个构造函数的时候)。这里有另外一个函数的功能能
够来提升这个设计,它叫做prototype,而我们将在下一节讨论它。
函数:原型
每一个构造函数都有一个属性叫做原型(prototype,下面都不再翻译,使用其原文
)。这个属性非常有用:为一个特定类声明通用的变量或者函数。
prototype的定义
你不需要显式地声明一个prototype属性,因为在每一个构造函数中都有它的存在
。你可以看看下面的例子:
Example PT1
CODE:

function Test()
{
}
alert(Test.prototype); // 输出 "Object"

给prototype添加属性
就如你在上面所看到的,prototype是一个对象,因此,你能够给它添加属性。你
添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性。
例如,我下面有一个数据类型Fish,我想让所有的鱼都有这些属性:
livesIn=”water”和price=20;为了实现这个,我可以给构造函数Fish的
prototype添加那些属性。
Example PT2
CODE:

function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;

接下来让我们作几条鱼:
CODE:

var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");

再来看看鱼都有哪些属性:
CODE:

for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i); // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}

输出应该是:
CODE:

"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别
声明这些属性。这时因为当一个对象被创建时,这个构造函数将会把它的属性
prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找
它的属性。
你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要
每次在构造一个对象的时候创建并初始化这个函数。为了解释这一点,让我们重
新来看Example DT9并使用prototype来重写它:
用prototype给对象添加函数
Example PT3
CODE:

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}
Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

我们可以象通常那样创建对象:
CODE:

var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);

并验证它:
CODE:

alert(boss1.getSalary()); // 输出 200000
alert(boss2.getSalary()); // 输出 100000
alert(boss3.getSalary()); // 输出 150000

这里有一个图示来说明prototype是如何工作的。这个对象的每一个实例(boss1,
boss2, boss3)都有一个内部属性叫做__proto__,这个属性指向了它的构造器
(Employee)的属性prototype。当你执行getSalary或者addSalary的时候,这个对
象会在它的__proto__找到并执行这个代码。
转自:http://www.cnblogs.com/dwyin/archive/2010/07/12/1775744.html

Java开源Apache项目

Commons-Pool:
创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一 些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟 机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而Jakarta Commons Pool组件则是处理对象池化的得力外援。
Commons-Math:
Math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题。
Commons-Jelly:
Jelly能够把XML转换成可执行代码,所以Jelly是一个基于XML与Java的脚本和处理引擎。 Jelly借鉴了JSP定指标签,Velocity, Cocoon和Xdoclet中的脚本引擎的许多优点。Jelly可以用在命令行,Ant或者Servlet之中。
Commons-FileUpload:
FileUpload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力。
Commons-BeanUtils:
Commons-BeanUtils 提供对 Java 反射和自省API的包装。
Commons-Chain:
Chain 提供实现组织复杂的处理流程的“责任链模式”。
Commons-Codec:
Codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder。
Commons-Discovery:
Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。
Commons-EL:
Commons-EL 提供在JSP2.0规范中定义的EL表达式的解释器。
Commons-Jexl:
Jexl是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言。
Apache Forrest:
Apache Forrest是一个把来自各种不同的输入数据源转换成用一种或多种输出格式(比如HTML,PDF等)来统一显示的发布系统。它基于Apache Cocoon并分离了内容与内容结构,不仅可以生成静态的文档也可以当作一个动态的服务器。
Apache POI:
Apache POI可以让你使用Java来读写MS Excel ,Word、PowerPoint文件。
MINA:
MINA (Multipurpose Infrastructure for Network Applications)是一个网络应用程序框架。用户利用它可以容易地开发高性能和高伸缩性的网络应用程序。
Commons-Configuration:
Commons-Configuration 工具可以从
*Properties文件
*XML文件
*JNDI
*JDBC 数据源
*System properties
*Applet parameters
*Servlet parameters等来读取相应的信息
Commons-validator:
commons-validator提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则。支持校验规则的和错误消息的国际化。
JXPath:
org.apache.commons.jxpath定义了一个简单的XPath表达式语言的解析器.JXPath应用XPath表达式来操纵各种类型的对象包括:JavaBeans,Maps,Servlet contexts,DOM等。
Commons Betwixt:
Commons Betwixt这个组件提供一个XML自省(introspection)机制用来把Java Bean映射到XML文档.他实现了与标准的Introspector及BeanInfo类相似的XMLIntrospector及XMLBeanIfno类。
Jakarta ORO:
Jakarta ORO一套文本处理工具,能提供perl5.0兼容的正则表达式,AWK-like正则表达式, glob表达式。还提供替换,分割,文件名过虑等功能。
Jakarta Regexp:
Regexp是一个100%纯java正则式处理包,是Jonathan Locke捐给Apache软件基金会的。
Commons-Convert:
Commons-Convert的目标是提供一个简单的包来完成把一种类型的对象转换成另一种.但它目前只支持String到Object或Object到String的转换。
Commons-Email:
Commons-Email的目的是提供一组用于发送Email的API,它基于Java Mail API基础之上并进行了简化。它提供的主要Java类有:SimpleEmail:这个类用于发送简单的文本类型email。MultiPartEmail:这个类允许发送文本消息并附带附件。HtmlEmail:用于发送Html格式的附件并支持内含图片。EmailAttachment:这是一个简单的容器类用于简化附件的处理。
Commons-IO:
Commons IO是Jakarta Commons项目的一个子项目。用来帮助进行IO功能开发.它包含三个主要的领域:Utility classes-提供一些静态方法来完成公共任务.Filters-提供文件过滤器的各种实现.Streams-提供实用的Stream,reader与writer实现。
XML Security:
XML Security项目是为XML安全标准(XML-Signature Syntax and Processing:XML数字签名语法和处理规则;XML Encryption Syntax and Processing:XML加密语法和处理规则)提供Java及C++实现的开源项目。它当前的Java类包中包括了一个成熟的数字签名实现。
ECS:
Jakarta Element Construction Set(ECS是一个使用Java语言和面向对象方法创建标记语言文档(HTML,XML)的开源项目。
Apache Harmony:
Apache Harmony是Apache软件基金会的一个开放源码JavaSE的实现。其目标是完成一个兼容的、独立的JavaSE5 JDK实现,并基于Apache License v2许可发布;和一个由社区开发的模块化的运行时(包括虚拟机和类库)体系结构。
Apache Commons-VFS:
VFS为访问各种不同的文件系统提供了单一的应用程序编程接口(API),大大的简化了应用程序本身代码的复杂度。目前VFS支持FTP、Local Files 、HTTP/HTTPS、SFTP、Temporary Files、Zip、Jar/Tar(uncompressed、tgz or tbz2)、gzip/bzip2、res、ram所有这些文件系统中的文件被封装成了FileObject这个类,文件的读写操作都通过此类来完成。文件的路径采用了URL的方式。
Apache Mahout:
Mahout项目Apache Lucene开源搜索引擎的一个子项目。在其中实现一些常见的用于集群、分类和CP的机器学习算法。
Apache Continuum:
Apache Continuum是一个企业级持续集成服务器。提供的特性包括:自动构建、发布管理、基于角色的权限管理、源码控制系统、与其它流行构建工具相集成。不管你是否需要集中构建,还是开发人员手动控制发布。Continuum都能够帮助你改进质量和维护一个协调的构建环境。
Apache Compress Ant Library:
Apache Compress Ant Library提供了一些Tasks和Types来读写各种类型的压缩文件和存档文件。支持AR、BZIP2、CPIO、GZIP、TAR和ZIP格式。
Apache Directory Studio:
Apache Directory Studio是一个LDAP工具平台,支持连接到任何 LDAP服务器并进行管理和开发工作,但主要还是设计用于Apache Directory Server。功能包括:LDAP浏览器、LDIF编辑器、Schema编辑器、嵌入式 ApacheDS、ACI编辑器。

SVN管理代码

SVN管理代码,本地与服务器有冲突,应该以服务器为准。如果出现一些奇怪的问题,撒手锏就是将相关名为SVN文件夹删除,这里保存着类似浏览器缓存之类的,乱七八糟的东西。
先更新后提交,一定要把冲突解决了,不要全部更新,先对比看同事们都提交了哪些代码,后更新。
配合上jenkins持续集成效果会更好!
PS:一定要把本地代码保存好了,不要丢失了宝贵的劳动成果!

Eclipse快捷键大全

Ctrl+1 快速修复(最经典的快捷键,就不用多说了)
Ctrl+D: 删除当前行
Ctrl+Alt+↓ 复制当前行到下一行(复制增加)
Ctrl+Alt+↑ 复制当前行到上一行(复制增加)
Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)
Alt+↑ 当前行和上面一行交互位置(同上)
Alt+← 前一个编辑的页面
Alt+→ 下一个编辑的页面(当然是针对上面那条来说了)
Alt+Enter 显示当前选择资源(工程,or 文件 or文件)的属性
Shift+Enter 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后)
Shift+Ctrl+Enter 在当前行插入空行(原理同上条)
Ctrl+Q 定位到最后编辑的地方
Ctrl+L 定位在某行 (对于程序超过100的人就有福音了)
Ctrl+M 最大化当前的Edit或View (再按则反之)
Ctrl+/ 注释当前行,再按则取消注释
Ctrl+O 快速显示 OutLine
Ctrl+T 快速显示当前类的继承结构
Ctrl+W 关闭当前Editer
Ctrl+K 参照选中的Word快速定位到下一个
Ctrl+E 快速显示当前Editer的下拉列表(如果当前页面没有显示的用黑体表示)
Ctrl+/(小键盘) 折叠当前类中的所有代码
Ctrl+×(小键盘) 展开当前类中的所有代码
Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代替)
Ctrl+Shift+E 显示管理当前打开的所有的View的管理器(可以选择关闭,激活等操作)
Ctrl+J 正向增量查找(按下Ctrl+J后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes line中显示没有找到了,查一个单词时,特别实用,这个功能Idea两年前就有了)
Ctrl+Shift+J 反向增量查找(和上条相同,只不过是从后往前查)
Ctrl+Shift+F4 关闭所有打开的Editer
Ctrl+Shift+X 把当前选中的文本全部变味小写
Ctrl+Shift+Y 把当前选中的文本全部变为小写
Ctrl+Shift+F 格式化当前代码
Ctrl+Shift+P 定位到对于的匹配符(譬如{}) (从前面定位后面时,光标要在匹配符里面,后面到前面,则反之)
下面的快捷键是重构里面常用的(注:一般重构的快捷键都是Alt+Shift开头的了)
Alt+Shift+R 重命名 (是我自己最爱用的一个了,尤其是变量和类的Rename,比手工方法能节省很多劳动力)
Alt+Shift+M 抽取方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用)
Alt+Shift+C 修改函数结构(比较实用,有N个函数调用了这个方法,修改一次搞定)
Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候)
Alt+Shift+F 把Class中的local变量变为field变量 (比较实用的功能)
Alt+Shift+I 合并变量(可能这样说有点不妥Inline)
Alt+Shift+V 移动函数和变量(不怎么常用)
Alt+Shift+Z 重构的后悔药(Undo)
编辑
作用域 功能 快捷键
全局 查找并替换 Ctrl+F
文本编辑器 查找上一个 Ctrl+Shift+K
文本编辑器 查找下一个 Ctrl+K
全局 撤销 Ctrl+Z
全局 复制 Ctrl+C
全局 恢复上一个选择 Alt+Shift+↓
全局 剪切 Ctrl+X
全局 快速修正 Ctrl1+1
全局 内容辅助 Alt+/
全局 全部选中 Ctrl+A
全局 删除 Delete
全局 上下文信息 Alt+?
Alt+Shift+?
Ctrl+Shift+Space
Java编辑器 显示工具提示描述 F2
Java编辑器 选择封装元素 Alt+Shift+↑
Java编辑器 选择上一个元素 Alt+Shift+←
Java编辑器 选择下一个元素 Alt+Shift+→
文本编辑器 增量查找 Ctrl+J
文本编辑器 增量逆向查找 Ctrl+Shift+J
全局 粘贴 Ctrl+V
全局 重做 Ctrl+Y
查看
作用域 功能 快捷键
全局 放大 Ctrl+=
全局 缩小 Ctrl+-
窗口
作用域 功能 快捷键
全局 激活编辑器 F12
全局 切换编辑器 Ctrl+Shift+W
全局 上一个编辑器 Ctrl+Shift+F6
全局 上一个视图 Ctrl+Shift+F7
全局 上一个透视图 Ctrl+Shift+F8
全局 下一个编辑器 Ctrl+F6
全局 下一个视图 Ctrl+F7
全局 下一个透视图 Ctrl+F8
文本编辑器 显示标尺上下文菜单 Ctrl+W
全局 显示视图菜单 Ctrl+F10
全局 显示系统菜单 Alt+-
导航
作用域 功能 快捷键
Java编辑器 打开结构 Ctrl+F3
全局 打开类型 Ctrl+Shift+T
全局 打开类型层次结构 F4
全局 打开声明 F3
全局 打开外部javadoc Shift+F2
全局 打开资源 Ctrl+Shift+R
全局 后退历史记录 Alt+←
全局 前进历史记录 Alt+→
全局 上一个 Ctrl+,
全局 下一个 Ctrl+.
Java编辑器 显示大纲 Ctrl+O
全局 在层次结构中打开类型 Ctrl+Shift+H
全局 转至匹配的括号 Ctrl+Shift+P
全局 转至上一个编辑位置 Ctrl+Q
Java编辑器 转至上一个成员 Ctrl+Shift+↑
Java编辑器 转至下一个成员 Ctrl+Shift+↓
文本编辑器 转至行 Ctrl+L
搜索
作用域 功能 快捷键
全局 出现在文件中 Ctrl+Shift+U
全局 打开搜索对话框 Ctrl+H
全局 工作区中的声明 Ctrl+G
全局 工作区中的引用 Ctrl+Shift+G(查找类、方法和属性的引用。这是一个非常实用的快捷键,例如要修改引用某个方法的代码,可以通过【Ctrl+Shift+G】快捷键迅速定位所有引用此方法的位置。
文本编辑
作用域 功能 快捷键
文本编辑器 改写切换 Insert
文本编辑器 上滚行 Ctrl+↑
文本编辑器 下滚行 Ctrl+↓
文件
作用域 功能 快捷键
全局 保存 Ctrl+X
Ctrl+S
全局 打印 Ctrl+P
全局 关闭 Ctrl+F4
全局 全部保存 Ctrl+Shift+S
全局 全部关闭 Ctrl+Shift+F4
全局 属性 Alt+Enter
全局 新建 Ctrl+N
项目
作用域 功能 快捷键
全局 全部构建 Ctrl+B
源代码
作用域 功能 快捷键
Java编辑器 格式化 Ctrl+Shift+F
Java编辑器 取消注释 Ctrl+
Java编辑器 注释 Ctrl+/
Java编辑器 添加导入 Ctrl+Shift+M
Java编辑器 组织导入 Ctrl+Shift+O
Java编辑器 使用try/catch块来包围 未设置,太常用了,所以在这里列出,建议自己设置。
也可以使用Ctrl+1自动修正。
运行
作用域 功能 快捷键
全局 单步返回 F7
全局 单步跳过 F6
全局 单步跳入 F5
全局 单步跳入选择 Ctrl+F5
全局 调试上次启动 F11
全局 继续 F8
全局 使用过滤器单步执行 Shift+F5
全局 添加/去除断点 Ctrl+Shift+B
全局 显示 Ctrl+D
全局 运行上次启动 Ctrl+F11
全局 运行至行 Ctrl+R
全局 执行 Ctrl+U
重构
作用域 功能 快捷键
全局 撤销重构 Alt+Shift+Z
全局 抽取方法 Alt+Shift+M
全局 抽取局部变量 Alt+Shift+L
全局 内联 Alt+Shift+I
全局 移动 Alt+Shift+V
全局 重命名 Alt+Shift+R
全局 重做 Alt+Shift+Y

DOM对象和JQuery对象的区别

jQuery对象和DOM对象使用说明,需要的朋友可以参考下。

1.jQuery对象和DOM对象
第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DOM对象,因此需要重点了解jQuery对象和DOM对象以及它们之间的关系.
DOM对象,即是我们用传统的方法(javascript)获得的对象,jQuery对象即是用jQuery类库的选择器获得的对象;
复制代码 代码如下:

var domObj = document.getElementById("id"); //DOM对象
var $obj = $("#id"); //jQuery对象;

jQuery对象就是通过jQuery包装DOM对象后产生的对象,它是jQuery独有的。如果一个对象是jQuery对象,那么就可以使用jQuery里的方法,例:
$("#foo").html(); //获取id为foo的元素内的html代码,html()是jQuery特有的方法;
上面的那段代码等同于:
document.getElementById("foo").innerHTML;

注意:在jQuery对象中无法使用DOM对象的任何方法。
例如$("#id").innerHTML 和$("#id").checked之类的写法都是错误的,可以用$("#id").html()和$("#id").attr ("checked")之类的 jQuery方法来代替。同样,DOM对象也不能使用jQuery方法。学习jQuery开始就应当树立正确的观念,分清jQuery对象和DOM对象之间的区别,之后学习 jQuery就会轻松很多的。

2.jQuery对象和DOM对象的互相转换

在上面第一点说了,jquery对象和dom对象是不一样的!比如jquery对象不能使用dom的方法,dom对象不能使用jquery方法,那假如我 jquery没有封装我要的方法,那能怎么办呢?
这时我们可以将jquer对象转换成dom对象

jquery对象转换成 dom对象
jquery提供了两种方法将一个jquery对象转换成一个dom对象,即[index]和get(index)。可能有人会觉得奇怪,怎么是用下标呢,没错,jquery对象就是一个数组对象.
下面代码将演示一个jquery对象转换成dom对象,再使用dom对象的方法
复制代码 代码如下:

var $cr=$("#cr"); //jquery对象
var cr = $cr[0]; //dom对象 也可写成 var cr=$cr.get(0);
alert(cr.checked); //检测这个checkbox是否给选中

dom对象转换成jquery对象
对于一个dom对象,只需要用$()把dom对象包装起来,就可以获得一个jquery对象了,方法为$(dom对象);
复制代码 代码如下:

var cr=document.getElementById("cr"); //dom对象
var $cr = $(cr); //转换成jquery对象

转换后可以任意使用jquery中的方法了.

通过以上的方法,可以任意的相互转换jquery对象和dom对象.
最后再次强调,dom对象才能使用dom中的方法,jquery对象不可以使用dom中的方法,但 jquery对象提供了一套更加完善的工具用于操作dom,关于jquery的dom操作将在后面的文章进行详细讲解.


ps: 平时用到的jquery对象都是通过$()函数制造出来的,$()函数就是一个jquery对象的制造工厂.
建议:如果获取的对象是 jquery对象,那么在变量前面加上$,这样方便容易识别出哪些是jquery对象,例如:
var $variable = jquery对象;
如果获取的是dom对象,则定义如下:
var variable = dom对象 

http://www.cnblogs.com/yellowapplemylove/archive/2011/04/19/2021583.html

处理遗留系统

最近一个同事要离职,而我刚到新公司没多久,需要从他手里接过原来的系统,经过这些天的交接,确实是难。找到三篇文章,应该帮助很大,
第一篇属于方法论(张逸老师),为我们处理遗留系统制定大目标和策略:
http://www.cnblogs.com/wayfarer/archive/2011/10/09/2203651.html
第二篇属于工具类,介绍了处理java遗留系统有哪些工具:
http://tieba.baidu.com/p/1331700739
第三篇是从工程角度来分析该如何处理遗留系统:
http://www.infoq.com/cn/articles/Dealing-with-legacy-code
第四篇文章则是张逸老师碰到的一个案例:
http://www.cnblogs.com/wayfarer/archive/2011/02/18/1957530.html
下面的文章是摘自第一篇文章,加粗部分是我认为的重点:
处理遗留系统,几乎是每个程序员都不可能绕过的一件麻烦事儿。因为时间压力,技能不足以及功能复杂等诸多原因,常常使得遗留系统的代码变得糟糕混乱,可读性与维护性差,无法保证功能的可测试性,纠缠不清的代码让类、方法之间紧紧耦合在一起。如果遗留系统能够正常工作,那么我们还可以置之不理,即使代码接近腐烂的边缘,我们还可以得过且过。倘若我们需要维护遗留系统,或者需要为它添加新的功能,又或者需要将新的系统与遗留系统进行集成,就必须正视遗留系统带来的问题了。
处理遗留系统,首先需要分析和了解遗留系统,尤其这个遗留系统并非你开发时,更需如此。我们可以考虑双管齐下的办法。一是从业务逻辑方面去了解。相比新系统而言,遗留系统的唯一好处就是它往往是可以运行可以使用的。因此,最好的办法是直接运行遗留系统,通过实际操作了解系统的各个功能点、业务流程。这样的直观感受可以最快地帮助你了解该系统:它能够做什么?它能达成什么目标?它的范围是什么?它存在什么问题?其二则需要从系统架构出发,了解遗留系统的逻辑结构和物理分布。可以阅读架构文档和源代码,如果能够咨询遗留系统的设计者或开发人员,就更好。尽快地描绘出遗留系统的轮廓图,可以帮助你从技术的宏观角度剖析遗留系统的结构与组成。再结合你对该系统业务的理解,快速地掌握遗留系统。如果需要阅读源代码,最好能够从主程序入口开始,找到一些主要的模块,了解其大体的设计方式与编码习惯。由于之前对系统架构已有了解,阅读代码时,不应在一开始就去理解代码实现的细节,而应结合架构文档,比对代码实现是否与文档的描述一致,并充分利用自己的技术与经验,找到阅读代码的终南捷径。例如,如果我们知道该系统采用了MVC架构,就可以很容易地根据Url找到对应的Controller对象,并在该对象中寻找业务功能实现的脉络。又例如我们知道系统采用了SSH框架,而我们又非常熟悉SSH框架,就可以基本忽略系统基础设施的部分,直接了解系统的业务实现。如果是Swing系统,而且在界面中混杂了大量的业务逻辑和界面逻辑,则需要找到系统实现的特点,譬如系统的业务都是通过菜单项进行操作,就可以在界面中找到相关的菜单对象,然后根据这些对象的Action操作,一步一步跟踪。甚至可以利用调试的方法,设置断点,来摸清楚系统的运行机制与执行顺序。
分析遗留系统需要有的放矢,根据目标快速锁定范围。例如,如果我们是因为系统性能出现问题,而要去分析遗留系统,就无需过于关心业务逻辑,而应从性能分析入手。考虑数据库的访问,IO操作,缓存机制,资源的使用等诸多方面。这就需要借助一些经验和技术了,当然也可以考虑使用一些工具,用以诊断性能瓶颈。我们曾经在一个项目中,发现遗留系统的性能问题非常严重。根据分析,我们发现系统对字符串的处理存在问题,大量使用了String类型的对象完成字符的拼接。而在进行数据库查询时,很多代码是直接性地一次将相关表的数据“拉”到内存的集合对象中,然后利用过滤器在集合对象中进行筛选。而在对数据进行更改时,又没有很好地利用业务特性,完成一次提交,导致产生多次数据库访问。在系统的某些公共模块中,重复多次加载了Spring的配置文件。还有某些占用了较大资源的对象,对于系统用户而言应该是同一个对象,但却没有设计为单例。因为我们抱着改善遗留系统性能的目的,所以在分析遗留系统时,就应该事先确定可能导致性能损耗的地方,而不是全方位地去了解整个系统。
在维护遗留系统时,需要根据不同的场景做出不同的决策。简言之,我们需要排定优先级。如果时间紧迫,则解决问题是第一要务。尽快通过出错情况和记录日志辨别出错原因,定位出错代码,并解决之,而不是去考虑设计的优雅,代码的重用。我在一次解决报表显示的问题时,采用的就是这种做法。虽然与错误相关的代码相当丑陋,但由于时间紧迫,解决Bug才是最要紧的,所以我基本上没有去修改或改善既有代码的结构,仅仅解决了问题就结束了对遗留系统的处理。这个问题的处理过程在我的另一篇博客《精益求精,抑或得过且过》中详细论述。是的,我在此时选择了得过且过的做法,主要原因就在于优先级列表中,问题的解决才是最高优先级。在这次处理过程中,我部分地利用了copy & paste的做法,并通过引入新方法的方式来解决bug,而不是直接修改出现问题的方法。这是因为该方法的引用点存在多处,由于遗留系统并没有单元测试的保护,我不能草率地修改,否则可能导致一个bug的修复,结果引入更多无法预测的Bug。
这样的处理方式其实是我不甘心的选择。在时间允许的情况下,我会考虑对相关代码进行一些小的重构,例如提取方法或提取类等。虽然这些重构不能改变遗留系统的本质,但至少可以提高代码的可读性,并能在一定程度下去除代码重复。
这一实例实际引出了单元测试的必要性。如果我们的开发都能够在单元测试的呵护下进行,即使当它随着时间的推移,慢慢变成了丑陋的遗留代码,因为有单元测试的保护,在对它进行手术时,成功的几率却会变得更大。因此,认为编写单元测试是浪费时间的观点,事实上是一种短视的做法。缺乏单元测试,就是一种技术债(technical debt)。现在欠下的,将来总是要还的。我们不能忽略软件的维护成本。事实上,在软件成本中,维护成本所占的比例远远大于开发成本。Kent Beck在《实现模式(Implementation Patterns)》一书中认为:“维护的代价很大,这是因为理解现有代码需要花费时间,而且容易出错。知道了需要修改什么以后,做出改动就变得轻而易举了。掌握现在的代码做了哪些事情是最需要花费人力物力的部分,改动之后,还要进行测试和部署。”
当然,在处理遗留系统时,仍然可以进行单元测试。但它需要的技能更高,付出的精力更多。若要完成对遗留系统的单元测试,首要必须解决的就是依赖。解除依赖是面向对象系统开发中似乎亘古不变的话题。无论你翻阅哪一本面向对象著作,都会提到这个问题。依赖的起源其实在于对象的协作。根据单一职责原则,一个类显然不能承担太多的职责。如果一个系统的所有功能都是一个对象完成的,那么这个对象就成了邪恶的“上帝”对象。它的粒度太大,因此难以重用,不够灵活,细节纠缠,无法维护。这样的设计是我们必须避免的。既然一个类不能承担太多职责,系统要完成客户要求的所有功能,就必须让许多对象互相协作,就像人类社会的人际交往一般,于是,就产生了对象之间的依赖。凡事有利必有弊,这种细粒度的对象协作,虽然保证了对象的重用性、灵活性,却也因为协作带来的依赖,导致对象之间存在一定的耦合度,变得难以替换。由于一个对象依赖另一个对象,就使得我们在单元测试时,无法独立地测试我们的目标对象。
依赖虽然不可避免,但如果能够保证对象之间的良好协作,就能够最大程度保证系统的松耦合。对象协作一般可以分为类协作、接口协作与回调协作(回调协作通常可以看做是接口协作的一种特殊方式,关于这三种协作的特点与方式,我会在以后的文章中谈到)。依赖最弱的是接口协作,这也是我们努力达到的目标,它符合“面向接口设计”的基本原则。同时,也能够保证对象的封装性,通过将实现细节隐藏起来,从而降低对象之间的依赖程度。
知易行难,要对付遗留系统中的依赖问题,通常需要付出百倍的努力。Michael Feathers在其大作《修改代码的艺术(Working Effectively with Legacy Code)》中,给出了很多好的解除遗留代码依赖的实践。例如他提出的接缝类型,隐藏依赖,以及影响结构图。解除依赖需要合理地利用封装与抽象,包括对方法参数的封装与抽象。对于遗留系统而言,Feathers提出的影响结构图尤其有用。当你需要修改某个方法时,影响结构图可以帮助你分析该方法的依赖传播途径,避免因为修改对其他代码造成影响。具体做法可以参考该书。当然,现在有很多IDE工具事实上还支持生成依赖图,它可以在一定程度上帮助你寻找依赖的方向与结构。不过,手工方式的分析尤其是通过手绘影响结构图仍然不可缺少,它更能帮助你深入地理解遗留系统。为遗留系统绘制包图(Package Diagram)同样非常重要,它既能帮助你理解遗留系统的结构,又能为我们找到系统中可能存在的双向依赖和循环依赖,找到分解不合理的包,这就为我们的系统重构奠定了良好的基础。
如果遗留系统非常复杂,以至于无法重构,同时我们又需要在遗留系统的基础上增加新的特性和功能,好的做法就是做好新、旧功能之间的隔离。暂时可以不去考虑遗留系统的原有结构和代码(大多数情况,这些遗留系统其实是可以正常工作的),而只需要为新增的功能做好单元测试,并以好的设计原则与编码规范来要求新功能的实现。同时,我们还可以编写一些自动化的回归测试用例(例如使用Cucumber结合Watir为Web系统编写回归测试),保证新增的功能不会影响到原有系统。如果新增功能的实现需要调用原有系统中的某些类或方法,而这些类和方法却又难以分解,则可以考虑参照原有实现编写新的类或方法,新的类一定要做到合理的封装与抽象,保证它的高内聚与松耦合;也可以在新的类中不去真正实现,而是通过运用Adapter模式来重用旧有的类。
只要不是更改开发平台,通常情况下,我们不会考虑重写遗留系统。如果需要重构遗留系统,就必须采取“分而治之,小步前进”的策略。可以首先选择实现较为容易,或者独立性较好的模块进行重构。将遗留系统逐步提取为一些可重用的模块与类,就可以形成“星星之火,可以燎原”的态势。其中,对于原有类或模块的调用方,由于在重构时可能会更改接口,因此可以考虑引入Facade模式或Adapter模式,或者对接口进行另一层的包装,或者对接口进行适配,使系统慢慢被替换,最后演化为一个结构合理的良好系统。在重构时,甚至可以考虑将一些独立性很好的功能,提取为单独进程下的应用或服务,通过Web Service、Socket或Restful的方式进行调用,既保证了重用,又能够使遗留系统变得更简单,成功瘦身。
我们还需谨记的一点是,虽然模块乃至服务的重构对系统的改造更加重要,但我们却不能忽视代码的质量。小到方法的提取,以及变量的命名都非常重要。也许它不会给系统带来根本的改变,但它却能够改善代码的可阅读性,进而提高系统的可维护性。所谓”聚沙成塔“,无论是系统还是模块,其实都是这些类和方法以及变量组成的。
对遗留系统的处理不可能“一蹴而就”,必须遵循循序渐进的做法,逐步改善。处理遗留系统影响深远,成本也非常高,所以我们也不能因为脑袋一发热,就开始气势汹涌地“提枪上阵”,错将风车当做了魔鬼,结果撞得头破血流。必须对整个遗留系统进行审慎的分析,并结合具体情况考虑这项工程的复杂度、成本与预算,了解团队的重构与设计能力。处理遗留系统的前路漫漫,我们固然需要上下而求索的决心与勇气,却也不能在茫然失措中迷失了前进的方向。遗留系统的处理,必须慎之,慎之,再慎之!

classloader-类加载器

什么是类加载器呢?
与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。
JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现的,它负责加载核心JavaClass(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由BootstrapClassLoader加载;其中Extension ClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类),ApplicationClassLoader负责加载应用程序自身的类。
当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

welcome-file-list失效

welcome-file-list是一个配置在web.xml中的一个欢迎页,用于当用户在url中输入工程名称或者输入web容器url(如http://localhost:8080/)时直接跳转的页面。碰到一个问题是访问报404找不到的错误,welcome-file-list没有起作用。
welcome-file-list的工作原理是,按照welcome-file的.list一个一个去检查是否web目录下面存在这个文件,如果存在,继续下面的工作(或者跳转到index.html页面,或者配置有struts的,会直接struts的过滤工作)。先去webcontent(这里是Eclipse的工程目录根目录)下是否真的存在index.html这个文件,如果不存在去找是否存在index.jsp这个文件,依次类推。

<welcome-file-list>
	<welcome-file>VideoRankAction!goVideoRank10.action</welcome-file>
</welcome-file-list>

welcome-file不一定是html或者jsp等文件,也可以是直接访问一个action。但要注意的是,一定要在webcontent下面建立一个index.action的空文件,然后使用struts配置去跳转,不然web找不到index.action这个文件,会报404错误。
如何不让地址栏显示后缀,只展示域名:
1、在没进行这样设置之前,当在地址栏中输入域名地址:http://www.xxxxx.com,浏览器解析后,地址栏会出
现http://www.xxxxx.com/xxx/xxx.do。
2、经过如上设置之后,输入http://www.xxxxx.com后,不再会出现后面的后缀。
做法:
1、在web.xml中,定义welcome-file为一个jsp页面:

<welcome-file-list>
        <welcome-file>/index.jsp</welcome-file>
</welcome-file-list>

2、然后在该jsp页面中引入需要显示的action,如:

<c:import url="/xxx.do"/>

注:在表头需要导入jstl包

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>