每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks

#

key words : 热部署 动态读取配置文件 动态读取properties文件

come from here

package com.javaeye.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;

/**
@author Robbin Fan

*/
public class ConfigUtil {

    
private static Properties props = null;   
    
private static File configFile = null;
    
private static long fileLastModified = 0L;
   
    
private static void init() {
        URL url 
= ConfigUtil.class.getClassLoader().getResource("global.properties");
        configFile 
= new File(url.getFile());
        fileLastModified 
= configFile.lastModified();     
        props 
= new Properties();
        load();
    }
   
    
private static void load() {
        
try {
            props.load(
new FileInputStream(configFile));
            fileLastModified 
= configFile.lastModified();
        } 
catch (IOException e) {           
            
throw new RuntimeException(e);
        }
    }

    
public static String getConfig(String key) {
        
if ((configFile == null|| (props == null)) init();
        
if (configFile.lastModified() > fileLastModified) load();
        
return props.getProperty(key);
    }

}

posted @ 2006-08-08 19:08 Alex 阅读(355) | 评论 (0)编辑 收藏

key words: 观察者模式 Observer模式

definition:
defines a one-to-many dependency between objects so that when one object changes state,all of its dependents are notified and updated automatically.

物理模型:
观察者注册
observerpattern_02.gif

观察者通知:
observerpattern_03.gif
观察者撤销注册
observerpattern_04.gif

business: weather station
天气预报,在预报的数据更新后自动通知到各类不同显示类型的终端,前提是这些终端向预报中心注册了预定服务(register the service),这一点类似于订阅报纸,你'订阅'了以后就可以呆在家等邮递员给你送过来.
看一下类图:weather.png


implement:
public interface Subject {
    
public void registerObserver(Observer o);
    
public void removeObserver(Observer o);
    
public void notifyObservers();
}

public class WeatherData implements Subject {
    
private ArrayList observers;
    
private float temperature;
    
private float humidity;
    
private float pressure;
    
    
public WeatherData() {
        observers 
= new ArrayList();
    }
    
    
public void registerObserver(Observer o) {
        observers.add(o);
    }
    
    
public void removeObserver(Observer o) {
        
int i = observers.indexOf(o);
        
if (i >= 0) {
            observers.remove(i);
        }
    }
    
    
public void notifyObservers() {
        
for (int i = 0; i < observers.size(); i++) {
            Observer observer 
= (Observer)observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }
    
    
public void measurementsChanged() {
        notifyObservers();
    }
    
    
public void setMeasurements(float temperature, float humidity, float pressure) {
        
this.temperature = temperature;
        
this.humidity = humidity;
        
this.pressure = pressure;
        measurementsChanged();
    }
    
    
// other WeatherData methods here
    
    
public float getTemperature() {
        
return temperature;
    }
    
    
public float getHumidity() {
        
return humidity;
    }
    
    
public float getPressure() {
        
return pressure;
    }
}

public interface Observer {
    
public void update(float temp, float humidity, float pressure);
}


public interface DisplayElement {
    
public void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    
private float temperature;
    
private float humidity;
    
private Subject weatherData;
    
    
public CurrentConditionsDisplay(Subject weatherData) {
        
this.weatherData = weatherData;
        weatherData.registerObserver(
this);
    }
    
    
public void update(float temperature, float humidity, float pressure) {
        
this.temperature = temperature;
        
this.humidity = humidity;
        display();
    }
    
    
public void display() {
        System.out.println(
"Current conditions: " + temperature 
            
+ "F degrees and " + humidity + "% humidity");
    }
}

public class WeatherStation {

    
public static void main(String[] args) {
        WeatherData weatherData 
= new WeatherData();
   
        //register to weatherData(subscribe to weatherData)
        CurrentConditionsDisplay currentDisplay 
=new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay 
= new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay 
= new ForecastDisplay(weatherData);

        weatherData.setMeasurements(
806530.4f);
        weatherData.setMeasurements(
827029.2f);
        weatherData.setMeasurements(
789029.2f);
    }
}


public class WeatherStationHeatIndex {

    
public static void main(String[] args) {
        WeatherData weatherData 
= new WeatherData();
        CurrentConditionsDisplay currentDisplay 
= new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay 
= new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay 
= new ForecastDisplay(weatherData);
        HeatIndexDisplay heatIndexDisplay 
= new HeatIndexDisplay(weatherData);

        weatherData.setMeasurements(
806530.4f);
        weatherData.setMeasurements(
827029.2f);
        weatherData.setMeasurements(
789029.2f);
    }
}


other places to use Observe-Pattern in JDK:

public class SwingObserverExample {
    JFrame frame;
    
    
public static void main(String[] args) {
        SwingObserverExample example 
= new SwingObserverExample();
        example.go();
    }
    
    
public void go() {
        frame 
= new JFrame();

        JButton button 
= new JButton("Should I do it?");
        //register to AngelListener and DevilListener

        button.addActionListener(
new AngelListener());
        button.addActionListener(
new DevilListener());
        frame.getContentPane().add(BorderLayout.CENTER, button);

        
// Set frame properties 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(BorderLayout.CENTER, button);
        frame.setSize(
300,300);
        frame.setVisible(
true);
    }
    
    
class AngelListener implements ActionListener {
        
public void actionPerformed(ActionEvent event) {
            System.out.println(
"Don't do it, you might regret it!");
        }
    }

    
class DevilListener implements ActionListener {
        
public void actionPerformed(ActionEvent event) {
            System.out.println(
"Come on, do it!");
        }
    }
}



implement it with java built-in Observe-Pattern
import java.util.Observable;
import java.util.Observer;
    
public class WeatherData extends Observable {
    
private float temperature;
    
private float humidity;
    
private float pressure;
    
    
public WeatherData() { }
    
    
public void measurementsChanged() {
        setChanged();
        notifyObservers();
    }
    
    
public void setMeasurements(float temperature, float humidity, float pressure) {
        
this.temperature = temperature;
        
this.humidity = humidity;
        
this.pressure = pressure;
        measurementsChanged();
    }
    
    
public float getTemperature() {
        
return temperature;
    }
    
    
public float getHumidity() {
        
return humidity;
    }
    
    
public float getPressure() {
        
return pressure;
    }
}






增加一个看到的通俗版的解说:

观察者模式 Observer Pattern — 三国演义之超级间谍战 — 美女貂蝉的故事

说明:我也是初学者,希望大家能提出宝贵意见。另外转载请注明作者左光和出处博客园,毕竟花费了很长时间才完成。

情节:

这一次讲的故事情节很简单,但是充满了谋略和斗争。大体意思就是三国初期,曹刘孙三家在徐州联手消灭了吕布,但是自己也伤了元气。而此时袁术得了传国玉玺,在淮南称帝,兵精将广,图谋不轨,对三家威胁都很大。于是曹刘孙三家在一起开了个会,决定派遣一名超级间谍打入到袁术身旁,监视他的一举一动,这样的话一旦袁术想干什么坏事,他们就可以立刻知道并做出相应的调整,知己知彼百战百胜嘛。

计是好计,问题是派谁去当这个超级间谍呢?正当大家愁眉苦脸的时候,美女貂蝉自告奋勇,想当年董卓那么厉害都让 我灭了,何况一个小小的袁术呢?大家一听,说的有理,于是就把貂蝉献给了袁术当了妃子。另外三家还各派出一名小奸细常住在淮南城内,他们的任务是当联络 员,貂蝉有什么情报总不能自己曹刘孙三家挨个跑着送吧?直接丢给各国联络员,然后让他们通知各自的主公就 OK 了!而三家只要一接到各自奸细的通知,就会立即做出反应。

还有一个小插曲,袁术虽然收下了貂蝉,但是对她看管很严,大大方方地把情报送出去不太可能,逼不得以,貂蝉自己设计了一套密码来传递情报,虽然加密 方法没有公开,但是她想总有人可以破解了吧?没错,对诸葛亮来说就是小菜一碟,从此袁术穿什么内裤曹刘孙三家都知道得清清楚楚。

分析:

下面我们用 观察者模式  来分析一下上面这个故事

1、美女貂蝉:貂蝉在观察者模式中叫做被观察者(Subject),主要任务是独立的管理后台数据和业务逻辑,同时尽可能不受前台客户端界面变化的影响。当然,还要负责登记或者注销各个观察者。

在这个故事里,貂蝉仅仅维护了一个数据 ,就是情报 —  私有变量 info ;另外还拥有一个业务逻辑,是用来加密 info 的方法 Reverse(string str) 。每次得到新的情报,她就会先加密,然后立刻找到在自己这登记过的联络员,让这些联络员通知自己的主公应变。

情报一旦发送出去, 貂蝉的任务就算完成了,具体曹刘孙三家怎么应对,是打是降还是另有其他方法,那是三家自己的事情,和她貂蝉就没什么关系了。

2、曹刘孙三家:曹刘孙三家在观察者模式里叫做观察者,主要任务就是从界面上用“各种方式”即时的反映出 被观察者,所谓“各种方式”就是说用字符、图形、声音都可以表示同样数据,外在表现不同而已,本质都是一些数据。所谓“即时”,就是说只要 被观察者 发生变化, 观察者 也会立刻跟着变化,用行话应该叫做刷新 Update吧。

在这个故事里,我们可以看到运行结果中,每次貂蝉有什么新的情报,三家都会在屏幕上显示出来,虽然很简单,但 他们确实是用各自不同的方式表示了同样的数据








posted @ 2006-08-07 20:07 Alex 阅读(439) | 评论 (0)编辑 收藏

先转一篇 jdon的文章:

Strategy策略模式是属于设计模式中 对象行为型模式,主要是定义一系列的算法,把这些算法一个个封装成单独的类.

Stratrgy应用比较广泛,比如, 公司经营业务变化图, 可能有两种实现方式,一个是线条曲线,一个是框图(bar),这是两种算法,可以使用Strategy实现.

这里以字符串替代为例, 有一个文件,我们需要读取后,希望替代其中相应的变量,然后输出.关于替代其中变量的方法可能有多种方法,这取决于用户的要求,所以我们要准备几套变量字符替代方案.

 

首先,我们建立一个抽象类RepTempRule 定义一些公用变量和方法:

public abstract class RepTempRule{

protected String oldString="";
public void setOldString(String oldString){
  this.oldString=oldString;
}

protected String newString="";
public String getNewString(){
  return newString;
}



public abstract void replace() throws Exception;


}

在RepTempRule中 有一个抽象方法abstract需要继承明确,这个replace里其实是替代的具体方法.
我们现在有两个字符替代方案,
1.将文本中aaa替代成bbb;
2.将文本中aaa替代成ccc;

对应的类分别是RepTempRuleOne RepTempRuleTwo

public class RepTempRuleOne extends RepTempRule{


public void replace() throws Exception{

  //replaceFirst是jdk1.4新特性
  newString=oldString.replaceFirst("aaa", "bbbb")
  System.out.println("this is replace one");
  
}


}

public class RepTempRuleTwo extends RepTempRule{


public void replace() throws Exception{

  newString=oldString.replaceFirst("aaa", "ccc")
  System.out.println("this is replace Two");
  
}


}

第二步:我们要建立一个算法解决类,用来提供客户端可以自由选择算法。

public class RepTempRuleSolve {

  private RepTempRule strategy;

  public RepTempRuleSolve(RepTempRule rule){
    this.strategy=rule;
  }

  public String getNewContext(Site site,String oldString) {
    return strategy.replace(site,oldString);
  }

  public void changeAlgorithm(RepTempRule newAlgorithm) {
    strategy = newAlgorithm;
  }

}

 

 

调用如下:

public class test{

......

