Tucany SCA软件架构设计理念分析之二
 

2009-08-31 作者:lijj 来源:网络

 

1. 概述

上一篇文章主要讲述了Tuscany SCA的插件板模式及工厂模式的发展和提升,本文主要介绍Tuscany SCA开源软件中动态代理模式(Dynamic Proxy)及方法调用Invocation 。在使用Tuscany SCA中,我们只需要写接口及实现类,这些接口和实现类就是普通的java代码,并没有特殊之处。Tuscany SCA会根据“.composite”文件把这些信息组装成Compoiste,至于如何组装,则是Tuscany SCA的Assembly模块的主要内容,我以后会介绍,但现在的问题是,如何来实现调用这些类的方法的问题,因为当调用的时候,外部接口是Tuscany SCA的Domain,Domain依赖的Runtime中就加载有Composite的内容,客户的逻辑在应用程序(即interface, implementClass),但这些都被组装进入了Composite中,所以要使用动态代理模式来实现,使得侵入性最小。

2. Java动态代理基础知识

Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:InvocationHandler和Proxy。

InvocationHandler必须被实现,实现这个接口的invoke(Object obj,Method method, Object[] args),第一个参数obj一般是指代理类,method是被代理的方法, args为该方法的参数数组。这个invoke方法的内容主要是在调用被代理的方法前后方便地加上你自己的逻辑,如下例子

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 System.out.println(“调用前你要插入的逻辑,如验证权限”);

 //实际调用被代理类的方法

 Object result = method.invoke(this.target,args);
 System.out.println(“调用后你要插入的逻辑,如调整系统状态”);

 return result;
}

Proxy:该类即为动态代理类,是Jdk实现的类,你无须实现,只使用就行其中主要包含以下内容:

Proxy.newProxyInstance(ClassLoader,cls. Interfaces[],InvocationHandler)

其中ClassLoader是加载类,Inerface数组表示要代理的类的所有接口,InvocationHandler即上面介绍的具体实现类。通过这个方法就可以获取动态代理类Proxy,其实例是$proxy。如下例子表示如何使用动态代理:

PersonImpl person = new PersonImpl(); //在这里指定被代理类
InvocationHandler ds = new DynamicSubject(person); //初始化代理类
Class cls = person.getClass();
Person person1=Person.class.
cast(Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),ds));
person1.getName();

上面的person1就是proxy对象,即动态代理,所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

另外动态生成proxy类有以下限制:接口数组中的每个元素应代表接口,不能表示类,或原始类型,不能出现重复的类型,接口可以通过名称访问到,两个接口不能存在方法名与参数列表相同但返回值不同的情况,最大数为65535。动态代理只能够对interfaces进行动态的代理, 也就是说它先找出需要代理的对象实现的所有的interface, 然后只对所有的这些interface的所有的方法代理,对象除了interfaces以外的方法不予处理。这就是说我们再做contribution时,只要可以被外部调用的方法,都必须在接口中声明。

3. Tuscany SCA软件动态代理模式(Dynamic Proxy)及方法调用

根据以上的JDK动态代理基础知识介绍,下面介绍Tuscany SCA软件在动态代理和方法调用方面的设计。

(1) Tuscany SCA中动态代理模式的应用

基于上面的动态代理的知识,我们来看Tuscany SCA是如何设计的,首先来看一下它最核心的org.apache.tuscany.sca.core.invocation.JDKProxyFactory类的方法createProxy

public <T> T createProxy(CallableReference<T> callableReference) throws ProxyCreationException {
  assert callableReference != null;
  Class<T> interfaze = callableReference.getBusinessInterface();
  JDKInvocationHandler handler = new JDKInvocationHandler(messageFactory, callableReference);

 ClassLoader cl = interfaze.getClassLoader();

 return interfaze.cast(Proxy.newProxyInstance(cl, new Class[] {interfaze}, handler));

}

和上面介绍JDK Proxy的例子来看多么的相似,第一个参数是ClassLoader,第二个参数是要代理的类的接口数组,第三个参数是InvocationHandler,这儿是JDKInvocationHandler,如下图所示,浅蓝色标注的类就是我们核心工作类,由它来完成创建Proxy的任务。

JDKInvocationHandler

上面绿色的DefaultSCADomain是SCA对外的接口类,在SCADomain接口下面的实现类,所有的外部操作都来自这个Domain,如何调用的呢

//创建SCADomain:扩展点注册器实例化,注册对象到扩展点注册器,加载ModuleActivator,//解析策略配置文件definitions.xml,资源加载(资源查找,解析),资源组装(build, activate, start)

SCADomain scaDomain = SCADomain.newInstance("Calculator.composite");

//获取Service,在这儿通过SCADomain的对外接口来获取CalculatorService代理类“$Proxy”

CalculatorService calculatorService =
scaDomain.getService(CalculatorService.class, "CalculatorServiceComponent");

如上图所示,绿色的依赖线显示了调用的先后顺序。实例化后的DefaultSCADomain是对外部的总接口,其中有HashMap<String, Component>,以“CalculatorServiceComponent”获取对应的RuntimeComponent,RuntimeComponent再获取RuntimeComponentContext,从而获取相关ServiceReference,从ServiceReference得到了ProxyFactory,这儿存在着一个代理模式,由DefaultProxyFactoryExtensionPoint类来创建真正的核心工作类JDKProxyFactory。然后由JDKProxyFactory创建Proxy。这儿的Proxy就是JDKInvocationHandler,就是这个类实现了InvocationHandler接口 ,如上面动态代理基础知识介绍,该类实现了invoke(Object proxy, Method method, Object[] args)方法。

(2) EJB组件和SCA Component的异曲同工之道

到这一步,其实就是我们有了需求,根据我们的请求,通过SCA的容器(请允许我这样讲,类似于Spring容器和Tapestry容器)获取动态代理对象。只不过绕了个弯,给人一种九曲回肠的感觉,为什么“以曲代直”,不直捣黄龙呢,就像我们MVC一样,分层的目的是解耦,同时可以无缝地插入一些东西。那么会插入什么东西呢,因为SCA是个SOA的基础框架,必须能够对付不同的语言,不同的平台,不同的网络协议,所以它必须封装这些内在的东西,展现给我们外部的仅仅是Composite,Component, Service,Reference,因此就会给人一种内部复杂,外部接口简单的感觉,也就是透明的感觉。

到了这儿,我想更深层次地介绍Component,类似于EJB功能的东东,但又不象EJB那样在内部做业务逻辑,并且需要继承某些固定的接口(Remote,Local接口),而是把业务逻辑写在POJO(普通的interface, class)中,通过配置文件把这些POJO组装起来(Assembly),形成Component的,进一步生成Composite,就相当于业务中的模块,对外提供服务(Service)。在SCADomain中就包含HashMap,其Value值就是组装好的Component,key值为配置文件中的名称“CalculatorServiceComponent”,从而获取到组装好可用的Component(RuntimeComponent),获取ProxyFactory,从ProxyFactory创建出能够实现代理的的Poxy。可以这样说SCADomain中包含着若干个Component,通过Component名称获取Component,component得到Proxy,也就是Component是SCA的核心概念。

从上面的论述中可以看出,Component实际上是个外壳,但外壳是包含很多底层功能,如安全(policy),网络协议(binding),上下文(context),RMI(远程调用),事务处理等等和业务无关的东西,类似于EJB容器的功能,只不过不象EJB那样依赖于EJB容器(应用服务器如weblogic,websphere等),而是依赖于轻量级的Component,之所以这样说,是指Component的实现是基于IOC原理

SCA Component是更高级别的抽象,同时也是更高级别的重用,是服务级别的,即业务级别的,而EJB更象是具体的增删改查操作,是较底层的计算机表现;Component的实现不象EJB必须用java实现,其他语言也支持,这就更能表现出Component是外壳,是容器。而真正的业务逻辑才是SOA开发人员要关注的问题,其他底层的功能都是依赖于配置文件。

(3) Tuscany SCA中动态代理的方法调用

上面我们介绍了Proxy的获取九曲回肠之路,这个Proxy最后露出JDKInvokeHandler庐山真面目,那么是如何通过这个代理来进行方法调用呢?如下图所示,绿色的类JDKInvokeHandler是发起方法调用的起点,最终实现调用功能的是蓝色的类JavaImplementationInvoke,其方法调用的核心语句如下:

if (payload != null && !payload.getClass().isArray()) {
 ret = method.invoke(instance, payload);
} else {
 ret = method.invoke(instance, (Object[])payload);
}

结合前面动态代理基础知识,方法调用就是这么简单,invoke方法的第一个参数是类的实例,第二个参数是调用方法的参数(或参数数组)。从这儿可以看出,SCA是工业化的Spring,吸收了IOC的思想,使用了动态代理就可以很明显地看出这一点, Component之间的组装是通过SCA框架来实现的,采用了类似于Spring的轻量级框架的设计思想。

当然,基础知识的方法调用是独木桥,要成为真正意义的大桥还需要很多工作,要考虑很多因素。

首先要JDKInvokeHandler的RunTimeWire获取Invocation chain,类似调用链,我们介绍SCA基础知识时介绍了Wire,是连接Reference和Service的连线,而连线中就有调用的Target, source, operation等内容,这样就可以得到调用链,从调用链中获取Invoke,这儿同样有代理模式存在,通过RuntimeSCABindingInvoke得到真正的工作类JavaImpletationInvoke。

如上面所说,反射的方法调用需要调用类的实例和参数列表,那么实例是如何创建出来的呢?

我们知道Web调用有Session的概念,SCA中同样也有,称之为Scope,从JavaImpletationInvoke中可以获取Scope中的是否有状态,类似于EJB中SessionBean,StatelessBean,从这些ScopeContain中获取RuntimeComponentProvide,这样就能够得到JavaImplementaionProvide,得到JavaInstanceFactory,从而可以得到RefelctInstanceFactory,呵呵,从名称上就可以看出要用到反射了,创建出InstanceWrapper,InstanceWrapper其实是把开发人员写的实际实现类封装了起来。如例子中的“calculator.CalculatorServiceImpl”。现在你终于可以明白转了一大圈,实际上是加入了状态控制。

JavaImplementationInvoker

4. 分专题来讨论SCA调用知识点

(1) Inject(IOC注射)

下面再涉及一个知识点Inject,如图所示,黄色的部分,即在创建实例时,要注射进取的东西,分为两类,MethodInject,和FieldInject,就是我们或者在源代码中annotation,或者在配置文件中配置的(Injects列表具体如何加入到系统中,将在Assembly中详细讲解)

<reference name="addService" target="AddServiceComponent" />
<reference name="subtractService" target="SubtractServiceComponent" />
<reference name="multiplyService" target="MultiplyServiceComponent" />
<reference name="divideService" target="DivideServiceComponent" />
<component name="AddServiceComponent">
<implementation.java class="calculator.AddServiceImpl"/>
</component>
<component name="SubtractServiceComponent">
<implementation.java class="calculator.SubtractServiceImpl"/>
</component>
<component name="MultiplyServiceComponent">
<implementation.java class="calculator.MultiplyServiceImpl"/>
</component>
<component name="DivideServiceComponent">
<implementation.java class="calculator.DivideServiceImpl"/>
</component>

都要求在这个实例创建时,初始化进去,即动态注射到我们的实例中来,其中象“AddServiceComponent”这样的类,同样也是通过IOC的方式创建出来。

org.apache.tuscany.sca.implementation.java.context.ReflectiveInstanceFactory<T>

