zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Apache OFbiz service engine 源代码解读

Apache 解读 源代码 Service Engine
2023-09-14 09:06:23 时间

上一篇看完了ofbiz entity engine,这篇再来过一下ofbiz的service engine。service engine层在设计模式的使用上跟entity engine有些相似,最典型的就是“业务代表”模式。service engine跟entity engine是紧密相关的,大部分的业务系统所要运行的服务都是跟关系数据库相关的。

service engine对于服务编写的方式有着很广泛的“自由”,你能够选择它内置引擎支持的不论什么一种方式来编写服务。同一时候对于eca的支持对于事务的支持以及对于权限的支持都很的完好。

Service运行方式的抽象——LocalDispatcher

该接口提供了ofbiz中service调度运行器基本协议方法的定义。包括:

  • 同步运行
  • 异步运行(事实上也是基于job加多线程)
  • 定时运行(基于job以及job pool)
  • 增加【提交/回滚事务时运行的】service
  • 一些关键“传输对象”的訪问器
dispatcher相关的类继承关系:

备注:此接口是service engine中比較关键的“业务代表”接口。其取名为LocalDispatcher是为了以示跟后面提及的RemoteDispatcher(用于支持RMI的remoteEngine)进行区分。


LocalDispatcher的抽象实现——GenericAbstractDispatcher

GenericAbstractDispatcher提供了对LocalDispatcher中定义的部分方法的实现(主要涉及到定时运行、事务相关的几个接口方法)。它内部依赖几个关键对象,是在其子类中注入的。这几个关键对象名:
  • ctx:serviceengine的上下文对象
  • dispatcher:ServiceDispatcher的实例,是真正的服务运行者

LocalDispatcher的标准通用实现——GenericDispatcher

上文提到GenericAbstractDispatcher中有几个关键对象是在其子类中被初始化的。事实上,就是在GenericDispatcher中(GenericDispatcher是GenericAbstractDispatcher的唯一子类)。关键对象的初始化位于init方法中,而init方法又是被GenericDispatcher的构造方法调用。此处须要注意的是,GenericDispatcher的两个构造器(包括无參构造器)都被设置为protected。也就是说你无法在外部通过调用无參构造器来初始化GenericDispatcher的实例(假设同意这种行为,那么其父类GenericAbstractDispatcher的那两个关键对象将不能保证被实例化)。那么GenericDispatcher是在哪里被实例化的?这须要谈到GenericDispatcher提供的几个公有静态方法:

假设你关注一下他们的返回值类型,你就能知道为什么之前提到LocalDispatcher是“业务代表”对象了。它将成为应用程序其它组件与service engine打交道的主要代理人(就跟entity engine层的Delegator接口一样),因此在这些方法中实例化的GenericDispatcher对象都向上转型为了LocalDispatcher。

由于它是关键对象创建它的代价比較大。考虑到性能因素GenericDispatcher为全部的dispatcher对象提供缓存。另外一个须要关注的是:GenericDispatcher大量依赖ServiceDispatcher(从其构造器须要注入ServiceDispatcher的实例就能够看出来)。

或者你能够看到以下实现的全部的runXXX接口方法基本都是依赖ServiceDispatcher去运行的。

真正的服务调度器——ServiceDispatcher

上面说到事实上之前的Dispatcher都不是服务的真正运行者。服务的真正运行者是:ServiceDispatcher。千万不要被上面的那些XXXDispatcher后缀的类所欺骗。它们仅仅只是是官方代表而已。ServiceDispatcher并没有继承以及实现不论什么东西。可是它拥有一些强大的部件:
protected Delegator delegator = null;
protected GenericEngineFactory factory = null;
protected Authorization authz = null;
protected Security security = null;
protected Map<String, DispatchContext> localContext = null;
protected Map<String, List<GenericServiceCallback>> callbacks = null;
protected JobManager jm = null;
protected JmsListenerFactory jlf = null;

ServiceDispatcher的构造方式跟GenericDispatcher相似。都是将构造器设置为protected,对外通过静态方法获得实例,同一时候拥有对它自身实例对象的缓存。

