架构师:SOA
使用 Oracle Service Bus 的新特性连接传统代码
作者:Francesco Marchioni
了解如何使用 Oracle Service Bus 3.0 的新特性和内置的 Eclipse 设计环境重新散列传统代码。
2008 年 7 月发布
在 3.0 版出现前,只能使用基于 Web 的控制台设计所有的 Oracle Service Bus(以前的 BEA AquaLogic Enterprise Service Bus)资产(如业务服务和代理服务)。在 3.0 版中,您既可以选择使用控制台,也可以选择使用新的 Eclipse 插件在 WorkSpace Studio 中设计所有资产。
在本文中,我还将介绍如何使用流程业务语言 (PBL) 将全新的 Oracle Service Bus 服务与 Oracle BPM 6.0 集成。
使用传输类型的服务:EJB
如果您在近几年开发了 Java 企业解决方案,则可能会有一些编码为 EJB 的旧有内容。将 EJB 发布到其他服务的典型解决方案是使用 Java Web 服务来包装 EJB 并将其作为服务发布。现在,可以使用 Oracle Service Bus 将 EJB 定义为业务服务并从其他服务直接调用。本文中的示例将演示调用无状态会话 bean 的 SOAP 代理服务。
我将为不熟悉代理服务和业务服务概念的读者提供一些简要的定义。代理服务是 Oracle Service Bus 在 WebLogic Server 上本地实施的中间 Web 服务的 Oracle Service Bus 定义。
一个代理服务可以将消息传输到多个业务服务;可以选择使用独立于与代理服务通信的业务服务的接口来配置代理服务。在这种情况下,需要配置信息流定义以将信息传输到相应的业务服务并将消息数据映射成业务服务接口要求的格式。
要使 EJB 代码可用,需要定义一个业务服务:业务服务是通常由非 Oracle Service Bus 服务器实施的远程服务或外部端点。它们是您要与之交换消息的企业服务的定义。
因此,假设我们有一些已有代码,它们连接到 RDBMS 并收集某条线路的航班信息。我们来构建一个包含将导出到 Oracle Service Bus 的业务方法的示例 EJB 项目:
package sample.ejb;
import java.sql.*;
import javax.ejb.SessionBean;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import weblogic.ejb.GenericSessionBean;
import weblogic.ejbgen.RemoteMethod;
import weblogic.ejbgen.Session;
import weblogic.ejbgen.JndiName;
import weblogic.ejbgen.FileGeneration;
import weblogic.ejbgen.Constants;
/**
* GenericSessionBean subclass automatically generated by Workshop.
*
*/
@Session(ejbName = "FlightEJB")
@JndiName(remote = "ejb.FlightEJBRemoteHome")
@FileGeneration(remoteClass = Constants.Bool.TRUE, remoteHome =
Constants.Bool.TRUE, localClass = Constants.Bool.FALSE,
localHome = Constants.Bool.FALSE)
public class FlightEJB extends GenericSessionBean implements SessionBean {
private static final long serialVersionUID = 1L;
/* (non-Javadoc)
* @see weblogic.ejb.GenericSessionBean#ejbCreate(
*/
public void ejbCreate() {
// IMPORTANT: Add your code here
}
public Connection getConnection() {
// Get connection here .....
}
@RemoteMethod
public long getFlightPrice(String strSymbol) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
long quote = 0;
conn = getConnection();
try {
stmt = conn.prepareStatement("SELECT flight_price
FROM flights where flight_line = ?");
stmt.setString(1, strSymbol);
rs = stmt.executeQuery();
rs.next();
quote = rs.getLong("flight_price");
} catch (SQLException e) {
e.printStackTrace();
}
finally {
closeConnection(conn, stmt, rs);
}
return quote;
}
private void closeConnection(Connection conn, Statement stmt,
ResultSet rs) {
if (rs != null) {
try { rs.close(); } catch (SQLException e) { ; }
rs = null;
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException e) { ; }
stmt = null;
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { ; }
conn = null;
}
}
}
代码很简单:只需使用业务方法 getFlightPrice 返回所选航班的价格。
@RemoteMethod
public long getFlightPrice(String strSymbol) {
......
}
构建并部署项目后,需要使 EJB Client jar 文件可用于我们的 Oracle Service Bus 项目。创建 EJB Client jar 文件的推荐方法为:从项目的根部,右键单击“Export EJB jar File”。现在,EJB 模块就准备好了,可以接着进行 Oracle Service Bus 配置。
所有的 Oracle Service Bus 项目都需要一个 Oracle Service Bus 配置项目;因此,我们首先创建这个项目。
因为您可能不打算在服务总线所在的服务器上部署 EJB,所以需要添加一个新的 JNDI 提供程序到配置中。设置 JNDI 提供程序很简单。从主菜单就可以创建一个新的 Oracle Service Bus 配置。在 Oracle Service Bus 配置项目内,只需右键单击并添加一个 JNDI 提供程序。为简单起见,在本例中,我们选择了“t3://localhost:7001”作为提供程序 URL。
现在,我们有了最低配置,可以开始创建新的 Oracle Service Bus 项目了。右键单击项目浏览器并选择“Create new Oracle Service Bus project”。为项目选择一个名称。
在创建代理服务和业务服务前,需要在项目中导入 EJB 类和 EJB 的 WSDL 文件。此处,我们将不赘述 WSDL 的创建细节:简要的说,Web 服务描述语言 (WSDL) 是基于 XML 的语言,用来描述 Web 服务和访问它们的方法。为我们的 EJB 获取 WSDL 的最快方法可能是通过 @WebService 表示将 EJB 转变为 Web 服务,然后从其生成 WSDL。
现在,我们在项目的根文件夹下创建两个文件夹: JAR 和 WSDL。这两个文件夹将存放我们刚才创建的文件。
首先,我们来创建业务服务。这里,我们将看到创建新服务是多么快速简单!只需创建一个新的业务服务,选择“Transport Type Service”作为服务类型,然后在下一屏幕 (Transport Configuration) 中,选择“ejb”作为传输协议。
需要为业务服务定义一个端点 URI。由两个参数组成:JNDI 提供程序名称和 EJB JNDI 名称。在本例中(见图 1),将此设置为:
ejb:localProvider:ejb.FlightEJBRemoteHome
图 1. 传输配置
添加端点 URI,然后进入 Transport Information 页面(见图 2)。此处,在“Client Jar”文本框中,我们只能选择此前包含到项目中的 jar 文件。您将看到插件自动发现 EJB 的 home、远程接口和本地接口。选择一个目标命名空间(在本例中,我们选择 http://www.bookflight.org/),然后验证已选择 getFlightPrice 方法。
图 2. 协议相关的传输信息页面
下一步是构建代理服务。从根菜单创建一个“new Proxy service”,然后选择一个方便的名称,例如 FlightProxyService。在下一屏幕中,为代理服务选择服务类型。选择“WSDL Web service”并浏览至 WSDL 文件夹下的资源。
已经基本完成。在下一屏幕中,选择“HTTP”作为协议,选择“/FlightProxy”作为端点 URI。在下一屏幕中接受默认值。
最后一步是消息流,负责将消息从代理服务传输到业务服务。需要注意的是这里有个小小的神奇之处:需要将请求传输到 EJB getFlightPrice 方法,然后用方法的返回值替换 SOAP 的正文值。
因此,创建一个到 getFlightPrice 方法的新路由。在请求路由内,需要定义两个操作: assign 操作和 replace 操作(见图 3)。
图 3. FlightProxyService 路由和操作
在 assign 操作中,定义一个名为 price 的变量,其值为:
$body/Oracle Service Bus:getFlightPriceRequest/Oracle Service Bus:strSymbol/text()
此表达式指向 getFlightPrice 方法的参数。
最后一步是替换 SOAP 消息的正文,让 getFlightPrice 返回 price 变量的值。添加一个 replace 操作,选择 body 作为变量并添加此表达式:
<bookFlight:getFlightPrice>
<bookFlight:strSymbol>{$price}</bookFlight:strSymbol>
</bookFlight:getFlightPrice>
如果不明白 bookFlight 的含义请不要着急。此处,我们只是定义了一个命名空间 ( bookFlight) 以使表达式有效。可以在插件内的多处定义命名空间,我们选择在 replace 操作窗口定义。只需添加一个名为 bookFlight 的命名空间,使其指向已定义为目标命名空间的命名空间 (http://www.bookflight.org/)。请勿忘记尾随斜线!
现在可以发布我们的服务配置了。可以直接从插件测试:右键单击 Proxy Service,然后选择“Run on the Server”。系统会要求您插入 getFlightPrice 方法的参数。
<Oracle Service Bus:getFlightPriceRequest xmlns:Oracle Service Bus="http://www.Oracle Service Bus.com">
<Oracle Service Bus:strSymbol>AZ1234</Oracle Service Bus:strSymbol>
</Oracle Service Bus:getFlightPriceRequest>
如果一切配置得当,会返回包含 getFlightPrice 方法结果的 SOAP 信封。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"/>
<env:Body xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<m:getFlightPriceResponse xmlns:m="http://www.bookflight.org/">
<m:return>225</m:return>
</m:getFlightPriceResponse>
</env:Body>
</soapenv:Envelope>
旧有组件是 POJO 怎么办?
可以从代理服务内部调用自定义 Java 代码以扩展 Oracle Service Bus 在组织中的功能。Oracle Service Bus 通过可以对普通传统 Java 对象 (POJO) 进行标准的 Java 标注操作来支持 Java 退出机制。从 POJO 只能访问静态方法。设计时,POJO 及其参数在 Oracle Service Bus 控制台中可见;可以将参数映射到消息上下文变量。
本次练习中,我们偏重于演示 POJO 的调用模型而非重复演示之前的集成。假设我们需要对使用 POJO 编写逻辑代码的参数做一些自定义验证:
package sample.pojo;
public class GenericValidator {
class CustomValidator {
public int validate(String parameter) {
if (parameter.equals("correct")) {
return 0;
}
else {
return -1;
}
}
}
public static int customValidator(String parameter) {
CustomValidator customValidator = Factory.customValidator();
int code = customValidator.validate(parameter);
return code;
}
}
创建 Java 项目并导出 jar 资源后,我们将创建一个新的 Oracle Service Bus 项目。本例和前例的主要差异在于代理服务。因为请求和响应不使用 XML 通信,所以选择 text 作为消息类型。
要收集 POJO 的返回值,需要创建一个管道对(见图 4)。管道对有请求和响应分支。在请求分支,使用 Assign 操作在 paramIn 内收集 $body/text() 的值。
图 4. 显示管道对的 POJO 代理服务
然后,在 Java 标注操作中生成 Oracle Service Bus 和 POJO 之间的真正桥梁。此处,指向 jar 资源并发现目标方法 customValidator。为结果值选择一个方便的变量名称。
到此基本完成。在响应管道中,我们只计算 CustomValidator 的返回值。如果不满足要求,我们将引发错误代码。错误代码通常是字符串参数。设置变量 $fault,然后调用有效范围内的下一个异常处理程序。
如果参数验证正确,我们只将正确的执行记录到日志中:
concat('Parameter correctly evaluated:', $paramIn)
讨论:POJO 或 EJB?
与 EJB 相比,POJO 的优势在于它在技术上易于创建且几乎没什么开销。但是,POJO 的这些优势也是劣势。POJO 开销少的原因之一是它不具备线程管理功能。POJO 也没有 EJB 的典型特性,如 CMT 划分。
然而,在某些情形下,则鼓励在 Oracle Service Bus 中使用针对 POJO 的 Java 标注,例如:
- 自定义验证:如同我们的示例,在 Java 中执行跨字段语义验证。
- 自定义转换:自定义转换的示例包括将二进制文档转化为 base64Binary 或反向转换,或者使用自定义 Java 转换类。
- 自定义验证和授权:自定义验证和授权的示例包括消息中的自定义令牌需要进行验证和授权的情形。但是,经验证的用户的身份不能由 Oracle Service Bus 传播到代理服务后续调用的服务或 POJO。
- 消息充实:例如,使用文件或 Java 表来查找可以充实消息的任何一段数据。
另一方面,在以下情况下则推荐使用 EJB 而非 POJO:
- 已经拥有 EJB 实施时。
- 需要对 JDBC 数据库进行读访问时。虽然 POJO 也可用于此目的,但是 EJB 是为此 专门设计 的,为管理和连接 JDBC 资源提供更好的支持。
- 也正因为如此,计划访问 J2EE 事务资源时,EJB 可以提供事务处理业务逻辑并更好地支持故障的妥善处理。但是,POJO 支持事务和安全上下文传播,也可用于此目的。
最后的细微考虑:编写 EJB 时通常不考虑服务。实体 EJB 通常包含从数据源的细微抽象,并主要作为一种机制在数据客户和数据库之间调节。甚至无状态会话 bean 也很少被写成面向服务的标准(确切的说,它们通常展示一种过程标准)。
直接将它们作为服务发布可以极大地降低抽象等级和企业服务总线 (ESB) 的内部耦合度。普通 EJB 接口中包含的低级实施细节可以直接影响企业级服务总线。虽然可以编写面向服务的 EJB,但是很少将 EJB 写成此级别。这由它们的属性决定的。
与 BPM 6.0 无缝集成
Oracle BPM 套件包含了可以改善业务效能和企业可扩展性的新特性。本文不列出所有这些特性,而仅关注与 Oracle Service Bus 集成这方面。
在 Oracle BPM 6.0 中,您可以轻松地连接到任何 Oracle Service Bus 的实例。无需再登录到总线控制台以获取所有可用服务的列表。也不再需要处理 WSDL 文件并费力推测特定服务的 URL。Oracle BPM 环境直接提供了所需的一切。
此产品提供了新的 Introspector 向导,允许用户在数秒内快速连接这两个产品。只需几次点击就可和总线建立连接并使用它的全部代理服务(见图 5)。
图 5. Introspector 示例
使用 Introspector 映射外部资源并将所需的组件添加到目录后,您就完成大部分工作了。
可以使用流程业务语言 (PBL) 集成 Oracle Service Bus 和 Oracle BPM。PBL,以前称为 Fuego 业务语言,是用于在 Oracle BPM 项目内编写代码以实施流程特性或与外部资源集成的编程语言。
PBL 是将组件处理为对象的简单高级语言。可以使用 PBL 在活动和特定类型的过渡中定义业务规则和逻辑。PBL 开发环境已集成到 Oracle BPM 中。
要调用 Oracle Service Bus 服务,必须调用在业务活动中要使用的方法。调用只需声明一个代表服务的变量(根据需要可以是 local 或 instance)。初始化调用构造器的服务:
service as Module.FlightProxyService.GetFlightProxyService =
Module.FlightProxyService.GetFlightProxyService();
然后初始化包含服务参数的请求:
request as Module.FlightProxyService.GetFlightPriceRequest =
Module.FlightProxyService.GetFlightPriceRequest();
request.strSymbol = "AZ1234";
现在对象已得到正确初始化,只需使用简单的语法调用服务:
getFlightPrice service
using parameters = request
returning parametersOutput = parametersOutput
display parametersOutput.result
return true
结论
熟悉 Oracle Service Bus 旧版本的人会很容易掌握基于 Eclipse 的新设计环境。事实上,开发者无需经历痛苦的学习过程就可体验到生产率的提高。
Oracle BPM 6.0 与业务流程管理的交互为市场建立了一个新标准,提供了涵盖人工业务流程到系统业务流程的所有挑战的单一平台。希望能够在 Oracle Service Bus 中轻松找到并重用已部署服务的客户,只需使用一个简单的向导即可轻松地找到和使用流程。
可用性的提高和开发流程的简化,加上齐备的功能和分析能力,这些确保了 Oracle Service Bus 和 Oracle BPM 将继续对业务流程分析人员和开发人员产生深远影响。
Francesco Marchioni 1997 年加入 Java 社区,通过了 Sun 企业架构师认证。他是 Pride SpA 的职员,设计并开发过许多基于 WebLogic 平台的 J2EE 应用程序。
|