public InstanceWrapper<T> newInstance() {
 T instance;
 try {
  if (ctrArgs != null) {
   Object[] args = new Object[ctrArgs.length];
   for (int i = 0; i < args.length; i++) {
    args[i] = ctrArgs[i].getInstance();
   }
   instance = ctr.newInstance(args);
  } else {
   instance = ctr.newInstance();
  }
 } catch (InstantiationException e) {
  String name = ctr.getDeclaringClass().getName();
  throw new AssertionError("Class is not instantiable [" + name + "]");
 } catch (IllegalAccessException e) {
  String name = ctr.getName();
  throw new AssertionError("Constructor is not accessible [" + name + "]");
 } catch (InvocationTargetException e) {
  String name = ctr.getName();
  throw new ObjectCreationException("Exception thrown by constructor: " + name, e);
}

if (injectors != null) {
 for (Injector<T> injector : injectors) {
  //FIXME Injectors should never be null
  if (injector != null)
   injector.inject(instance);
 }
}

(2) 同步调用和异步调用

当使用异步调用时,必须设置CallbackEndpoint,这样当系统获取请求后,在后台处理,至于处理的结果则需要调用CallbackEndpointer才能获得,同步调用时设置把处理结果直接放在调用的Target中,其实就是谁调用,把结果返回给谁。

org.apache.tuscany.sca.core.invocation.JDKInvocationHandler

if (wire.getSource() != null && wire.getSource().getCallbackEndpoint() != null) {
 if (callbackObject != null) {
  if (callbackObject instanceof ServiceReference) {
    msg.setFrom(((CallableReferenceImpl)callbackObject).getRuntimeWire().getTarget());
  } else {
   if (contract != null) {
    if (!contract.isConversational()) {
     throw new NoRegisteredCallbackException(
"Callback object for stateless callback is not a ServiceReference");
    } else {
     //FIXME: add callback object to scope container
     msg.setFrom(wire.getSource().getCallbackEndpoint());
    }
   }
  }
 } else {
  //FIXME: check that the source component implements the callback interface
  msg.setFrom(wire.getSource().getCallbackEndpoint());
 }
}

if (endpoint != null) {
 msg.setTo(endpoint);
} else {
 msg.setTo(wire.getTarget());
}

(3) 调用的状态的维护

其调用的状态范围分为:STATELESS,REQUEST,SESSION,CONVERSATION,COMPOSITE,SYSTEM,UNDEFINED。从字面意思上很容易理解“STATELESS,REQUEST,SESSION”,这和我们一起接触到的web应用非常的类似,都是基于同一个jvm之上的概念,“CONVERSATION”是基于对话级别的,类似于单jvm中的Application,“COMPOSITE”是composite级别的,因为Composite可以跨进程(jvm),可以跨机器部署,所以“COMPOSITE”是更高级别的状态共享。“SYSTEM”是基于domain级别的状态控制。

org.apache.tuscany.sca.core.scope.scope

public static final Scope STATELESS = new Scope("STATELESS");
public static final Scope REQUEST = new Scope("REQUEST");
public static final Scope SESSION = new Scope("SESSION");
public static final Scope CONVERSATION = new Scope("CONVERSATION");
public static final Scope COMPOSITE = new Scope("COMPOSITE");
public static final Scope SYSTEM = new Scope("SYSTEM");
public static final Scope UNDEFINED = new Scope("UNDEFINED");

其中CONVERSATION控制更进一步,设置了CONVERSATION会话顺序ConversationSequence 的Enumeration

public enum ConversationSequence {
 CONVERSATION_NONE, CONVERSATION_START, CONVERSATION_CONTINUE, CONVERSATION_END
}

在org.apache.tuscany.sca.core.invocation.JDKInvocationHandler中有根据会话顺序的状态进行控制的逻辑,在调用的参数封装类Message中设置会话顺序状态。

Interface contract = operation.getInterface();

if (contract != null && contract.isConversational()) {
 ConversationSequence sequence = operation.getConversationSequence();
 if (sequence == ConversationSequence.CONVERSATION_END) {
  msg.setConversationSequence(ConversationSequence.CONVERSATION_END);
  conversationStarted = false;
  if (conversation != null) {
   conversation.setConversationID(null);
  }
 } else if (sequence == ConversationSequence.CONVERSATION_CONTINUE) {
  if (conversationStarted) {
   msg.setConversationSequence(ConversationSequence.CONVERSATION_CONTINUE);
  } else {
   conversationStarted = true;
   msg.setConversationSequence(ConversationSequence.CONVERSATION_START);
  }
 }
}

同时org.apache.tuscany.sca.implementation.java.invocation.JavaImplementationInvoker的代码中根据从message中获取的会话顺序状态,来获取ScopeContainer中保存的InstanceWrapper的实例封装类,其中ScopeContainer中也有个HashMap来保存相应的实例封装类。Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

protected Map<KEY, InstanceWrapper<?>> wrappers = new ConcurrentHashMap<KEY, InstanceWrapper<?>>();

protected volatile int lifecycleState = UNINITIALIZED;
private InstanceWrapper getInstance(ConversationSequence sequence, Object contextId)
throws TargetResolutionException, InvalidConversationSequenceException {

 if (sequence == null) {
  return scopeContainer.getWrapper(contextId);
 } else {
  switch (sequence) {
   case CONVERSATION_START:
    return scopeContainer.getWrapper(contextId);
   case CONVERSATION_CONTINUE:
   case CONVERSATION_END:
    return scopeContainer.getAssociatedWrapper(contextId);
   default:
    throw new InvalidConversationSequenceException("Unknown sequence type: " + String.valueOf(sequence));
  }
 }
}

(4) 在传递过程Message的线程副本的应用

如在org.apache.tuscany.sca.core.invocation.ThreadMessageContext

相当于内存共享区,其中key为 Thread,value值为message,即要保存的内容

private static final ThreadLocal<Message> CONTEXT = new ThreadLocal<Message>() {
 @Override
 protected synchronized Message initialValue() {
  return new MessageImpl();
 }
};

public static Message setMessageContext(Message context) {
 Message old = CONTEXT.get();
 CONTEXT.set(context);
 return old;
}

public static Message getMessageContext() {
 return CONTEXT.get();
}

ThreadLocal线程局部变量, 就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。类似的应用还出现在spring,hibernate中。

5. 结束语

本文首先介绍了动态代理Poxy及Invocation方面的基础知识,分析了Tuscany SCA在运用这一原理来实现IOC功能,并深入介绍了Component的设计理念和EJB的异曲同工之妙,底层实现却采用了类似轻量级框架Spring的实现原理,以后还有Assembly,Contribute和Binding的具体设计思想,最后介绍Tuscany整体架构设计思想,敬请关注。共享是我的准则,共同提高是我的目标。

努力,在于我热爱我的事业,与中国的软件一起走向成熟,走向世界。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织