事实上。在业务系统中大部分的操作都是跟数据库打交道。而Delegator又是entity层的业务代表,所以ServiceDispatcher很依赖于Delegator。

而服务终于究竟是怎样被运行的呢?终于每一个服务都是依赖于符合它运行条件的运行引擎来运行的!(关于各种引擎,后面会介绍)。上面的关键部件里有个GenericEngineFactory的实例,它用于创建合适的运行引擎。

以下简述一个通用服务的运行过程:
  • 定义或者初始化一堆运行过程中须要用到的參数
  • 检測服务是否带有锁标识:假设有。则创建一个锁,并获得锁对象
  • 创建一个上下文对象,假设服务的运行过程中须要携带參数,将參数都增加上下文对象
  • 检測上下文对象的Locale
  • 对当前service进行log
  • 检測当前服务是否定义有event,假设有,一次全部取出
  • 取得合适的运行引擎
  • 更新上下文对象中,IN 类型參数的默认值
  • 假设该服务被定义为在事务中运行。则启用事务
  • 依次运行“global-rollback”、“global-commit”、“auth”相关event的action。并带出运行结果
  • 检查pre-auth的运行结果是否为失败或错误
  • 检查上下文对象中键值对參数的权限。同一时候设置【key为"userLogin"】的value为【用户信息对象】
  • 检查userLogin相应的用户信息对象。假设权限验证失败,则抛出异常
  • 运行“in-validate”预检查事件,同一时候带出对action的运行结果
  • 检查是否有失败或错误产生
  • 检查全部上下文參数中输入參数是否合法
  • 运行“invoke”事件。同一时候带出action的运行结果
  • 检查是否有失败或错误产生
  • 通过服务运行引擎来运行服务并取得运行结果
  • 通过服务运行引擎发送服务回调。假设运行结果不为空,则将其增加result结果集
  • 检查是否有错误或失败产生
  • 错误处理
  • 又一次构建一个eca的上下文对象
  • 运行“out-validate”事件,同一时候带出结果集
  • 验证结果集中得输出參数是否合法
  • 运行“commit”事件,同一时候带出结果集
  • 检測结果集是否含有失败或者错误
  • 运行“global-commit-post-run”事件。同一时候带出结果
  • 进入异常处理部分:通过服务引擎。发送带异常的回调,回滚事务
  • 进入finally部分:假设有错误。回滚事务,否则提交事务,调用notification,这里的事件是由结果集来决定的
  • 进入外层finally部分:假设锁没有被释放,则释放锁。

    恢复父级事务

  • 运行“return”事件。同一时候带出结果集
  • 返回结果集,方法结束

服务调度的上下文——DispatchContext

在之前的不少地方事实上已经用到过DispatchContext,它为服务的运行提供上下文(主要是一些关键对象及參数)。它主要提供了一堆get訪问器,以及一些辅助方法,比方载入本地servicemap以及globalservice map等。


异步请求器——GenericRequester


该接口定义了两个回调方法:一个用于接受一个map(一般是上面serviceDispatcher的运行结果)。还有一个接受一个Throwable对象。它基本的用途就是在调用runAsync方法的时候(异步运行服务)。将该接口的实现者作为參数传入(能够理解为回调对象)。

基本的过程是这种:

首先,ServiceDispatcher实例调用runAsync方法,在该方法接受一个GenericRequester类型的參数。在方法内部真正的运行引擎会调用它自己的runAsync方法,该接口的实例继续向前传递作为參数。

而运行引擎对该runAsync的实现主要在GenericAsyncEngine中。实现的方式是这种:它终于会用GenericRequester的实例以及一些其它对象创建一个我们以下会提到的GenericServiceJob的实例。终于所谓的Async仅仅只是是转化为了其它线程去独立运行一个Job而已,在该job的exec方法中。dispatcher会同步运行runSync,然后得到结果,提供给GenericRequester调用(上面截图中得方法)。因此这里所谓的runAsync事实上是在另外一个独立的线程上去运行runSync。然后将结果传递给回调对象。运行回调方法而已。

Service中的任务——Job

job在serviceengine中有很关键的数据,除了用于支持定时运行外,所谓的异步运行也是通过job来实现的。

上图为ofbizservice engine中的Job继承链。当中,Job接口定义了在serviceengine中作为一个Job应该遵守的“协议”。协议列表例如以下:

当中有两个关键方法:
  • exec: 定义怎样运行一个job
  • queue: 标识一个job是否“已压入队列”

用于运行服务的抽象Job——AbstractJob


从之前的继承关系图可见,它实现了Job接口,并对其进行了一定的扩展。它提供了一个带两个參数的构造方法,用于注入jobId/jobName。

并额外定义了两个protected的 成员变量:runtime/sequence。

对于最关键的exec方法。此处仍然将其标记为abstract,以待继承者实现。


通用服务Job的异步运行类——GenericServiceJob

它继承了AbstractJob类。并注入了两个“上下文”实例。
  • dctx: DispatchContext类的实例
  • context: Map<String, Object>的实例

同一时候还通过构造器注入了GenericRequester类的一个实例,来达到它异步获取结果的目的。

在exec方法的实现中,它首先通过dctx获得LocalDispatcher类的实例。

然后通过调用其runSync的实例方法来获得一个Map的结果对象。假设运行结果出现错误,则进入错误处理流程,否则通过GenericRequester的实例requester来接收结果。

大致的方式就是在还有一个线程上同步运行服务。

在此基础上,GenericServiceJob又定义了一个public方法:getServiceName。后面我们会看到它的继承类会override该方法。

对持久化的job的调度运行——PersistedServiceJob

该类实现了entity service job。

支持将job持久化到数据库中。然后经过调度再运行。这里牵扯到数据库中的一张表:JOB_SANDBOX(该表位于数据库ofbiz中)。它用于存储持久化到数据库中的job信息。

因此该类的构造函数包括一个GenericValue类型的实例。用来表示JOB_SANDBOX中的一条job信息(在之前的讲ofbiz entity engine的文章中,我们提到过GenericValue,它用于表示一个通用的数据实体)。

queue方法的实现:

首先。它调用父类的queue方法(父类的实现。没有什么特别的意义)。然后它通过又一次获得该job对象,并显式刷新它(以防缓存等导致数据不一致),假设产生异常,则直接将runtime设置为-1,在AbstractJob中对isVaild的实现么。假设runtime小于0,则该方法返回false。接着。假设通过了验证。我们还须要check该job的“cancelDateTime”、“startDateTime”属性。假设它们两个其一不为null则说明该job不可用(已经运行过了)。仍然将runtime设置为-1。否则就设置startDateTime,statusId并将其持久化到数据库。

还有一个关键方法exec,在这里没有被override。而是沿用父类的实现。

但该类提供了许多私有的辅助方法。来处理相关逻辑。

比方怎样初始化,假设重试,假设处理失败以及完毕逻辑。

job调用运行器——JobInvoker

JobInvoker专门用于运行job。

它提供多个接口用于对job的运行进行控制,包括:开启运行、停止运行、唤醒、杀死运行线程、以及提供对一系列运行參数的获取。

它实现了Runnable接口。可见它利用多线程技术来运行job。

构建它须要至少一个參数:JobPoller的实例(JobPoller是后面要谈到的job的轮询器)。

来看下它的构造方法:

public JobInvoker(JobPoller jp, int wait) {
        this.created = new Date();
        this.run = true;
        this.count = 0;
        this.jp = jp;
        this.wait = wait;

        // service dispatcher delegator name (for thread name)
        String delegatorName = jp.getManager().getDelegator().getDelegatorName();

        // get a new thread
        this.thread = new Thread(this);
        this.name = delegatorName + "-invoker-" + this.thread.getName();

        this.thread.setDaemon(false);
        this.thread.setName(this.name);

        if (Debug.verboseOn()) Debug.logVerbose("JobInvoker: Starting Invoker Thread -- " + thread.getName(), module);
        this.thread.start();
    }

在初始化了相关參数之后。它将自身实例用于构造它的内部thread变量,在将线程显式设置为非daemon之后。即刻启动该线程的运行。