  public void testReplace(){

  //使用第一套替代方案
  RepTempRuleSolve solver=new RepTempRuleSolve(new RepTempRuleSimple());
  solver.getNewContext(site,context);

  //使用第二套

  solver=new RepTempRuleSolve(new RepTempRuleTwo());
  solver.getNewContext(site,context);

  }

.....

}

我们达到了在运行期间,可以自由切换算法的目的。

实际整个Strategy的核心部分就是抽象类的使用,使用Strategy模式可以在用户需要变化时,修改量很少,而且快速.

Strategy和Factory有一定的类似,Strategy相对简单容易理解,并且可以在运行时刻自由切换。Factory重点是用来创建对象。

Strategy适合下列场合:

1.以不同的格式保存文件;

2.以不同的算法压缩文件;

3.以不同的算法截获图象;

4.以不同的格式输出同样数据的图形,比如曲线 或框图bar等


另:

ahead first design 中的第一篇举例的模型如下


strategy.png
posted @ 2006-08-01 18:26 Alex 阅读(314) | 评论 (0)编辑 收藏

Oracle常用的函数:

sysdate
systimestamp
to_char
to_date:
select to_date('06-5月-1957','DD-Mon-YY'from dual;

nvl:

select nvl(sal,''from emp;


decode:
decode(条件,值1,翻译值1,值2,翻译值2,...值n,翻译值n,缺省值)

该函数的含义如下:
IF 条件=值1 THEN
    RETURN(翻译值1)
ELSIF 条件=值2 THEN
    RETURN(翻译值2)
    ......
ELSIF 条件=值n THEN
    RETURN(翻译值n)

ELSE
    RETURN(缺省值)
END IF
String sql =
        "
select product_name,sum(decode(bill_month,'"+chargePeriod+"',charge))/100 as cur_charge,
sum(decode(bill_month,'"+lastPeriod+"',charge))/100 as last_charge, "
        
+
        " 
sum(decode(bill_month,'"+chargePeriod+"',bill_time_len))/60 as cur_time,
sum(decode(bill_month,'"+lastPeriod+"',bill_time_len))/60 as last_time "
        
+
        " 
from tl_acc_report "
        
+
        " 
group by product_name ";


SELECT sid,serial#,username,

DECODE(command

,
0,’None’

,
2,’Insert

,
3,’Select

,
6,’Update

,
7,’Delete

,
8,’Drop

,’Other’) cmd 
from des

posted @ 2006-07-31 16:34 Alex 阅读(398) | 评论 (0)编辑 收藏

熟练人员经过多年的积累加上自己的CodeSnip的总结,基本不用额外再查找资料。而一般的开发人员在开发过程中会花掉10-20%时间去查找资料。

熟练人员注意代码复用,并且时刻注意重构和抽取公用代码。一般开发人员是代码拷来拷去完成功能。

熟练人员非常注意查找,定位,标签等各种快捷键的使用,定位查找方便快捷,IDE环境也根据习惯定义到最方便状态。

熟练人员编码前先思考清楚整个流程,在头脑或纸张上规划好整个实现方式和方法函数的划分。一般人员想到哪里写到哪里。

熟练人员写了50行以上或更多代码才Debug一两次,一般人员写了几行代码就要Debug多次,完全通过Debug来验证代码正确性。

熟练人员注重代码的质量,单元测试和可维护性,注重各种业务逻辑的验证和边界条件的校验。一般人员只注重简单功能的简单完成。

熟练人员提交测试的代码BUG很少,返工工作量很小。一般开发人员由于自测不完善BUG较多,造成大量的返工工作量。

熟练人员合理分配自己的时间,规划好每天工作任务,开发过程各位专注。一般开发人员一心多用,边开发边聊Q。

熟练人员善于知识的总结和积累,形成自我的知识库和经验库

熟练人员善于发现问题,分析不足而自我持续改进。一般人员在外力干预侠被动改进。

熟练开发人员开发重点已经专业到对业务的深刻理解,一般开发人员考虑的是开发上编程的语言和工具。

熟练人员善于从各种影响自己开发效率的因素中挤时间,善于使用各种辅助开发工具。而一般人员则不善于这种总结。
posted @ 2006-07-31 11:21 Alex 阅读(450) | 评论 (0)编辑 收藏

级别: 初级

易立, IBM 中国软件开发实验室 SOA设计中心 高级软件工程师
赵勇, IBM 中国软件开发实验室 SOA设计中心 高级软件工程师

2006 年 4 月 20 日

在本文中,作者通过一个Web Service访问的实例,具体描述了SOA应用中所遇到的一系列具体问题,并描述如何利用IoC和AOP等技术进行代码重构,从而构建结构更加良好、灵活的SOA应用。

1.引言

SOA是一种构造分布式系统的方法,它将业务应用功能以服务的形式提供出来,以便更好的复用、组装和与外部系统集成,从而降低开发成本,提高开发效率。SOA的目标是为企业构建一个灵活,可扩展的IT基础架构来更好地支持随需应变的商务应用。

随着SOA技术和产品的不断成熟,现在越来越多的用户开始了解并认同SOA的理念,但对SOA项目的实施还缺乏信心。其主要原因是:SOA应用开发还相对比较复杂。

一年多来,本文作者所在的部门已经从事了许多国内外的SOA项目的实施和支持工作,积累了许多SOA应用开发经验。我们希望能够通过一系列的文章与读者分享这些想法,帮助您更好地构建SOA应用。

本 文将从Web Service调用入手,在解决一系列具体问题的过程中,使用IoC (Inversion of Control) 和AOP (Aspect- Oriented Programming) 等方法重构Web Service的访问代码,使得业务逻辑与Web Service访问解耦,为您提供一个更加灵活和易于扩展的访问模式。

Spring是一个流行的轻量级容器,对IoC和AOP提供了良好的 支持。本文为您提供了一个基于Spring的实现供您下载学习。示例代码工程使用Eclipse3.1/3.02和JDK1.4开发, 您还需要Spring 1.2.5和Axis1.3提供的支持。详细的下载信息请参见参考资源部分。





回页首


2.Web Service调用

Web Service是目前实现SOA应用的一项基本的,适用的技术,它为服务的访问提供了一个被广泛接受的开放标准。为了便于说明问题,我们将使用XMethods 网站(http://www.xmethods.net/)发布的货币兑换服务作为示例。并针对JAX-RPC 1.1,说明如何编写Web Service 的调用代码。

2.1 示例说明

http://xmethods.net 作为最早推出Web Service实际示例的网站,提供了很多优秀的Web Service 样例。其中有一个汇率计算服务,可以返回两个国家之间的货币兑换比例。获取该服务的详细信息,请参考该服务的服务描述文档(获取WSDL 文档) 。在此就不具体解析该服务描述文档了。读者可以从WSDL2Java生成的接口中了解该服务的用法:


												
														
public interface CurrencyExchangePortType extends java.rmi.Remote {
public float getRate(String country1, String country2) throws java.rmi.RemoteException;
}

2.2 客户端调用方法

JAX-RPC作为Java平台的RPC服务调用标准接口,为Web Service客户端调用提供了3种方法,分别是DII,动态代理,和静态Stub。 DII(Dynamic Invocation Interface)采用直接调用方式,可以在程序中设置诸多的调用属性,使用较为灵活,但是调用过程却相对繁琐复杂,易造成代码膨胀且可重用性低,每次调用不同的Web Service都要重复进行大量编码。

JAX-RPC中动态代理(Dynamic Proxy)的方法实现对Web Service的动态调用,可以在运行时根据用户定义的Client端接口创建适配对象。从而避免了直接操作底层的接口,减少了客户端的冗余,屏蔽了调用相关的复杂性。

使 用静态Stub和Service Locator是目前最常用的调用方式。JAX-RPC使用静态的Stub方式包装对底层接口的调用,从而提供一种更为简便的调用方式。使用该方式需要利 用支持环境(比如Axis)所提供的工具根据WSDL预生成Web Service客户端的实现代码。因此如果服务的WSDL发生变化,就必须重新生成新的客户端代码并进行重新部署。

为了更详细的了解静态Stub的调用方式,您可以将示例代码的WebServiceClient.jar导入到您现有Eclipse工作区之中。

客户端生成代码包括如下4个类:如图 1 所示:


图 1: 客户端代码类图
图 1: 客户端代码类图

在上图中包括的几个类中:

CurrencyExchangePortType:服务端点接口,定义了Web Service的方法签名。

CurrencyExchangeService:Service接口,定义了获取服务端点接口的方法。

CurrencyExchangeServiceLocator:ServiceLocator类,实现了Service接口。

CurrencyExchangeBindingStub: Stub实现类,实现了服务端点接口,封装了对Web Service访问的底层逻辑。

使用Stub调用Web Service的过程也非常简单,读者可以参考清单 1:


清单 1:Web Service 调用代码示例
												
														
try {
//创建ServiceLocator
CurrencyExchangeServiceLocator locator = new
CurrencyExchangeServiceLocator();
//设定端点地址
URL endPointAddress = new URL("http://services.xmethods.net:80/soap");
//创建Stub实例
CurrencyExchangePortType stub =
locator.getCurrencyExchangePort(endPointAddress);
//设定超时为120秒
((CurrencyExchangeBindingStub)stub).setTimeout(120000);
//调用Web Service计算人民币与美元的汇率
float newPrice = stub.getRate("China", "USA") * 100;
} catch (MalformedURLException mex) {
//...
} catch (ServiceException sex) {
//...
} catch (RemoteException rex) {
//...
}





回页首


3.重构Web Service调用代码

3.1 实例代码中的"坏味道"

上面的基于Service Locator的Web Service访问代码虽然简单但暴露出以下几个问题:

1.访问Web Service所需的配置代码被嵌入应用逻辑之中
在Web Service调用中,我们需要设定一系列必要的参数。比如:服务端点地址、用户名/密码、超时设定等等。这些参数在开发和运行环境中都有可能发生变化。我们必须提供一种机制:在环境变化时,不必修改源代码就可以改变Web Service的访问配置。

2 客户端代码与Web Service访问代码绑定
在上面的代码中,业务逻辑与Web Service的Stub创建和配置代码绑定在一起。这也不是一种良好的编程方式。客户端代码只应关心服务的接口,而不应关心服务的实现和访问细节。比 如,我们既可以通过Web Service的方式访问远程服务,也可以通过EJB的方式进行访问。访问方式对业务逻辑应该是透明的。

这 种分离客户端代码与服务访问代码的方式也有利于测试。这样在开发过程中,负责集成的程序员就可能在远程服务还未完全实现的情况下,基于服务接口编写集成代 码,并通过编写POJO(Plain Old Java Object)构建伪服务实现来进行单元测试和模拟运行。这种开发方式对于保证分布式系统代码质量具有重要意义。

因此,为了解决上面的问题我们需要:

1、将Web Service访问的配置管理与代码分离;

2、解除客户端代码与远程服务之间的依赖关系;

3.2 利用IoC模式进行重构代码

我 们先介绍在Core J2EE Patterns一书中提到的一种业务层模式:Business Delegate。它所要解决的问题是屏蔽远程服务访问的复杂性。它的主要思想就是将Business Delegate作为远程服务的客户端抽象,隐藏服务访问细节。Business Delegate还可以封装并改变服务调用过程,比如将远程服务调用抛出的异常(例如RemoteException)转换为应用级别的异常类型。

其类图如图 2 所示:


图 2:Business Delegate 模式的类图图解
图 2:Business Delegate 模式的类图图解

Business Delegate模式实现很好地实现了客户端与远程访问代码的解耦,但它并不关注Delegate与远程服务之间的解耦。为了更好解决Business Delegate和远程服务之间的依赖关系,并更好地进行配置管理,我们可以用IoC模式来加以解决。

IoC(Inversion of Contro)l意为控制反转,其背后的概念常被表述为"好莱坞法则":"Don't call me, I'll call you." IoC将一部分责任从应用代码交给framework(或者控制器)来做。通过IoC可以实现接口和具体实现的高度分离,降低对象之间的耦合程度。 Spring是一个非常流行的IoC容器,它通过配置文件来定义对象的生命周期和依赖关系,并提供了良好的配置管理能力。

现在我们来重构我们的Web Service应用程序,我们首先为Business Delegate定义一个接口类型,它提供了一个应用级组件接口,所有客户端都应通过它来执行汇率计算,而不必关心实现细节,如清单 2 所示:


清单 2:接口定义的代码示例
												
														

Public interface CurrencyExchangeManager {
//货币兑换计算
//新价格 = 汇率 * 价格
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException;
}

Business Delegate的实现非常简单,主要工作是包装汇率计算 Web Service的调用,如清单 3 所示。


清单 3:Business Delegate的代码示例
												
														
public class CurrencyExchangeManagerImpl implements CurrencyExchangeManager {
//服务实例
private CurrencyExchangePortType stub;
//获取服务实例
public CurrencyExchangePortType getStub() {
return stub;
}
//设定服务实例
public void setStub(CurrencyExchangePortType stub) {
this.stub = stub;
}
//实现货币兑换
public float calculate(String country1, String country2, float price)
throws CurrencyExchangeException {
try {
//通过Stub调用WebService
float rate = stub.getRate(country1, country2);
return rate * price;
} catch (RemoteException rex) {
throw new CurrencyExchangeException(
"Failed to get exchange rate!", rex);
}
}
}

下面我们需要讨论如何利用Spring的IoC机制,来创建和配置对象,并定义它们的依赖关系。

Spring 利用类工厂来创建和配置对象。在Spring框架中,已经为基于JAX-RPC的Web Service调用提供了一个客户端代理的类工厂实现:JaxRpcPortProxyFactoryBean。在配置文件bean.xml中,我们将使 用JaxRpcPortProxyFactoryBean来创建和配置Web Service的客户端代理"CurrencyExchangeService",如清单 5 所示。我们还将定义一个名为"CurrencyExchangeManager"的CurrencyExchangeManagerImpl实例,并建立 它与CurrencyExchangeService之间的依赖关系。有关Spring 配置和JaxRpcPortProxyFactoryBean的使用细节请参见参考资料。


清单 5:bean.xml的配置文件
												
														

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="CurrencyExchangeService"
class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.
CurrencyExchangePortType</value>
</property>
<property name="wsdlDocumentUrl">
<value>http://www.xmethods.net/sd/2001/CurrencyExchangeService.
wsdl</value>
</property>
<property name="namespaceUri">
<value>http://www.xmethods.net/sd/CurrencyExchangeService.
wsdl</value>
</property>
<property name="serviceName">
<value>CurrencyExchangeService</value>
</property>
<property name="portName">
<value>CurrencyExchangePort</value>
</property>
<property name="endpointAddress">
<value>http://services.xmethods.net:80/soap</value>
</property>
</bean>
<bean id="CurrencyExchangeManager"
class="test.ws.CurrencyExchangeManagerImpl">
<property name="stub">
<ref bean="CurrencyExchangeService"/>
</property>
</bean>
</beans>

最后我们创建一个测试程序来验证我们的代码,如清单6 所示:


清单 6:测试代码
												
														

public class Main {
// For test only
public static void main(String[] args) {
// Spring Framework将根据配置文件创建并配置CurrencyExchangeManager实例
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
// 获取CurrencyExchangeManager实例
CurrencyExchangeManager manager = (CurrencyExchangeManager) ctx
.getBean("CurrencyExchangeManager");
try {
System.out.println(manager.calculate("China", "USA", 100));
System.out.println(manager.calculate("China", "Japan", 200));
System.out.println(manager.calculate("China", "USA", 200));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

此时运行测试客户端,等待片刻将会看见测试结果,如清单 7 所示:


清单 7:测试结果。
												
														
12.34
2853.26
24.68

注:该结果会随着汇率的变化而出现不同的值。

该程序的类图和顺序图如图3及图4所示:


图 3:示例程序的类图
图 3:示例程序的类图

从 上面的类图我们可以看到,我们的测试程序(Main.java)通过Spring框架获取了BusinessDelegate的实例。而且Spring 框架还会根据配置中的依赖关系,在运行时将Web Service的客户端代理" 注射"到CurrencyExchangeManagerImpl实例中,这就是依赖注入(Dependency Injection)。通过这种方式解决了应用逻辑和BusinessDelegate之间的依赖关系,以及BusinessDelegate的实现与远程服务之间的依赖关系,如图 4 所示。


图 4: 示例程序的顺序图
图 4:  示例程序的顺序图

Spring 框架提供的ApplicationContext实现会根据配置文件中的描述信息来实现对象生命周期管理,配置管理以及依赖管理等功能。这一切对于应用程 序是透明的,应用程序代码只依赖接口进行编程,而无需考虑其它复杂问题。无论是Web Service的配置发生变化,或是改用不同的服务实现时,都不会对客户端应用代码的产生影响。这很好地实现了业务逻辑与Web Service调用之间的解耦。

3.3 构建自己的 Web Service代理工厂

Spring 所提供的JaxRpcPortProxyFactoryBean封装了构造Web Service客户端代理的细节,可以通过参数配置来创建Dynamic Proxy和DII类型的Web Service客户端代理。(如果您希望深入了解其实现细节可以参考org.springframework.remoting.jaxrpc包下的源代 码。)但由于JaxRpcPortProxyFactoryBean需要使用者对WSDL中Port,Service,名空间等概念有深入的了解;而且如 果Web Service使用了复杂数据类型,开发人员需要手工定义类型映射代码。所以JaxRpcPortProxyFactoryBean并不适合Web Service的初学者来使用。

为了进一步简化Web Service代理的创建,并帮助读者更好地理解类工厂在Spring框架下的作用。我们提供了一个基于静态Stub的Web Service客户端代理工厂实现。其核心代码非常简单,就是通过ServiceLocator提供的方法来创建Web Service客户端代理。

其主要代码如清单8所示:


清单8:静态代理工厂的代码
												
														
public class WebServiceStubFactoryBean implements FactoryBean,
InitializingBean {
private Class serviceInterface;
private Class serviceLocator;
private Object stub;

public void afterPropertiesSet() throws Exception {
//利用serviceLocator和服务接口创建Web Service客户端代理
stub = ((javax.xml.rpc.Service)
serviceLocator.newInstance()).getPort(serviceInterface);
//为Stub设定endpointAddress,usernam, 超时等参数
preparePortStub((javax.xml.rpc.Stub) stub);
}
public Object getObject() {
// 返回客户端代理
return stub;
}
public Class getObjectType() {
// 返回服务接口
return serviceInterface;
}
public boolean isSingleton() {
return true;
}
}

我们需要修改配置文件bean.xml中有关Web Service代理创建的部分,让新的Web Service 代理工厂发挥作用。如清单9所示:


清单9:修改后的bean.xml的配置文件
												
														

<bean id="CurrencyExchangeService" class="test.ws.WebServiceStubFactoryBean">
<property name="serviceInterface">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangePortType</value>
</property>
<property name="serviceLocator">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.CurrencyExchangeServiceLocator</value>
</property>
<property name="endpointAddress2">
<value>http://services.xmethods.net:80/soap</value>
</property>
<property name="timeout">
<value>120000</value>
</property>
</bean>

得益于Spring框架,虽然我们已经替换了对象的类工厂,却并不需要更改应用代码。通过Spring框架的IoC机制,我们可以完全使用面向接口的编程方式,而将实现的创建、配置和依赖管理交由Spring在运行时完成。即使实现发生了变化,也不需要改变应用程序结构。





回页首


4.新的思考

故事并没有结束,在开发过程中,我们又遇到了一系列关于Web Service调用的问题。

4.1性能

系 统性能是分布式应用中的一个重要问题。许多用户都担心由Web Service技术所引入的额外开销是否会影响到产品的性能。随着技术的不断发展,Web Service引擎性能已经有了很大提高,一般来说使用Web Service的系统的性能可以满足绝大部分应用的需求。但在特定情况下,如果系统性能无法满足客户需求,我们首先需要对系统性能进行科学地分析和测定才 能定位真正的性能瓶颈。这个问题在上文简单的示例中并不难解决,只需要在Web Service调用前后加入日志代码记录调用时间即可实现。但在实际系统中,比如一个产品目录的Web Service可能提供数十种查询方法,而程序中很多组件都会依赖于该服务提供的查询功能。如果在系统中所有的地方加入性能测定代码,这个工作就变得非常 繁琐和困难。我们需要用一种更加优雅的解决方式,在增添新功能的同时并不影响系统代码或结构。

4.2缓存

在 项目实践中,一个有效的改善Web Service系统性能的方法就是利用缓存来减少Web Service的重复调用。在具体实现中我们可以采用客户端缓存和服务器端缓存等不同方式,他们具有不同的特点和适用范围。在本文例子中,我们希望实现客 户端缓存来提高系统性能。但由于Web Service业务逻辑的差别,我们希望能够为特定的Web Service提供特定的缓存策略,而且这些策略应该是能够被灵活配置的,它们不应于应用程序的逻辑代码耦合在一起。

4.3故障恢复:

对 于Web Service应用,系统的可用性也是一个需要考虑的重要问题。在运行时由于网络运行环境的复杂性和不确定性,用户希望能够对Web Service访问提供一定的故障恢复机制:比如重试或者访问备份服务(当系统在调用Web Service失败后,使用备份Web Service的服务地址来继续访问)。这些故障恢复策略应该是可配置的,对应用逻辑透明的。





回页首


5.使用AOP解决SOA应用中的Crosscutting Concern

通过对上边一系列问题的分析,读者也许会发现这些问题并不是Web Service访问的核心问题,但会影响系统中许多不同的组件。而且其中一些问题需要我们能够灵活配置不同的实现策略,因此我们不应该将处理这些问题的代码与应用代码混合。

下 面我们将利用AOP(Aspect-Oriented Programming)提供的方法来解决上述的问题。AOP是一种新兴的方法学,它最基本的概念就是关注隔离(Separation of Concern)。AOP提供了一系列的技术使得我们能够从代码中分离那些影响到许多系统模块的crosscutting concerns,并将他们模块化为Aspects。AOP的主要目的仍然是解耦,在分离关注点后,才能将关注点的变更控制一定范围内,增加程序的灵活 性,才能使得关注能够根据需求和环境作出随时调整。

我们将利用Spring所提供的AOP功能支持来解决以上问题。这里我们只简单地介绍涉及到的AOP基本概念以及实现,如果您希望更好地了解AOP的概念以及Spring AOP支持的细节请参见参考资料。

  • Joinpoint 是程序的运行点。在Spring AOP中,一个Joinpoint对应着一个方法调用。
  • Advice 定义了AOP框架在特定的Joinpoint的处理逻辑。Spring AOP框架通过interceptor方式实现了advice,并且提供了多种advice类型。其中最基本的"around advice"会在一个方法调用之前和之后被执行。

下面我们将利用Spring提供的MethodInterceptor来为Web Service调用实现我们的定义的处理逻辑。

5.1 PerformanceMonitorInterceptor

性能测量是AOP最简单的例子之一,我们可以直接利用Spring提供的实现在bean.xml中声明我们的WebServicePerformanceMonitorInterceptor。

5.2 CacheInterceptor

为 了不引入缓存策略的复杂性,我们只提供了一个利用HashMap的简单实现:它利用 Web Service的调用参数列表作为HashMap键值。在Web Service调用之前,首先检查缓存中是否拥有与现在参数列表相同的项,如果有则返回缓存的结果,否则调用Web Service并将<参数列表,结果>记录在HashMap中。在实际应用中,您应该根据具体情况来选择、构造适合Web Service的业务特性的Cache实现,也可以采用成熟的Cache实现。

在下面代码实现中有一个生成Web Service调用主键的小技巧。因为Web Service引擎要求所有调用参数必须是可序列化的,所以我们可以利用Java提供的序列化功能来实现对象的克隆。如清单10所示:


清单10:SimpleCacheInterceptor的代码示例
												
														

public class SimpleCacheInterceptor implements MethodInterceptor {
private Map cache = new HashMap();
private Object cloneObject(Object obj) throws Exception {
Object newObj = null;
if (obj != null) {
// 通过序列化/反序列化来克隆对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(obj);
out.flush();
out.close();
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
newObj = in.readObject();
}
return newObj;
}
//基于参数列表数组,生成用于HashMap的键值
public Object generateKey(Object[] args) throws Exception {
Object[] newArgs = (Object[]) cloneObject(args);
List key = Arrays.asList(newArgs);
return key;
}
//实现使用缓存技术的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
Object data = null;
Object key = null;

try {
key = generateKey(methodInvocation.getArguments());
data = cache.get(key);
} catch (Exception ex) {
logger.error("Failed to find from the cache", ex);
}

if (data == null) {
//如果Cache中没有缓存结果,调用服务执行生成用于HashMap的键值
result = methodInvocation.proceed();
try {
data = cloneObject(result);
cache.put(key, data);
} catch (Exception ex) {
logger.error("Failed to cache the result!", ex);
}
} else {
result = data;
}
return result;
}
}

5.3 FailoverInterceptor

下面代码提供了一个基于服务备份切换的故障恢复实现,在运行时,如果Interceptor检测到服务调用由于网络故障抛出异常时,它将使用备份服务的端点地址并重新调用。如清单11所示:


清单 11: SimpleFailoverInterceptor的代码示例
												
														

public class SimpleFailoverInterceptor implements MethodInterceptor { …

//实现支持端点运行时切换的invoke方法
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
try {
result = methodInvocation.proceed();
} catch (Throwable ex) {
if (isNetworkFailure(ex)) {
//切换服务端点地址
switchEndPointAddress((Stub) methodInvocation.getThis());
result = methodInvocation.proceed();
} else {
throw ex;
}
}
return result;
}
}

为了支持备份服务切换的功能,我们在WebServicePortProxyFactoryBean中为填加了配置参数"endpointAddress2",它会在创建的Web Service客户端代理对象中记录备份URL。

我 们可以在CurrencyExchangeService加入下列参数来试验SimpleFailoverInterceptor的功能。其中第一个端点 地址为一个错误的URL。在第一次调用服务时,SimpleFailoverInterceptor会侦测到网络故障的发生,并自动切换使用第二个端点地 址继续访问。如清单12所示:


清单12:配置文件种增加的属性
												
														


<property name="endpointAddress">
<value>http://localhost/wrong_endpoint_address</value>
</property>
<property name="endpointAddress2">
<value>http://services.xmethods.net:80/soap</value>
</property>

5.4配置文件和运行结果

现 在我们需要在Spring配置文件中,为所有interceptor添加定义,并描述如何为CurrencyExchangeService构建AOP Proxy。需要指出的是,我们要在interceptorName列表中声明interceptor链的调用顺序,还要将原有 CurrencyExchangeManager引用的stub对象替换为新AOP Proxy。如清单13所示:


清单13:修改后的配置文件片段
												
														


<bean id="WebServicePerformanceMonitorInterceptor"
class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
<property name="prefix">
<value>Web Service </value>
</property>
<property name="suffix">
<value></value>
</property>
</bean>
<bean id="CacheInterceptor" class="test.ws.SimpleCacheInterceptor"/>
<bean id="FailoverInterceptor" class="test.ws.SimpleFailoverInterceptor"/>
<bean id="CurrencyExchangeProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>net.xmethods.www.sd.CurrencyExchangeService_wsdl.
CurrencyExchangePortType</value>
</property>
<property name="target">
<ref local="CurrencyExchangeService"/>
</property>
<property name="interceptorNames">
<list>
<value>WebServicePerformanceMonitorInterceptor</value>
<value>CacheInterceptor</value>
<value>FailoverInterceptor</value>
</list>
</property>
</bean>
<bean id="CurrencyExchangeManager"
class="test.ws.CurrencyExchangeManagerImpl">
<property name="stub">
<ref bean="CurrencyExchangeProxy"/>
</property>
</bean>

这里我们通过为AOP 的ProxyFactoryBean为 Web Service Stub创建了一个AOP代理,并且建立了一个Interceptor链。这样在调用Web Service时,Spring框架会依次调用Interceptor执行。实例执行的顺序图将如图5所示:


图5系统运行顺序图
图5系统运行顺序图

5.5 Interceptor与JAX-RPC Handler的关系与区别

SOAP Message Handler是JAX-RPC为用户自定义Web Service处理过程提供的一种扩展机制。在处理Web Service请求/响应过程中,Web Service 引擎会根据部署描述中的定义,按照一定的次序调用Handler的处理代码。用户编写的Handler实现可以截获并修改Web Service消息和处理流程,从而实现对Web Service引擎处理行为的定制和增强。

比如,我们可以实现一个服务器端Handler,记录Web Service在受到请求消息和发出响应消息之间的时间间隔来实现对服务器端业务性能的测定。而且我们只需在部署描述中增加Handler声明即可,无需修改任何服务器端代码。

从 此可以看出,JAX-RPC Handler与我们在上文中所提供的AOP Interceptor都可以帮助我们的SOA应用程序实现关注分离(Separate Concern)的目标,在不改变应用代码的同时,增强或改变Web Service服务访问的功能。虽然我们可以利用它们实现一些类似的功能,但它们具有着不同的特点和适用范围。

JAX-RPC Handler是Web Service引擎的扩展机制。如果我们需要实现对SOAP消息进行的修改和处理,加入自定义的SOAP Header或对消息内容进行加密,Handler是我们的最佳选择。而AOP是针对对象级别的扩展机制,它更适合对应用层逻辑进行操作。

比 如,我们在上文展示的利用AOP实现的CacheInterceptor,它缓存的是Web Service调用参数和结果。而我们也可以通过JAX-RPC Handler实现一个面向SOAP消息的实现,它将缓存Web Service的请求消息和响应消息。这两个实现相比,基于AOP的实现更加简单、直观、快速、对资源消耗也比较小。而面向SOAP消息的实现则更加灵 活,对于不采用RPC方式的Web Service访问也能提供支持。

所以在具体的实践过程中,开发人员应该根据具体的需求选择合适的技术,也可以将这两种技术结合使用。





回页首


6.总结

"分而治之"的方法是人们解决复杂问题的一种常见做法。而IoC、AOP等技术都体现了这种思想。通过更好的切分程序逻辑,使得程序结构更加良好,更加富有弹性,易于变化。也使得开发人员可以更加专注于业务逻辑本身,而将一部分其他逻辑交给容器和框架进行处理。

在本文中,我们通过一个Web Service访问的实例,具体描述了SOA应用中所遇到的一系列具体问题,并描述如何利用IoC和AOP等技术进行代码重构,构建更加结构良好、灵活的SOA应用。综上所述,我们可以看到:

1使用IoC框架来实现对象的生命周期管理、配置管理和依赖管理,可以解除业务逻辑对服务调用的依赖关系;

2 使用AOP方法来解决Web Service调用中的crosscutting concerns,将为系统增加新的功能而不必更改应用程序。

3通过IoC和AOP来屏蔽Web Service访问的复杂性,使得开发人员可以更加专注于业务逻辑本身,也使得系统更加稳定和富有弹性。






回页首


下载

描述 名字 大小 下载方法
code sample code.zip 27 KB HTTP
关于下载方法的信息 Get Adobe® Reader®




回页首


参考资料





回页首


作者简介


易立 IBM 中国软件开发实验室 SOA设计中心 高级软件工程师。



赵勇 IBM 中国软件开发实验室 SOA设计中心 软件工程师。

posted @ 2006-07-28 13:29 Alex 阅读(477) | 评论 (1)编辑 收藏

key words: 真正的测试先行开发 测试驱动 Fitnesse


摘要
本文描述了如何使用开源的FitNesse来实现真正的测试先行开发过程,并让客户、需求提报工程师、开发人员、以及测试人员进行协同工作,达到需求更精准、减少需求更改、测试数据与Junit单元测试代码分离的目的,让这一切更简洁、更易于维护。
作者:Stephan Wiesner
译者:陈海青(joson)

在过去的几年里,我在开发测试工作中担任过各种角色,使用过服务器端的JavaScript,Perl,PHP,Struts,Swing以及模型驱动架构等各类技术。尽管项目不同,但是他们有一些共同点:项目结束的时间越晚,就越难以达到客户的真正需求。
每个项目都有一些需求,有的非常详细,有的却只有几页纸,这些需求一般要经历以下三个阶段:
---由客户或者承包人来书写或采纳一些官方的验收标准
---测试者试图根据需求来找出软件中不符合要求的地方
---项目开发完毕进入验收测试, 可是客户突然又提出对软件需求进行补充或变更的要求

最后一个阶段将导致项目发生变化,开发期甚至要超出最后期限,使开发人员的工作压力剧增,从而导致发生更多的错误。Bug的数量将快速增长,系统的质量将下降。听上去是不是很熟悉?

现 在让我们看一下在上述的项目开发中发生了哪些错误:客户、开发、测试人员没能协同工作;需求已经确认通过,但是在不同位置的人可能还用不同的需要未考虑。 另外,开发者一般会写一些自动测试代码,测试人员也试图进行自动化测试,但是他们往往不能充分协同,许多项目被重复测试,但另一些(经常是更困难的部分) 却没能被测试,客户也没参与到测试工作中。本文所介绍的就是通过自动测试与项目需求相结合的方式来解决这些问题的一种方案。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Stephan Wiesner ;joson(作者的blog:http://blog.matrix.org.cn/page/joson)
原文:http://www.javaworld.com/javaworld/jw-02-2006/jw-0220-fitnesse.html
Matrix:http://www.matrix.org.cn/resource/article/44/44507_FitNesse.html
关键字:FitNesse;Test

开始FitNesse

FitNesse 是一个增加了可触发Junit 测试等附加功能的wiki程序。如果这些测试能够与业务需求结合起来,就会使业务需求更加清晰。而且,测试数据的组织更有逻辑性。使用FitNesse更 重要的是学习隐含在其中的一些思想,某些部分需求可以作为测试的一部分,这意味着,这些需求是可以测试的,或者说是可以进行校验的。

利用 FitNesse,开发的工作过程可以这样描述:需求工程师使用FitNesse书写业务需求(取代了一般文档)。他试图尽可能让客户参与其中,当然这并 不是每天都能做到的。而测试者在反复研究这些文档,并从第一天起就开始提问各种问题,因为他们考虑问题的方式不同,不是在考虑“软件应该实现些什么”?而 是在考虑“怎样才能让软件出错?如何让软件中断运行?”等。开发者更象一个需求工程师,他更想知道“软件必须要完成它的功能是什么”?

测试人员可以更早地开始测试,甚至在需求没有全部完成前,而且可以把测试写进业务需求中,这些测试不仅仅成为需求的一部分,而且也将成为需求评审和验收的重要过程,并具有以下几方面的重要优点:

---客户也会被吸引来开始考虑关于测试的事情,通常他们还会参与到建立测试的工作中来(你也许会吃惊,他们怎么对这些这么感兴趣了。)
---相关规范将更详细、更周密,因为测试总比单纯的文字要准确.
---通过这种方式,可以更清晰明确地了解软件(象一个软件原形,但是功能更多),因此可以更早地考虑真实的运行场景,提供测试数据和测算结果。

最后,需求将提交给开发人员,他的工作要比以前要更容易些,因为需求都附带具体的实例,因而更贴近实际需求,因此减少了被突然改变的机会。下面,就让我们看一下这个过程是如何使开发者工作更轻松的吧。

测试先行的实现

通 常情况下,测试先行开发中最困难的是没人愿意花费那么多的时间来写测试,而更愿意去考虑如何让软件工作起来。按照上述的过程,开发者把功能测试看作合同的 一部分,他的任务要从“按要求编程,检测并修改”转变为“让测试运行起来”。现在,在确定应该做什么,何时完成,项目的定位等方面,我们有了更好的方法。

并非所有的测试都可以自动进行,并非所有的部分都可进行单元测试。我们通常将测试划分为以下几种类别:
---数据驱动的测试,需要通过单元测试来完成,如计算就是一种典型的例子.
---关键字驱动 (Keyword-driven) 测试,常自动化进行。这是一些系统测试,要求应用程序能够运行,按钮能够被点击,数据可以被键入,而输出的结果中包含规定的值。测试团队一般都能实现这种测试,但是可能开发者更容易完成这些工作。
---手工测试。这类测试适用于或者实现自动化测试的代价太昂贵并且是对出错的要求不高的情况,或者是一些基础功能(如,起始页面不能显示),可以很容易地发现错误的情况。

我是在2004年首次接触FitNesse,我曾经嘲笑过它并扬言它是不可能工作的。把测试写入wiki并自动进行测试,这个主意看起来太荒唐。但是,我错了,FitNesse真的象看起来得那样简单高效。

FitNesse的简单是从安装时就开始体现了,只要完全下载了FitNesse的发行包,并解压缩就可以了。在以下的讨论中假设解压到了c:\fitnesse目录中。

运 行C:\fitnesse 下的run.bat(linux 下运行run.sh)来启动FitNesse,FitNesse作为一个web服务运行在80端口上,当然你可以指定端口,如设定81,在运行脚本的首行 加上 –p 81 即可,这就是设置的全部工作。现在你可以用http://locahost:81来访问你的FitNesse了。

在本文,我使用windows平台的java版FitNesse,当然,这些例子也可以用于其它版本和其它平台(如Python,.net等)

一些测试

你 可以从FitNesse的在线文档提供了一些例子(可以与有名的Junit的货币用例相提并论)开始做起,这些例子非常适合学习如何使用 FitNesse,但是它们还不算是解决复杂问题的例子。因此,我将使用一些在我最近项目中的真实的用例。我已经简化了问题、代码,而不是直接取自项目 中,并写了一些说明。尽管如此,这些例子还是足够复杂,能够充分展示FitNesse简洁直观的威力
现在,假设我们正在从事一个为一个大型的保险公司开发开发项目,这是一个复杂的基于java的企业级应用。产品将涵盖公司的全部业务,包括客户和合同管理,以及支付业务,在我们的例子里,我们只关注其中的极小的一部分。

在瑞士,父母有权利获得给每个孩子的儿童津贴,经过确认家庭环境后,将根据情况得到相应的津贴。以下是这个需求的简化版本,我们将从一个传统的需求开始,并把它迁移到FitNesse中。

儿童津贴的发放存在几种状态。有关条款规定在孩子出生月份的第一天开始生效,在孩子达到规定年龄、就业或死亡的所在月份的最后一天失效。

按规定在到了12岁所在月份的第一天,就可以领到190瑞士法郎(瑞士官方货币)的补贴。
根据父母全职或兼职工作等情况,依据不同的条款,如表1所示进行分类.

image
图 1. 儿童津贴资格表.

就业率要根据工作合同来计算,合同需要被确认是有效的,如果有终止日期,还要确认是否在“有效期”内。表2显示了根据孩子年龄的不同等条件,父母可以得到的补贴数量。

image
图 2. 年龄相关条款

正常情况下,每两年要对款项进行有规律地调整。

第一次看到这些需求,也许会认为这些需求是很明确的,开发人员有能力轻松地实现它,但是,我们真的确定了这些边界条件了吗?该如何测试呢?

边界条件
边界条件是指达到、超过、低于某个给定的输入输出之的情况。经验表明,在边界条件附近的测试比其他测试更有价值,典型的例子就是著名的“只执行一次”的条件跳转和程序段.


场景对于查找例外条件和边界条件有很大帮助,因为这是得到关于商业规则的行业经验的好方法。

场景

对 大部分项目而言,是由需求工程师来提供给开发人员的需求规格说明书,然后开始学习了解需求,提问,然后开始设计、编码、测试。再后来,开发人员把软件提供 给测试团队,在经过几番改写和修补后,最后交付给客户(客户也许更喜欢考虑提出一些意外的需求)。如果采用了FitNesse,将不会改变这一过程,只是 要增加一些测试用例、场景,和测试意愿。

场景在启动测试过程中具有特别的的帮助。以下是一些例子,在回答将支付多少儿童津贴等为题上,就要分清多种情况:
---玛丽亚是单亲家庭,她又两个儿子 (鲍勃, 2岁,  彼得 15岁) ,从事兼职秘书工作 (每周工作20个小时).
---玛丽亚失业了,后来她找到一个每周工作10个小时的商店助手和每周工作5个小时的临时照顾幼儿的工作
---保罗和拉瑞(Lara)有一个17岁的女儿丽莎(Lisa),她是一个残疾人,还有一个儿子弗兰克,18岁,还在上大学。
即使是仅谈论一下这些场景,也有助于测试工作的开展,哪怕是软件中手工运行这些例子,也几乎可以确信会发现程序的遗漏,难道因为没有原型就不做这些吗?为什么不做呢?

关键字驱动(Keyword-driven)的测试

关 键字驱动的测试常用于模拟原型,FirNesse允许定义关键字驱动的测试类型(详见“完全的数据驱动自动化测试”(“Totally Data-Driven Automated Testing”))甚至在没有软件支持的情况下(不能自动运行),运行基于关键字驱动的测试也会很有好处。

image
图 3. ActionFixture 测试

图3 展示的是关键自驱动测试的示例。第一列描述的是来自FitNesse的关键字,第二列描述的是java类的方法(开发者根据这里的描述,在Java程序使 用这些名字来命名)。第三列描述的是来自第二列的方法所产生的数据。最后一行演示了测试失败的情形(测试通过为绿色)。正如你所看见的那样,找出错误是很 容易的。

这会使人很容易甚至很乐意去建立这些测试。测试者不必具备编程技能,经过简短介绍后,客户也会很容易的读懂。
用这种方法定义的测试,几乎就是业务需求,并具备传统测试用例没有的重要优势, 甚至不必去自动生成也有很多优势:
---测试内容很容易获得,不必特别训练就可以轻松完成。
---与其改变需求,到不如先改变测试,这样反而更直观(这与使用某些工具的情况完全不同)
---在确定新需求或修改需求时,可以随时运行测试来看看需要修正哪些内容。

为自动运行测试,需要建立一个简单的软件层,用于代表真实的测试代码。这些测试在自动化测试GUI操作时特别有用。我曾开发过一个基于HTTPUnit的自动测试web页

这里就是FitNesse自动运行的代码:
package stephanwiesner.javaworld;

import fit.ColumnFixture;

public class ChildAllowanceFixture extends ColumnFixture
{
   public void personButton() {
      System.out.println("pressing person button");
   }
   public void securityNumber(int number) {
      System.out.println("entering securityNumber " + number);
   }
   public int childAllowance() {
      System.out.println("calculating child allowance");
      return 190;  
   }
   [...]
  }


在FitNesse中的运行结果如图4所示,很有助于调试。与Junit相比,Junit抑制了调试信息的输出,但我认为在自动Web测试中这些输出绝对是有必要的。

image
图 4. 标准输出

在测试基于Web的应用时,错误也将被包含在FitNesse页面里,并被显示出来,这是的条施工作比使用log文件更容易。

数据驱动的测试

关 键字驱动的测试是GUI自动测试的最佳选择,同样,数据驱动测试也是各类计算类项的首选测试方式。如果你曾经写过单元测试,那么测试中什么是最令人厌倦的 呢?也许,你会认为是数据。你的测试要进行数据填充,但是数据经常改变,使维护工作变成了可怕的恶梦,测试不同的组合,需要不同的数据,这也许会使你的测 试工作变得日益复杂,变成了”丑陋的怪兽”。

使用数据驱动的测试,数据将从代码中分离。一般情况下,建立几种类型的表格,存储为CSV文件,由测试代码来读取。如果再使用FitNesse,我们就可以更便利地进行存储、改变,以及访问这些测试数据。

现 在,让我们把图2种的表格转变成一个测试吧。首先,将表格拷贝到Excel中,进行修改,然后使用FitNesse的EXCEL导入功能输入。这是一个有 用的功能,因为在FitNesse中,表越大越难以维护。输入的结果如图5所示。每一行表示一个测试,有问号的列表示需要写Java方法,没有问号的行表 示输入的测试数据。

image
图 5. 数据驱动测试表Data-driven test table.

再 重复一次,在阅读了业务需求后,这种测试就非常容易理解了。如果你曾经担心如何在你的测试用例里的进行等值测试的话,那就看看这里吧。最后一行大概是最重 要的,因为需求并没有说明如何处理非法数据,或者处理20岁以上的孩子但仍在上学等情况,这里的关键字“error”表明需要处理异常了。

常见问题解答

这里讨论使用FitNesse在需求、测试和开发过程中遇到的未解决的问题的解决办法,以下是部分答案。

手工测试会遇到什么问题?
手 工测试者最常做的就是重复的手工回归测试,不但代价昂贵,而且容易出错。自动化测试可以减少但不能消除这种工作的工作量。测试者可以有更多的时间去从事更 有趣的测试,例如在应用程序在复杂的场景下的不同处理等,尽管测试就是要花费更长的时间找到错误,但比不意味着因此而要付出更高的代价.

用户验收测试不再有必要了吗?
也许有人会这样认为。如果客户在需求中定义了所有的测试,当所有测试测试都变绿(通过了)时,软件就“完工”了。我的实践经验是,客户仍会提出附加的需求来,尽管如此,他们的需求变化会更少,并更容易地结合实际变化进行分类(同时也会带来追加的款项)。

在FitNesse书写比Word documents更有优势吗?
甚至在没用测试的情况下,使用wiki 来写业务需求也能带给你更多好处。文档可以自动用在Web 服务器上,可以被多人并发访问,可以全文检索,可以被链接。我发现还有以下两个特点:
---更容易建立数据字典,并链接上明细内容。更重要的是,具有查找被引用位置("where-used")的功能。 如果修改某个细目,就会立即找到是否被引用过,以避免发生冲突.
---由于Bugzilla (或者 Jira,二者均为缺陷跟踪系统,译者注) 是基于Web的, 我们可以方便地在两个系统之间互相链接文档(artifacts) (bugs, 任务, 讨论等),便于提高工作效率。

FitNesse 允许按照确定的顺序运行测试,而JUnit 禁止这样做,你有何体验?
使 用JUnit,需要对同一个对象分别建立许多setUp() 和tearDown() 方法. 在某些情况下,可以在一个FitNesse页面里放上所有的相关测试,并能够给定执行顺序. 因此,第一个测试可以建立一个”人”,下一个测试可以对它进行修改,最后一个可以进行删除操作.如果第一个测试失败了,而且那个”人”没有被建立,这该怎 么办?没问题,因为第一个测试失败了,随意在把它修正好之前,我是不会继续的. 我一般不会经常使用这种顺序执行方式,但有时它会给我带来很大方便.

FitNesse 可以用于大项目吗?
大 部分开发者会明白,FitNesse的优势就是简单易用。但没有技术背景的人,如果不使用自动检查特性以及所见即所得风格的编辑器,工作起来会很困难。一 个简单的例子就是FitNesse 只有允许使用三种类型的标题, 但现在我们需要五种,这就需要我们就可以对FitNesse 进行扩充.

FitNesse 不包含输出为类似PDF文件的功能。我曾经使用过长度达到60页的Word文档,尽管编辑起来有些困难,但是可以轻松打印出来. 但是当我把它转到FitNesse里后,我就难以打印了,因为被分解为几个不同的页面,最后不得不通过编程解决.

FitNesse 具有一个简单的机制可以显示那些文档被修改了,被谁修改了,但不能显示修改了什么, 权限管理有局限。使用FitNesse重开一个项目要比改写一个运行的项目更容易.对中小型项目而言,我极力推荐FitNesse. 大项目也可以使用,但是进行更仔细的评估后再实施会更好.

结论
我认为FitNesse是需求分析的好工具,对测试优先开发很有帮助,它是很有利用价值的。我被迫花费几个周来适应FitNesse及其处理过程,我的项目目前尚未完成,但是我已经看到了需求质量和软件质量均得到明显提高,而对测试者而言,工作起来会获得更多的乐趣 。

关于作者
Stephan Wiesner 是瑞士卢赛恩L&ouml;wenfels Partner AG的测试管理人员。他是“Learning Jakarta Struts 1.2”(伽利略出版社,德语版,ISBN:3898426130;英语版:ISBN: 190481154X)的作者,” Java der Code”(由德国MITP出版社发行,ISBN: 3826614623)。另外还在德文杂志上写过大量的与java相关的文章。他有着丰富的开发和测试经验,是一位ISTQB认证的测试管理员和认证的 Scrum主管(ScrumMaster,Scrum是一种敏捷开发方式,译者注)。

陈海青(joson),本文译者,生活在中国的山东省烟台市,先后从事软件开发、数据库管理、系统管理等工作,2001年获得高级程序员资格。

相关资源
---Matrix-Java和中间件社区:http://www.matrix.org.cn
---“完全的数据驱动自动测试”("Totally Data-Driven Automated Testing" ),作者Keith Zambelich ,详细阐述了数据驱动和关键字驱动的测试: http://www.sqa-test.com/w_paper1.html
---以下站点提供了更多的关于优化FitNesse的内容: http://fitnesse.testmanager.info
---官方的FitNesse网站,经常更新: http://fitnesse.org/
---下在完整的FitNesse发行版: http://fitnesse.org/FitNesse.DownLoad
---部分资料可以在译者的站点找到:http://www.chq.name/

作者的其他文章

其他相关文章

posted @ 2006-07-28 11:43 Alex 阅读(2960) | 评论 (0)编辑 收藏

key words: SOA

come from here

2004512据业内分析,面向服务的架构(SOA)的基本概念--重用性和互用性--已经提出了大约20年。那么SOA具有哪些新的特色呢?为什么其他技术和标准都惨遭失败,而SOA却能够成功呢?BEA的首席信息官Rhonda Hocker回答了有关SOA如何发挥IT潜力方面的问题。


问:您认为SOA的哪些方面在其成功中起到了至关重要的作用?

答:第一点就是灵活性。就长期以来在广大公司中的知名度而言,SOA可能名列IT架构第一,而内容一直在变化。一个SOA实质上就是一套松散耦合的服务。在必要的情况下,每一项服务都可以进行构造和替换,而相关的费用很低。松散耦合甚至还可以让架构适应一些改变,并不像传统的紧耦合架构表现得那么脆弱;在一个SOA中,您能够使用一种服务替换另一种服务,无需考虑下列技术:接口问题,它是否在Web服务和XML的通用标准中已经定义。这就是通过互用性所体现出来的灵活性。灵活性还表现为利用现有资产、遗留应用程序和数据库的能力,通过将他们扩展到SOA中,而非进行替换,使其成为整个企业解决方案的组成部分。最终结果就是具备快速高效发展的能力,换句话说,就是按照业务需求"有机地"进行适应。这就是真正的新特色。

第二点就是"业务相关性"SOA就是最终表现为对业务人员意义重大这一层面上的IT架构。如果您也相信IT架构的核心问题就是业务和IT专家的联盟和协作,那么这就是关键。今天的SOA服务能够完成映射为业务流程活动的各部分工作:例如,想起一个命名为"更新客户订单状态"的服务。这种服务与那些能够参与创造和使用这些服务定义新流程的业务分析人员密切相关,因而能够形成那种服务驱动型的企业。因为Web服务已将其大部分技术作了摘要,所以几乎不再需要技术说明。公司和IT业能够将关注的重点转移到业务逻辑和通讯上。他们最终共享"服务"的通用语言。也就是说,是真正的新特色,在IT架构的交付中具有深刻的蕴涵。

问:您认为什么是SOA成功的最大障碍呢?
答:SOA是新型IT架构的蓝图。由于总是伴随着大的变化发生,因此最大的障碍就是组织,而非技术。主要包括以下几个方面:

管理:共享服务是SOA方法的核心。这种快速组装应用或编排流程的能力是在一些现有的能被共享的服务的基础上实现的。共享资源需要进行管理。

开发文化:切换到SOA要求开发风格发生极大的变化。大多数开发人员仍然适应那种将每一个应用程序作为一个独立的问题解决的方式。目前可以重用的代码还非常少。在SOA中,开发人员需要编写自己的应用程序,同时还要留意要让自己编写的代码可以重用,不仅包括使用现有代码,还要包括计划在未来的应用程序中重用他们的代码。

业务流程架构技能:SOA方法让公司和IT合作伙伴能够在业务流程的创造中完成更高效率的协同工作。他们的成功将有赖于其实施业务流程架构的技能。也需要他们灵活应对业务流程并且要将自己看作是业务流程架构设计师。

这些方面的确都具有极大的挑战性,但是便于管理。最后,那些善于管理,IT和业务人员知道如何有效合作,流程和架构技能受到重视的公司将会从自身的SOA中得到更多的回报。这几乎是最好的方式,它有助于解决IT 问题。

问:现在的SOA与以前的集成/连通标准,如CORBA有哪些不同之处?

答:很好,我将会用案例说明以上我所描述的两个SOA有别于其他标准的优点。问题是为什么分布式架构中的CORBA和其他方法无法表现出这些优点呢?
高度概括性的回答就是:CORBASOA具有更大的技术难度,在其执行过程中需要强大的技能和知识支持。那些技能十分贫乏,如果没有真正的CORBA标准将会无济于事。而比较而言,SOA简单,基于真正通用的标准。这就确保了构造它们的这些技能是广泛可用的。

更为详细的回答请您查看他们的架构方法和原理的基本差别。

在一个SOA中,分布式资产就是"粗粒度"服务,它可以完成一些非常有用的功能,如"更新客户订单状态"。使用CORBA,分布式资产就是一些对象,每个对象都拥有自己的属性和方法。例如,订单对象具有"状态"属性和"更新"方法。这样对架构设计师而言是相当繁琐的,它需要具有很高水平的知识和技能。在这种细粒度级别之下很难保证一致性。而使用SOA,会控制少和动力少,却易于管理。这种方法在技术上并不是非常强大,但在IT成功方面的组织和人员角色上却体现出了相当的敏捷度。

问:为什么SOA会成功而CORBA会导致失败呢?

答:SOA会成功主要取决于合作伙伴的帮助。就重用效率或企业广泛一致性而言,由中心IT组织独立推动的架构都无法在长时间的运行中获得成功。我认为使用SOA,我们将会拥有第一个业务合作伙伴帮助推动企业架构的实例。这不是因为他们喜欢架构本身,而是因为他们的支持是基于SOA的业务相关性的,很快就可以从结果上看出,开发生命周期改变了它的重点,由原来较长的交付周期应用程序的交付转变成小单元代码--服务的交付和集成。持续的结果将使业务合作伙伴效忠于这种方法。

问:JavaSOA的潜在成功中起到了什么作用?

答:Java作为实现服务的最流行的编程标准,是非常重要的。Java社区的规模和技能保证大量高质量的技能可以用于构造SOA。这就是Java实现帮助SOA成功的方式。也就是说,Java只是实现服务的一种方式。没有一个大的IT组织会只运作一个单一的编程标准。有利的方面就是使用SOA您不需要一个单一的编程标准。服务范例的定义只能以Web服务和XML的接口标准识别出这种内在的多相性并设立需求。


posted @ 2006-07-28 11:29 Alex 阅读(1135) | 评论 (0)编辑 收藏

key words:soa
转自这里

最近,SOA成为跨技术平台(特别是J2EE和.Net)软件开发中的热门话题。然而,如果我们比较一下围绕着SOA的宣传和90年代后期EJB和服务件 的宣传,你会发现这没有什么区别。1998年,EJB带领互联网的潮流并推翻了以CORBA的统治和由PB/Oracle Forms和其他主导的CS架构标准。SOA,作为一种新技术的术语,还不具有那么大的破坏性。SOA只是一种想法/概念和一组构建应用功能的最佳实践。 相反地,J2EE是一套完整地开发技术,可以用来设计所有的东西。

  我对SOA的主要关注在于企业级Java应用通用的问题:复杂性。 次要关注的是SOA通常作为一种解决方案被用来跨越J2EE应用各层,虽然这好像没有什么意义。本文提取出SOA的基本元素并介绍他们。一旦我们理解这 些,就可以理解SOA系统中的更复杂的组件了。最后,我们可以了解一下SOA给J2EE应用带来的实际价值,同时并不增加无用的复杂性。
本文分为个部分:首先,提出了我对SOA作为一种标准参考点的定义。其次,检查那些主要的软件工种问题通过SOA可以解决而不是用SOA来检查。再次,会给出基于复杂需求的SOA的建议分类。最后,给出三种主要SOA分类的建议实现。

  SOA是什么?

  SOA有很多定义。下面是我的定义:
  SOA是宏级别的应用到应用架构级的设计模式:
  1、可选地暴露应用的功能作为一组离散的组件。
  2、使这些组件能被用来构建更复杂的组件和应用。
  3、仅包含基于消息的组件内部通讯。

  我还遗漏了什么呢?还有一些方面,包括:
  1、安全性
  2、事务
  3、状态或无状态会话
  4、消息无数据
  5、 消息特性
  6、 消息协议
  7、 消息内容
  8、  具体技术实现

  这些方面也是重要的,但不是主要的。我的定义提取了SOA的核心规则,但没有抛弃概念本身。
注意我在定义中引用了设计模式。我认为这是关键。SOA不是什么新技术,事实上,其最吸引人的一个地方是可以利用现有的技术并使其泛出新的光芒。对我来说,SOA更像是一幅蓝图,一组最佳实践,或者说是一个定义下一代的软件应用应该如何设计和实现的规范。

  基础SOA方法

  从上面的定义,我们应该可以标识出组成SOA应用的必须提供的软件服务的最小集合。简洁地说,这些服务是:

  1、消息层,允许消息通过特定的协议传输和接收。用SOA的说法,这一层称为企业服务母线或简写为ESB。
  2、一个组件模型,如应用必须遵循的发送和接收来消息母线的消息的最小约定。

  取决于你自己的业务需求,这两种服务可以极度的扩大,但在核心来说,消息层和通用组件模型就代表了SOA。

   注意,我没有在SOA的定义中包含自动定位和发现服务(在大部分JEE场景中,这是很有杀伤力的)。在UDDI(通用描述/发现/集成协议)后的原始想 法是认为企业最终会使用软件服务(通过一个大的基于元数据搜索服务仓库)来购买和销售。这个美梦至少也得十年后,也许永远不会实现,因为人们是需要做的实 际的业务而不是软件。

  JEE应用不需要自动发现服务,例如登录或支付服务,这些服务应该在初始化时设置。不要误导我,如果这些服务的实现不应该硬编码到应用中,那么你也不需要SOA来解决这些问题了。

  下一节,我们会来考虑一下究竟需要SOA来解决什么,或者他能替代什么。

  为什么要SOA?

   最近的两拨企业级软件开发的主浪潮是C/S架构和多层架构。虽然多层架构提供了C/S架构中布署/平台支持/性能/伸缩性上更好的效果,但两者都没有解 决一个关键的企业级计算机领域的软件工程问题:如何重用软件功能。作为软件开发人员和架构师,我们始终没有完全解决软件重用的问题。再往下看,你会看到我 也不认为SOA能解决这个问题。然而,我认为软件重用是SOA出现的最重要原因(至少在JEE应用中是这样)。

  其他SOA使用现有的Jini和风格计算。基于Jini环境的特点如下:
  1、自动发现组件/服务
  2、自愈的

   然而,这些特性并没有与JEE应用等同的重要性。使用JDBC配置数据库的位置只需要一次。我期望数据库来提供容错和除错功能,而且我不需要JEE应用 来尝试当产品实例当机时自动发现其他的数据库实例。另一方面,对一个有2000个工作站的办公室来说自动发现一个彩色打印机是一件好事,这也是符合 Jini硬件的一个关键好处。

  平等地主,在一个真实的全球网格计算环境中,自动发现和枚举计算资源来解决问题是基础框架的关键部分,但这不是一个JEE环境,那儿硬件预先计算的以便在定义用户数据和服务性能之间平衡。

  我的观点是,SOA对不同的需求需要不同对待。在本文中,我只关心JEE架构方面的SOA,而我认为这意味着功能重用。其他从JEE观点来看SOA的优点还有:
  1、松耦合的组件,这是软件设计中重要的部分
  2、引入ESB作为消息层意味着强制“面向接口编程,而不是实现”
  3、异步消息增加了应用的伸缩性

  让我们通过问三个特定的问题来看一下软件重用中更细节的问题:
  1、为什么重用软件是重要的?
  2、SOA是如何提出解决软件重用问题的?
  3、是否SOA的允诺能够使软件重用应用到现实中?

  首先,软件重用是重要的原因如下:
  1、时间和花费上的效率—能够重用已经的组件来满足陈述的业务需求将节省大量的时间和金钱。
  2、重要的特性包括但不限于如稳定性/性能/可管理性/文档/可配置性。因为一个组件被重用的次数越多,对这个组件的投资也越多,他的优势也越多。
  3、 良好设计的可重用框架无论在哪里被使用都拥有正面的效果,而且你愿意的话可以封装更好的想法来解决通用问题。

   因此我们需要重用性。那么最简单的方法是什么呢?就是打包软件作为一组良好定义的组件来满足离散的功能需求。然后,如果其他应用需要相同的组件,他就可 以重用了。还有些细节需要考虑,如如何配置,但这些细节已经偏离了主题:重用任何语言编写的代码,那些代码必须被设计成一组离散的组件或重构为集合。

  可以参考我在JavaWorld上的第一篇文章,“节省时间的框架”(2000.9),有更多细节善于JEE项目的软件重用。

   其次,SOA是如何解决软件重用的问题呢?是通过基于组件模型来构建和引入一个重要的强制约定:组件间的通讯要通过下发到ESB的消息来进行,而这就确 保了松耦合。实际上,最广泛布署的SOA实现—Web services可以通过使消息层技术中性来缝合用不同语言开发的组件。

  最 后,SOA对软件重用的允诺真有实际意义吗?不,我想念如果SOA在1945(大概是和ENIAC同时代吧)被发明的话确实可以解决软件重用的问题。但没 有,现存的大量代码是用不同的开发语言编写的,有COBOL/C/C++http://java.chinaitlab.com/C#和其他语言。这些代 码没有作为离散的组件来编写,因此也没有SOA魔法来解决。事实上,我认为有大量的SOA项目的工作是花费在重构相同的代码库。

  现在,让我们来看一下对于JEE应用SOA可以解决的一些问题。

  SOA缺点

  SOA缺点包括下面三方面:
  1、 SOA自身的缺点,主要当前还没有成熟的实现
  2、 SOA的复杂性
  3、  厂商对SOA在更广泛的JEE产品和方案中的位置

  那么我们就心批判的眼光来看一下:

  ·并没有像JEE规范那样有自己的正式规范。虽然有一个发布的规范,但那个太复杂了并且没有遵循80:20法则(80%的应用需要简单的SOA,只有20%的应用需要更强大而复杂的功能)
  ·有状态会话依然存在广泛争议而且现在还没有被SOA的缺省实现(Web services)所解决。而无状态会话已经是完全支持了。
  ·由于缺省正式或推荐的规范,Web services已经成为许多人眼里SOA的代名词了,但Web services通常是过于强大了。
  ·SOA增加了复杂性。可能你更喜欢硬编码和紧耦合,而不需要XML配置文件来运行简单的应用。
   ·SOA兼容的应用对本身来说没有什么意义。其商业价值来自于能够提供离散的功能块通过SOA被用于其他的应用和模块。例如,如果你对订单的较验规则是 通过JSP页面中的Java代码来实现的,那么你还需要重构代码将其放到服务端对象中以便于SOA调用—但很多厂商并没有提及这一点。
  ·在某些情况下,厂商将SOA作为网页应用框架的替代者!我认为,WAF是SOA定义功能中的消费者,只是作为一种补充,而不存在竟争关系。
  · 与厂商提供的相反,一些应用根本不需要SOA而只需要简单使用MVC框架就可以了。这很短视吗?我不这么认为,即使SOA的特性是需要的,在上面的情况下,最重要的部分是用来服务于企业服务总线的良好定义的业务逻辑层,而不是ESB自身。

  虽然我不认为SOA是一颗解决现有和新建应用中问题的银弹,便我相信SOA在他相应的位置上还是有其内在的价值的。现在让我们来看一下在应用中增加有效的SOA解决方案是如何提供体现其商业价值的。

  建议的SOA分类

   现在,你应该对我保持事物的简单性的热忱表示感激吧。但我本质上并不是简单论者,我是一个实用主义者。对软件项目来说,我认为实用主义是一方面要平衡项 目的商业和实际价值,另一方面是使用软件设计上的最佳实践。简单的说,就是在我们现有条件下构建我们所能创建的最好的系统。

  一个实用主义的好例子来自于民间的工程历史。在修铁路时常修木桥,而我们知道用铁桥会更好。当铁路公司的股东想使用铁路尽快开工而且初始投资要有限制时,他就是这是最好的工程方案了。是否听起来耳熟?同样的原则可以应用于软件工程。

  根据实用主义的精神,我建议将SOA分为三个级别:简单/中等/复杂,衡量标准是需要满足的业务需求。如果你需要简单的SOA,那么不要浪费时间和金钱在复杂的SOA上。

  级别1:简单的SOA

  样例实现:
  1、使用自己的POJO队列实现来发送和接收消息。
  2、带有MDB(消息驱动Bean)的JMS队列/主题作为消息的消费者。

  这里涵盖的关键SOA概念有:
  1、企业服务总线
  2、生产者/消费者的组件模型。

resized image


  Figure 1. Schematic illustrating the core components of the simple SOA. Click on thumbnail to view full-sized image.

  级别2:中等的SOA

  样例实现:
  1、带有MDB的JMS队列/主题作为消息的消费者,并附加其他特性如安全性/事务/JMS元数据属性等
  2、 Web services,例如Apache Axis

  这里涵盖的关键SOA概念在包含简单SOA外还有:
  1、用来增加健壮性和可靠性的错误/重试队列。
  2、引入XML作为消息的有效负载内容来代替序列化Java对象,从而支持其他技术如.Net

resized image


   Figure 2. Schematic illustrating the core components of the medium-complexity SOA. Click on thumbnail to view full-sized image.

  级别3:复杂的SOA

  样例实现:
  1、带有MDB的JMS队列/主题作为消息的消费者,并附加其他特性如安全性/事务/JMS元数据属性等
  2、Web services
  3、厂商/标准相关的SOA兼容工具包(如专门的金融服务)

  这里涵盖的关键SOA概念在包含中等SOA外还有:
  1、良好定义而且严格的组件模型(例如Java业务集成/服务组件架构及其他)
  2、增强的厂商支持,如可插拔的新生产者/消费者组件创建
  3、 详细枚举特定SOA实现上可用服务的组件注册表。

resized image


  Figure 3. Schematic illustrating the core components of the complex SOA. Click on thumbnail to view full-sized image.

  小结

   目前SOA是作为一种架构体现,也将会成为与C/S或多层架构一样存在。但是,他目前还是不够成熟而且只是作为厂商利用的工具。我对SOA的建议是,从 简单的做起并保持SOA尽可能的简单。不要将SOA与Web services等同起来,也不要强制使用SOA的设计模式在JEE应用的各层上,告别是网页层。

  那么我会为大多数JEE应用推荐哪 一个SOA实现呢?级别2上的SOA实现如带有MDB的JMS队列作为消费者,而POJO或无状态的会话Bean作为消息生产者。当然,如果你确信你需要 集成非Java应用,那么考虑一下Web services实现。还要考虑你现在采用的解决方案在以后要有足够的扩展空间。虽然预测多久通常都有争议的,但我还是建议最远不超过36个月。如果你预 见到那个时间段内有额外的SOA需求,那么现在就来构建吧。

  关于作者

  Humphrey Sheil是英国服务业企业级应用供应商CedaropenAccounts的首席技术架构师。特别擅长于集成领域。拥有爱尔兰都柏林大学的计算机科学硕士学位。点击这里进入他的博客。

  资源

  ·Jini技术,最早的SOA实现之一:http://www.jini.org
  ·JEE规范:http://java.sun.com/j2ee/download.html#platformspec
  ·学习.Net的的入门点:http://msdn2.microsoft.com/en-us/library/ms310245(en-us,MSDN.10).aspx
  ·http://www.uddi.org UDDI协议
  ·创建SOA的准备:http://weblog.infoworld.com/techwatch/archives/004644.html
   ·Java业务集成,用来为Java应用(特别指基于SOA的应用)定义组件模型的规范。这更正规些,因此允许厂商根据标准提供工具和框架以实现最终的 交互性。目前许多失败就是因为缺少这些支持:http://www.jcp.org/en/jsr/detail?id=208
  ·http://ws.apache.org/axis/ 开源的JEE网页服务实现- Apache Axis

  版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
  原文:http://www.javaworld.com/
  译文:http://www.matrix.org.cn/

posted @ 2006-07-28 11:03 Alex 阅读(344) | 评论 (0)编辑 收藏

key words: 表连接
内连接

select statement from table1 join table2 on table1.fieled1 = table2.field2

select statement from table1 ,table2 where table1.field1 = table2.field2


左连接

select .....table1 left outer join table2 on table1....= table2.field

select .... table1 ,table2 where table1.field1= table2.field(+)

合并查询
union:获得并集,并自动去掉重复行,并且会以第一列的结果进行排序
union all: 获得并集,但不去掉重复行,也不排序

子查询:
对于多行子查询,必须要用多行运算符(IN,NOT IN,EXISTS,NOT EXISTS,ALL,ANY)

posted @ 2006-07-27 21:21 Alex 阅读(948) | 评论 (0)编辑 收藏

key words: 数据库复制 
come from here

一、背景
  DB2 联合数据库是分布式数据库管理的特殊形式。在联合数据库系统中,可以通过一个 SQL 命令来发出对多个数据源的命令请求。DB2 与非 DB2 数据库之间进行复制之前,首先需要保证非 DB2 数据源可以被 DB2 ESE Version 8 federated database访问。对于DB2 Replication Version 8 所需的联合数据库功能可以在现有发布的 DB2 ESE Version 8 和 DB2 Connect Enterprise Edition Version 8 中提供。

  "SQL复制"又称为"DB2复制",是为 DB2 开发的两种数据复制类型中的一种,它是通过 SQL 进行的复制。在这里简单提一下,DB2 复制中的另一种"Q复制"是通过 Websphere MQ 消息队列进行的。在进行 SQL 复制时,Capture 程序读取 DB2 恢复日志以获取对指定源表的更改。该程序将更改保存到传输表格中,也称作变化数据表(changed data table),Apply 程序并行读取更改并应用于目标事务,见图1。




  图1:SQL复制的结构

  WebSphere II 全球信息集成复制,通过不同数据库之间的复制,有效的利用了数据资源,为提高效率提供了良好的平台。

  DB2 与非 DB2 数据库之间的复制需要用到 WebSphere II。本文力争通过复制实例让读者对不同数据库之间的复制有一个整体的概念。

二、动机

  商业上出于很多原因使用复制,可以归纳为:

分散:把数据分散到各个位置;
整合:把其他位置的数据联合起来;
交换:与其他位置进行双向的数据交换;
灵活应用:对上面提到的方式进行一些改变或者结合。

   联合 (Federated) 数据库系统的诞生,利用了现有的数据资源,把不同商业数据库软件的数据整合到一起,很大程度的提高了数据利用率。联合数据库可以用一个SQL语句对分布在 不同地点的多种数据源发出请求。联合数据库系统可以把本地表和远程数据源联接起来,就像数据都在本地一样,并且可以通过对数据源进行分布请求来提高数据源 处理能力,还可以通过在联合服务器处理部分分布请求来补充数据源的 SQL 限制。

  联合数据库具有两个与其他应用服务器不同的特点:

联合服务器可以被配置为接收全部或接收部分针对数据源的请求。联合服务器把这些请求分散到数据源。
与其他应用服务器一样,一个联合服务器用 DRDA 通信协议(例如 SNA 和 TCP/IP)与 DB2 家族实例通信。然而,与其他应用服务器不同的是,与非 DB2 家族实例通信时用其他协议。

  图2描述了联合数据库系统的设置流程:


  图2:联合数据库系统的设置流程


   WebSphere II 包括两种包装器(Wrapper),一种为关系型包装器,负责DB2 UDB, Informix, Oracle, Microsoft SQL Server, Sybase, ODBC, OLE DB 等数据的复制。另一种为非关系型包装器,负责 Flatfile, Excel, XML 等非关系型数据的复制。

  包装器定义了一个负责本地数据库与远程数据库通信的库。包装器执 行很多任务,比如:它可以连接到数据源,包装器应用了数据源的标准连接API。它还可以给数据源提交请求。联合数据库系统可以操作远程联合系统的表。远程 表在本地联合数据库中虚拟存在,客户应用程序可以操作这些虚拟表,但是它们真正存在于远端数据库中。每个远程虚拟数据库,把联合数据库当作数据库客户端, 他们只对数据库客户端的请求有回应。因此联合数据库需要下载各种远程数据库的客户端。

一个联合系统的构造,需要一个作为联合服务器的 DB2 实例,一个作为联合数据库的数据库,一个或多个数据源,和可以存取数据库和数据源的客户(用户和应用)。如果要完成远程不同数据库之间的复制,还需要应用DB2的数据复制功能。

IBM DB2 复制(在一些平台上被称为数据传播)是一个从一个位置到另一个位置复制 DB2 和/或其他数据库厂商数据的强大的,灵活的工具。IBM的复制支持数据转换,数据连接和过滤数据。可以在不同的平台之间搬运数据,也可以把数据分散到不同 的地点或从分散的地方把数据聚合到一个地方。可以在不同的系统之间交换数据。

  IBM复制由四个主要部分组成:管理 (Administrator),Capture,Apply,警报监视器 (Alert Monitor)。

  管理的部分主要通过复制中心的图形界面来实现。通过复制中心可以定义复制源,定义从数据源到目标数据的地图。它也用来管理和监控本地和远程的 Capture 和 Apply 进程。从图3中可以看出复制中心图形界面对其他几个部分的支持关系。


  图3:复制中心的应用


   在源数据服务器上运行的 Capture 程序可以获取 DB2 源数据表中的变化。DB2 的源数据服务器可以为 DB2 在 z/os, os/390 上的版本 6,7和8,也可以是 iseries 在 os/400 V5R2,或 DB2 在 Windows, Unix 系统中的版本 8。当定义数据源的时候会自动生成相应的触发器 (Triggers),可以用来捕获数据源的变化。要复制的数据可以在 Capture 进程中通过选择列来进行过滤。被捕获的更改信息首先存放到本地的源数据所在的数据库的表中并且当更改应用到目标数据中之后会自动删除。

   当对源表进行改动时,DB2 把相关的记录写入日志。这些日志服务于数据库发现和复制。Capture 程序通过数据库自动连接并获取日志记录。每个源表都有相应的 CD (change data) 表来获取数据的变化。当定义一个复制数据源时,复制中心自动生成 CD 表。

对于 Apply 部分,捕获的改变通过 Apply 程序应用到目标表中。Apply 程序可以在任何服务器上运行并且必须对所用到的源服务器和目标服务器都有连通性。数据可以通过列,行进行过滤,可以进行合并(例如通过视图),也可以在 Apply 过程中通过 SQL 表达式进行传送。DB2 与其他相关的数据间进行复制的时候,必须通过联合数据库系统来进行昵称的创建。在本地机器上需要安装关系型包装器和非关系型包装器。对于本例中 db2<->ORACLE之间的复制,需要安装关系型包装器。见图4。

  图4:进行远程复制关系图



  报警监视器用来进行对Capture和Apply部分的错误监控。





posted @ 2006-07-27 19:54 Alex 阅读(455) | 评论 (0)编辑 收藏

如何在Oracle里设置访问多个SQL Server数据库?假设我们要在ORACLE里同时能访问SQL Server里默认的pubs和Northwind两个数据库。

1、 在安装了ORACLE9i Standard Edition或者ORACLE9i Enterprise Edition的windows机器上(IP:192.168.0.2), 产品要选了透明网关(Oracle Transparent Gateway)里访问Microsoft SQL Server数据库

$ORACLE9I_HOME\tg4msql\admin下新写initpubs.ora和initnorthwind.ora配置文件.
initpubs.ora内容如下:
HS_FDS_CONNECT_INFO="SERVER=SQLSERVER_HOSTNMAE;DATABASE=pubs"
HS_DB_NAME=pubs
HS_FDS_TRACE_LEVEL=OFF
HS_FDS_RECOVERY_ACCOUNT=RECOVER
HS_FDS_RECOVERY_PWD=RECOVER
initnorthwind.ora内容如下:
HS_FDS_CONNECT_INFO="SERVER=sqlserver_hostname;DATABASE=Northwind"
HS_DB_NAME=Northwind
HS_FDS_TRACE_LEVEL=OFF
HS_FDS_RECOVERY_ACCOUNT=RECOVER
HS_FDS_RECOVERY_PWD=RECOVER

$ORACLE9I_HOME\network\admin 下listener.ora内容如下:
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)(PORT = 1521))
)
)
)

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = test9)
(ORACLE_HOME = d:\oracle\ora92)
(SID_NAME = test9)
)
(SID_DESC=
(SID_NAME=pubs)
(ORACLE_HOME=d:\Oracle\Ora92)
(PROGRAM=tg4msql)
)
(SID_DESC=
(SID_NAME=northwind)
(ORACLE_HOME=d:\Oracle\Ora92)
(PROGRAM=tg4msql)
)
)


重启动这台做gateway的windows机器上(IP:192.168.0.2)TNSListener服务.

(凡是按此步骤新增可访问的SQL Server数据库时,TNSListener服务都要重启动)

2、ORACLE8I,ORACLE9I的服务器端配置tnsnames.ora, 添加下面的内容:

pubs =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)(PORT = 1521))
)
(CONNECT_DATA =
(SID = pubs)
)
(HS = pubs)
)

northwind =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)(PORT = 1521))
)
(CONNECT_DATA =
(SID = northwind)
)
(HS = northwind)
)
保存tnsnames.ora后,在命令行下
tnsping pubs
tnsping northwind


出现类似提示,即为成功

Attempting to contact (DESCRIPTION = (ADDRESS_LIST = 
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)
(PORT = 1521))) (CONNECT_DATA = (SID = pubs)) (HS = pubs))
OK(20毫秒)
Attempting to contact (DESCRIPTION = (ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)
(PORT = 1521))) (CONNECT_DATA = (SID = northwind)) (HS = northwind))
OK(20毫秒)


设置数据库参数global_names=false。

设置global_names=false不要求建立的数据库链接和目的数据库的全局名称一致。global_names=true则要求, 多少有些不方便。

oracle9i和oracle8i都可以在DBA用户下用SQL命令改变global_names参数

