Contents
来源: BlogBus 原始链接: http://mmi.blogbus.com:80/index.html 存档链接: https://web.archive.org/web/20060110093930id_/http://mmi.blogbus.com:80/index.html
衣不如新 茕茕白兔,东走西顾。 | 首页 2005-11-25 突破JUnit的局限 TAG: JUNIT http://www.matrix.org.cn/resource/article/43/43628_JUnit_Pisces.html 作者:Amir Shevat 07/13/2005 翻译: tetsu 版权声明 :可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明 英文原文地址: http://www.onjava.com/pub/a/onjava/2005/07/13/pisces.html 中文地址: http://www.matrix.org.cn/resource/article/43/43628_JUnit_Pisces.html 关键词: JUnit Pisces “没有人喜欢bug。”大多数关于单元测试的文章以这句话开篇。的确,我们都希望代码如设计的那样准确地执行,但是就好像叛逆孩子一样,程序在完成之后产生的行为将难以控制。比那些家长们幸运的是,我们可以运用工具以确保程序达到预期效果。 市面上有很多用于测试,分析以及debug程式的工具,其中以JUnit最为有名。这是一个协助软件工程师,QA(品质监管)工程师测试阶段性代码的平台。几乎每个接触过JUnit的人都对它有强烈的感情:要么喜欢,要么讨厌。主要的抱怨之一是它缺少做复杂场景测试的能力。 通过突破传统模式的思考,这一问题可以得到解决。这篇文章将介绍JUnit如何利用Pisces来实现复杂测试。Pisces是一个开源项目,作为JUnit的扩展,它可以让你写出由一些JUnit测试组成的测试单元,每个测试单元可以以串行或并行的方式运行在一个远程主机上。Pisces可以让你构成、运行复杂场景,并在一个地点协调它们。 JUnit 基础 JUnit中有两个基本对象,TestCase和TestSuite。TestCase通过提供一组方法来实现一系列测试。例如,setup()方法用来在每项测试开始前建立测试所需的测试环境,而teardown()方法用来在测试后销毁该环境。其他的方法都会完成各式各样的任务,例如,在测试中进行性能检测,判断变量是否为null,比较变量以及捕捉异常。 要创建测试程序,需要继承TestCase类,覆写setup和teardown方法,然后添加自己的测试函数,这些函数通常以“test测试名”的形式命名。 下面是一个测试程序的例子: public class MyTestCase extends TestCase { /**
- call super constructor with a test name
- string argument.
- @param testName the name of the method that
- should be run. / public MyTestCase(String testName){ super(testName); } /*
- called before every test
- 在每项测试开始前被调用 / protected void setUp() throws Exception { initSomething(); } /*
- called after every test
- 在测试完成后被调用 / protected void tearDown() throws Exception { finalizeSomething(); } /*
- this method tests that ...
- 这个方法测试…… / public void testSomeTest1(){ ... } /*
- this method tests that ...
- 这个方法测试…… */ public void testSomeTest2 (){ ... } } TestSuite是由几个TestCase或其他的TestSuite构成的。你可以很容易的构成一个树形测试,每个测试都由持有另外一些测试的TestSuite来构成。被加入到TestSuite中的测试在一个线程上依次被执行。 ActiveTestSuite是TestSuite的一个子类。被添加到ActiveTestSuite中的测试程序以并行方式执行,每个测试在一个独立的线程上运行。创建一个测试单元的方法之一是继承TestSuite类并覆写suite()方法。 下面是一个简单的例子: public class MyTestSuite extends TestSuite { public static Test suite() { TestSuite suite = new TestSuite("Test suite for ..."); // add the first test // 添加第一个测试 MyTestCase mtc = new MyTestCase("testSomeTest1"); suite.addTest(mtc); // add the second test // 添加第一个测试 MyTestCase mtc2 = new MyTestCase("testSomeTest2"); suite.addTest(mtc2); return suite; } } 运行一个测试或测试单元非常简单,因为从JUnit提供的GUI开始,到所有的IDE开发环境,例如Eclipse,都有GUI可以使用。 图1, 显示了TestSuite在Eclipse中是如何表示的。 图1,集成在Eclipse中的JUnit 因为介绍JUnit并不是这篇文章的主题,而且有很多关于JUnit的文章,本文就只提供这些JUnit基础概念的概要。在“资源”小节里有对JUnit更深入介绍的文章。 JUnit的优点和缺点 JUnit是一个易用的,灵活的,开源的,测试平台。就像所有其他项目一样,它有很多优点,但也有不足之处。通过使用无需人工干预的JUnit自动测试平台,我们很容易累积起大量的JUnit测试程序从而保证以往的bug不会重现。另外,JUnit便于和编译单元(如,Ant)以及IDE单元(如,Eclipse)集成。 JUnit的弱点也众所周知。它仅支持同步测试,而且不支持重现和其他异步单元。JUnit是一个黑箱测试平台,因此测试那些不会直接影响功能的bug(例如,内存泄漏)就非常困难。除此之外,它不支持易用的脚本语言,因此,想要使用JUnit就要懂得Java。 JUnit的另一个不足是JUnit测试被限制于一个JVM之上。当要测试复杂或分布式场景的时候,这就变成个大问题。本文剩下的部分,就这个问题及其解决方法进行论述。 复杂场景测试: 我们为什么需要测试复杂的分布式场景?
- 那些确保小单元的完整性的测试很有用,但同时也有局限性。经验告诉我们,大多数bug是在完整的测试中被发现的。这些问题从两个模块不能一同正常协调工作,到两个独立应用程序的异常。无论这是两个应用服务,还是客户/服务环境,甚至是点对点模式,对这些复杂场景的测试尤其重要,因为那些难缠的bug往往寄生于此。但是用JUnit对此几乎无能为力。
- 虽然Java具有平台无关性,但测试一个应用程序在多种操作系统上的表现还是一个明智的选择。你的程序可能在所有的操作系统上都能运行,但是却不一定严格地按照你设计的那样正常工作。在所有的操作系统上重复同样的一组测试程序是一件耗时的工程,而用JUnit你不能进行分布式测试,因此你无法让同样的一组测试同时运行在几个JVM之上,而每个JVM运行在不同的操作系统之上。
- 一些单元代码,只能在多JVM场景下被测试。例如,测试建立一个连接(TCP socket或者HTTP连接)以及从中取得的信息的完整性。这样的测试不可能(或者说很难)在单一JVM上测试。而这是JUnit给我们的唯一选择。 用Ant协同测试 仅使用JUnit和Ant来协调几个运行在不同JVM上的测试是可能的。Ant可以以并行或者串行的方式执行任务(使用标记),而且可以设置当有任何测试失败的时候,就停止运行。 但这种方法有其局限性,首先,使用JUnit和Ant仍然把你限制在一个操作系统之上。其次,随着你的测试程序的累积,你会发现Ant XML文件会变得越来越大,以至于无法管理。第三,我们发现在某些情况下,分支JVM会在Ant任务结束后保留下来甚至继续运行。 Pisces项目就是为了解决JUnit的这些限制而来的,它给予JUnit复杂场景和分布式测试的能力。本文下面的章节将介绍这个开源项目。 利用Pisces打破JUnit的局限 Pisces基础知识 Pisces是一个开源项目,它扩展了JUnit平台。就像许多其他的扩展程序一样,Pisces添加了新功能的同时也保证了扩展前后JUnit操作的一致性。 Pisces的核心是在同一主机或不同主机上实现在远程JVM上运行JUnit测试的能力。这些远程测试程序会封装在本地运行的JUnit测试程序中,因此开发人员或者QA(品质测试)人员可以用通常的JUnit GUI工具来运行这些通常(本地)的测试程序。用来包装远程测试的对象叫做RemoteTestCase,它也是TestCase册子类。 图2显示的是远程测试程序和它的包装器。 图2,远程测试和它的包装器 在每一个远端,我们运行一个Pisces代理程序,并指定唯一的代理名。这个代理负责运行实际的JUnit测试程序并将结果返回到本地。现在,一旦我们能运行一个包装在本地测试中的远程测试程序,我们就能通过组合几个这样的测试来创建一个更为复杂的场景。 图3, 由几个远程测试组成的测试单元 改变默认输出 每个代理运行一组测试程序,而每个测试程序都可能写信息到默认输出。因为保证测试人员或开发人员在测试过程中得到这些信息很重要,所以默认输出被拷贝到本地测试单元的控制台中。这样,便可以避免在测试单元中查看每个代理的控制输出。 Pisces的可扩展通信层 在各个代理及本地主测试程序之间的通信是件复杂的事情,因为Pisces必须能在各种不同的环境以及网络配置下正常工作。为了解决这一问题,Pisces有一个可以扩展的通信层。它的每一个实现解决一个指定的网络环境下的问题。 Pisces提供两个默认的基本通信层,一个是易于配置但只能工作在局域网内的multicast实现,另一个是JMS实现。它需要安装面向消息中间件/消息导向中间件(MOM, Message-Oriented Middleware),可以应付大多数网络环境。 配置并运行Pisces测试 1.配置并运行Pisces代理 正如前面所提到的,Pisces测试单元是由几个运行在远程代理之上的Junit测试程序组成的。每个代理都是一个Java应用程序,它根据从主测试程序接收的指令来运行JUnit测试,并将结果及默认输出返回到主测试单元。 运行代理程序最简单的方式是在Pisces提供的脚本文件夹中配置并运行相关的可执行脚本。此外,你也可以在已经提供的脚本的基础上构建你自己的脚本。脚本文件容许用户配置代理的通用参数,例如唯一标识符,为通信层指定multicast IP地址和端口。 下面是一个代理程序的脚本文件,该代理程序提供了通信层,并运行在Linux系统上:(下面是一个运行在Linux系统上的,具有通信层的代理程序的脚本文件) #!/bin/sh
the folder were the agent can find junit.jar
export JUNIT_HOME=/opt/junit3.8.1
the folders were the agent can find
the junit tests
export JUNIT_TEST_CLASSPATH=../examples/
the multicast port that the communication
layer uses
export PISCES_MCP=6767
the multicast IP that the communication
layer uses
export PISCES_MCIP=228.4.19.76
the unique name of the agent
export AGENT_NAME=remote1
java -classpath
"$JUNIT_HOME/junit.jar:../pisces.jar:$JUNIT_TEST_CLASSPATH"
org.pisces.RemoteTestRunnerAgent
-name $AGENT_NAME -com multicast
-mcp $PISCES_MCP -mcip $PISCES_MCIP
利用JMS通信层的Pisces代理的可执行脚本和multicast方式的差不多是一样的。JMS通信层以装载类的名字作为参数,该装载类返回你的JMS提供者的ConnectionFactory。
2.配置并运行Pisces-Enabled测试单元
在配置并运行了所有相关代理后,我们还需要配置并运行我们的Pisces-enabled测试单元。
首先,我们需要把通信层的配置添加到我们的TestSuite的开头。显示如下:
// create the multicast communication layer
MulticastRemoteTestCommunicationLayer com =
new MulticastRemoteTestCommunicationLayer(
RemoteTestRunner.name ,"228.4.19.76",6767);
// set the communication layer
RemoteTestRunner.setCom(com);
接下来,我们需要创建一个TestSuite的实例,并添加想要远程调用的测试。这一步骤可以通过指定JUnit类(有一个可选的测试方法)和将要运行这一特定测试的代理的名字来实现。
下面是一段示例代码:
// 创建一个通常的TestSuite实例
TestSuite suite =
new TestSuite("Test for org.pisces.testers");
// 为这个实例创建一个包装器
RemoteTestCase rtc =
RemoteTestCase.wrap(RemoteTestTestCase.class
,"someTestMethod", "remote1");
// 添加测试实例到TestSuite
suite.addTest(rtc);
文章的下一节,将提供一个分布式测试的例子和TestSuite的代码。
示例:并发登录测试
假设我们有一个网络应用程序。一个用户登录后,获得服务,然后退出。我们想确认我们的安全模块能够阻止单一用户在两台不同的电脑上同时登录。
我们创建了一个名字为MyTestCase的测试对象,正如前面所表示的,我们有两个测试方法。第一个,testLogin(),用来确认我们第一次能正确登录。第二个方法,testLoginFails(),用来确认第二次登录时会失败。
下面是利用了Pisces的测试单元:
public class RemoteTestSuiteExample extends TestSuite {
public static Test suite() {
// 配置并启动通信层
JMSRemoteTestCommunicationLayer com = null;
try {
com =
new JMSRemoteTestCommunicationLayer
(RemoteTestRunner.name,
new MantaConnectionFactory());
} catch (JMSException e) {
throw new RuntimeException(e);
}
RemoteTestRunner.setCom(com);
// 实例化JUnit
TestSuite suite =
new TestSuite("Test for org.pisces.testers");
// 在远程代理remote1上运行MyTestCase类中的testLogin RemoteTestCase rtc =
RemoteTestCase.wrap(MyTestCase.class,
"testLogin", "remote1");
suite.addTest(rtc);
// 在远程代理remotel2上运行MyTestCase类中的
// testLoginFails方法
RemoteTestCase rtc1 =
RemoteTestCase.wrap(MyTestCase.class,
"testLoginFails", "remote2");
suite.addTest(rtc1);
return suite;
}
}
如果一切正常,远程代理(remote1)将成功登录。当另外一个代理(remote2)试图以同一个用户名和密码登录的时候,将失败,因为该用户已经在另外一台电脑上登录了。
这个测试可以更为复杂。例如,通过添加testLogOut()方法来实现其他的安全测试,但是作为一个示例程序,我希望其保持简单。
在这个例子中,我利用了叫做MantaRay的无服务JMS,它是一个开源的,消息中间件包,基于点对点的技术。在最新一版的Pisces中,你可以找到几个利用JMS通信层的例子和利用multicast通信层的例子。
结论
Bug并不好玩,但是借助JUnit,一个被广泛利用的,开源的,自动测试的平台,运行多重测试变得容易了许多。许多项目扩展了JUnit的功能,但是到目前为止,测试还被局限在一台机器上。有了Pisces的辅助,和一些不同以往的思考方式,我们终于可以创建复杂的,分布式测试了。
资源
JUnit (参见: CodeZoo: JUnit)
Project Pisces
JUnit Cookbook
"Using JUnit with the Eclipse IDE"
MantaRay home page (参见: CodeZoo: MantaRay)
JMS Amir Shevat 是一个有8年计算经验的高级软件开发人员。
jerrydan
发表于
10:15
|
阅读全文
|
评论(0)
|
引用(0)
|
编辑
2005-11-25
junit实现过程
TAG:
JUNIT
http://www.evget.com/view/article/viewArticle.asp?article=562
测试分类:白箱测试、黑箱测试、单元测试、集成测试、功能测试...。白箱测试是指在知道被测试的软件如何(How)完成功能和完成什么样(What)的功能的条件下所作的测试,一般是由开发人员完成,单元测试是一种白箱测试,因为开发人员最了解自己编写的软件。JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架,回归测试就是你不断地对所编写的代码进行测试(如单元测试):编写一些,测试一些,调试一些,然后循环这一过程,你会不断地重复先前的测试,哪怕你正编写其他的类。
第一步:
去Junit主页(
http://www.junit.org
)下载最新版本3.8.1程序包junit-3.8.1.zip。解开压缩包到c:\junit(可自定义)。
第二步:
假如目录是c:\junit那么,在classpath中加入:”c:\junit;c:\junit\junit.jar;“定义类路径。在命令提示符下运行:java junit.swingui.TestRunner,如果一切正确,就会打开应用程序。在下拉菜单中寻找程序自带的例子,比如:junit.samples.AllTests,点击”Run“观察结果。
第三步:
实现自己的TEST计划,目前有一个叫MyBean的数据库操作类需要测试,如下:
package junit.samples;
import java.sql.;
import java.io.;
public class MyBean{
Statement stmt=null;
ResultSet rs=null;
Connection conn=null;
String result=null;
public String con(){ //初始化数据库
try{
Class.forName("org.gjt.mm.mysql.Driver").newInstance();
String url ="jdbc:mysql://192.168.0.88/weboa?user=root&password=";
conn= DriverManager.getConnection(url);
return "Connection Success!";
}
catch(Exception e){
System.out.println(e);
return "Connection Error!";
}
}
public String gogo(String lmdm){ //查询数据库
try{
stmt=conn.createStatement();
String sql="select * from TB_LM where N_LMDM='"+lmdm+"'";
rs=stmt.executeQuery(sql); //执行查询
while (rs.next()){
result=rs.getString("N_SJID");
}
}
catch(Exception e){
result=e.toString();
}
finally { //关闭JDBC资源
if(rs != null) try { rs.close(); } catch(SQLException ex) { ex.printStackTrace(); }
if(conn != null) try { conn.close(); } catch(SQLException ex) { ex.printStackTrace(); }
}
return result;
}
}
接着,创建一个测试类:TestMyBean,如下:
package junit.samples;
import junit.samples.MyBean;
import junit.framework.;
public class TestMyBean extends TestCase { //TestCase的子类
private MyBean aName; //构造被测类的对象
public TestMyBean(String name) {
super(name);
}
protected void setUp() { //进行初始化的任务
aName= new MyBean();
}
public static Test suite() { //进行测试
return new TestSuite(TestMyBean.class);
}
public void testCon() { //对预期的值和con方法比较
Assert.assertTrue(!aName.equals(null)); //断言
Assert.assertEquals("Connection Success!",aName.con());
}
public void testGogo() { //对预期的值和gogo方法比较
aName.con();
Assert.assertTrue(!aName.equals(null)); //断言
Assert.assertEquals("0",aName.gogo("1"));
}
}
解释如下:
首先要引入待测试的类import junit.samples.MyBean;接着引入Junit框架import junit.framework.;。与一个Servlet类似,需要继承父类TestCase;在setUp()方法中实例化一个MyBean,供后面的测试方法使用;suite()是一个很特殊的静态方法,它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件,确定有多少个测试可以执行;testCon()方法对MyBean的Con方法进行测试,并断言(Assert)结果是"Connection Success!",并在Assert.assertEquals()方法中验证;testGogo()方法和testCon()方法类似。
第四步:
把TestMyBean、MyBean类编译成*.class文件,在Junit的控制台上选择刚才定义的TestMyBean类,并运行。如果一切正确,就会显示绿条,证明测试正确。如果显示红色,在Results中会有相应显示,根据提示检查MyBean类中的错误。一般的,只要断言符合MyBean类的规范,TestMyBean类几乎不可能出错。
一些扩展:
对于WEB应用程序,我们可以把Junit引入,只需适当配置环境。另外,可以把众多的测试类集成到一起,形成总测试类,并且只需要实现suite()方法,例如:
public static Test suite ( ) {
TestSuite suite= new TestSuite("All JUnit Tests");
suite.addTest(VectorTest.suite());
suite.addTest(TestMyBean.suite());
return suite;
}
jerrydan
发表于
10:13
|
阅读全文
|
评论(0)
|
引用(0)
|
编辑
2005-11-25
应用JUnit实施单元测试
TAG:
JUNIT
应用JUnit实施单元测试
http://www.chinaunix.net 作者:
lanbird
发表于:2003-12-03 10:21:27
应用JUnit实施单元测试(原创作者:eric )
(献给想保证java coding软件质量的朋友们)
测试的概念 长期以来,我所接触的软件开发人员很少有人能在开发的过程中进行测试工作。大部分的项目都是在最终验收的时候编写测试文档。有些项目甚至没有测试文档。现在情况有了改变。我们一直提倡UML、RUP、软件工程、CMM,目的只有一个,提高软件编写的质量。举一个极端的例子:如果你是一个超级程序设计师,一个传奇般的人物。(你可以一边喝咖啡,一边听着音乐,同时编写这操作系统中关于进程调度的模块,而且两天时间内就完成了!)我真得承认,有这样的人。(那个编写UNIX中的vi编辑器的家伙就是这种人。)然而非常遗憾的是这些神仙们并没有留下如何修成正果的README。所以我们这些凡人--在同一时间只能将注意力集中到若干点(据科学统计,我并不太相信,一般的人只能同时考虑最多7个左右的问题,高手可以达到12个左右),而不能既纵览全局又了解细节--只能期望于其他的方式来保证我们所编写的软件质量。 为了说明我们这些凡人是如何的笨。有一个聪明人提出了软件熵(software entropy)的概念:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻。你可能会争辩,在这个例子中,设计很好的状态实际上并不好,如果好的话,就不会发生你所说的情况。是的,看来你变聪明了,可惜你还应该注意到两个问题:1)我们不能指望在恐龙纪元(大概是十年前)设计的结构到了现在也能适用吧。2)拥有签字权的客户代表可不理会加入一个新功能是否会对软件的结构有什么影响,即便有影响也是程序设计人员需要考虑的问题。如果你拒绝加入这个你认为致命的新功能,那么你很可能就失去了你的住房贷款和面包(对中国工程师来说也许是米饭或面条,要看你是南方人还是北方人)。 另外,需要说明的是我看过的一些讲解测试的书都没有我写的这么有人情味(不好意思...)。我希望看到这片文章的兄弟姐妹能很容易地接受测试的概念,并付诸实施。所以有些地方写的有些夸张,欢迎对测试有深入理解的兄弟姐妹能体察民情,并不吝赐教。 好了,我们现在言归正传。要测试,就要明白测试的目的。我认为测试的目的很简单也极具吸引力:写出高质量的软件并解决软件熵这一问题。想象一下,如果你写的软件和Richard Stallman(GNU、FSF的头儿)写的一样有水准的话,是不是很有成就感?如果你一致保持这种高水准,我保证你的薪水也会有所变动。 测试也分类,白箱测试、黑箱测试、单元测试、集成测试、功能测试...。我们先不管有多少分类,如何分类。先看那些对我们有用的分类,关于其他的测试,有兴趣的人可参阅其他资料。白箱测试是指在知道被测试的软件如何(How)完成功能和完成什么样(What)的功能的条件下所作的测试。一般是由开发人员完成。因为开发人员最了解自己编写的软件。本文也是以白箱测试为主。黑箱测试则是指在知道被测试的软件完成什么样(What)的功能的条件下所作的测试。一般是由测试人员完成。黑箱测试不是我们的重点。本文主要集中在单元测试上,单元测试是一种白箱测试。目的是验证一个或若干个类是否按所设计的那样正常工作。集成测试则是验证所有的类是否能互相配合,协同完成特定的任务,目前我们暂不关心它。下面我所提到的测试,除非特别说明,一般都是指单元测试。 需要强调的是:测试是一个持续的过程。也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程。Martin Fowler(有点儿像引用孔夫子的话)甚至认为:“在你不知道如何测试代码之前,就不应该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。”我并不指望所有的开发人员都能有如此高的觉悟,这种层次也不是一蹴而就的。但我们一旦了解测试的目的和好处,自然会坚持在开发过程中引入测试。 因为我们是测试新手,我们也不理会那些复杂的测试原理,先说一说最简单的:测试就是比较预期的结果是否与实际执行的结果一致。如果一致则通过,否则失败。看下面的例子: //将要被测试的类 public class Car { public int getWheels() { return 4; } } //执行测试的类 public class testCar { public static void main(String[] args) { testCar myTest = new testCar(); myTest.testGetWheels(); } public testGetWheels() { int expectedWheels = 4; Car myCar = Car(); if (expectedWheels==myCar.getWheels()) System.out.println("test [Car]: getWheels works perfected!"); else System.out.println("test [Car]: getWheels DOESN'T work!"); } } 如果你立即动手写了上面的代码,你会发现两个问题,第一,如果你要执行测试的类testCar,你必须必须手工敲入如下命令: [Windows] d:>java testCar [Unix] % java testCar 即便测试如例示的那样简单,你也有可能不愿在每次测试的时候都敲入上面的命令,而希望在某个集成环境中(IDE)点击一下鼠标就能执行测试。后面的章节会介绍到这些问题。第二,如果没有一定的规范,测试类的编写将会成为另一个需要定义的标准。没有人希望查看别人是如何设计测试类的。如果每个人都有不同的设计测试类的方法,光维护被测试的类就够烦了,谁还顾得上维护测试类?另外有一点我不想提,但是这个问题太明显了,测试类的代码多于被测试的类!这是否意味这双倍的工作?不!1)不论被测试类-Car 的 getWheels 方法如何复杂,测试类-testCar 的testGetWheels 方法只会保持一样的代码量。2)提高软件的质量并解决软件熵这一问题并不是没有代价的。testCar就是代价。 我们目前所能做的就是尽量降低所付出的代价:我们编写的测试代码要能被维护人员容易的读取,我们编写测试代码要有一定的规范。最好IDE工具可以支持这些规范。 好了,你所需要的就是JUnit。一个Open Source的项目。用其主页上的话来说就是:“JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。用于Java开发人员编写单元测试之用。”所谓框架就是 Erich Gamma 和 Kent Beck 定下了一些条条框框,你编写的测试代码必须遵循这个条条框框:继承某个类,实现某个接口。其实也就是我们前面所说的规范。好在JUnit目前得到了大多数软件工程师的认可。遵循JUnit我们会得到很多的支持。回归测试就是你不断地对所编写的代码进行测试:编写一些,测试一些,调试一些,然后循环这一过程,你会不断地重复先前的测试,哪怕你正编写其他的类,由于软件熵的存在,你可能在编写第五个类的时候发现,第五个类的某个操作会导致第二个类的测试失败。通过回归测试我们抓住了这条大Bug。 回归测试框架-JUnit 通过前面的介绍,我们对JUnit有了一个大概的轮廓。知道了它是干什么的。现在让我们动手改写上面的测试类testCar使其符合Junit的规范--能在JUnit中运行。 //执行测试的类(JUnit版) import junit.framework.; public class testCar extends TestCase { protected int expectedWheels; protected Car myCar; public testCar(String name) { super(name); } protected void setUp() { expectedWheels = 4; myCar = new Car(); } public static Test suite() { /
- the type safe way
TestSuite suite= new TestSuite(); suite.addTest( new testCar("Car.getWheels") { protected void runTest() { testGetWheels(); } } ); return suite; / /
- the dynamic way */ return new TestSuite(testCar.class); } public void testGetWheels() { assertEquals(expectedWheels, myCar.getWheels()); } } 改版后的testCar已经面目全非。先让我们了解这些改动都是什么含义,再看如何执行这个测试。 1>import语句,引入JUnit的类。(没问题吧) 2>继承 TestCase 。可以暂时将一个TestCase看作是对某个类进行测试的方法的集合。详细介绍请参看JUnit资料 3>setUp()设定了进行初始化的任务。我们以后会看到setUp会有特别的用处。 4>testGetWheeels()对预期的值和myCar.getWheels()返回的值进行比较,并打印比较的结果。assertEquals是junit.framework.Assert中所定义的方法,junit.framework.TestCase继承了junit.framework.Assert。 5>suite()是一个很特殊的静态方法。JUnit的TestRunner会调用suite方法来确定有多少个测试可以执行。上面的例子显示了两种方法:静态的方法是构造一个内部类,并利用构造函数给该测试命名(test name, 如 Car.getWheels ),其覆盖的runTest()方法,指明了该测试需要执行那些方法--testGetWheels()。动态的方法是利用内省(reflection )来实现runTest(),找出需要执行那些测试。此时测试的名字即是测试方法(test method,如testGetWheels)的名字。JUnit会自动找出并调用该类的测试方法。 6>将TestSuite看作是包裹测试的一个容器。如果将测试比作叶子节点的话,TestSuite就是分支节点。实际上TestCase,TestSuite以及TestSuite组成了一个composite Pattern。 JUnit的文档中有一篇专门讲解如何使用Pattern构造Junit框架。有兴趣的朋友可以查看JUnit资料。 如何运行该测试呢?手工的方法是键入如下命令: [Windows] d:>java junit.textui.TestRunner testCar [Unix] % java junit.textui.TestRunner testCar 别担心你要敲的字符量,以后在IDE中,只要点几下鼠标就成了。运行结果应该如下所示,表明执行了一个测试,并通过了测试: . Time: 0 OK (1 tests) 如果我们将Car.getWheels()中返回的的值修改为3,模拟出错的情形,则会得到如下结果: .F Time: 0 There was 1 failure:
- testGetWheels(testCar)junit.framework.AssertionFailedError: expected:<4> but was:<3> at testCar.testGetWheels(testCar.java:37) FAILURES!!! Tests run: 1, Failures: 1, Errors: 0 注意:Time上的小点表示测试个数,如果测试通过则显示OK。否则在小点的后边标上F,表示该测试失败。注意,在模拟出错的测试中,我们会得到详细的测试报告“expected:<4> but was:<3>”,这足以告诉我们问题发生在何处。下面就是你调试,测试,调试,测试...的过程,直至得到期望的结果。 Design by Contract(这句话我没法翻译) Design by Contract本是Bertrand Meyer(Eiffel语言的创始人)开发的一种设计技术。我发现在JUnit中使用Design by Contract会带来意想不到的效果。Design by Contract的核心是断言(assersion)。断言是一个布尔语句,该语句不能为假,如果为假,则表明出现了一个bug。Design by Contract使用三种断言:前置条件(pre-conditions)、后置条件(post-conditions)和不变式(invariants)这里不打算详细讨论Design by Contract的细节,而是希望其在测试中能发挥其作用。 前置条件在执行测试之前可以用于判断是否允许进入测试,即进入测试的条件。如 expectedWheels > 0, myCar != null。后置条件用于在测试执行后判断测试的结果是否正确。如 expectedWheels==myCar.getWheels()。而不变式在判断交易(Transaction)的一致性(consistency)方面尤为有用。我希望JUnit可以将Design by Contract作为未来版本的一个增强。 Refactoring(这句话我依然没法翻译) Refactoring本来与测试没有直接的联系,而是与软件熵有关,但既然我们说测试能解决软件熵问题,我们也就必须说出解决之道。(仅仅进行测试只能发现软件熵,Refactoring则可解决软件熵带来的问题。)软件熵引出了一个问题:是否需要重新设计整个软件的结构?理论上应该如此,但现实不允许我们这么做。这或者是由于时间的原因,或者是由于费用的原因。重新设计整个软件的结构会给我们带来短期的痛苦。而不停地给软件打补丁甚至是补丁的补丁则会给我们带来长期的痛苦。(不管怎样,我们总处于水深火热之中) Refactoring是一个术语,用于描述一种技术,利用这种技术我们可以免于重构整个软件所带来的短期痛苦。当你refactor时,你并不改变程序的功能,而是改变程序内部的结构,使其更易理解和使用。如:该变一个方法的名字,将一个成员变量从一个类移到另一个类,将两个类似方法抽象到父类中。所作的每一个步都很小,然而1-2个小时的Refactoring工作可以使你的程序结构更适合目前的情况。Refactoring有一些规则: 1> 不要在加入新功能的同时refactor已有的代码。在这两者间要有一个清晰的界限。如每天早上1-2个小时的Refactoring,其余时间添加新的功能。 2> 在你开始Refactoring前,和Refactoring后都要保证测试能顺利通过。否则Refactoring没有任何意义。 3> 进行小的Refactoring,大的就不是Refactoring了。如果你打算重构整个软件,就没有必要Refactoring了。 只有在添加新功能和调试bug时才又必要Refactoring。不要等到交付软件的最后关头才Refactoring。那样和打补丁的区别不大。Refactoring 用在回归测试中也能显示其威力。要明白,我不反对打补丁,但要记住打补丁是应该最后使用的必杀绝招。(打补丁也需要很高的技术,详情参看微软网站) IDE对JUnit的支持 目前支持JUnit的Java IDE 包括 IDE 方式 个人评价(1-5,满分5) Forte for Java 3.0 Enterprise Edition plug-in 3 JBuilder 6 Enterprise Edition integrated with IDE 4 Visual Age for Java support N/A 在IDE中如何使用JUnit,是非常具体的事情。不同的IDE有不同的使用方法。一旦理解了JUnit的本质,使用起来就十分容易了。所以我们不依赖于具体的IDE,而是集中精力讲述如何利用JUnit编写单元测试代码。心急的人可参看资料。 JUnit简介 既然我们已经对JUnit有了一个大致的了解,我希望能给大家提供一个稍微正式一些的编写JUnit测试文档的手册,明白其中的一些关键术语和概念。但我要声明的是这并不是一本完全的手册,只能认为是一本入门手册。同其他OpenSource的软件有同样的问题,JUnit的文档并没有商业软件文档的那种有规则,简洁和完全。由开发人员编写的文档总是说不太清楚问题,全整的文档需要参考"官方"指南,API手册,邮件讨论组的邮件,甚至包括源代码中及相关的注释。 事实上问题并没有那么复杂,除非你有非常特别的要求,否则,只需参考本文你就可以得到所需的大部分信息。 安装 首先你要获取JUnit的软件包,从JUnit下载最新的软件包(截至写作本文时,JUnit的最新版本是3.7)。将其在适当的目录下解包。这样在安装目录(也就是你所选择的解包的目录)下你找到一个名为junit.jar的文件。将这个jar文件加入你的CLASSPATH系统变量。(IDE的设置会有所不同,参看你所喜爱的IDE的配置指南)JUnit就安装完了。太easy了! 你一旦安装完JUnit,就有可能想试试我们的Car和testCar类,没问题,我已经运行过了,你得到的结果应该和我列出的结果类似。(以防新版JUnit使我的文章过时) 接下来,你可能会先写测试代码,再写工作代码,或者相反,先写工作代码,再写测试代码。我更赞成使用前一种方法:先写测试代码,再写工作代码。因为这样可以使我们编写工作代码时清晰地了解工作类的行为。 要注意编写一定能通过的测试代码(如文中的例子)并没有任何意义,只有测试代码能帮助我们发现bug,测试代码才有其价值。此外测试代码还应该对工作代码进行全面的测试。如给方法调用的参数传入空值、错误值和正确的值,看看方法的行为是否如你所期望的那样。 你现在已经知道了编写测试类的基本步骤: 1>扩展TestCase类; 2>覆盖runTest()方法(可选); 3>写一些testXXXXX()方法; Fixture 解下来的问题是,如果你要对一个或若干个的类执行多个测试,该怎么办?JUnit对此有特殊的解决办法。 如果需要在一个或若干个的类执行多个测试,这些类就成为了测试的context。在JUnit中被称为Fixture(如testCar类中的 myCar 和 expectedWheels )。当你编写测试代码时,你会发现你花费了很多时间配置/初始化相关测试的Fixture。将配置Fixture的代码放入测试类的构造方法中并不可取,因为我们要求执行多个测试,我并不希望某个测试的结果意外地(如果这是你要求的,那就另当别论了)影响其他测试的结果。通常若干个测试会使用相同的Fixture,而每个测试又各有自己需要改变的地方。 为此,JUnit提供了两个方法,定义在TestCase类中。 protected void setUp() throws java.lang.Exception protected void tearDown() throws java.lang.Exception 覆盖setUp()方法,初始化所有测试的Fixture(你甚至可以在setUp中建立网络连接),将每个测试略有不同的地方在testXXX()方法中进行配置。 覆盖tearDown()(我总想起一首叫雨滴的吉他曲),释放你在setUp()中分配的永久性资源,如数据库连接。 当JUnit执行测试时,它在执行每个testXXXXX()方法前都调用setUp(),而在执行每个testXXXXX()方法后都调用tearDown()方法,由此保证了测试不会相互影响。 TestCase 需要提醒一下,在junit.framework.Assert类中定义了相当多的assert方法,主要有assert(), assert(), assertEquals(), assertNull(), assertSame(), assertTrue(), fail()等方法。如果你需要比较自己定义的类,如Car。assert方法需要你覆盖Object类的equals()方法,以比较两个对象的不同。实践表明:如果你覆盖了Object类的equals()方法,最好也覆盖Object类的hashCode()方法。再进一步,连带Object类的toString()方法也一并覆盖。这样可以使测试结果更具可读性。 当你设置好了Fixture后,下一步是编写所需的testXXX()方法。一定要保证testXXX()方法的public属性,否则无法通过内省(reflection)对该测试进行调用。 每个扩展的TestCase类(也就是你编写的测试类)会有多个testXXX()方法。一个testXXX()方法就是一个测试。要想运行这个测试,你必须定义如何运行该测试。如果你有多个testXXX()方法,你就要定义多次。JUnit支持两种运行单个测试的方法:静态的和动态的方法。 静态的方法就是覆盖TestCase类的runTest()方法,一般是采用内部类的方式创建一个测试实例: TestCase test01 = new testCar("test getWheels") { public void runTest() { testGetWheels(); } } 采用静态的方法要注意要给每个测试一个名字(这个名字可以任意起,但你肯定希望这个名字有某种意义),这样你就可以区分那个测试失败了。 动态的方法是用内省来实现runTest()以创建一个测试实例。这要求测试的名字就是需要调用的测试方法的名字: TestCase test01 = new testCar("testGetWheels"); JUnit会动态查找并调用指定的测试方法。动态的方法很简洁,但如果你键入了错误的名字就会得到一个令人奇怪的NoSuchMethodException异常。动态的方法和静态的方法都很好,你可以按照自己的喜好来选择。(先别着急选择,后面还有一种更酷的方法等着你呢。) TestSuite 一旦你创建了一些测试实例,下一步就是要让他们能一起运行。我们必须定义一个TestSuite。在JUnit中,这就要求你在TestCase类中定义一个静态的suite()方法。suite()方法就像main()方法一样,JUnit用它来执行测试。在suite()方法中,你将测试实例加到一个TestSuite对象中,并返回这个TestSuite对象。一个TestSuite对象可以运行一组测试。TestSuite和TestCase都实现了Test接口(interface),而Test接口定义了运行测试所需的方法。这就允许你用TestCase和TestSuite的组合创建一个TestSuite。这就是为什么我们前面说TestCase,TestSuite以及TestSuite组成了一个composite Pattern的原因。例子如下: public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new testCar("testGetWheels")); suite.addTest(new testCar("testGetSeats")); return suite; } 从JUnit 2.0开始,有一种更简单的动态定义测试实例的方法。你只需将类传递给TestSuite,JUnit会根据测试方法名自动创建相应的测试实例。所以你的测试方法最好取名为testXXX()。例子如下: public static Test suite() { return new TestSuite(testCar.class); } 从JUnit的设计我们可看出,JUnit不仅可用于单元测试,也可用于集成测试。关于如何用JUnit进行集成测试请参考相关资料。 为了兼容性的考虑,下面列出使用静态方法的例子: public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest( new testCar("getWheels") { protected void runTest() { testGetWheels(); } } ); suite.addTest( new testCar("getSeats") { protected void runTest() { testGetSeats(); } } ); return suite; } TestRunner 有了TestSuite我们就可以运行这些测试了,JUnit提供了三种界面来运行测试 [Text UI] junit.textui.TestRunner [AWT UI] junit.awtui.TestRunner [Swing UI] junit.swingui.TestRunner 我们前面已经看过文本界面了,下面让我们来看一看图形界面: 界面很简单,键入类名-testCar。或在启动UI的时候键入类名: [Windows] d:>java junit.swingui.TestRunner testCar [Unix] % java junit.swingui.TestRunner testCar 从图形UI可以更好的运行测试可查单测试结果。还有一个问题需要注意:如果JUnit报告了测试没有成功,JUnit会区分失败(failures)和错误(errors)。失败是一个期望的被assert方法检查到的结果。而错误则是意外的问题引起的,如ArrayIndexOutOfBoundsException。 由于TestRunner十分简单,界面也比较直观,故不多介绍。朋友们可自行参考相关资料。 JUnit最佳实践 Martin Fowler(又是这位高人)说过:“当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。 你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真正有用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试,或你认为应该失败却成功的测试。 我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要一直确保其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才运行测试,每天你都应该运行一下测试代码。这种投资很小,但可以确保你得到可以信赖的工作代码。你的返工率降低了,你会有更多的时间编写工作代码。 不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,应为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代码。下面是一些具体的编写测试代码的技巧或较好的实践方法:
- 不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。
- 不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。
- 避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的会滚就可以了。
- 当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。
- 将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junit的task.)
- 测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类名。
- 确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。
- 如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。
- 尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。
10.测试要尽可能地小,执行速度快。
事实上,JUnit还可用于集成测试,但我并没涉及到,原因有两个:一是因为没有单元测试,集成测试无从谈起。我们接受测试地概念已经很不容易了,如果再引入集成测试就会更困难。二是我比较懒,希望将集成测试的任务交给测试人员去做。在JUnit的网站上有一些相关的文章,有空大家可以翻一翻。
JUnit与J2EE
如果大家仔细考虑一下的话,就会发现,JUnit有自己的局限性,比如对图形界面的测试,对servlet/JSP以及EJB的测试我们都没有举相关的例子。实际上,JUnit对于GUI界面,servlet/JSP,JavaBean以及EJB都有办法测试。关于GUI的测试比较复杂,适合用一整篇文章来介绍。这里就不多说了。
前面我们所做的测试实际上有一个隐含的环境,JVM我们的类需要这个JVM来执行。而在J2EE框架中,servlet/JSP,EJB都要求有自己的运行环境:Web Container和EJB Container。所以,要想对servlet/JSP,EJB进行测试就需要将其部署在相应的Container中才能进行测试。由于EJB不涉及UI的问题(除非EJB操作XML数据,此时的测试代码比较难写,有可能需要你比较两棵DOM树是否含有相同的内容)只要部署上去之后就可以运行测试代码了。此时setUp()方法显得特别有用,你可以在setUp()方法中利用JNDI查找特定的EJB。而在testXXX()方法中调用并测试这些EJB的方法。
这里所指的JavaBean同样没有UI的问题,比如,我们用JavaBean来访问数据库,或用JavaBean来包裹EJB。如果这类JavaBean没有用到Container的提供的服务,则可直接进行测试,同我们前面所说的一般的类的测试方法一样。如果这类JavaBean用到了Container的提供的服务,则需要将其部署在Container中才能进行测试。方法与EJB类似。
对于servlet/JSP的测试则比较棘手,有人建议在测试代码中构造HttpRequest和HttpResponse,然后进行比较,这就要求开发人员对HTTP协议以及servlet/JSP的内部实现有比较深的认识。我认为这招不太现实。也有人提出使用HttpUnit。由于我对Cactus和HttpUnit 了解不多,所以无法做出合适的建议。希望各位先知们能不吝赐教。
正是由于JUnit的开放性和简单易行,才会引出这篇介绍文章。但技术总在不断地更新,而且我对测试并没有非常深入的理解;我可以将一个复杂的概念简化成一句非常容易理解的话。但我的本意只是希望能降低开发人员步入测试领域的门槛,而不是要修改或重新定义一些概念。这一点是特别要强调的。最后,如果有些兄弟姐妹能给我指出一些注意事项或我对某些问题的理解有误,我会非常感激的。
【
发表回复
】【
查看论坛原帖
】【
添加到收藏夹
】【
关闭
】
http://chinaunix.net/jh/26/215124.html
lanbird
回复于:2003-12-08 18:26:27
需要HttpUnit有关例程介绍!
hlcucu
回复于:2004-02-20 14:55:09
这篇文章帮了 我的大忙! :D
unmask
回复于:2004-02-20 22:38:47
我最近要用junit测试ejb,用jbuilder中自建的junit test client生成的代码,接下来该怎么做呢?除了输入测试值还要做什么呢?
jerrydan
发表于
10:12
|
阅读全文
|
评论(0)
|
引用(0)
|
编辑
2005-11-25
JUnit入门
TAG:
JUNIT
http://www.javaresearch.org/article/showarticle.jsp?column=526&thread=9138
安装JUnit
安装很简单,先到以下地址下载一个最新的zip包:
http://download.sourceforge.net/junit/
下载完以后解压缩到你喜欢的目录下,假设是JUNIT_HOME,然后将JUNIT_HOME下的junit.jar包加到你的系统的CLASSPATH环境变量中,对于IDE环境,对于需要用到的junit的项目增加到lib中,其设置不同的IDE有不同的设置,这里不多讲。
如何使用JUnit写测试?
最简单的范例如下:
1、创建一个TestCase的子类:
package junitfaq;
import java.util.;
import junit.framework.;
public class SimpleTest extends TestCase {
public SimpleTest(String name) {
super(name);
}
2、写一个测试方法断言期望的结果:
public void testEmptyCollection() {
Collection collection = new ArrayList();
assertTrue(collection.isEmpty());
}
注意:JUnit推荐的做法是以test作为待测试的方法的开头,这样这些方法可以被自动找到并被测试。
3、写一个suite()方法,它会使用反射动态的创建一个包含所有的testXxxx方法的测试套件:
public static Test suite() {
return new TestSuite(SimpleTest.class);
}
4、写一个main()方法以文本运行器的方式方便的运行测试:
public static void main(String args[]) {
junit.textui.TestRunner.run(suite());
}
}
5、运行测试:
以文本方式运行:
java junitfaq.SimpleTest
通过的测试结果是:
.
Time: 0
OK (1 tests)
Time上的小点表示测试个数,如果测试通过则显示OK。否则在小点的后边标上F,表示该测试失败。
每次的测试结果都应该是OK的,这样才能说明测试是成功的,如果不成功就要马上根据提示信息进行修正了。
如果JUnit报告了测试没有成功,它会区分失败(failures)和错误(errors)。失败是你的代码中的assert方法失败引起的;而错误则是代码异常引起的,例如ArrayIndexOutOfBoundsException。
以图形方式运行:
java junit.swingui.TestRunner junitfaq.SimpleTest
通过的测试结果在图形界面的绿色条部分。
以上是最简单的测试样例,在实际的测试中我们测试某个类的功能是常常需要执行一些共同的操作,完成以后需要销毁所占用的资源(例如网络连接、数据库连接,关闭打开的文件等),TestCase类给我们提供了setUp方法和tearDown方法,setUp方法的内容在测试你编写的TestCase子类的每个testXxxx方法之前都会运行,而tearDown方法的内容在每个testXxxx方法结束以后都会执行。这个既共享了初始化代码,又消除了各个测试代码之间可能产生的相互影响。
jerrydan
发表于
10:08
|
阅读全文
|
评论(0)
|
引用(0)
|
编辑
2005-11-17
追MM与JAVA设计模式
TAG:
设计模式
创建型模式
1、FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory
2、BUILDER—MM最爱听的就是“我爱你”这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到MM我只要按对应的键,它就能够用相应的语言说出“我爱你”这句话了,国外的MM也可以轻松搞掂,这就是我的“我爱你”builder。(这一定比美军在伊拉克用的翻译机好卖)
3、PROTOTYPE—跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。(100块钱一份,你要不要)
4、SINGLETON—俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事)
结构型模式
5、ADAPTER—在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)
6、BRIDGE—早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了
7、COMPOSITE—Mary今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite模式了,你会了没有?
8、DECORATOR—Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?
9、FACADE—我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。
10、FLYWEIGHT—每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。
11、PROXY—跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。
行为模式
12、CHAIN OF RESPONSIBLEITY—晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!
13、COMMAND—俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。”,:-(
14、INTERPRETER—俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了。
15、ITERATOR—我爱上了Mary,不顾一切的向她求婚。
Mary:“想要我跟你结婚,得答应我的条件”
我:“什么条件我都答应,你说吧”
Mary:“我看上了那个一克拉的钻石”
我:“我买,我买,还有吗?”
Mary:“我看上了湖边的那栋别墅”
我:“我买,我买,还有吗?”
Mary:“你的小弟弟必须要有50cm长”
我脑袋嗡的一声,坐在椅子上,一咬牙:“我剪,我剪,还有吗?”
……
16、MEDIATOR—四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。
17、MEMENTO—同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。
18、OBSERVER—想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦
19、STATE—跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说“有事情啦”,对你不讨厌但还没喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。
20、STRATEGY—跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,但目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。
21、TEMPLATE METHOD——看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现);
22、VISITOR—情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;
jerrydan
发表于
15:33
|
阅读全文
|
评论(0)
|
引用(0)
|
编辑
2005-11-16
ant使用简介
TAG:
ant
http://www.chinaitpower.com/A200507/2005-07-24/167032.html
ant使用简介
ant 是apache 工程的一个子工程,是一个基于
java
的build工具。ant类似于make工具,但没有传统的make工具的缺点。传统的make往往只能限制在某一平台上使用,ant本身用
java
类实现,要构建的工程的配置
文件
用xml格式描述,可以很方便实现多平台编译,非常适合build大型工程。
1.安装配置ant:
ant 可以从
http://ant.apache.org
下载,目前最新版本为1.5.2。下载完毕后直接解压缩,目录如下:
ant
+-- bin // contains launcher scripts
|
+-- lib // contains Ant jars plus necessary dependencies
|
+-- docs // contains documentation
| +-- ant2 // a brief description of ant2 requirements
| |
| +-- images // various logos for html documentation
| |
| +-- manual // Ant documentation (a must read )
|
+-- etc
需要设置的环境变量:
ANT_HOME:ant的安装目录
JAVA_HOME:jdk的安装目录
PATH:把%ANT_HOME%\bin目录加到path变量,以便于从命令行下直接运行ant
假定ant装在c:\ant jdk装d:\j2sdk1.4.0
则在命令行下执行以下命令:
set ANT_HOME=c:\ant
set JAVA_HOME=D:\j2sdk1.4.0
set PATH=%PATH%;c:\ant\bin
在
win2000
命令提示符下工作时,每次都必须进行上面的设置,退出命令提示符后,这些变量的值又会恢复成原来的样子。为了避免这些麻烦,可以在 控制面板
系统 \高级\环境变量 中设置。 上述设置完成后,就可以使用ant了。 2.建立工程描述 文件 build.xml 用ant编译规模较大的工程非常方便,每个工程都对应一个build.xml 文件 ,这个 文件 包含与这个工程有关的路径信息和任务。下面是一个build.xml的例子:
/** Execution of any Servlet request methods. / public pointcut monitoredOperation(Object operation) : execution(void HttpServlet.do(..)) && this(operation);
/** Advice that records statistics for each monitored operation. */ void around(Object operation) : monitoredOperation(operation) { long start = getTime();
proceed(operation);
PerfStats stats = lookupStats(operation); stats.recordExecution(getTime(), start); }
/**
- Find the appropriate statistics collector object for this
- operation.
- @param operation
- the instance of the operation being monitored */ protected PerfStats lookupStats(Object operation) { Class keyClass = operation.getClass(); synchronized(operations) { stats = (PerfStats)operations.get(keyClass); if (stats == null) { stats = perfStatsFactory. createTopLevelOperationStats(HttpServlet.class, keyClass); operations.put(keyClass, stats); } } return stats; }
/**
- Helper method to collect time in milliseconds. Could plug in
- nanotimer. */ public long getTime() { return System.currentTimeMillis(); }
public void setPerfStatsFactory(PerfStatsFactory perfStatsFactory) { this.perfStatsFactory = perfStatsFactory; }
public PerfStatsFactory getPerfStatsFactory() { return perfStatsFactory; }
/** Track top-level operations. / private Map/<Class,PerfStats>*/ operations = new WeakIdentityHashMap(); private PerfStatsFactory perfStatsFactory; }
/**
- Holds summary performance statistics for a
- given topic of interest
- (e.g., a subclass of Servlet). / public interface PerfStats { /*
- Record that a single execution occurred.
- @param start time in milliseconds
- @param end time in milliseconds */ void recordExecution(long start, long end);
/**
- Reset these statistics back to zero. Useful to track statistics
- during an interval. */ void reset();
/**
- @return total accumulated time in milliseconds from all
- executions (since last reset). */ int getAccumulatedTime();
/**
- @return the largest time for any single execution, in
- milliseconds (since last reset). */ int getMaxTime();
/**
- @return the number of executions recorded (since last reset). */ int getCount(); }
/**
- Implementation of the
- @link PerfStats interface. */ public class PerfStatsImpl implements PerfStats { private int accumulatedTime=0L; private int maxTime=0L; private int count=0;
public void recordExecution(long start, long end) { int time = (int)(getTime()-start); accumulatedTime += time; maxTime = Math.max(time, maxTime); count++; }
public void reset() { accumulatedTime=0L; maxTime=0L; count=0; }
int getAccumulatedTime() { return accumulatedTime; } int getMaxTime() { return maxTime; } int getCount() { return count; } }
public interface PerfStatsFactory { PerfStats createTopLevelOperationStats(Object type, Object key); } 可以看到,第一个版本相当基础。 HttpServletMonitor 定义了一个切入点,叫作 monitoredOperation ,它匹配 HttpServlet 接口上任何名称以 do 开始的方法的执行。这些方法通常是 doGet() 和 doPost() ,但是通过匹配 doHead() 、 doDelete() 、 doOptions() 、 doPut() 和 doTrace() ,它也可以捕捉不常用的 HTTP 请求选项。 管理开销 在这篇文章的后半部分,我将把重点放在管理监视框架开销的技术上,但是现在,值得注意的是基本策略:在速度慢的事情发生时(像访问 servlet 或数据库),我要做一些在内存中的操作,这只花几毫秒。在实践中,对大多数应用程序的端对端响应时间只会添加微不足道的开销。 每当其中一个操作执行的时候,系统都会执行 around 通知去监视性能。建议启动一个秒表,然后让原始请求继续进行。之后,通知停止秒表并查询与指定操作对应的性能统计对象。然后它再调用 PerfStats 接口的 recordExecution() ,记录操作经历的时间。这仅仅更新指定操作的总时间、最大时间(如果适用)以及执行次数。自然也可以把这种方式扩展成计算额外的统计值,并在问题可能发生的地方保存单独的数据点。 我在方面中使用了一个哈希图为每种操作处理程序保存累计统计值。在这个版本中,操作处理程序是 HttpServlet 的子类,所以 servlet 的类被用作键。我还用术语 操作 表示 Web 请求,以便把它与应用程序可能产生的其他请求(例如,数据库请求)区分开。在这篇文章的第二部分,我将扩展这种方式,来解决更常见的在控制器中使用的基于类或方法的跟踪操作情况,例如 Apache Struts 的动作类或 Spring 的多动作控制器方法。 回页首 公开性能数据 线程安全性 Glassbox Inspector 监视系统的统计值捕捉代码不是线程安全的。我宁愿维护(可能)略微不准确的统计值(由于多个线程很少会同时访问一个 PerfStats 实例),也不想向程序执行添加额外的同步。如果您偏爱更高的准确性,也只要让互斥体同步即可(例如,与方面同步)。如果正在跟踪的累计时间超过 32 位的长度,那么同步会很重要,因为 Java 平台不保证对 64 位数据的原子更新。但是,在毫秒的精度情况下,32 位的长度会提供 46 天的累计时间。我建议对于真实的应用,应当更加频繁地搜集和重设统计值,所以我坚持使用 int 值。 一旦捕捉到了性能数据,让它可以使用的方式就很多了。最简单的方式就是把信息定期地写入日志文件。也可以把信息装入数据库进行分析。由于不增加延迟、复杂性以及合计、日志及处理信息的开销,提供到即时系统数据的直接访问通常会更好。在下一节中我将介绍如何做到这一点。 我想使用一个现有管理工作能够显示和跟踪的标准协议,所以我将用 JMX API 来共享性能统计值。使用 JMX 意味着每个性能统计实例都会公开成一个管理 bean,从而提供详细的性能数据。标准的 JMX 客户端(像 Sun 公司的 JConsole)也能够显示这些信息。请参阅 参考资料 学习有关 JMX 的更多内容。 图 3 是一幅 JConsole 的截屏,显示了 Glassbox Inspector 监视 Duke 书店示例应用程序性能的情况。(请参阅 参考资料 )。清单 2 显示了实现这个特性的代码。 图 3. 用 Glassbox Inspector 查看操作统计值 传统上,支持 JMX 包括用样本代码实现模式。在这种情况下,我将把 JMX 与 AspectJ 结合,这个结合可以让我独立地编写管理逻辑。 清单 2. 实现 JMX 管理特性 /** Reusable aspect that automatically registers
- beans for management */ public aspect JmxManagement {
/** Defines classes to be managed and
- defines basic management operation / public interface ManagedBean { /* Define a JMX operation name for this bean.
- Not to be confused with a Web request operation. / String getOperationName(); /* Returns the underlying JMX MBean that
- provides management
- information for this bean (POJO). */ Object getMBean(); }
/** After constructing an instance of
- ManagedBean, register it */ after() returning (ManagedBean bean): call(ManagedBean+.new(..)) { String keyName = bean.getOperationName(); ObjectName objectName = new ObjectName("glassbox.inspector:" + keyName);
Object mBean = bean.getMBean(); if (mBean != null) { server.registerMBean(mBean, objectName); } }
/**
- Utility method to encode a JMX key name,
- escaping illegal characters.
- @param jmxName unescaped string buffer of form
- JMX keyname=key
- @param attrPos position of key in String / public static StringBuffer jmxEncode(StringBuffer jmxName, int attrPos) { for (int i=attrPos; i<jmxName.length(); i++) { if (jmxName.charAt(i)==',' ) { jmxName.setCharAt(i, ';'); } else if (jmxName.charAt(i)=='?' || jmxName.charAt(i)=='' || jmxName.charAt(i)=='\' ) { jmxName.insert(i, '\'); i++; } else if (jmxName.charAt(i)=='\n') { jmxName.insert(i, '\'); i++; jmxName.setCharAt(i, 'n'); } } return jmxName; }
/** Defines the MBeanServer with which beans
- are auto-registered. */ private MBeanServer server;
public void setMBeanServer(MBeanServer server) { this.server = server; }
public MBeanServer getMBeanServer() { return server; } } JMX 工具 有几个比较好的 JMX 实现库支持远程 JMX。Sun 公司在免费许可下提供了 JMX 和 JMX Remote 的参考实现。也有一些开放源码的实现。MX4J 是其中比较流行的一个,它包含辅助库和工具(像 JMX 客户端)。Java 5 把 JMX 和 JMX 远程支持集成进了虚拟机。Java 5 还在 javax.management 包中引入了虚拟机性能的管理 bean。Sun 的 Java 5 虚拟机包括标准的 JMX 客户端 JConsole。 可以看出这个第一个方面是可以重用的。利用它,我能够用 after 建议自动为任何实现 ManagedBean 接口的类登记对象实例。这与 AspectJ 标记器接口的理念类似(请参阅 参考资料 ):定义了实例应当通过 JMX 公开的类。但是,与真正的标记器接口不同的是,它还定义了两个方法 。 这个方面提供了一个设置器,定义应当用哪个 MBean 服务器管理对象。这是一个使用反转控制(IOC)模式进行配置的示例,因此很自然地适合方面。在最终代码的完整清单中,将会看到我用了一个简单的辅助方面对系统进行配置。在更大的系统中,我将用 Spring 框架这样的 IOC 容器来配置类和方面。请参阅 参考资料 获得关于 IOC 和 Spring 框架的更多信息,并获得关于使用 Spring 配置方面的介绍。 清单 3. 公开负责 JMX 管理的 bean /** Applies JMX management to performance statistics beans. / public aspect StatsJmxManagement { /* Management interface for performance statistics.
- A subset of @link PerfStats */ public interface PerfStatsMBean extends ManagedBean { int getAccumulatedTime(); int getMaxTime(); int getCount(); void reset(); }
/**
- Make the @link PerfStats interface
- implement @link PerfStatsMBean,
- so all instances can be managed */ declare parents: PerfStats implements PerfStatsMBean;
/** Creates a JMX MBean to represent this PerfStats instance. / public DynamicMBean PerfStats.getMBean() { try { RequiredModelMBean mBean = new RequiredModelMBean(); mBean.setModelMBeanInfo (assembler.getMBeanInfo(this, getOperationName())); mBean.setManagedResource(this, "ObjectReference"); return mBean; } catch (Exception e) { / This is safe because @link ErrorHandling
- will resolve it. This is described later! */ throw new AspectConfigurationException("can't register bean ", e); } }
/** Determine JMX operation name for this
- performance statistics bean. */ public String PerfStats.getOperationName() { StringBuffer keyStr = new StringBuffer("operation=""); int pos = keyStr.length();
if (key instanceof Class) { keyStr.append(((Class)key).getName()); } else { keyStr.append(key.toString()); }
JmxManagement.jmxEncode(keyStr, pos);
keyStr.append("""); return keyStr.toString(); }
private static Class[] managedInterfaces = { PerfStatsMBean.class }; /**
- Spring JMX utility MBean Info Assembler.
- Allows @link PerfStatsMBean to serve
- as the management interface of all performance
- statistics implementors. / static InterfaceBasedMBeanInfoAssembler assembler; static { assembler = new InterfaceBasedMBeanInfoAssembler(); assembler.setManagedInterfaces(managedInterfaces); } } 清单 3 包含 StatsJmxManagement 方面,它具体地定义了哪个对象应当公开管理 bean。它描述了一个接口 PerfStatsMBean ,这个接口定义了用于任何性能统计实现的管理接口。其中包括计数、总时间、最大时间的统计值,还有重设操作,这个接口是 PerfStats 接口的子集。 PerfStatsMBean 本身扩展了 ManagedBean ,所以它的任何实现都会自动被 JmxManagement 方面登记成进行管理。我采用 AspectJ 的 declare parents 格式让 PerfStats 接口扩展了一个特殊的管理接口 PerfStatsMBean 。结果是 JMX Dynamic MBean 技术会管理这些对象,与使用 JMX 的标准 MBean 相比,我更喜欢这种方式。 使用标准 MBean 会要求定义一个管理接口,接口名称基于每个性能统计的实现类,例如 PerfStatsImplMBean 。后来,当我向 Glassbox Inspector 添加 PerfStats 的子类时,情况变糟了,因为我被要求创建对应的接口(例如 OperationPerfStatsImpl )。标准 MBean 的约定使得接口依赖于实现,而且代表这个系统的继承层次出现不必要的重复。 部署这些方面 这篇文章中使用的方面只能应用到它们监视的每个应用程序上,不能应用到第三方库或容器代码上。所以,如果要把它们集成到生产系统中,可以把它们编译到应用程序中,或者编织到已经编译的应用程序中,或者使用装入时编织(这是这种用例下我偏爱的方式)。在这篇文章的第二部分,您将学到有关装入时编程的更多内容。 这个方面剩下的部分负责用 JMX 创建正确的 MBean 和对象名称。我重用了来自 Spring 框架的 JMX 工具 InterfaceBasedMBeanInfoAssembler ,用它可以更容易地创建 JMX DynamicMBean(用 PerfStatsMBean 接口管理 PerfStats 实例)。在这个阶段,我只公开了 PerfStats 实现。这个方面还用受管理 bean 类上的类型间声明定义了辅助方法。如果这些类中的任何一个的子类需要覆盖默认行为,那么可以通过覆盖这个方法实现。 您可能想知道为什么我用方面进行管理而不是直接把支持添加到 PerfStatsImpl 的实现类中。虽然把管理添加到这个类中不会把代码分散,但是它会把性能监视系统的实现与 JMX 混杂在一起。所以,如果我想把这个系统用在一个 没有 JMX 的系统中,就要被迫包含 JMX 的库,还要禁止有关服务。而且,当扩展系统的管理功能时,我还要公开更多的类用 JMX 进行管理。使用方面可以让系统的管理策略保持模块化。 回页首 数据库请求监视 分布式调用是应用程序性能低和出错误的一个常见源头。多数基于 Web 的应用程序要做相当数量的数据库工作,所以对查询和其他数据库请求进行监视就成为性能监视中特别重要的领域。常见的问题包括编写得有毛病的查询、遗漏了索引以及每个操作中过量的数据库请求。在这一节,我将对监视系统进行扩展,跟踪数据库中与操作相关的活动。 分布式调用 在这一节,我介绍了一种处理数据库分布式调用的方式。虽然数据库通常位于不同的机器上,但我的技术也适用于本地数据库。我的方式也可以自然地扩展到其他分布式资源上,包括远程对象调用。在这篇文章的第二部分中,我将介绍如何用 SOAP 把这项技术应用到 Web 服务调用上。 开始时,我将监视数据库的连接次数和数据库语句的执行。为了有效地支持这个要求,我需要归纳性能监视信息,并允许跟踪嵌套在一个操作中的性能。我想把性能的公共元素提取到一个抽象基类。每个基类负责跟踪某项操作前后的性能,还需要更新系统范围内这条信息的性能统计值。这样我就能跟踪嵌套的 servlet 请求,对于在 Web 应用程序中支持对控制器的跟踪,这也会很重要(在第二部分讨论)。 因为我想根据请求更新数据库的性能,所以我将采用 composite pattern 跟踪由其他统计值持有的统计值。这样,操作(例如 servelt)的统计值就持有每个数据库的性能统计。数据库的统计值持有有关连接次数的信息,并聚合每个单独语句的额外统计值。图 4 显示整体设计是如何结合在一起的。清单 4 拥有新的基监视方面,它支持对不同的请求进行监视。 图 4. 一般化后的监视设计 清单 4. 基监视方面 /* Base aspect for monitoring functionality.
- Uses the worker object pattern. */ public abstract aspect AbstractRequestMonitor {
/** Matches execution of the worker object
- for a monitored request. / public pointcut requestExecution(RequestContext requestContext) : execution( RequestContext.execute(..)) && this(requestContext);
/** In the control flow of a monitored request,
- i.e., of the execution of a worker object. */ public pointcut inRequest(RequestContext requestContext) : cflow(requestExecution(requestContext));
/** establish parent relationships
- for request context objects. */ // use of call is cleaner since constructors are called // once but executed many times after(RequestContext parentContext) returning (RequestContext childContext) : call(RequestContext+.new(..)) && inRequest(parentContext) { childContext.setParent(parentContext); }
public long getTime() { return System.currentTimeMillis(); }
/** Worker object that holds context information
- for a monitored request. / public abstract class RequestContext { /* Containing request context, if any.
- Maintained by @link AbstractRequestMonitor / protected RequestContext parent = null; /* Associated performance statistics.
- Used to cache results of @link #lookupStats() / protected PerfStats stats; /* Start time for monitored request. */ protected long startTime;
/**
- Record execution and elapsed time
- for each monitored request.
- Relies on @link #doExecute() to proceed
- with original request. */ public final Object execute() { startTime = getTime();
Object result = doExecute();
PerfStats stats = getStats(); if (stats != null) { stats.recordExecution(startTime, getTime()); }
return result; }
/** template method: proceed with original request */ public abstract Object doExecute();
/** template method: determines appropriate performance
- statistics for this request */ protected abstract PerfStats lookupStats();
/** returns performance statistics for this method */ public PerfStats getStats() { if (stats == null) { stats = lookupStats(); // get from cache if available } return stats; }
public RequestContext getParent() { return parent; }
public void setParent(RequestContext parent) { this.parent = parent; } } } 不出所料,对于如何存储共享的性能统计值和基方面的每请求状态,有许多选择。例如,我可以用带有更底层机制的单体(例如 ThreadLocal )持有一堆统计值和上下文。但是,我选用了工人对象(Worker Object)模式(请参阅 参考资料 ),因为它支持更加模块化、更简洁的表达。虽然这会带来一些额外的开销,但是分配单一对象并执行建议所需要的额外时间,比起为 Web 和数据库请求提供服务来说,通常是微不足道的。换句话说,我可以在不增加开销的情况下,在监视代码中做一些处理工作,因为它运行的频繁相对很低,而且比起在通过网络发送信息和等候磁盘 I/O 上花费的时间来说,通常就微不足道了。对于 profiler 来说,这可能是个糟糕的设计,因为在 profiler 中可能想要跟踪每个请求中的许多操作(和方法)的数据。但是,我是在做请求的统计汇总,所以这个选择是合理的。 在上面的基方面中,我把当前被监视请求的中间状态保存在匿名内部类中。这个工人对象用来包装被监视请求的执行。工人对象 RequestContext 是在基类中定义的,提供的 final execute 方法定义了对请求进行监视的流程。execute 方法委托抽象的模板方法 doExecute() 负责继续处理原始的连接点。在 doExecute() 方法中也适合在根据上下文信息(例如正在连接的数据源)继续处理被监视的连接点之前设置统计值,并在连接点返回之后关联返回的值(例如数据库连接)。 每个监视方面还负责提供抽象方法 lookupStats() 的实现,用来确定为指定请求更新哪个统计对象。 lookupStats() 需要根据被监视的连接点访问信息。一般来说,捕捉的上下文对于每个监视方面都应当各不相同。例如,在 HttpServletMonitor 中,需要的上下文就是目前执行操作对象的类。对于 JDBC 连接,需要的上下文就是得到的数据源。因为要求根据上下文而不同,所以设置工人对象的建议最好是包含在每个子方面中,而不是在抽象的基方面中。这种安排更清楚,它支持类型检测,而且也比在基类中编写一个建议,再把 JoinPoint 传递给所有孩子执行得更好。 回页首 servlet 请求跟踪 AbstractRequestMonitor 确实包含一个具体的 after 建议,负责跟踪请求上下文的双亲上下文。这就让我可以把嵌套请求的操作统计值与它们双亲的统计值关联起来(例如,哪个 servlet 请求造成了这个数据库访问)。对于示例监视系统来说,我明确地 需要 嵌套的工人对象,而 不想 把自己限制在只能处理顶级请求上。例如,所有的 Duke 书店 servlet 都把调用 BannerServlet 作为显示页面的一部分。所以能把这些调用的次数分开是有用的,如清单 5 所示。在这里,我没有显示在操作统计值中查询嵌套统计值的支持代码(可以在本文的源代码中看到它)。在第二部分,我将重新回到这个主题,介绍如何更新 JMX 支持来显示像这样的嵌套统计值。 清单 5. 更新的 servlet 监视 清单 5 should now read public aspect HttpServletMonitor extends AbstractRequestMonitor {
/** Monitor Servlet requests using the worker object pattern */ Object around(final Object operation) : monitoredOperation(operation) { RequestContext requestContext = new RequestContext() { public Object doExecute() { return proceed(operation); }
public PerfStats lookupStats() { if (getParent() != null) { // nested operation OperationStats parentStats = (OperationStats)getParent().getStats(); return parentStats.getOperationStats(operation.getClass()); } return lookupStats(operation.getClass()); } }; return requestContext.execute(); } ... 清单 5 显示了修订后进行 serverlet 请求跟踪的监视建议。余下的全部代码与 清单 1 相同:或者推入基方面 AbstractRequestMonitor 方面,或者保持一致。 回页首 JDBC 监视 设置好性能监视框架后,我现在准备跟踪数据库的连接次数以及数据库语句的时间。而且,我还希望能够把数据库语句和实际连接的数据库关联起来(在 lookupStats() 方法中)。为了做到这一点,我创建了两个跟踪 JDBC 语句和连接信息的方面: JdbcConnectionMonitor 和 JdbcStatementMonitor 。 这些方面的一个关键职责是跟踪对象引用的链。我想根据我用来连接数据库的 URI 跟踪请求,或者至少根据数据库名称来跟踪。这就要求跟踪用来获得连接的数据源。我还想进一步根据 SQL 字符串跟踪预备语句(在执行之前就已经准备就绪)。最后,我需要跟踪与正在执行的语句关联的 JDBC 连接。您会注意到:JDBC 语句 确实 为它们的连接提供了存取器;但是,应用程序服务器和 Web 应用程序框架频繁地使用修饰器模式包装 JDBC 连接。我想确保自己能够把语句与我拥有句柄的连接关联起来,而不是与包装的连接关联起来。 JdbcConnectionMonitor 负责测量数据库连接的性能统计值,它也把连接与它们来自数据源或连接 URL 的元数据(例如 JDBC URL 或数据库名称)关联在一起。 JdbcStatementMonitor 负责测量执行语句的性能统计值,跟踪用来取得语句的连接,跟踪与预备(和可调用)语句关联的 SQL 字符串。清单 6 显示了 JdbcConnectionMonitor 方面。 清单 6. JdbcConnectionMonitor 方面 /**
- Monitor performance for JDBC connections,
- and track database connection information associated with them. */ public aspect JdbcConnectionMonitor extends AbstractRequestMonitor {
/** A call to establish a connection using a
- DataSource */ public pointcut dataSourceConnectionCall(DataSource dataSource) : call(Connection+ DataSource.getConnection(..)) && target(dataSource);
/** A call to establish a connection using a URL string */ public pointcut directConnectionCall(String url) : (call(Connection+ Driver.connect(..)) || call(Connection+ DriverManager.getConnection(..))) && args(url, ..);
/** A database connection call nested beneath another one
- (common with proxies). / public pointcut nestedConnectionCall() : cflowbelow(dataSourceConnectionCall() || directConnectionCall(*));
/** Monitor data source connections using
- the worker object pattern */ Connection around(final DataSource dataSource) : dataSourceConnectionCall(dataSource) && !nestedConnectionCall() { RequestContext requestContext = new ConnectionRequestContext() { public Object doExecute() { accessingConnection(dataSource); // set up stats early in case needed
Connection connection = proceed(dataSource);
return addConnection(connection); }
}; return (Connection)requestContext.execute(); }
/** Monitor url connections using the worker object pattern */ Connection around(final String url) : directConnectionCall(url) && !nestedConnectionCall() { RequestContext requestContext = new ConnectionRequestContext() { public Object doExecute() { accessingConnection(url);
Connection connection = proceed(url);
return addConnection(connection); } }; return (Connection)requestContext.execute(); }
/** Get stored name associated with this data source. */ public String getDatabaseName(Connection connection) { synchronized (connections) { return (String)connections.get(connection); } }
/** Use common accessors to return meaningful name
- for the resource accessed by this data source. */ public String getNameForDataSource(DataSource ds) { // methods used to get names are listed in descending // preference order String possibleNames[] = { "getDatabaseName", "getDatabasename", "getUrl", "getURL", "getDataSourceName", "getDescription" }; String name = null; for (int i=0; name == null && i<possibleNames.length; i++) { try { Method method = ds.getClass().getMethod(possibleNames[i], null); name = (String)method.invoke(ds, null); } catch (Exception e) { // keep trying } } return (name != null) ? name : "unknown"; }
/** Holds JDBC connection-specific context information:
- a database name and statistics */ protected abstract class ConnectionRequestContext extends RequestContext { private ResourceStats dbStats;
/** set up context statistics for accessing
- this data source */ protected void accessingConnection(final DataSource dataSource) { addConnection(getNameForDataSource(dataSource), connection); }
/** set up context statistics for accessing this database */ protected void accessingConnection(String databaseName) { this.databaseName = databaseName;
// might be null if there is database access // caused from a request I'm not tracking... if (getParent() != null) { OperationStats opStats = (OperationStats)getParent().getStats(); dbStats = opStats.getDatabaseStats(databaseName); } }
/** record the database name for this database connection */ protected Connection addConnection(final Connection connection) { synchronized(connections) { connections.put(connection, databaseName); } return connection; }
protected PerfStats lookupStats() { return dbStats; } };
/** Associates connections with their database names / private Map/<Connection,String>*/ connections = new WeakIdentityHashMap();
} 清单 6 显示了利用 AspectJ 和 JDBC API 跟踪数据库连接的方面。它用一个图来关联数据库名称和每个 JDBC 连接。 在 jdbcConnectionMonitor 内部 在清单 6 显示的 JdbcConnectionMonitor 内部,我定义了切入点,捕捉连接数据库的两种不同方式:通过数据源或直接通过 JDBC URL。连接监视器包含针对每种情况的监视建议,两种情况都设置一个工人对象。 doExecute() 方法启动时处理原始连接,然后把返回的连接传递给两个辅助方法中名为 addConnection 的一个。在两种情况下,被建议的切入点会排除来自另一个连接的连接调用(例如,如果要连接到数据源,会造成建立 JDBC 连接)。 数据源的 addConnection() 委托辅助方法 getNameForDataSource() 从数据源确定数据库的名称。 DataSource 接口不提供任何这类机制,但是几乎每个实现都提供了 getDatabaseName() 方法。 getNameForDataSource() 用反射来尝试完成这项工作和其他少数常见(和不太常见)的方法,为数据库源提供一个有用的标识。 addConnection() 方法然后委托给 addConnection() 方法,这个方法用字符串参数作为名称。 被委托的 addConnection() 方法从父请求的上下文中检索可以操作的统计值,并根据与指定连接关联的数据库名称(或其他描述字符串)查询数据库的统计值。然后它把这条信息保存在请求上下文对象的 dbStats 字段中,更新关于获得连接的性能信息。这样就可以跟踪连接数据库需要的时间(通常这实际是从池中得到连接所需要的时间)。 addConnection() 方法也更新到数据库名称的连接的连接图。随后在执行 JDBC 语句更新对应请求的统计值时,会使用这个图。 JdbcConnectionMonitor 还提供了一个辅助方法 getDatabaseName() ,它从连接图中查询字符串名称找到连接。 弱标识图和方面 JDBC 监视方面使用 弱标识 哈希图。这些图持有 弱 引用,允许连接这样的被跟踪对象在只有方面引用它们的时候,被垃圾收集掉。这一点很重要,因为单体的方面通常 不会 被垃圾收集。如果引用不弱,那么应用程序会有内存泄漏。方面用 标识 图来避免调用连接或语句的 hashCode 或 equals 方法。这很重要,因为我想跟踪连接和语句,而不理会它们的状态:我不想遇到来自 hashCode 方法的异常,也不想在对象的内部状态已经改变时(例如关闭时),指望对象的哈希码保持不变。我在处理动态的基于代理的 JDBC 对象(就像来自 iBatis 的那些对象)时遇到了这个问题:在连接已经关闭之后调用对象上的方法就会抛出异常。在完成操作之后还想记录统计值时会造成错误。 从这里可以学到的教训是:把对第三方代码的假设最小化。使用标识图是避免对接受建议的代码的实现逻辑进行猜测的好方法。在这种情况下,我使用了来自 DCL Java 工具的 WeakIdentityHashMap 开放源码实现(请参阅 参考资料 )。跟踪连接或语句的元数据信息让我可以跨越请求,针对连接或语句把统计值分组。这意味着可以只根据对象实例进行跟踪,而不需要使用对象等价性来跟踪这些 JDBC 对象。另一个要记住的教训是:不同的对象经常用不同的修饰器包装(越来越多地采用动态代理) JDBC 对象。所以假设要处理的是这类接口的简单而原始的实现,可不是一个好主意! jdbcStatementMonitor 内部 清单 7 显示了 JdbcStatementMonitor 方面。这个方面有两个主要职责:跟踪与创建和准备语句有关的信息,然后监视 JDBC 语句执行的性能统计值。 清单 7. JdbcStatementMonitor 方面 /**
- Monitor performance for executing JDBC statements,
- and track the connections used to create them,
- and the SQL used to prepare them (if appropriate). */ public aspect JdbcStatementMonitor extends AbstractRequestMonitor {
/** Matches any execution of a JDBC statement / public pointcut statementExec(Statement statement) : call( java.sql...execute(..)) && target(statement);
/**
- Store the sanitized SQL for dynamic statements. */ before(Statement statement, String sql, RequestContext parentContext): statementExec(statement) && args(sql, ..) && inRequest(parentContext) { sql = stripAfterWhere(sql); setUpStatement(statement, sql, parentContext); }
/** Monitor performance for executing a JDBC statement. */ Object around(final Statement statement) : statementExec(statement) { RequestContext requestContext = new StatementRequestContext() { public Object doExecute() { return proceed(statement); } }; return requestContext.execute(); }
/**
- Call to create a Statement.
- @param connection the connection called to
- create the statement, which is bound to
- track the statement's origin / public pointcut callCreateStatement(Connection connection): call(Statement+ Connection.(..)) && target(connection);
/**
- Track origin of statements, to properly
- associate statistics even in
- the presence of wrapped connections */ after(Connection connection) returning (Statement statement): callCreateStatement(connection) { synchronized (JdbcStatementMonitor.this) { statementCreators.put(statement, connection); } }
/**
- A call to prepare a statement.
- @param sql The SQL string prepared by the statement. / public pointcut callCreatePreparedStatement(String sql): call(PreparedStatement+ Connection.(String, ..)) && args(sql, ..);
/** Track SQL used to prepare a prepared statement */ after(String sql) returning (PreparedStatement statement): callCreatePreparedStatement(sql) { setUpStatement(statement, sql); }
protected abstract class StatementRequestContext extends RequestContext { /**
- Find statistics for this statement, looking for its
- SQL string in the parent request's statistics context */ protected PerfStats lookupStats() { if (getParent() != null) { Connection connection = null; String sql = null;
synchronized (JdbcStatementMonitor.this) { connection = (Connection) statementCreators.get(statement); sql = (String) statementSql.get(statement); }
if (connection != null) { String databaseName = JdbcConnectionMonitor.aspectOf(). getDatabaseName(connection); if (databaseName != null && sql != null) { OperationStats opStats = (OperationStats) getParent().getStats(); if (opStats != null) { ResourceStats dbStats = opStats.getDatabaseStats(databaseName);
return dbStats.getRequestStats(sql); } } } } return null; } }
/**
- To group sensibly and to avoid recording sensitive data,
- I don't record the where clause (only used for dynamic
- SQL since parameters aren't included
- in prepared statements)
- @return subset of passed SQL up to the where clause */ public static String stripAfterWhere(String sql) { for (int i=0; i<sql.length()-4; i++) { if (sql.charAt(i)=='w' || sql.charAt(i)== 'W') { if (sql.substring(i+1, i+5).equalsIgnoreCase( "here")) { sql = sql.substring(0, i); } } } return sql; }
private synchronized void setUpStatement(Statement statement, String sql) { statementSql.put(statement, sql); }
/** associate statements with the connections
- called to create them / private Map/<Statement,Connection>*/ statementCreators = new WeakIdentityHashMap();
/** associate statements with the
- underlying string they execute / private Map/<Statement,String>*/ statementSql = new WeakIdentityHashMap(); } JdbcStatementMonitor 维护两个弱标识图: statementCreators 和 statementSql 。第一个图跟踪用来创建语句的连接。正如前面提示过的,我不想依赖这条语句的 getConnection 方法,因为它会引用一个包装过的连接,而我没有这个连接的元数据。请注意 callCreateStatement 切入点,我建议它去监视 JDBC 语句的执行。这个建议匹配的方法调用是在 JDBC 连接上定义的,而且会返回 Statement 或任何子类。这个建议可以匹配 JDBC 中 12 种不同的可以创建或准备语句的方式,而且是为了适应 JDBC API 未来的扩展而设计的。 statementSql 图跟踪指定语句执行的 SQL 字符串。这个图用两种不同的方式更新。在创建预备语句(包括可调用语句)时,在创建时捕捉到 SQL 字符串参数。对于动态 SQL 语句,SQL 字符串参数在监视建议使用它之前,从语句执行调用中被捕捉。(建议的先后次序在这里没影响;虽然是在执行完成之后才用建议查询统计值,但字符串是在执行发生之前捕捉的。) 语句的性能监视由一个 around 建议处理,它在执行 JDBC 语句的时候设置工人对象。执行 JDBC 语句的 statementExec 切入点会捕捉 JDBC Statement (包括子类)实例上名称以 execute 开始的任何方法的调用,方法是在 JDBC API 中定义的(也就是说,在任何名称以 java.sql 开始的包中)。 工人对象上的 lookupStats() 方法使用双亲(servlet)的统计上下文来查询指定连接的数据库统计值,然后查询指定 SQL 字符串的 JDBC 语句统计值。直接的语句执行方法包括:SQL 语句中在 where 子句之后剥离数据的附加逻辑。这就避免了暴露敏感数据的风险,而且也允许把常见语句分组。更复杂的方式就是剥离查询参数而已。但是,多数应用程序使用预备语句而不是动态 SQL 语句,所以我不想深入这一部分。 回页首 跟踪 JDBC 信息 在结束之前,关于监视方面如何解决跟踪 JDBC 信息的挑战,请静想一分钟。 JdbcConnectionMonitor 让我把数据库的文本描述(例如 JDBC URL)与用来访问数据库的连接关联起来。同样, JdbcStatementMonitor 中的 statementSql 映射跟踪 SQL 字符串(甚至是用于预备语句的字符串),从而确保可以用有意义的名称,把执行的查询分成有意义的组。最后, JdbcStatementMonitor 中的 statementCreators 映射让我把语句与我拥有句柄(而不是包装过)的连接关联。这种方式整合了多个建议,在把方面应用到现实问题时,更新内部状态非常有用。在许多情况下,需要跟踪来自 一系列 切入点的上下文信息,在单一公开上下文的 AspectJ 切入点中无法捕捉到这个信息。在出现这种情况时,一个切入点的跟踪状态可以在后一个切入点中使用这项技术就会非常有帮助。 这个信息可用之后, JdbcStatementMonitor 就能够很自然地监视性能了。在语句执行切入点上的实际建议只是遵循标准方法 ,创建工人对象继续处理原始的计算。 lookupStats() 方法使用这三个不同的映射来查询与这条语句关联的连接和 SQL。然后它用它的双亲请求,根据连接的描述找到正确的数据库统计值,并根据 SQL 键字符串找到语句统计值。 lookupStats() 是防御性的,也就是说它在应用程序的使用违背预期的时候,会检查 null 值。在这篇文章的第二部分,我将介绍如何用 AOP 系统地保证监视代码不会在被监视的应用程序中造成问题。 回页首 第 1 部分结束语 迄今为止,我构建了一个核心的监视基础设施,可以系统地跟踪应用程序的性能、测量 servlet 操作中的数据库活动。监视代码可以自然地插入 JMX 接口来公开结果,如图 5 所示。代码已经能够监视重要的应用程序逻辑,您也已经看到了扩展和更新监视方式有多容易。 图 5. 监视数据库结果 虽然这里提供的代码相当简单,但却是对传统方式的巨大修改。AspectJ 模块化的方式让我可以精确且一致地处理监视功能。比起在整个示例应用程序中用分散的调用更新统计值和跟踪上下文,这是一个重大的改进。即使使用对象来封装统计跟踪,传统的方式对于每个用户操作和每个资源访问,也都需要多个调用。实现这样的一致性会很繁琐,也很难一次实现,更不用说维护了。 在这篇文章的第二部分中,我将把重点放在开发和部署基于 AOP 的性能监视系统的编程问题上。我将介绍如何用 AspectJ 5 的装入时编织来监视运行在 Apache Tomcat 中的多个应用程序,包括在第三方库中进行监视。我将介绍如何测量监视的开销,如何选择性地在运行时启用监视,如何测量装入时编织的性能和内存影响。我还会介绍如何用方面防止监视代码中的错误造成应用程序错误。最后,我将扩展 Glassbox Inspector,让它支持 Web 服务和常见的 Web 应用程序框架(例如 Struts 和 Spring )并跟踪应用程序错误。欢迎继续阅读! 致谢 感谢 Ramnivas Laddad、Will Edwards、Matt Hutton、David Pickering、Rob Harrop、Alex Vasseur、Paul Sutter、Mik Kersten 和 Eugene Kuleshov 审阅本文并给出非常深刻的评价。 jerrydan 发表于 09:47 | 阅读全文 | 评论(0) | 引用(0) | 编辑 2005-09-28 Meta标签详解 TAG: HTML Meta标签详解,在网上转的,希望对大家有用 引言 您的个人网站即使做得再精彩,在“浩瀚如海”的网络空间中,也如一叶扁舟不易为人发现,如何推广 个人网站,人们首先想到的方法无外乎以下几种: ● 在搜索引擎中登录自己的个人网站 ● 在知名网站加入你个人网站的链接 ● 在论坛中发帖子宣传你的个人网站 很多人却忽视了HTML标签META的强大功效,一个好的META标签设计可以大大提高你的个人网站被搜索到的可能性,有兴趣吗,谁我来重新认识一下META标签吧! META标签是HTML语言HEAD区的一个辅助性标签,它位于HTML文档头部的标记和标记之间,它提供用户不可见的信息。meta标签通常用来为搜索引擎robots定义页面主题,或者是定义用户浏览器上的cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置RASC内容等级,等等。 详细介绍 下面介绍一些有关 标记的例子及解释。 META标签分两大部分:HTTP标题信息(HTTP-EQUIV)和页面描述信息(NAME)。 ★HTTP-EQUIV HTTP-EQUIV类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正确和精确地显示网页内容。常用的HTTP-EQUIV类型有: 1、Content-Type和Content-Language (显示字符集的设定) 说明:设定页面使用的字符集,用以说明主页制作所使用的文字已经语言,浏览器会根据此来调用相应的字符集显示page内容。 用法:
注意: 该META标签定义了HTML页面所使用的字符集为GB2132,就是国标汉字码。如果将其中的“charset=GB2312”替换成“BIG5”,则该页面所用的字符集就是繁体中文Big5码。当你浏览一些国外的站点时,IE浏览器会提示你要正确显示该页面需要下载xx语支持。这个功能就是通过读取HTML页面META标签的Content-Type属性而得知需要使用哪种字符集显示该页面的。如果系统里没有装相应的字符集,则IE就提示下载。其他的语言也对应不同的charset,比如日文的字符集是“iso-2022-jp ”,韩文的是“ks_c_5601”。 Content-Type的Content还可以是:text/xml等文档类型; Charset选项:ISO-8859-1(英文)、BIG5、UTF-8、SHIFT-Jis、Euc、Koi8-2、us-ascii, x-mac-roman, iso-8859-2, x-mac-ce, iso-2022-jp, x-sjis, x-euc-jp,euc-kr, iso-2022-kr, gb2312, gb_2312-80, x-euc-tw, x-cns11643-1,x-cns11643-2等字符集;Content-Language的Content还可以是:EN、FR等语言代码。 2、Refresh (刷新) 说明:让网页多长时间(秒)刷新自己,或在多长时间后让网页自动链接到其它网页。 用法: 注意:其中的5是指停留5秒钟后自动刷新到URL网址。 3、Expires (期限) 说明:指定网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新调阅。 用法: 注意:必须使用GMT的时间格式,或直接设为0(数字表示多少时间后过期)。 4、Pragma (cach模式) 说明:禁止浏览器从本地机的缓存中调阅页面内容。 用法: 注意:网页不保存在缓存中,每次访问都刷新页面。这样设定,访问者将无法脱机浏览。 5、Set-Cookie (cookie设定) 说明:浏览器访问某个页面时会将它存在缓存中,下次再次访问时就可从缓存中读取,以提高速度。当你希望访问者每次都刷新你广告的图标,或每次都刷新你的计数器,就要禁用缓存了。通常HTML文件没有必要禁用缓存,对于ASP等页面,就可以使用禁用缓存,因为每次看到的页面都是在服务器动态生成的,缓存就失去意义。如果网页过期,那么存盘的cookie将被删除。 用法: 注意:必须使用GMT的时间格式。 6、Window-target (显示窗口的设定) 说明:强制页面在当前窗口以独立页面显示。 用法: 注意:这个属性是用来防止别人在框架里调用你的页面。Content选项:_blank、_top、_self、_parent。 7、Pics-label (网页RSAC等级评定) 说明:在IE的Internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级 别就是通过该参数来设置的。 用法: 注意:不要将级别设置的太高。RSAC的评估系统提供了一种用来评价Web站点内容的标准。用户可以设置Microsoft Internet Explorer(IE3.0以上)来排除包含有色情和暴力内容的站点。上面这个例子中的HTML取自Microsoft的主页。代码中的(n 0 s 0 v 0 l 0)表示该站点不包含不健康内容。级别的评定是由RSAC,即美国娱乐委员会的评级机构评定的,如果你想进一步了解RSAC评估系统的等级内容,或者你需要评价自己的网站,可以访问RSAC的站点: http://www.rsac.org/ 。 8、Page-Enter、Page-Exit (进入与退出) 说明:这个是页面被载入和调出时的一些特效。 用法: 注意:blendTrans是动态滤镜的一种,产生渐隐效果。另一种动态滤镜RevealTrans也可以用于页面进入与退出效果: Duration 表示滤镜特效的持续时间(单位:秒) Transition 滤镜类型。表示使用哪种特效,取值为0-23。 0 矩形缩小 1 矩形扩大 2 圆形缩小 3 圆形扩大 4 下到上刷新 5 上到下刷新 6 左到右刷新 7 右到左刷新 8 竖百叶窗 9 横百叶窗 10 错位横百叶窗 11 错位竖百叶窗 12 点扩散 13 左右到中间刷新 14 中间到左右刷新 15 中间到上下 16 上下到中间 17 右下到左上 18 右上到左下 19 左上到右下 20 左下到右上 21 横条 22 竖条 23 以上22种随机选择一种 9、MSThemeCompatible (XP主题) 说明:是否在IE中关闭 xp 的主题 用法: 注意:关闭 xp 的蓝色立体按钮系统显示样式,从而和win2k 很象。 10、IE6 (页面生成器) 说明:页面生成器generator,是ie6 用法: 注意:用什么东西做的,类似商品出厂厂商。 11、Content-Script-Type (脚本相关) 说明:这是近来W3C的规范,指明页面中脚本的类型。 用法: 注意: ★NAME变量 name是描述网页的,对应于Content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。 name的value值(name="")指定所提供信息的类型。有些值是已经定义好的。例如description(说明)、keyword(关键字)、refresh(刷新)等。还可以指定其他任意值,如:creationdate(创建日期) 、 document ID(文档编号)和level(等级)等。 name的content指定实际内容。如:如果指定level(等级)为value(值),则Content可能是beginner(初级)、intermediate(中级)、advanced(高级)。 1、Keywords (关键字) 说明:为搜索引擎提供的关键字列表 用法: 注意:各关键词间用英文逗号“,”隔开。META的通常用处是指定搜索引擎用来提高搜索质量的关键词。当数个META元素提供文档语言从属信息时,搜索引擎会使用lang特性来过滤并通过用户的语言优先参照来显示搜索结果。例如: 2、Description (简介) 说明:Description用来告诉搜索引擎你的网站主要内容。 用法: 注意: 3、Robots (机器人向导) 说明:Robots用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。Content的参数有all、none、index、noindex、follow、nofollow。默认是all。 用法: 注意:许多搜索引擎都通过放出robot/spider搜索来登录网站,这些robot/spider就要用到meta元素的一些特性来决定怎样登录。 all:文件将被检索,且页面上的链接可以被查询; none:文件将不被检索,且页面上的链接不可以被查询;(和 "noindex, no follow" 起相同作用) index:文件将被检索;(让robot/spider登录) follow:页面上的链接可以被查询; noindex:文件将不被检索,但页面上的链接可以被查询;(不让robot/spider登录) nofollow:文件将不被检索,页面上的链接可以被查询。(不让robot/spider顺着此页的连接往下探找) 4、Author (作者) 说明:标注网页的作者或制作组 用法: 注意:Content可以是:你或你的制作组的名字,或Email 5、Copyright (版权) 说明:标注版权 用法: 注意: 6、Generator (编辑器) 说明:编辑器的说明 用法: 注意:Content="你所用编辑器" 7、revisit-after (重访) 说明: 用法: 注意: ★Head中的其它一些用法 1、scheme (方案) 说明:scheme can be used when name is used to specify how the value of content should be interpreted. 用法: 注意: 2、Link (链接) 说明:链接到文件 用法: 注意:很多网站如果你把她保存在收件夹中后,会发现它连带着一个小图标,如果再次点击进入之后还会发现地址栏中也有个小图标。现在只要在你的页头加上这段话,就能轻松实现这一功能。 用来将目前文件与其它 URL 作连结,但不会有连结按钮,用於 标记间, 格式如下: