- 浏览: 413636 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
liyuanhoa_:
...
struts2.0中struts.xml配置文件详解 -
chenmingde:
...
Velocity应用(一) -
weizhikai_ai:
第二十六,当一个线程进入一个对象的一个synchronized ...
Java常见面试题(含答案) -
Aurora_lr:
...
Spring宠物商店学习笔记(一) - -
zs911zs:
all copy from http://www.iteye ...
Mule入门文档
在本文中,作者通过一个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 所示:
在上图中包括的几个类中:
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 模式的类图图解
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:示例程序的类图
从上面的类图我们可以看到,我们的测试程序(Main.java)通过Spring框架获取了BusinessDelegate的实例。而且Spring 框架还会根据配置中的依赖关系,在运行时将Web Service的客户端代理" 注射"到CurrencyExchangeManagerImpl实例中,这就是依赖注入(Dependency Injection)。通过这种方式解决了应用逻辑和BusinessDelegate之间的依赖关系,以及BusinessDelegate的实现与远程服务之间的依赖关系,如图 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 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
关于下载方法的信息
参考资料
JAX-RPC: http://java.sun.com/xml/jaxrpc/index.html
Spring 参考文档 : http://www.springframework.org/docs/reference/remoting.html
参考文章: SOA adventures, Part 1: Ease Web services invocation with dynamic decoupling
关于AOP:请参考IBM developerWorks上的更多资源。
J2EE核心模式: Core J2EE™ Patterns: Best Practices and Design Strategies, Second Edition By Deepak Alur, John Crupi, Dan Malks
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 所示:
在上图中包括的几个类中:
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 模式的类图图解
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:示例程序的类图
从上面的类图我们可以看到,我们的测试程序(Main.java)通过Spring框架获取了BusinessDelegate的实例。而且Spring 框架还会根据配置中的依赖关系,在运行时将Web Service的客户端代理" 注射"到CurrencyExchangeManagerImpl实例中,这就是依赖注入(Dependency Injection)。通过这种方式解决了应用逻辑和BusinessDelegate之间的依赖关系,以及BusinessDelegate的实现与远程服务之间的依赖关系,如图 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 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
关于下载方法的信息
参考资料
JAX-RPC: http://java.sun.com/xml/jaxrpc/index.html
Spring 参考文档 : http://www.springframework.org/docs/reference/remoting.html
参考文章: SOA adventures, Part 1: Ease Web services invocation with dynamic decoupling
关于AOP:请参考IBM developerWorks上的更多资源。
J2EE核心模式: Core J2EE™ Patterns: Best Practices and Design Strategies, Second Edition By Deepak Alur, John Crupi, Dan Malks
发表评论
-
利用动态代理的 Java 验证
2008-12-18 17:41 821从业务对象实现中去耦 ... -
Java 理论与实践: 用动态代理进行修饰
2008-12-18 17:41 778动态代理是构建 Decorator 和 Adapter 的方便 ... -
分布式软件系统
2008-12-18 15:49 1443分布式软件系统(Distributed Software Sy ... -
SOA 案例研究:SOA 设计
2008-12-17 15:00 1043http://www.ibm.com/developerwor ... -
RUP
2008-12-12 16:53 689RUP(Rational Unified Proces ... -
什么是敏捷开发?
2008-12-05 11:17 2426敏捷开发(agile development)是一种以人为核心 ... -
深入理解敏捷开发的常见九大误区
2008-12-05 11:16 1468任人、开发者和用户应 ... -
对领域模型实现的总结性观点
2008-12-04 17:14 1124陶文发起的对领域模型 ... -
领域模型的价值与困境
2008-12-04 17:02 954很久以前大家就关于这 ... -
OO设计原则----依赖倒置原则(DIP)
2008-12-02 01:52 1078这是一个类与类之间调用规则,术语上解释就是对于组合之间的规范。 ... -
工厂模式与OO设计原则
2008-12-02 01:50 1127如果把创建看作一个职 ... -
设计模式之--动态代理
2008-11-26 18:03 1605动态代理类是一个在运行时由开发人员所指定的一列接口的实现。动态 ... -
Facade外观模式
2008-11-26 16:06 977GOF《设计模式》一书对Facade模式是这样描述的: ... -
Adapter适配器模式
2008-11-26 16:05 827GOF《设计模式》一书对Adapter模式是这样描述的: ... -
Strategy策略模式
2008-11-26 16:05 1065GOF《设计模式》一书对Strategy模式是这样描述的: ... -
Bridge桥接模式
2008-11-26 16:04 897设计模式》一书对Bridge是这样描述的: 将抽象与其实现解 ... -
Abstract Factory抽象工厂模式
2008-11-26 16:03 743GOF《设计模式》一书对Abstract Factory模式是 ... -
Decorator装饰模式
2008-11-26 16:01 868《设计模式》一书对Decorator是这样描述的: 动态地给 ... -
Observer观察者模式
2008-11-26 15:59 914《设计模式》一书对Observer是这样描述的: 定义对象间的 ... -
Template Method模式
2008-11-26 15:58 968factory模式(包括简单工厂和抽象工厂),Strategy ...
相关推荐
Spring 框架是Java开发中的核心框架,它主要由两个关键部分组成:IOC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)。这两个概念是Spring框架的核心特性,极大地简化了企业...
在Spring框架中,依赖注入(Inversion of Control, IoC)和面向切面编程(Aspect Oriented Programming, AOP)是两大核心特性。本篇将深入探讨如何通过注解方式来模拟Spring的这两种机制,帮助你理解其底层原理。 #...
标题 "ISpring-IOC-AOP:自定义IOC & AOP实现" 涉及到的是在Java编程领域中,对Spring框架的两个核心概念——控制反转(Inversion of Control, IOC)和面向切面编程(Aspect-Oriented Programming, AOP)的自定义实现...
这个名为“GreeFramOfficial”的压缩包文件,很可能是提供了一个基于C#实现的IOC和AOP框架,供开发者学习和使用。 IOC(Inversion of Control)的核心思想是将对象的创建和管理交给一个容器来处理,而不是由对象...
包括spring的ioc和aop,ioc是用比较流行的set注入方式,aop是用比较流行的xml配置方式。由于是maven项目,所以用普通java项目的话,需要把配置文件放到和程序一个目录下(src下),还要自己导入spring依赖的包。
Spring框架是Java开发中不可或缺的一部分,它通过提供两种核心特性——控制反转(IoC)和面向切面编程(AOP)来简化应用的构建。理解并掌握这两种技术对于任何Java开发者来说都至关重要。 **控制反转(IoC)**,也...
力求获得一种简洁实用的方法实现IOC和AOP相结合的使用方式。 查阅了多个技术资料。经过多次测试,基本达到目的。 IOC使用微软的 Microsoft.Practices.Unity,AOP 使用微软企业库的 Microsoft.Practices.Enterprise...
Unity是Microsoft推出的一款轻量级、可扩展的依赖注入(DI)容器,...理解并熟练掌握这些概念和实践,能够显著提升软件开发的效率和质量。通过深入学习UnityStudy中的示例,你可以更好地掌握这两个关键概念的实际应用。
在“spring-1”、“spring-2”和“spring-3”这三个文件中,很可能是包含了一系列关于Spring框架使用、IOC和AOP实践的示例代码。这些代码可能涵盖了如何定义bean、如何进行依赖注入、如何声明切面以及如何在实际项目...
基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)基于Cglib简单实现Spring体系(Ioc+Aop+Mvc)...
今天有空,写了基于C#使用Spring.Net的演示实例,希望能给有需要的人带来帮助,其中演示了配置下的IOC、AOP、属性注入、构造函数注入、通知过滤器、以及不使用配置直接代码硬编的AOP动态代码过程,另外还增加了...
spring version: 5.0.0; jdk: 1.8 IOC大致调用顺序(IOC调用的AOP标签解析)
项目"spring-ioc-aop-demo"将带你逐步探索这些概念。首先,你会看到一个简单的Spring配置文件,其中定义了各种Bean及其依赖关系。接着,你会看到如何使用@Autowired注解进行自动装配,这是一种便捷的DI方式。然后,...
Spring的核心:IOC与AOP。IOC是控制反转或依赖注入,AOP是面向切面编程。
文档的目的是让读者不仅理解IoC和AOP这两个核心概念,而且能够亲手实现它们,从而提升软件设计和开发的实践能力。通过这样的过程,读者将能够获得深入理解Spring框架内部工作原理的机会,并且能够在没有Spring框架的...
Spring 3.1 是一个非常重要的Java框架,它在企业级应用开发中广泛使用,尤其在控制反转(IOC)和面向切面编程(AOP)方面。这些概念是Spring框架的核心特性,帮助开发者实现松耦合和代码复用,提高了应用程序的可...