alter system set global_names=false;


建立公有的数据库链接:

create public database link pubs 
connect to testuser identified by testuser_pwd using 'pubs';
create public database link northwind
connect to testuser identified by testuser_pwd using 'northwind';
(假设SQL Server下pubs和northwind已有足够权限的用户登陆testuser,
密码为testuser_pwd)


访问SQL Server下数据库里的数据:

select * from stores@pubs;
...... ......
select * from region@northwind;
...... ......


3、使用时的注意事项

ORACLE通过访问SQL Server的数据库链接时,用select * 的时候字段名是用双引号引起来的。

例如:

create table stores as select * from stores@pubs;
select zip from stores;
ERROR 位于第 1 行:
ORA-00904: 无效列名
select "zip" from stores;
zip
-----
98056
92789
96745
98014
90019
89076


已选择6行。

用SQL Navigator或Toad看从SQL Server转移到ORACLE里的表的建表语句为:

CREATE TABLE stores
("stor_id" CHAR(4) NOT NULL,
"stor_name" VARCHAR2(40),
"stor_address" VARCHAR2(40),
"city" VARCHAR2(20),
"state" CHAR(2),
"zip" CHAR(5))
PCTFREE 10
PCTUSED 40
INITRANS 1
MAXTRANS 255
TABLESPACE users
STORAGE (
INITIAL 131072
NEXT 131072
PCTINCREASE 0
MINEXTENTS 1
MAXEXTENTS 2147483645
)
/


总结:

WINDOWS下ORACLE9i网关服务器在$ORACLE9I_HOME\tg4msql\admin目录下的initsqlserver_databaseid.ora

WINDOWS下ORACLE9i网关服务器listener.ora里面

(SID_DESC=
(SID_NAME=sqlserver_databaseid)
(ORACLE_HOME=d:\Oracle\Ora92)
(PROGRAM=tg4msql)
)
UNIX或WINDOWS下ORACLE8I,ORACLE9I服务器tnsnames.ora里面
northwind =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.2)(PORT = 1521))
)
(CONNECT_DATA =
(SID = sqlserver_databaseid)
)
(HS = sqlserver_databaseid)
)


sqlserver_databaseid一致才行.
posted @ 2006-07-27 19:49 Alex 阅读(339) | 评论 (0)编辑 收藏

key words:自动脚本 代理脚本 代理 冲出窗子

本文献给那些对自动代理脚本有兴趣、想自己写的朋友。

1、什么是代理脚本(PAC)
 一个PAC文件其实就是一个文本文件,最简单的格式就是包含一个叫FindProxyForURL的
 JScript函数,IE通过传入两个变量来调用这个函数,一个是用户浏览的地址URL全路经,
 一个是这个URL中的主机名部分(host)。这个FindProxyForURL函数有三种可能的字符串
 返回值,一是"DIRECT",就是直接连接,不通过代理;二是"PROXY proxyaddr:port",
 其中proxyaddr和port分别是代理的地址和代理的端口;三是"SOCKS socksaddr:port",
 其中socksaddr和port分别是socks代理的地址和端口,一个自动代理文件可以是多个
 选择的组合,其中用分号(;)隔开,如:
   
   function FindProxyForURL(url,host)
   {
     if (host == "www.mydomain.com")
         return "DIRECT";
 
         return "PROXY myproxy:80;
                 PROXY myotherproxy:8080; 
                 DIRECT";
   }
   
   