这种构建方式能够使得从对象被创建開始,它就以一个独立的线程開始运行,同一时候由于该thread成为它的一个内部成员变量,它拥有对该thread的全然控制权。

在正常情况下,run方法内运行着一个while循环,它首先从jobPoller获得下一个待运行的job,假设为空(代表临时没有等待运行的Job)则进入等待状态。假设获取到Job则运行。

持久化Job的轮询器——JobPoller

JobInvoker负责job的调用运行,在jobinvoker中我们看到它是通过jobpoller的next方法获得即将运行的job的。所以,真实提供job的是JobPoller!

在JobPoller内部维护着两个关键变量:
  • pool:一个“线程池”。它是List<JobInvoker>类型的。前面提及到由于JobInvoker是一个独立的线程,所以pool是它的一个集合。
  • run:是一组待运行的job集合

在构建jobPoller的时候,通过构造方法注入一个JobManager的实例(在run方法中。真正获取job的就是JobManager的实例)。在初始化相关參数之后。JobPoller以跟JobInvoker同样的方式启动线程。

线程启动。运行run方法。它首先睡眠30s(这能够让它等待jobManager又一次载入crashedjob)。然后通过jobManager获取一组joblist。将他们一个个入队,假设产生不论什么异常则运行stop动作。

以下我们来看入队流程:

首先。它将当前处理的job增加上面的run变量中。然后依据当前run的size以及pool的size又一次计算是否须要扩大pool(创建新的jobInvoker,来增加吞吐量)。

另外pool里的每一个jobInvoker实例都是从jobpoller的next方法获得job,而next方法就是从run集合里删除并返回一个job。

pool的初始化是通过createThreadPool方法来完毕的。它会创建最小数量的JobInvoker。而上面我们也提到过,JobInvoker是创建即运行的线程。

job 的控制中心——JobManager

上面有提到过JobManager。事实上它是 “传输对象”模式的应用。

它集成了JobPoller/Delegator等关键对象来完毕对job的管理。

提供的功能包括:触发运行一个job、获取job list、以及创建schedulejob、获取job运行的相关运行时信息、对job运行器的控制等。

引擎的接口抽象——GenericEngine

是时候关注serviceengine中那些默默无闻的工作者——服务运行引擎。上面说到ServiceDispatcher是真实的运行服务的所在地。事实上,说到最后它还是借助于这些引擎来完毕的!

GenericEngine定义了引擎的一些契约方法。主要包括了服务的同步/异步调用。以及发送服务回调方法。



能够看到全部runXXX方法的第一个參数都是类型为String。形參名为localName的參数。该參数用于传入指定的LocalDispatcher的名称。

第二个共同拥有參数ModelService类型的实例:它是一个通用的服务Model封装类,封装了运行一个服务所须要的一切(后面会提到)。第三个共同拥有參数为一个上下文对象。

抽象运行引擎——AbstractEngine

AbstractEngine作为一个抽象的服务引擎。实现了GenericEngine。这个实现主要提供了两个保护级别的变量:
  • dispatcher:ServiceDispatcher的实例
  • locationMap:一个map,用于存储servicename跟该service的location之间的相应关系
事实上GenericEngine还有一个实现类:InterfaceEngine。这里称之为接口引擎,但由于在ofbiz中很少用到,所以这里不作过多介绍。

JMS服务引擎——JmsServiceEngine

该实如今AbstractEngine的基础上,提供了几个跟jms相关的方法:
  • runTopic - 以jms的“topic”模式运行(publish/subscribe)
  • runQueue - 以jms的“queue”模式运行
  • runXaQueue - 以jms的“xaQueue”模式运行
当然还有一些辅助方法。比方查找服务列表,构建消息对象等。


通用异步引擎——GenericAsyncEngine

该抽象类是对AbstractEngine的两个实现者之中的一个(还有一个就是刚刚提到的JmsServiceEngine)。

同一时候。该抽象类也是众多子引擎的公共父类。

它将runSync的一系列重载方法标识为“Abstract”。仅仅实现了runAsync相关的方法。

简单来理一下对runAsync方法的实现:

假设该服务/job是须要持久化:

它首先往数据表RUNTIME_DATA中插入一条数据。并将上下文对象序列化入字段“runtimeInfo”。同一时候往表JOB_SANDBOX中插入一条数据

假设无需持久化。它直接构建一个GenericServiceJob的实例运行job。

Entity服务引擎——EntityAutoEngine

该类提供对entity engine的实现,主要针对entity的CRUD操作。它继承了上述的GenericAsyncEngine。并不表示它在语义上就被定义为async运行模式的引擎,而事实上这里的继承基本的作用是代码复用(从而不必多次实现runAsync),下同。


SOAPclient引擎——SOAPClientEngine

内部的serviceInvoker方法。主要提供了对调用remotesoap 服务的实现。而该方法内部採用apacheaxis 2来实现远程soap服务的调用实现。

脚本引擎——ScriptEngine

这是一个脚本引擎,用于对多种脚本语言提供支持。

它基于J2SE提供的ScriptEngine来完毕脚本的运行。

在方法内部会封装一个上下文參数,将其传递给Java提供的ScriptEngine,终于通过调用Java提供的ScriptEngine的eval方法。

服务组引擎——ServiceGroupEngine

这个引擎在ofbiz中没有使用,不作过多介绍。

标准Java方法服务引擎——StandardJavaEngine

该引擎用于运行标准的Java方法。在内部它通过反射来实现。首先通过传入的ModelService的实例。获得其定义的location,再通过classloader载入服务类的Class实例,终于通过反射运行给定的方法。

Groovy脚本服务引擎——GroovyEngine

该引擎用于支持groovyscript在ofbiz上运行,ofbiz中对groovy的依赖还是蛮大的。GroovyEngine与ScriptEngine有些相似。须要提供script的location。以及运行的參数作为上下文。

最后通过第三方groovy运行时实现运行groovyscript。

HTTP 引擎——HttpEngine

该引擎用于运行httppost请求,并返回请求结果。

该service的location是其URL。

通过传递该service的location以及相关的參数,构造一个HTTPClient的实例来触发post请求。

BeanShell引擎——BeanShellEngine

这个不作过多介绍,在ofbiz使用groovy作为数据訪问的脚本语言之前,主要使用beanshell做这件事。但后来被groovy所替代。


BSF 引擎——BSFEngine

BSF 是一系列的Java类的集合,它为Java应用程序提供对脚本语言的支持。

在runSync内部。它借助apache的BSFEngine来运行。


XML RPC引擎——XMLRPCClientEngine

ofbiz提供了对RPC的支持。在实现的时候,首先有个对client配置的封装类,须要设置基本的配置信息,比方身份认证。

然后依据配置信息创建一个XmlRpcClient。

它封装了详细的实现细节(详细内部也是通过apache的xmlrpcclient库来实现的)。终于调用XmlRpcClient实例的execute方法。

Rmi 服务引擎——RmiServiceEngine

RmiServiceEngine的实现借助于ofbiz的RemoteDispatcher。顾名思义。它是一个remotedispatcher。但它仅仅是定义了远程方法调用的接口。

RmiServiceEngine先通过service的location查找到属于该service的RemoteDispatcher然后调用它的runSync方法。


通用消息监听器——GenericMessageListener

GenericMessageListener是ofbiz service engine里关于jms的顶层接口。

它继承了j2ee规范中关于jms的MessageListener(该接口须要实现一个方法:onMessage)。

而GenericMessageListener又定义了几个新的接口方法:


  • close:关闭listener以及全部的连接
  • load:启动listener以及全部的连接
  • refresh:刷新连接
  • isConnected:推断连接是否可用

Jms listener的抽象实现——AbstractJmsListener

AbstractJmsListener实现了GenericMessageListener以及j2ee的ExceptionListener接口。

在构造方法中,它初始化了一个LocalDispatcher类的实例。

对onMessage方法。它在接受到message之后,首先推断它是否是MapMessage的实例,假设是则运行runService方法。

而runService方法接受的參数就是终于在onMessage中拆封服务信息的message,然后通过localDispatcher运行。

refresh方法的实现很easy,先close然后再load

onException方法在产生异常的时候将会被触发。首先,它先将当前连接的状态设置为close状态。

然后一直不停得调用refresh方法。直到isConnected方法返回true。当然,一旦产生异常就会睡眠10秒钟。

Jms topic listener——JmsTopicListener

jms将消息传递模式分为两大类:topic/queue,当中topic模式支持publish/subscribe。

JmsTopicListener继承自AbstractJmsListener,又一次实现了两个方法:

  • close:除了关闭连接,它还须要关闭topicsession
  • load:做一些初始化动作,同一时候開始建立连接
在load方法中,先通过jndi找到相关定义,然后创建topicsubscriber

Jms queue listener——JmsQueueListener

基本的实现同JmsTopicListener,它也又一次实现了close/load方法,仅仅只是不同的是:在load方法中。创建的是queuereceiver。

Jms listener创建工厂——JmsListenerFactory

JmsTopicListener与JmsQueueListener的构造器的凝视中说明了:建议不要自己手动创建它们。而是通过JmsListenerFactory来创建。

JmsListenerFactory实现了Runnable接口,因此它利用的是多线程技术。在它的构造器中,他就会启动自身实例的run方法。并将自身设置为非daemon线程。在启动之后,它会依据xml定义,去创建listener。另外,它还定义了关闭/刷新listener的方法。

远程调用的dispatcher接口——RemoteDispatcher

之前我们谈到ofbiz支持的众多service engine中就有一个RmiServiceEngine,它用于远程方法调用。

而它的运行则依赖于RemoteDispatcher。

之所以最開始我们介绍的“业务代理”称之为LocalDispatcher。就是为了跟这边的RemoteDispatcher以示差别。

RemoteDispatcher实现了JDK提供的Remote接口(该接口未提供不论什么接口方法的定义,属于标记接口)。

在RemoteDispatcher中定义了一系列运行service的方法:


RemoteDispatcher的直接实现者——RemoteDispatcherImpl

RemoteDispatcherImpl实现了RemoteDispatcher接口。同一时候它还继承了UnicastRemoteObject(UnicastRemoteObject来自于jdk的rmi包,用于负责跟远程对象通信)。而事实上真正的方法运行者,还是通过构造方法注入的一个LocalDispatcher的实例。


服务锁实现的关键——ServiceSemaphore

跟经常使用的相互排斥訪问资源的机制相似。这里对锁的实现也是通过信号量的原理。

因此,在ofbiz中服务能够具有具有锁的特性,它就是通过ServiceSemaphore实现的。

ofbiz眼下对service定义有三个信号:

  • fail
  • wait
  • none
锁相关的方法:

ServiceSemaphore是通过将“锁”存储于数据库来实现的。这能够从它的lock变量看出来。

而存储“锁”的逻辑被封装在dbWrite方法:它接受两个參数:

  • value - 这个是当前锁的entity对象
  • delete - 标识是否删除锁的bool值

整个对锁的数据訪问操作都被包裹在事物中,依据delete来推断是删除还是创建锁。

对锁检查看是否须要等待的逻辑实如今方法:checkLockNeedToWait中。首先从数据库中去获取当前model对象的锁,假设没有则创建(创建即表示当前service拥有了锁)并返回false。

假设找到则返回true表示须要等待该service的锁被释放。

而详细对等待还是失败信号的处理位于方法waitOrFail中,假设当前servicemodel支持的是fail信号。则直接抛出一个异常。假设支持wait信号则在同意范围内不断尝试检測该service的锁是否被删除。

直到超时或者锁从数据库中删除。假设是由于超时则抛出一个异常。

好了,谈了这么多,上面的这些方法都是为两个基本的公共方法服务的:
  • acquire: 获得锁(假设锁不存在。则新建,新建后视为拥有;假设锁已存在。则锁住当前service的运行)
  • release: 释放锁(从数据库中删除锁记录)

服务參数——ModelParam

服务方法不都是无參数的,有些服务的运行须要依赖外部參数。那么在定义服务的时候就须要给出它依赖的參数相关的信息,形如以下的定义:
<service name="lookupRoutingTask" engine="java"
            location="org.ofbiz.manufacturing.techdata.TechDataServices" invoke="lookupRoutingTask" auth="true">
        <description>Only used to defined the query lookup for Routing Task</description>
        <attribute name="workEffortName" type="String" form-display="true" form-label="${uiLabelMap.ManufacturingTaskName}" mode="IN" optional="true"/>
        <attribute name="fixedAssetId" type="String" form-display="true" form-label="${uiLabelMap.ManufacturingMachineGroup}" mode="IN" optional="true"/>
        <attribute name="lookupResult" type="List" mode="OUT" optional="false"/>
    </service>

当中的attribute节点就是服务的參数节点。而该节点信息映射到程序中的model就是ModelParam。

实现服务接口的定义——ModelServiceIface

在服务的定义中。是同意一个服务实现还有一个接口服务的。比方系统中有个关于权限检查的接口服务:permissionInterface。

其它许多服务都实现了该接口,而一个服务实现还有一个接口服务须要给出“实现”的定义,在程序中相应的model即为:ModelServiceIface。相关配置形如:

<service name="basicGeneralLedgerPermissionCheck" engine="simple"
            location="component://accounting/script/org/ofbiz/accounting/permissions/PermissionServices.xml" invoke="basePermissionCheck">
        <description>Basic General Ledger Permission Checking Logic</description>
        <implements service="permissionInterface"/>
    </service>

响应全部服务的消息通知——ModelNotification

该实体类封装了位于${project_base}/framework/service/config/serviceengine.xml中的关于通知分组的定义。

所谓的通知组(notificationgroup)可用于响应全部服务的消息(眼下仅仅支持sendMailFromScreen的响应方式)。

通常情况下通知能够邮件的形式发出,所以其拥有处理邮件相关属性的方法比方获取from、to、cc、bcc等属性。发出通知的关键方法是:callNotify(它会构建通知的上下文,然后运行sendMailFromScreen服务).

service编程级别的权限实现——ModelPremGroup/ModelPermission

在之前一篇关于ofbiz中权限模型介绍的文章中,我以前提到过ofbiz有个权限控制级别为——service编程级别。详细的实现就在这里。

通过ofbiz的miniLang,你能够给service配置相应的权限。比方以下就是一种简单权限的配置:

<required-permissions join-type="AND">
            <check-permission permission="SERVICE_INVOKE_ANY"/>
        </required-permissions>

当中:required-permissions节点名称用于表示一个权限组的节点,在程序中用ModelPremGroup表示。

它内部能够包括多种权限,比方:
  • check-permission
  • check-role-member
  • permission-service

服务的权限在这里相应的model是:ModelPermission。

当然permission-service权限不一定要包括在ModelPremGroup里,它也能够独立存在。

这里,能够简单理解ModelPremGroup是ModelPermission的集合,与此同一时候ModelPremGroup还存储了ModelPermission之间的join关系(and /or)这个能够理解为多条件的逻辑表达式。

ModelPremGroup仅仅包括一个关键方法:evalPermissions。它会依据join关系以及每一个ModelPermission得出的bool结果计算终于的逻辑表达式结果。有点相似权限1join 权限2join 权限3(join:and / or)

而详细对每一个权限的验证则在ModelPermission里通过方法:evalPermission完毕。

它是一个对外的接口,内部有四个私有方法分别用于计算不同的权限:

第一个是检查认证权限。第二个用于检查对entity的操作权限。第三个用于检查角色。第四个用于权限服务(PermissionService)。

关于ECA——Rule& Condition & Action

除了普通的service还有一种广泛使用的基于事件和条件触发的service。它就是——SECA(Service event condition action)。来理解一下怎样的场景会使用这种service。首先须要了解。它是基于事件或者规则触发的。我们来看一个场景描写叙述:当你离开房间的时候,看一下有没有下雨。假设下雨了。带着伞。

这里e就是:当你离开房间时,它表示一个事件。c就是看看是否下雨了,它表示触发的条件。a就是:带着伞。这就是action。

在ofbiz的业务系统中包括许多的eca。它们都定义在:${project_baseDir}/applications/${project}/servicedef/secas.xml中。

