使用WAS V6.1 SOA Feature Pack开发SCA应用

日期: 2008-03-10 作者:方伟漆池李旭王征 来源:TechTarget中国

  本文介绍了 WAS(Websphere Application Server)V6.1 的 SOA Feature Pack 的一些概念和功能,并且通过两个实例描述怎样利用这个 Feature Pack 进行 SCA 开发。读者通过本文既可以对 SOA Feature Pack 这个产品组件有一定的了解,也可以对 SCA 加深理解,体会到 SCA 架构在开发 SOA 应用中的特点和优势。本文的目标读者技术级别为初级。


  SCA 简介


  什么是 SCA


  在 SOA 蓬勃发展的当今业界,传统的编程模型往往表现出各自的局限性,不能胜任复杂多变的企业级应用。并且不同平台上编程模型之间的业务整合以及协同工作比较困难。SCA 作为一种全新的,跟具体语言、平台无关的编程模型应运而生。它是一个面向服务的组件模型,在这个模型中,业务逻辑以服务(Service)的形式体现,并且根据的业务目标按照一定的方式整合在一起。SCA 为建立在各种不同平台上的现有系统或者新系统作业务整合,并且提供统一的封装和调用方式。从技术层面来说,SCA 连接和覆盖了用于服务端组件的各种技术和方法,不仅在编程语言上,还包括了各语言中普遍使用的框架和环境,因此,SCA 提供了高度的灵活性和扩展性。


  SCA 的一些基本概念


  在 SCA 1.0 规范中,最大粒度的组件称为一个 SCA Domain(域),Domain 是一个逻辑概念,一个 SCA Domain 代表企业应用中的单一组织所能提供业务功能集和服务集,它可以是独立的或者分布式的。一个 SCA Domain 中可以部署多个 SCA Composite。图 1 列出了一个 SCA Composite 的基本结构,包含了 SCA 构架中的各基本元素,包括 Component,Service,Reference,Interface,Binding,Wire,Property 等等。下面分别简要阐述这些基本概念。


  图 1 SCA Composite




 
  Component


  Component 是 SCA 构架中最基本的元素。一个 Composite 可以包含多个 Component,并且将它们组合成完整的业务方案。Component 是业务逻辑的实现单元,它封装了可配置的具体实现(Implementation),包括各种传统编程语言,如 Java、C++、BEPL 等;脚本型语言,如 PHP、Javascript 等;指令型语言,比如 XQuery,SQL 等;或者各种技术框架,如 osgi、spring 等。同时,Implementation 也可以是另一个 Component,这就为 Component 之间的相互嵌套提供了机制。一个 Component 可以包含多个 Property,Service 或者 Reference,各 Component 之间通过 Service 及 Reference 交互。


  Service


  Service 是 SCA 架构中用来暴露业务功能的组件,可以暴露给其他 SCA Component,也可以暴露给外部应用。一个 Component 可以含有 0 到多个 Service;一个 Composite 也可以含有 0 到多个 Service,这种 Service 称为 Composite Service,Composite Service 提供的业务功能可以由 Component Service 间接提供,这个过程称为 promote,即可以把 Component Service 提升为 Composite Service,它们完成同样的业务功能,只是作用域有所不同。一个 Service 可以有 0 到 1 个 Interface 和 0 到多个 Binding。


  Reference


  Reference 是 SCA 组件用来调用其他组件的接口,可以是一个 Component 调用其他 Component 功能的接口,也可以是调用外部引用(比如 web service)的接口。与 Service 类似,Reference 也分 Component Reference 和 Composite Reference 两种,Component Reference 也可以 promote 称 Composite Reference。一个 Reference 可以有 0 到 1 个 Interface 和 0 到多个 Binding。


  Interface


  Interface 是 Reference 或者 Service 的接口定义方式,Interface 可以有多种类型,如 Java Interface,WSDL 1.1 portTypes,WSDL 2.0 interfaces 等等。Interface 可以是远程的也可以是本地的。可以是单向的也可以是双向的(即含有一个 callback interface),还可以是会话型的(conversational)。


  Binding


  Binding 顾名思义就是 Reference 或者 Service 的接口绑定方式,即通过哪种途径来表现 Reference 或者 Service 的接口。Binding 是 SCA 结构用来整合各种平台应用的重要途径。它是可扩展的,目前在 Tuscany 的 SCA 实现里面,已经有如下几种类型的 binding:sca、web service、http、rmi、ejb、jsonrpc、dwr、feed 等等。随着 SCA 实现的不断成熟还会有越来越多的 binding 形式。每种 binding 形式都有自己独有的一些定义,在 SCA 中有专门的 binding 规范描述各种不同类型的 binding。


  Wire


  Wire 是用来的连接 Component Reference 和 Component Service 的组件,它是一个逻辑组件,实现方式由具体的 SCA 实现决定。Wire 可以单独定义(在 Composite 中),也可以通过在 reference 中定义 target 实现,还可以是隐含的(即通过指定 auto-wire 让 SCA 运行环境自动创建 wire,创建的依据是 Service 和 Referecne 的相容性)。


  Property


  Property 是对 SCA 中的组件进行配置的接口。通过设定 property 的值来影响 SCA 组件的业务功能。Property 是一系列名值对,同时需要指定类型,值的类型可以是简单类型(如 String、Integer 等),也可以是复杂类型(如 XML Shcema 中的 complexType)。Property 即可以用于 Component 也可以用于 Composite。


  SOA Feature Pack 简介


  SOA Feature Pack 是建立在 WAS 6.1(IBM Webshpere Application Server 6.1)平台上的扩展包,它为 WAS 6.1 提供了一些新功能,并且扩展了一些新的概念。它带来的新机制增加了企业应用在适应业务逻辑变化方面的敏捷性,大大提高了业务组件的重用性,它提供了一种简单而强大的方式让用户在 WAS 平台之上从零散的松耦合的业务组件建立完整的 SOA 应用。


  SOA Feature Pack 利用 Apache Tuscany 这个 Apache 孵化器中的开源项目作为其 SCA 运行时底层构架。依靠 Tuscany 对 SCA 1.0 规范以及 SDO 2.1 规范做了较为完整的支持。除此以外,SOA Feature Pack 对 Tuscany 的很多机制做了一定的扩展(如 binding 和 data binding),使其支持如 EJB,JMS 等 J2EE 环境下常用的调用机制。


  下面对 SOA Feature Pack 中提供的一些新的概念和功能做简单的描述。


  Asset


  SOA Feature Pack 允许用户将 SCA Composite 部署成相对独立的,松散耦合的可复用组件,这种组件称为 Asset。一个 Asset 可以是一个功能独立的 SCA Composite,包含 composite 描述符文件,Java 实现,以及 SCA 模块所需要用到的各种资源文件,如 WSDL,XML Schema 等。Asset 一般以 Jar 包的形式部署,当然也可以以其他形式。一个 Asset 可以被多个业务层应用复用。SOA Feature Pack 以组件库的方式保存所有的 Asset。Asset 不能被单独启动,它们必须被整合到具体的应用(Business Level Application)中才能被使用。


  Business Level Application


  Business Level Application,简称 BLA,是 SOA Feature Pack 引入的另一个概念。它与传统的 J2EE 应用有所区别,它从业务整合的角度出发,将各种完成独立业务功能的模块整合在一起,并且为这些模块建立调用关系,如图 1 所示。BLA 中的模块可以是通过不同的具体技术实现的,如 EJB,Web Service,SCA 等。BLA 中可以部署多个 Asset,通过 SOA Feature Pack 扩展的 WAS 6.1 管理控制台,可以对 BLA 中的 Asset 进行添加,删除等管理。每个 BLA 具有独立的生命周期,可以进行启动,停止等操作。本文后面的章节会举例说明 SOA Feature Pack 中对 BLA 的管理操作。


  图 2 Business Level Application




 
  代码生成工具


  在 SOA 开发中服务接口一般以 WSDL 的形式描述,而业务数据一般以 XML Schema 的形式定义。因此在对 SCA 组件进行 Java 实现时必须从这些元数据生成具体的 Java 接口,SOA Feature Pack 提供了具体的工具做这样的代码生成,分别是 XSD2Java,和 WSDL2Java。


  XSD2Java 来自 Tuscany-SDO-Java 中的 tools 子项目,其入口实现在 org.apache.tuscany.sdo.generate.XSD2JavaGenerator 类,它利用 EMF 的代码生成技术从 XML Schema 或者 WSDL 文件中内嵌的 Schema 生成具体的 SDO 实现类以及工厂类。通过这些类,开发人员在进行 SCA 实现的开发时,可以通过 JavaBean 和 DataObject 两种形式引用 SDO 中的属性。


  WSDL2Java 来自 Tuscany-SCA-Java 中的 wsdl2Java 子项目,其入口实现在 org.apache.tuscany.tools.wsdl2java.generate.WSDL2JavaGenerator 类。它通过解析 WSDL 文件生成 SCA 实现中使用的 Java 接口。在代码生成的过程中,它将 WSDL 中的 Port Type 映射到 Java 类,WSDL 中的 operation 映射到 Java 方法,并且在 XML Shema 的类型与 SDO 类型之间做了转换,与 XSD2Java 的生成结果相结合,为基于 SCA 的 Java 开发提供了基础。


  SCA 资产组件的开发


  下面我们通过一个示例来演示 SCA 组件的基本开发,以及在 SOA Feature Pack 中如何建立 Asset,如何建立 Business Level Application,如何将 Asset 添加到 Business Level Application 中的 SCA Composites 等,此外我们还在此示例中演示 SCA 的异步回调机制。


  示例概述


  首先简要说明我们需要建立的示例。客户在开始一段旅程之前可以通过网络服务预订目的地酒店的房间。我们将预订房间的服务封装成一个 SCA 的 Asset,该服务接受客户的一些基本信息,如客户号,需要预订的房间类型,客户的到达时间等等,在预定操作成功后,通过一个回调接口通知客户预订到的房间号。除了 Asset 以外,还需要建立一个客户端应用,该应用是一个简单的动态 Web 工程,包含 SCA 的客户端组件,组件的实现(包括回调接口的实现),以及预订操作的 Web 界面。


  组件接口定义


  下面我们为所需要创建的 SCA Asset 定义接口,该接口需同时具有 WSDL 和 Java 接口两种形式。其中,WSDL 用于将 SCA 的 Service 暴露为 Web 服务,Java 接口用于 SCA 组件的实现。


  WSDL 接口如图 1 所示,此 WSDL 包含两个 portType,HotelDemo 和 HotelDemoCallback,分别用于描述酒店预定服务的接口以及服务回调的接口。这两个 portType 各自有一个方法,分别为 bookRoom 及 bookRoomCallback。其中 bookRoom 具有三个参数,用于指定客户信息;bookRoomCallback 具有一个参数,用于返回预订到的酒店房间号。该 WSDL 在定义 Service 的 port 时不需要指定具体的 endpint,因为此 WSDL 用于 SCA Asset,服务的 endpoint 将会由 SCA 运行环境自动生成。


  图 3 示例中 WSDL 结构




 
  清单 1 给出了酒店预定服务 Java 接口 HotelDemoService,清单 2 给出了其回调接口 HotelDemoCallback。这两个接口一方面用于 SCA Composite 定义中的接口声明,另一方面作为 SCA 实现类的基础。有两点需要注意的地方:


  这两个接口都必须声明为远程的,即使用 SCA 的 JDK 注解 @Remotable