2、下面是代理脚本可能用到的函数和说明:
PAC Helper Functions

dnsDomainIs(host, domain)              Returns true if the host is part of the
                                       specified domain, false otherwise.
 
isInNet(hostname,                      Resolves the hostname and subnet IP,
                                       subnet mask) returns true if the
                                       hostname is within the subnet specified
                                       by the IP address and the subnet mask,
                                       false otherwise.
 
isPlainHostName(host)                  Returns true if there are no dots in the
                                       hostname, false otherwise.
 
isResolvable(host)                     Internet Explorer tries to resolve the
                                       hostname through DNS and returns true if
                                       successful, false otherwise.
 
localHostOrDomainIs                    Returns true if the host matches (host,
                                       domain) the host portion of the domain,
                                       or if the host matches the host and
                                       domain portions of the domain, false
                                       otherwise. (Executed only for URLs in
                                       the local domain.)
 
dnsDomainLevels(host)                  Returns the number of dots in the
                                       hostname.
 
dnsResolve(host)                       Returns a string containing the IP
                                       address of the specified host.
 
myIPAddress( )                         Returns a string containing the local
                                       machine’s IP address.
 
shExpMatch(url, shexp)                 Returns true if the supplied URL matches
                                       the specified shell expression, false
                                       otherwise. 
 