我们来关注一下它在service层的表现方式。

对于某个eca的定义在service层相应的model为:ServiceEcaRule,它拥有一个名为eavl的方法。用于计算全部condition的值。

触发的条件相应的model为:ServiceCondition。它也包括一个名为eval的方法。用于计算当前Condition的值(Condition可能为某个service也可能为计算表达式)。

对于action的抽象,相应的model为:ServiceEcaAction。它包括一个runAction方法。用于运行action(事实上action也是某个service)。

eca package中还包括一个工具类:ServiceEcaUtil,它定义了一些辅助方法。

一个常见的eca定义形如:

<eca service="updateCreditCardAndAddress" event="in-validate">
        <condition field-name="expMonth" operator="is-not-empty"/>
        <condition field-name="expYear" operator="is-not-empty"/>
        <action service="buildCcExpireDate" mode="sync"/>
    </eca>

服务定义的解析器——ModelServiceReader

以上谈到的不管是引擎、消息通知还是权限都是定义或者配置在XML里的。

这样就须要一个对定义文件的解析器。它就是ModelServiceReader。它会解析服务定义文件的各个组成元素:


运行时的Service实体——RunningService

在运行service的时候。有可能会对它的运行状态进行保存以便在应用程序中获取(比方记录日志等)。它包括了当前服务的实体对象(ModelService)的实例、服务名称、模式、開始运行的时间戳、停止运行的时间戳。

服务相关的辅助类——ServiceUtil

ServiceUtil是service engine的辅助方法,包括了这样几个方面:
  • 服务返回结果的推断
  • 构造服务返回结果对象
  • 构造不同的状态信息
  • 改动/操作job的状态
  • 其它辅助方法

服务运行时事务包装器——ServiceXaWrapper

该类对运行时服务提供事务支持。它继承自我在上篇文章中谈到的entity engine中的GenericXaResource(底层依赖于Java自带的事务机制)。

毫无疑问,有些服务依据其业务特性。他们须要事务的支持,比方在事务commit或rollback时我们须要去运行特定的服务。

ofbiz service engine也对事务进行了支持。事务的终结大概分为两种方式:commit/ rollback。

我们来看看ServiceXaWrapper是怎样支持事务的。

两个关键方法:


它们负责触发事务不同的终结动作,但并不真正去运行事务,而是收集它须要的一些字段信息(比方服务的名称,服务须要的上下文參数。服务的运行模式是同步还是异步),然后再开启一个独立的线程去以事务的机制运行特定的服务方法。

commit以及rollback所收集的那些字段是通过什么方法设置的?ServiceXaWrapper对外提供了多个重载的setter:

事实上,上面的方法都仅仅是在做一些外围动作,真正的运行方法是runService方法(上面的commit/rollback方法中会以一个独立的线程来运行此方法)。而且服务的运行真正被包裹在事务中。终于它是通过dispatcher的runAsync或runSyncIgnore来真正运行服务的,终于事务会被commit。(须要注意的是,此处的commit是事务真实运行的commit。此处没有rollback。

上面提到的ServiceXaWrapper提供的commit/rollback方法,能够理解为当某个事务commit/rollback时,须要运行的服务,而不是技术层面上的commit/rollback。

既然是须要运行的服务,终于都应该被commit。这边有些绕,须要分清楚,哪种是技术上的事务动作,哪种是业务上的动作名称,另外这里的事务主要指的是分布式事务,它们拥有一个Xid来作为事务的唯一标识)。

通用的服务model——ModelService


之前一篇解说entity engine里提及的ModelEntity一样。这边的ModelService也是作为传输对象。而不仅仅仅仅是一个model。

当然了。之前所提及的Service定义文件中的一些属性的model也被增加到ModelService中来。另外一个值得关注的是ModelService实现了AbstractMap,这也表明了它自身还是一个map(map中包括的成员都是服务定义文件中的服务节点的属性以及子节点)。

ModelService大概包括这样几类方法:
  • AbstractMap 定义的方法
  • 提供对modelservice中各种属性不同的筛选以及获取方法
  • 对数据的验证方法
  • 对权限以及消息通知的计算