在 HotelDemoService 中需要声明其回调接口为 HotelDemoCallback,即使用 SCA 的 JDK 注解 @Callback


  清单 1 Java 接口 HotelDemoService


  import org.osoa.sca.annotations.Callback;
  import org.osoa.sca.annotations.Remotable;


  @Remotable
  @Callback(HotelDemoCallback.class)
  public interface HotelDemoService {
  public void bookRoom(String customerId, int roomType, String   checkInTime);
  }
 
  清单 2 Java 接口 HotelDemoCallback


  import org.osoa.sca.annotations.Remotable;


  @Remotable
  public interface HotelDemoCallback {
 public void bookRoomCallback(String roomNumber);
  }
 
  组件实现


  清单 3 给出了酒店预定服务的组件实现。该实现继承 HotelDemoService 接口,并且用 set 注入的方式为回调接口实现依赖注入。需要在 set 方法处用 @Callback 声明。在实现类的开始需要用 @Service 声明该 SCA 组件实现的接口,并且用 @Scope 声明作用范围。在 bookRoom 的实现中,使用 SCA 运行时注入的 hotelDemoCallback 对象调用回调接口,将预订房间号返回给客户端。


  清单 3 实现类


  import org.osoa.sca.annotations.Callback;
  import org.osoa.sca.annotations.Scope;
  import org.osoa.sca.annotations.Service;


  @Service(HotelDemoService.class)
  @Scope(“COMPOSITE”)
  public class HotelDemoImpl implements HotelDemoService {


 private HotelDemoCallback hotelDemoCallback;
 private static int currentId = 1001;


 @Callback
 public void setHotelDemoCallback(HotelDemoCallback hotelDemoCallback) {
  this. hotelDemoCallback = HotelDemoCallback;
 }


 public void bookRoom(String customerId, int roomType, String checkInTime) {
  try {
   System.out.println(“ID:” + customerId);
   System.out.println(“Room Type:” + roomType);
   System.out.println(“Check In Time:” + checkInTime);
hotelDemoCallback.bookRoomCallback(“R” + currentId);
currentId++;
} catch (Throwable t) {
   t.printStackTrace();
  }
 }
}
 
  组件部署


  清单 4 给出了示例 SCA 组件的 composite 文件,该文件是 SCA 模块的组装配置文件,如上面“SCA 的一些基本概念”中所述,该文件定义了一个 SCA composite,并且为此 composite 定义了一个名为 HotelClientComponent 的 component。该 component 采用 Java 类型的实现,在配置中给出了实现的 Java 类全名。该 component 定义了一个 service,该 service 是我们所需要部署的 SCA Asset 的服务入口,在此采用 Java 接口声明其 interface,并且给出 service 及其回调的 binding 定义。


  清单 4 composite 文件


  
  http://www.osoa.org/http://www.w3.org/2006/01/wsdl-instance”>http://www.w3.org/2006/01/wsdl-instance“
 name=”HotelDemo”>



        callbackInterface=”sca.demo.hotel.HotelDemoCallback” />
         wsdlElement=”http://demo.sca/Hotel#wsdl.port (HotelDemoService/HotelDemoSoapPort)” />
   
     wsdlElement=”http://demo.sca/Hotel#wsdl.binding (HotelDemoCallbackSoapBinding)” />
   

  

  我们将前文所提到的接口定义以及实现,按照图 2 的目录结构打成 jar 包,命名为 HotelDemoAsset.jar。下面我们具体描述怎样将此 jar 包部署到装有 SOA Feature Pack 的 WAS6.1 服务器中,使其成为一个 Asset,并且演示怎样建立一个业务层引用去调用此 Asset。


  图 4 HotelDemo Asset 目录结构




 
  Asset 的安装部署


  进入装有 SOA Feature Pack 的 WAS6.1 的管理控制台,在左边“应用程序”主菜单下可以发现有两个新增的菜单项:Business Level Application 以及 Assets,如图 3 所示。


  图 5 SOA Feature Pack 新增的管理控制台菜单




 
  点击 Assets,进入 Assets 的管理控制页,进行我们刚才所开发的 Asset 的安装部署。如图 4 所示,Assets 列表中列出了当前服务器中已存在的所有 Asset,列表上端有一系列管理按钮,可以对 Asset 进行添加,删除,更新等操作。点击 Import 安装新的 Asset。


  图 5 Assets 列表




 
  在 Upload Asset 界面中,将之前导出的 HotelDemoAsset.jar 从文件系统中载入准备上传,如图 5 所示。


  图 6 Asset 的安装




 
  点击下一步进入 Asset 的选项配置界面,如图 6 所示,在此可以对 Asset 的安装属性进行设置,包括 Asset 的名称,描述,目标服务器内部地址等等,还可以设定一些文件访问权限。我们取默认值点击“下一步”


  图 7 Asset 的安装配置




 
  进入“摘要”页面,在此页面检查 Asset 的所有安装配置,再点击下一步。


  进行安装操作,在结果显示列表中,可以看到 Asset 的安装情况,成功与否。如果安装成功了,可以点击下面的“管理 Assets”链接,回到 Assets 列表对 Asset 进行其他操作。


  BLA 的创建及管理


  在创建了 Asset 之后,我们就有了一个可重用的服务资源在资源库中,下面需要创建一个 Business Level Application 去使用该资源。


  点击 WAS 控制台菜单中“应用程序”主菜单下的 Business Level Application 菜单项,进入 Business Level Application 的管理界面,如图 7 所示,在所有 Business Level Application 的列表顶端有一系列控制按钮,包括新建,删除,启动,停止等。点击“新建”。


  图 8 Business Level Application 列表




 
  进入创建新的 Business Level Application 的界面,如图 9 所示。输入 Business Level Application 的名称以及描述,点击“确定”


  图 9 新建 Business Level Application




 
  在保存了 WAS 配置以后,在 Business Level Application 的管理界面中对刚创建的应用进行管理,如图 9 所示,SCA Composites 列表中列出了该 Business Level Application 中所有已加入的 Asset,我们需要把之前创建的 Asset 加入到该 Business Level Application,点击“添加”按钮


  图 9 为 BLA 添加 Composition Unit




 
  在 Asset 列表中,选中之前创建的 HotelDemoAsset.jar,并且点击“确定”。如图 10。


  图 10 选择 Asset




 
  进入一个选项配置页面,在该页面中可以对 Composition Unit 的添加进行一些配置,我们保留默认配置,并且点击“下一步”,进入另一个配置页面,该页面中将加入 Business Level Application 的 Asset 映射到具体的 J2EE 服务器,该配置与普通的 J2EE 应用安装时的映射配置类似。我们依然保留默认配置,并且点击“下一步”。


  在检查完安装摘要之后点击“完成”,则会启动具体的安装过程,安装成功后,并且点击“管理 Business Level Application”,回到 Business Level Application 的管理界面,可以看到此时 SCA Composites 多出了刚才添加的 Asset。此时,该 Asset 为停止状态,因此需要通过启动 BLA 来启动它。


  通过主菜单回到 Business Level Application 的列表页面,选中新创建的 Business Level Application,并且点击“启动”按钮,如图 11。此时 Business Level Application,包含之前建立的 Asset,就成功启动了。


  图 11 启动 Business Level Application




 
  客户端应用


  通过上一节的介绍,我们已经将酒店预定示例的 SCA 服务端组件创建并且安装到 WAS 服务器上,包括 Asset 和 Business Level Application,现在我们需要创建一个 Web 应用 HotelDemoClient 去使用并验证上述组件。在该 Web 应用中,需要创建一个 SCA Component,该 Component 包含一个 reference,可以引用到已部署的 SCA Service,并且提供回调接口的实现。同时该 Web 应用中包含示例的 Web 界面,即一个 JSP 文件。我们将在 JSP 文件演示如何在客户端调用 SCA 组件。


  客户端接口与实现


  客户端应用需要使用服务端应用中的 WSDL 文件以及 HotelDemoCallback、HotelDemoCallback 接口。WSDL 文件需要做一点修改,在服务端组件中,Service 的 Endpoint 是不需要指定的,由 SCA 运行环境自动分配,但是在客户端需要指定 SCA reference 所引用到的 Web 服务的 Endpoint,因此需要在 WSDL 中添加(也可以在客户端 composite 文件中 binding 配置中指定,效果相同)。


  除了上述接口外,客户端应用的 SCA Component 需要一个独立的接口用来暴露客户端的一些功能,比如说返回预订到的房间号等等。清单 5 给出了此接口的定义。


  清单 5 客户端 SCA 组件接口


 import org.osoa.sca.annotations.Service;


 @Service
 public interface HotelDemoClient {
 public void book(String customerId, int roomType, String  checkInTime);


 public String getRoomNumber();
 }
 
  清单 6 给出了客户端 SCA 组件的实现代码,HotelClientComponent 类实现了客户端组件的接口 HotelDemoClient,以及回调接口 HotelDemoCallback。此外,该代码中还有依赖注入的 Reference 定义。


  清单 6 客户端 SCA 组件实现


  import org.osoa.sca.annotations.Reference;
  import org.osoa.sca.annotations.Scope;
  import org.osoa.sca.annotations.Service;


  @Scope(“COMPOSITE”)
  @Service(HotelDemoClient.class)
  public class HotelClientComponent implements HotelDemoClient,   HotelDemoCallback {


 private String roomNumber = “booking failed”;


 private HotelDemoService hotelService;


 public HotelDemoService getHotelDemoService() {
  return hotelService;
 }


 @Reference
 public void setHotelDemoService(HotelDemoService hotelService) {
  this.hotelService = hotelService;
 }


 public void book(String customerId, int roomType, String checkInTime) {
  hotelService.bookRoom(customerId, roomType, checkInTime);
 }


 public void bookRoomCallback(String roomNumber) {
  System.out.println(“Callback: ” + roomNumber);
  this.roomNumber = roomNumber;
 }


 public String getRoomNumber() {
  return roomNumber;
 }
}
 
  客户端部署


  清单 7 列出了客户端应用的 SCA Composite 配置,在此配置中可以看出,客户端的 SCA Component:HotelClientComponent 定义的 Reference 与服务端 Component 中建立的 Service 相对应。因此我们可以看出:客户端与服务端通过 Web Service 进行信息交互,但是 SCA 封装了具体实现过程,通过 SCA 的包装,用户关心的是服务和调用的业务逻辑,而不是它们的技术实现。


  清单 7 客户端 Composite 配置


 http://www.osoa.org/http://tuscany.apache.org/>
 
     class=”sca.demo.hotel.HotelClientComponent” />


  
       callbackInterface=”sca.demo.hotel.HotelDemoCallback” />
       wsdlElement=”http://demo.sca/Hotel#wsdl.port(HotelDemoService/HotelDemoSoapPort)” />
   
         wsdlElement=”http://demo.sca/Hotel#wsdl.binding(HotelDemoCallbackSoapBinding)” />



  清单 8 给出了客户端的界面 JSP 中调用 SCA 服务的相关代码,通过这段代码可以看出 SCA 组件的应用方式。首先从 CurrentCompositeContext 中获取 CompositeContext 对象,该对象包含了 default.composite 中所有的 SCA Component,从 CompositeContext 可以获取所需的 Component 的接口,并且对其进行业务调用。在我们的示例中,SCA 组件包含一个回调过程,因此,示例程序等待片刻以便得到服务端的回调结果。


  清单 8 客户端演示页面部分代码


  CompositeContext compositeContext =   CurrentCompositeContext.getContext();
  HotelDemoClient hotelDemo = (HotelDemoClient)compositeContext.   locateService(
  HotelDemoClient.class,”HotelClientComponent”);
  hotelDemo.book(customerId , Integer.parseInt(roomType) , checkInTime);
  Thread.sleep(5000);
  String number = hotelDemo.getRoomNumber();
 
  将客户端应用中的必要组件按照图 12 的目录结构打成 war 包,并且将其作为一个企业应用程序进行安装部署(独立 Web 模块)。在通过 WAS 管理控制台部署后,其中的 SCA 组件会自动部署到 SOA Feature Pack 的 SCA 运行环境中。再通过管理控制台启动该 Web 应用。


  图 12 客户端 SCA 应用目录结构




 
  运行结果


  通过浏览器进入客户端模块的界面 JSP,如图 13 所示,填入预订酒店房间所需要的客户信息,点击“提交”按钮,一段时间之后,就可以看到服务端的回调结果:返回的酒店房间号。此时,一个完整的 SCA 调用以及回调流程顺利完成。


  图13示例运行结果




 
  示例总结


  通过上面的示例介绍了利用 SOA Feature Pack 进行 SCA 开发的一些方法和流程,同时展示了 SOA Feature Pack 的部分功能点。主要着重在 SCA 服务端组件方面,介绍了如何利用 SOA Feature Pack 建立 Asset,Business Level Application 等,如何创建 SCA 客户端来调用这些服务端组件。此外,本示例演示了如何在 SCA 应用中创建包含回调接口的 SCA Component,说明了这种回调方式的异步特点以及应用场景。


  由于篇幅的问题,本文被分为上下两部分发表。你可以继续访问本文的下半部分。


  作者简介


  作者为 IBM 软件工程师,在 IBM 中国软件开发实验室 – SOA Design Center工作,从事 SOA Tooling 方面相关工作,对 J2EE 轻量级技术,Web 开发,SOA,SCA/SDO 等方面有浓厚兴趣。目前从事 SOA Testing 项目的开发工作。
 
  漆池,IBM CDL SOA 设计中心软件工程师,从事 SOA 测试工具的开发工作。
 
  李旭,IBM CDL SOA 设计中心软件工程师,从事 SOA 测试工具的开发工作。
 
  王征,IBM CDL SOA 设计中心软件工程师,从事 SOA 测试工具的开发工作。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