dateRange(parmList)                    Returns true if the current date falls
                                       within the dates specified in parmList,
                                       false otherwise. 
 
timeRange(parmList)                    Returns true if the current time falls
                                       within the times specified in parmList,
                                       false otherwise. 
 
weekdayRange(parmList)                 Returns true if today is within the days
                                       of the week specified in parmList, false
                                       otherwise. 

3、下面是各个函数应用的例子:
  a、isPlainHostName(host),本例演示判断是否为本地主机,如http://myservername/
  的方式访问,如果是直接连接,否则使用代理
  function FindProxyForURL(url, host)
  {
    if (isPlainHostName(host))
      return "DIRECT";
    else
      return "PROXY proxy:80";
  }
  
  b、dnsDomainIs(host, "")、localHostOrDomainIs(host, ""),本例演示判断访问主机
  是否属于某个域和某个域名,如果属于.company.com域的主机名,而域名不是
  www.company.com和home.company.com的直接连接,否则使用代理访问。
  function FindProxyForURL(url, host)
  {
    if ((isPlainHostName(host) ||
       dnsDomainIs(host, ".company.com")) &&
      !localHostOrDomainIs(host, "www.company.com") &&
      !localHostOrDomainIs(host, "home.company.com"))

      return "DIRECT";
    else
      return "PROXY proxy:80";
  }
  
  c、isResolvable(host),本例演示主机名能否被dns服务器解析,如果能直接访问,否
  则就通过代理访问。
  function FindProxyForURL(url, host)
  {
    if (isResolvable(host))
      return "DIRECT";
    else
      return "PROXY proxy:80";
  }
  
  d、isInNet(host, "", ""),本例演示访问IP是否在某个子网内,如果是就直接访问,
  否则就通过代理,例子演示访问清华IP段的主页不用代理。
  function FindProxyForURL(url, host)
  {
    if (isInNet(host, "166.111.0.0", "255.255.0.0"))
      return "DIRECT";
    else
      return "PROXY proxy:80";
  }
  
 e、shExpMatch(host, ""),本例演示根据主机域名来改变连接类型,本地主机、*.edu、
  *.com分别用不同的连接方式。
  function FindProxyForURL(url, host)
  {
    if (isPlainHostName(host))
      return "DIRECT";
    else if (shExpMatch(host, "*.com"))
      return "PROXY comproxy:80";
    else if (shExpMatch(host, "*.edu"))
      return "PROXY eduproxy:80";
    else
      return "PROXY proxy:80";
  }
  
 f、url.substring(),本例演示根据不同的协议来选择不同的代理,http、https、ftp、
  gopher分别使用不同的代理。
  function FindProxyForURL(url, host)
  {
      if (url.substring(0, 5) == "http:") {
        return "PROXY proxy:80";
      }
      else if (url.substring(0, 4) == "ftp:") {
        return "PROXY fproxy:80";
      }
      else if (url.substring(0, 7) == "gopher:") {
        return "PROXY gproxy";
      }
      else if (url.substring(0, 6) == "https:") {
        return "PROXY secproxy:8080";
      }
      else {
        return "DIRECT";
      }
  }
  
  g、dnsResolve(host),本例演示判断访问主机是否某个IP,如果是就使用代理,否则直
  接连接。
  unction FindProxyForURL(url, host)
  {
      if (dnsResolve(host) == "166.111.8.237") {
        return "PROXY secproxy:8080";
      }
      else {
        return "PROXY proxy:80";
      }
  }
  
  h、myIpAddress(),本例演示判断本地IP是否某个IP,如果是就使用代理,否则直接使
  用连接。
  function FindProxyForURL(url, host)
  {
      if (myIpAddress() == "166.111.8.238") { 
        return "PROXY proxy:80";
      }
      else {
        return "DIRECT";
      }
  }
  
  i、dnsDomainLevels(host),本例演示访问主机的域名级数是几级,就是域名有几个点
  如果域名中有点,就通过代理访问,否则直接连接。
  function FindProxyForURL(url, host)
  {
      if (dnsDomainLevels(host) > 0) { // if number of dots in host > 0
        return "PROXY proxy:80";
      }
        return "DIRECT";
  }
  
  j、weekdayRange(),本例演示当前日期的范围来改变使用代理,如果是GMT时间周三
  到周六,使用代理连接,否则直接连接。
  function FindProxyForURL(url, host)
  {
    if(weekdayRange("WED", "SAT", "GMT")) 
     return "PROXY proxy:80";
    else 
     return "DIRECT";
  }
  
  k、最后一个例子是演示随机使用代理,这样可以好好利用代理服务器。
 function FindProxyForURL(url,host)
 {
      return randomProxy();
 }
 
 function randomProxy()
 {
     switch( Math.floor( Math.random() * 5 ) )
     {
         case 0:
             return "PROXY proxy1:80";
             break;
         case 1:
             return "PROXY proxy2:80"; 
             break;
         case 2:
             return "PROXY proxy3:80";
             break;
         case 3:
             return "PROXY proxy4:80";
             break;
         case 4:
             return "PROXY proxy5:80";
             break;
     }    
 }
 
