服务的实现
一些服务类型有本地的实现,其他一些没有实现(接口)或服务定义是其他东西的一个代理、位置指向一个外部的实现(remote-xml-rpc, remote-json-rpc和camel)。远程和Apache Camel类型在系统接口章节详细描述
服务脚本
除了为实体CRUD操作的自动实现外,脚本通常是实现一个服务的最好方式。当缓存被清理时脚本被自动重新加载,并且在开发模式,这些缓存默认在很短的时间内过期以获得自动更新。
脚本能非常有效地运行,特别是 Groovy 脚本,在运行时编译成Java类并且以编译后的形式缓存,这样他们能快速运行。XML Actions脚本被转换成Groovy脚本(详情请查看 XmlActions.groovy.ftl 文件),然后被编译及缓存,所有性能上就像纯 Groovy 脚本。
Resource Facade能运行的任意脚本能被用作服务的实现。详情请查看渲染模板与运行脚本 章节。总的来说,默认支持的脚本有Groovy, XML Actions和JavaScript。可通过javax.script或Moqui的特定接口来支持任意的脚本语言。这是一个用Groovy脚本实现的服务的例子,定义在 org.moqui.impl.EmailServices.xml 文件中。
<service verb="send" noun="Email" type="script"
location="classpath://org/moqui/impl/sendEmailTemplate.groovy" allow-remote="false">
<implements service="org.moqui.EmailServices.send#EmailTemplate"/>
</service>
此例中,location是一个classpath位置,但是任意Resource Facade支持的位置都可以用。关于如何指向组件内的文件、在本地文件系统甚至普通的URL的详情,请查看资源位置。
在一个脚本的开始,所有的入参被传递给服务,或设置为在服务定义中定义的默认值,将被作为字段放在上下文中,以供在脚本中使用。像用Moqui中的其他构件,也有一个ec字段是当前的 ExecutionContext 对象。
注意脚本有一个上下文,是用 ContextStack.pushContext() 和 popContext() 方法和调用者隔离的,意味着不仅是在上下文中创建的字段不会在服务运行后保存,服务也访问不到调用者的上下文,即使它在本地运行并且和调用者使用同一个 ExecutionContext。
方便起见,上下文中有一个Map<String, Object>类型的结果字段。你可以将出参放入此Map以返回,但是这么做不是必要的。脚本运行后,脚本服务运行器会在上下文中查找服务上定义的出参,并将它们加到结果。脚本也能返回(求值)一个Map对象以返回结果。
内联动作
在 服务定义 中的例子展示了默认的服务类型,内联(inline)。此例的实现在 service.actions 元素中,该元素包含一个 XML Actions 脚本。它被当作就像由服务位置指向的外部脚本,但是为了简单和减少要处理的文件数量,它能被内联到服务定义中。
Java方法
一个服务实现也可以是一个Java方法,类(静态)方法或者对象方法都可以。如果方法不是静态的,服务运行器使用默认(无参)构造函数创建一个此对象的新实例。此方法必须有单个ExecutionContext变量并且返回Map<String, Object>,此方法的签名像是这样:
Map<String, Object> myService(ExecutionContext ec)
实体自动服务
Moqui有entity-auto类型的服务,你不必实现此服务,实现是自动基于 verb 和 noun 属性的值的。动词(verb)可以是 create、update、delete或store(如果记录不存在则创建(create),如果存在则更新(update))。名词(noun)是一个实体的名字,有包名的全名或只是简单的没有包名的实体名。
实体自动服务可以只是通过调用一个名为${动词}#${名词}、不带路径(包或者文件名)的服务来隐式(自动)地定义。例如:
ec.service.sync().name("create", "moqui.example.Example").parameters([exampleName:’Test Example’]).call()
定义一个服务并且使用entity-auto实现时,可指定使用哪个输入参数(必须匹配实体上的字段),不管它们是否必须、默认值等。当使用隐式定义的实体自动服务时,它基于传递给服务调用的内容来决定行为。上面的例子中,没有传入exampleId参数,而它是moqui.example.Example实体的主键字段,所以会为此字段自动地生成一个序列ID并作为出参返回。
对于创建操作,除了自动生成缺少的主序列ID外,如果实体的主键由主次两部分组成,且指定了主的未指定次的,也会生成次序列ID。如果有一个fromDate主键字段没有传入,也有特殊的行为,会使用当前时刻来填充它。
更新操作的模式是传入所有的主键字段(这是必需的)和需要的任意非主键字段。对于更新操作也有特殊的行为。如果实体有statusId字段并且传入了statusId参数,值有变化时,将自动在oldStatusId出参中返回原始(数据库中)的值。实体有statusId字段时,也会返回一个statusChanged的boolean参数,如果statusId参数值和原始(数据库)的值不一样则为true,否则为false。实体自动服务也会通过检查是否有moqui.basic.StatusFlowTransition记录存在且指向有效的状态转换。如果没有发现有效的转换,将会返回一个错误。
添加你自己的服务运行器
要添加自己的服务运行器,有自己的服务类型,实现 org.moqui.impl.service.ServiceRunner 接口并添加一个 service-facade.service-type 元素到Moqui配置XML文件中。
ServiceRunner接口有3个方法要实现:
package org.moqui.impl.service
import org.moqui.service.ServiceException
interface ServiceRunner {
ServiceRunner init(ServiceFacadeImpl sfi);
Map<String, Object> runService(ServiceDefinition sd, Map<String, Object> parameters) throws ServiceException;
void destroy();
}
这是一个来自 MoquiDefaultConf.xml 文件的 service-facade.service-type 元素的例子:
<service-type name="script" runner-class="org.moqui.impl.service.runner.ScriptServiceRunner"/>
service-type.name 属性和 service.type 属性匹配,runner-class 属性很简单,就是实现 ServiceRunner 接口的类。