利用上面的函数和例子说明,大家就可以写出比较复杂有效的自动代理脚本。
posted @ 2006-07-21 14:57 Alex 阅读(977) | 评论 (0)编辑 收藏

key words:js捕捉浏览器退出 关闭浏览器 刷新浏览器

有时在用户session管理时,需要跟踪会话状态,比如当前在线用户,如果用户非正常退出,需要知道。

用js控制浏览器的关闭是一个辅助的手段,当然也可以设置session的有效期,不过不够及时。

js代码如下:

<html>
<script language="javascript"> 
<!-- 
var s="close"
function window.onunload(){ 
    
if(s=="fresh"
        
if(window.screenLeft>10000){alert('¹Ø±Õ');}
        
else{alert('Ë¢ÐÂ');} 
    
else alert('¹Ø±Õ'); 

function window.onbeforeunload()

    s
="fresh"

--> 
</script>
</html>

posted @ 2006-07-20 10:50 Alex 阅读(1222) | 评论 (0)编辑 收藏

key words:20% 80*法则

一个人在人力市场的价值,是从两种截然不同的因素衍生出来的──「潜力价值」以及「经验价值」。我们基本上是带着满满一袋潜力和空空一袋经验开始职业生涯,诀窍就在于,在潜力袋被掏空之前,先把经验袋装满。


你现在正处在事业曲线的哪个位置?你的价值是属于潜力型,还是经验型?
回答之前,你必须了解到,你的价值会随着事业进展不断改变,从潜力价值转到经验价值,再转回潜力价值。事业成功的第 1 个法则,就是建立并管理你的潜力与经验价值,在事业生涯的每个阶段为自己创造最大价值。

事业生涯 3 阶段

事实上,在你的职业生涯中,不管上升速度如何,不论你所处的职业、产业或是地理位置,你都可能经历 3 个不同的阶段:潜力阶段、动能阶段与收割阶段(见图)。

图中那条稳定向上攀升的事业线就表示成功的事业。成功人士和泛泛之辈通常就在事业生涯的动能阶段分道扬镳

走对第 1 步,非凡主管的黄金 5 年:毕业后~ 30

潜力阶段是指正式教育结束到 30 岁左右这个阶段,你的已知价值主要取决于潜力。在这个时期,如果加入绩优公司、接触各种不同的经验、找到自己想做的事情、发现自己擅长的工作,对于形塑未来的事业将有重大影响。我们建议,年轻上班族在这个阶段最好早一点到绩优公司历练。

在这个阶段,你的报酬相对较低,你和其它同僚的工作经验差别也相去无几。但是,这个阶段的确很重要,是导正事业方向最好的时机。根据我们调查,成功非凡的高阶主管,认为他们职业生涯最初 5 年很成功的比率,是一般职员的两倍。

在潜力阶段必须完成两项目标:至少到一家备受推崇的公司或机构工作,取得背书;同时必须更了解自己,引导自己走向一家能够在未来几年发挥个人优势与兴趣的企业。完成这两个目标,未来表现将会维持一定水平,也能让你找到一个未来几年都乐在工作的环境。

被挖角的黄金期: 38 40

事业起步后的 5 7 年,你经历了一个个不同职位,也换过几家公司,慢慢累积了经验;此时,你的个人价值,是以技能及经历的不同处境为基础。当你能够驾驭专业技能、不断写下成功纪录、承担更广的责任、管理其它人,并且培养事业关系的网络,你的潜力价值就稳定地转为经验价值。人通常是在 35 岁左右,进入事业的动能阶段。

这个阶段是专业生涯往上走或往下滑的关键,动能阶段也是工作人将经验价值发挥极致的阶段。

你 的潜力价值并没有消失,毕竟未来的路还很长。但是到了这个时点,你已经累积足够经验,可以往组织高层职位突破,或是引起其它公司注意。这个阶段,最容易根 据过往战功升迁。因为你有成功纪录可考,也蓄积了足够技能与经验,足堪承担更大责任。这个阶段的主管,通常是猎人头公司捕猎的对象,有兴趣挖角的公司也最 多。我们的研究显示,被挖角的黄金高峰期,平均在 38 岁到 40 岁之间。

如 果好好管理这个阶段,通常会有很多选择。你的表现大家有目共睹,尤其是你能直通消费者、客户与供货商,并且有机会在产业研讨会上演讲。相反的,没有好好把 握这个阶段的人,就会看到事业停滞不前。如果你无法发挥本身优势与热情,或是创造影响力吸引最重要机会,就错失了动能阶段的大好良机。因为在这个阶段,是 你和其它同辈展开不同事业轨道的重要分水岭。

收割期:事业倒吃甘蔗

工作了 25 年或 30 年,来到 45 50 岁 时,多数人也进入了事业的收割阶段。每个人的事业轨道在动能阶段的中期开始分岔,成功的高阶主管不断被赋予最重要职务与责任,经验较浅、但才华洋溢的经理 人,在这段时期还需要多加磨练。有些人的事业会继续进展,有些人则已达高原。相对而言,在动能阶段,高阶主管还有相当大的进步空间。毕竟,在这个阶段,大 多数的工作人还是处在受雇的状态。

事业最明显的差异就出现在收割阶段,在这个阶段,事业成功的高阶主管有许多工作机会供其挑选:经营公司、加入董事会、联盟投资银行、当顾问、写书或发表文章、上电视、演讲、领导非营利的计划等。

为什么有些人可以平步青云、进入成熟阶段呢?

80
20 法则

成功事业的第 2 个法则,就是善用 80 20 表现法则,让你的老板记得你最擅长的 20 %。成功运用这项法则,你就能赢得职业生涯中的大赛。

2002
年,阿姆斯特朗( Lance Armstrong )连续第 4 度赢得环法自行车大赛( Tour de France )的冠军。他决定除了一、两场特别的赛事外,不再在美国出赛。

为什么?对世界第一流的运动员而言,阿姆斯特朗的决定并不意外,因为他们把有限的精力留给最重要的比赛。高尔夫球迷绝对希望老虎伍兹( Tiger Woods )参加每一场比赛,电视制作人更乐见此事。但是伍兹一年参加的赛事比其它职业选手,或是之前的球王都要少。他的目光只放在高球 4 大赛。他并未因此道歉,反而被视为传奇。

多数人都知道 80 20 法则,这是意大利经济学家帕雷图( Vilfredo Paredo )所提出的理论。前 20 %最有价值的客户,贡献 80 %的利润。伍兹和阿姆斯特朗都决定专注最重要的比赛,因为这些比赛会带来最大的效益。我们得到一个简单有力的教训:做对的事,比你做多少事重要得多。这条法则同样适用你的职业生涯。

但不像职业运动员,工作生涯中所要完成的任务和目标,并非总是充满荣耀。

事实上,我们的工作中有 80 %的内容,是单调狭隘、一成不变的,实在很难靠这些事让自己与众不同。但是,我们要与伍兹和阿姆斯特朗同行,就必须赢得关键战役,才能真正与众不同。

赢得重要比赛

要更了解这个观念,我们可以反过来,先看看失败的例子。有一个失意的专家马丁,他因为没有应用 80 20 法则,使自己的职场生涯陷入平庸的困境。

和数百万的企业人一样,在 2001 2002 年间,马丁遭遇了几次挫败。最初,他将责任归咎于经济不景气。但是,随着时间过去,他愈来愈觉得自己的失败,也许是某些行为的结果。

毕业后,马丁进入一家大型制造公司当会计,但很快就升到中阶管理职位。他总是准时结帐,面对询问,也都能立刻拿出正确的财务报表。但是慢慢的,马丁觉得公司一些重大的策略决定,好像愈来愈不需要他,渐渐消失在升迁名单中。

马丁上班、做该做的事,是个扎实的员工。但从某种角度看,也不会有太大的突破。他和某些运动员一样,参加所有比赛,但从未赢过重要大赛。马丁工作努力但和别人没什么不同,最后,变成公司的消耗品。

相较下,本书共同作者瑞克.史密斯( Rick Smith )就掌握了 80 20 法则。他早期曾是知名软件服务商 EDS 的市场分析师。负责搜集、分析市场信息,提供给 EDS 的高阶经理人。

做「多」不如做「精」

有一天,他发现一份档案,内容是 EDS 过去 15 年来所有重要案子的财务表现,每一笔都写得非常详细。他很好奇,不知大案子的利润是否比小案子高。他快速分析,发现在许多方面都有很大的差异。他兴奋不已,于是在办公室待到午夜,思考如何运用这些分析结果。

隔天,他对主管做了简报,并提议:「如果我们不只帮公司赢得更多案子,而是赢得对的案子呢?」一个星期内,他的报告被送到执行长办公室。

从此,事情开始不一样了。瑞克除了做日常的市场研究外,他还被要求对公司的事业做更深入的分析。有 1 年半的时间,瑞克的分析报告都会交给高层主管,使他比别人有更多机会,让研究成果发挥最大影响,最后导致公司大规模地改变市场策略。

在这段期间,瑞克主要的工作还是市场分析,但他重新归类自己的例行公事,想办法挤出额外的时间来做企业绩效分析。

他一方面训练自己的小组成员,逐步将非关键性的工作交给他们。另一方面与信息部合作,将某些研究报告的产出流程自动化,以挤出珍贵的时间用在他认为能增加价值的事上。

事实上,他已经重新定义自己的角色,从「协助赢得生意」到「协助赢得对的生意」。因为这样,瑞克常受邀参与许多超越他权责范围、但有助于事业开展的活动,得到别人没有的机会和经验。

80
20 法则的重点在于,清楚什么该做与什么不该做。卓越的经理人正是懂得把这 20 %放在最前面。瑞克善用此一法则,最后让他发挥正面影响力。
posted @ 2006-07-20 09:41 Alex 阅读(378) | 评论 (0)编辑 收藏

仅列出标题
共15页: First 上一页 7 8 9 10 11 12 13 14 15 下一页