IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Open source  >

深入 web2.0 应用框架 zk

构建基于 zk 框架的 web 2.0 应用

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

杨 凡 (yangffan@cn.ibm.com), 软件工程师, IBM
郑 霞锦 (xjzheng@cn.ibm.com), 软件工程师, IBM

2009 年 9 月 25 日

本文将介绍基于 Web2.0 的 OpenSource Framework ZK 。具体阐述 ZK 框架的搭建、在 ZK 中使用 JDBC 接口与数据库连接池操作,以及如何整合 Hibernate。

简介

ZK 是一个事件驱动 (event-driven) 的,基于组件 (component-based) 的,用以丰富网络程序中用户界面的框架。 ZK 包括一个基于 AJAX 事件驱动的引擎 (engine),一套丰富的 XUL 和 XHTML,以及一种被称为 ZUML(ZK User Interface Markup Language,ZK 用户界面标记语言 ) 的标记语言。

用户可以利用 XUL 和 XHTML 的丰富特性来呈现您的 Web 应用,操纵它们来处理因用户活动而引发的事件,就像在桌面应用程序中那样。不同于大多数其它框架,就 ZK 而言,AJAX 是一种幕后 (behind-the-scene) 技术,组件内容的同步和流水线事件 (pipelining of events) 都由 ZK 引擎自动完成。

这样大大简化了应用程序的开发周期。同时又使得前端程序美观,易用;

除了简单的模型和丰富的组件,ZK 也支持一种文本标记语言,称为 ZUML 。 ZUML,感兴趣的朋友可以深入了解这种语言,从中体会到他的强大。

ZK 的安装

作为一个基于 java 的 web framework,再使用 ZK 之前,我们需要成功安装 JDK 和 tomcat5.5 或者更高的版本。 在这之后,我们可以去 ZK 的官方网站(http://www.zkoss.org)上 download 我们开发所需要的 library 。

创建第一个工程“ Hello World ”

创建一个新的工程对于 ZK 来说非常的简单,在我们的 tomcat 的目录下($TOMCAT_HOME/webapps),建立新的目录 zkdemo 。其目录结构如下所示:

+zkdemo 
     +WEB-INF 
       web.xml 
       index.zul

解压缩我们刚才 download 的 library 并将 jar 文件 copy 到我们的目录中, $TOMCAT_HOME/webapps/$PROJECT_NAME/WEB 。结果如下,

dist/lib/*.jar 
 dist/lib/zkforge/*.jar 
 dist/lib/ext/*.jar

编辑 web.xml,将如下代码加入其中:

<!-- ZK --> 
  <listener> 
    <description>Used to clean up when a session is destroyed</description> 
    <display-name>ZK Session Cleaner</display-name> 
    <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class> 
  </listener> 

  <servlet> 
    <description>ZK loader for ZUML pages</description> 
    <servlet-name>zkLoader</servlet-name> 
    <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class> 
    <init-param> 
      <param-name>update-uri</param-name> 
      <param-value>/zkau</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
  </servlet> 
  <servlet-mapping> 
    <servlet-name>zkLoader</servlet-name> 
    <url-pattern>*.zul</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>zkLoader</servlet-name> 
    <url-pattern>*.zhtml</url-pattern> 
  </servlet-mapping> 

  <servlet> 
    <description>The asynchronous update engine for ZK</description> 
    <servlet-name>auEngine</servlet-name> 
    <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class> 
  </servlet> 
  <servlet-mapping> 
    <servlet-name>auEngine</servlet-name> 
    <url-pattern>/zkau/*</url-pattern> 
  </servlet-mapping>

使用 zk 标记,创建 web 页面 index.zul 。加入代码:

<window title="My First window" border="normal" width="200px"> 
	 Hello, World! 
 </window>

重启 Tomcat

打开浏览器,键入 URL:http://localhost:8080/zkdemo/index.zul。此时 , 您就能看到我们的第一个 ZK 的 web 程序。

当然,我们也可以添加一些互动的元素来扩展程序的功能。

<window title="Hello" border="normal"> 
   <button label="Say it" onClick="alert(&quot;Hello World!&quot;)"/> 
 </window>


图 1: 第一个 ZK 程序





回页首


基于 ZK 的数据库应用实例

下面我们通过一个实例来开发一个基于数据库的 Web 2.0 应用。在此实例中,我们使用简单的 JDBC 方式作为数据库连接。使用 java.sql.DriverManager 。在此,我们选择的数据库是文本型的 hsqldb,数据表是一个事物记录,由 3 个字段组成(item:事件名; priority:事件优先级; Date: 事件时间)

事前的准备

与所有的 application 类似,在我们开始动手之前,需要下载我们这个应用所必须的 jar package 。幸运的是,这些 package 我们都可以在 zk5.2 的 image 中找到。并且将这些 package 拷贝到我们的 application 的目录中,${tomcat_home}/webapps/${Application_name}/WEB-INF/lib 。

DAO 对象

当然,我们还是需要把 JDBC 访问写入到我们的 DAO 对象中,与大多数 java 的 jdbc programming 类似,请参考下面的代码:

public class EventDAO 
 { 
 private String url = "jdbc:hsqldb:file:/hsqldb/event"; 
 private String user = "sa"; 
 private String pwd = ""; 
 public EventDAO() { 
 try 
 { 
	 Class.forName("org.hsqldb.jdbcDriver"); 
 } 
 catch (ClassNotFoundException e) 
 { 
	 e.printStackTrace(); 
 } 
 } 
 public List findAll() 
 { 
 Statement stmt = null; 
 Connection conn = null; 
 List allEvents = new ArrayList(); 
 try { 
	 conn = DriverManager.getConnection(url, user, pwd); 
	 stmt = conn.createStatement(); 
	 ResultSet rs = stmt.executeQuery("select * from event"); 
	 Event evt; 
	 while (rs.next()) { 
	 evt = new Event(); 
	 evt.setId(rs.getString(1)); 
	 evt.setName(rs.getString(2)); 
	 evt.setPriority(rs.getInt(3)); 
	 evt.setDate(rs.getDate(4)); 
	 allEvents.add(evt); 
 } 
 } catch (SQLException e) { 
	 e.printStackTrace(); 
	 }finally{ 
     try { 
     stmt.close(); 
     } 
 catch (SQLException e) { 
     e.printStackTrace(); 
  } 
  try { 
     conn.close(); 
  } catch (SQLException e) { 
     e.printStackTrace(); 
  } 
  }    
  return allEvents; 
 } 
  
 public boolean delete(Event evt){ 
 Connection conn = null; 
  Statement stmt = null; 
  boolean result = false; 
  try { 
    conn = DriverManager.getConnection(url, user, pwd); 
    stmt = conn.createStatement(); 
     if (stmt.executeUpdate("delete from event where id = '" 
 + evt.getId() + "'") > 0); 
      result = true; 
      
    } catch (SQLException e) { 
      e.printStackTrace(); 
    }finally { 
       try { 
      stmt.close(); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
      try { 
         conn.close(); 
      } catch (SQLException e) { 
         e.printStackTrace(); 
      } 
    } 
    
    return result; 
  } 
  
 public boolean insert(Event evt){ 
  Connection conn = null; 
  Statement stmt = null; 
  boolean result = false; 
  try { 
    conn = DriverManager.getConnection(url, user, pwd); 
    stmt = conn.createStatement(); 
    if (stmt.executeUpdate("insert into event(id,name,priority,date) " + 
    "values ('" + UUID.randomUUID().toString() + "','" + evt.getName() + 
    "'," + evt.getPriority() + ",'" + new SimpleDateFormat("yyyy-MM-dd 
 HH:mm:ss").format(evt.getDate()) + "')") > 0); 
    result = true; 
    } catch (SQLException e) { 
      e.printStackTrace(); 
    }finally{ 
       try { 
       stmt.close(); 
    } 
 catch (SQLException e) { 
       e.printStackTrace(); 
    } 
    try { 
       conn.close(); 
    } catch (SQLException e) { 
       e.printStackTrace(); 
    } 
  } 
    
  return result; 
 } 
  
 public boolean update(Event evt){ 
  Connection conn = null; 
  Statement stmt = null; 
  boolean result = false; 
  try { 
        conn = DriverManager.getConnection(url, user, pwd); 
        stmt = conn.createStatement();        
        if (stmt.executeUpdate("update event set name = '" + evt.getName() + 
        "', priority = " + evt.getPriority() + ", date = '" + 
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(evt.getDate())+ 
        "' where id = '" + evt.getId() + "'") > 0); 
        result = true; 
  } catch (SQLException e) { 
        e.printStackTrace(); 
  }finally{ 
        try { 
    stmt.close(); 
        } catch (SQLException e) { 
    e.printStackTrace(); 
        } 
    try { 
    conn.close(); 
    } catch (SQLException e) { 
    e.printStackTrace(); 
    } 
  } 
  return result; 
  } 
 }

编译并将 class 文件拷贝到 ${tomcat_home}/webapps/${application_name}/WEB-INF/classes/${package_name} 。

页面 ZUL 脚本

现在我们框架已经被建立起来了,下面我们需要实现的就是前端页面的展示以及对数据库的操作。页面代码如下所示:

<window title="To do list" width="640px" border="normal"> 
  <zscript> 
    import org.zkforge.todo.event.Event; 
    import org.zkforge.todo.event.EventDAO; 
    import java.util.ArrayList; 
    import java.text.SimpleDateFormat; 
    import java.util.UUID; 

    //fetch all events from database 
    EventDAO evtdao = new EventDAO(); 
    List allEvents = evtdao.findAll(); 
                  
 // 添加一条记录
    void add(){ 
    //insert into database 
    Event newEvt = new Event(UUID.randomUUID().toString(), 
      name.value,priority.value.intValue(),date.value); 
    evtdao.insert(newEvt); 

    //synchronized data with database 
    allEvents = evtdao.findAll(); 

    //insert a listEvent into the listbox 
    Listitem li = new Listitem(); 
    li.setValue(newEvt); 
    li.appendChild(new Listcell(name.value)); 
    li.appendChild(new Listcell(priority.value.toString())); 
    li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value))); 
    box.appendChild(li); 
    } 
    
 // 修改一条记录
    void update(){ 
    //update database 
    Event editEvt = (Event)box.selectedItem.value; 
    editEvt.setName(name.value); 
    editEvt.setPriority(priority.value); 
    editEvt.setDate(date.value); 
      evtdao.update(editEvt); 
    
    //update listbox 
    List children = box.selectedItem.children; 
    ((Listcell)children.get(0)).label = name.value; 
    ((Listcell)children.get(1)).label = priority.value.toString(); 
    ((Listcell)children.get(2)).label = 
          new SimpleDateFormat("yyyy-MM-dd").format(date.value); 
    } 
    
 // 删除一条记录

    void delete(){ 
    evtdao.delete((Event)box.selectedItem.value); 
    box.removeItemAt(box.getSelectedIndex()); 
    cleargb(); 

    } 
  
  </zscript> 
  <listbox id="box" multiple="true" rows="4" 
    onSelect="move()"> 
    <listhead> 
      <listheader label="Item" /> 
      <listheader label="Priority" width="50px" /> 
      <listheader label="Date" width="90px" /> 
    </listhead> 
    <listitem forEach="${allEvents}" value="${each}"> 
      <listcell label="${each.name}" /> 
      <listcell label="${each.priority}" /> 
      <listcell label="${each.date}" /> 
    </listitem> 
  </listbox> 
  <groupbox> 
    <caption label="Event" /> 
    Item: <textbox id="name" cols="25" /> 
    Priority: <intbox id="priority" cols="1" /> 
    Date: <datebox id="date" cols="8" /> 
    <button label="Add" width="36px" height="24px" onClick="add()" /> 
    <button label="Update" width="46px" height="24px" onClick="update()" /> 
    <button label="Delete" width="46px" height="24px" onClick="delete()" /> 
  </groupbox> 
 </window>

编写完成之后将其存储为 todu.zul 拷贝到我们的 application 目录下,我的本机目录是:/${tomcat_home}/webapps/todo

打开我们的浏览器,输入 http://localhost:8080/todo/todo.zul


图 2:通过 JDBC 获取的数据

Add 用来增加一条记录,Update 用来修改,Delete 用于删除。点击任何按钮我们可以对这个数据表实现无刷新的操作。而不需要去编写复杂的 javascript 与 Ajax 特效脚本。 ZK 已经帮助我们 integrate 到 Framework 中了。

除以上办法外,所有的 J2EE 框架和 Web 服务器都支持连接池操作。这将有助于节约我们有限的 web 资源,减少系统开销。

优化:配置连接池

这个修改其实非常简单,但是对于程序的效率却有相当的提高,尤其是规模的访问来说。我们只需要在 tomcat 中去配置一下就好。

编辑 $Tomcat/conf/context.xml,加入如下的内容。

<Context> 
  <Resource name="hsqldb/event" auth="Container" 
  type="javax.sql.DataSource" username="sa" password="" 
  driverClassName="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:file:c:/hsqldb/event" 
  maxActive="8" maxIdle="4" /> 
 </Context>

重启 TOMCAT. 验证一下我们的修改。还是打开我们的浏览器,输入 http://localhost:8080/todo/todo.zul


图 3:通过数据连接池获得的数据

自此我们这个基于 ZK 的数据库应用就已经完成了。

ZK 还有许多强大的功能有待于我们去挖掘。下面我们来看看 ZK 是如何整合 Hiberate 的。





回页首


ZK 深层次应用 : 整合 Hibernate

Hibernate

Hibernate 是一个对象关系映射 (ORM) 解决方案,专门针对 Java 语言。 Hibernate 的主要特点是简化了对于数据库的访问。

Hibernate 的安装与 ZK 的配置

在使用 Hibernate 之前,我们要下载 Hibernate(http://www.hibernate.org),将所有的 jar 文件放到 $myApp/WEB-INF/lib/ 下。

为使 ZK 和 Hibernate 顺利的工作,你需要完成下面的工作。

  1. 在 $myApp/WEB-INF/lib/ 下创建 zk.xml;
  2. 在 zk.xml 中加入如下的内容:
<!-- Hibernate SessionFactory lifecycle --> 
 <listener> 
 <description>Hibernate SessionFactory lifecycle</description> 
 <listener-class>org.zkoss.zkplus.hibernate.HibernateSessionFactoryListener 
 </listener-class> 
 </listener> 
 <!-- Hibernate OpenSessionInView Pattern --> 
 <listener> 
 <description>Hibernate Open Session In View life-cycle</description> 
 <listener-class>org.zkoss.zkplus.hibernate.OpenSessionInViewListener 
 </listener-class> 
 </listener> 
 <!-- Hibernate thread session context handler --> 
 <listener> 
 <description>Hibernate thread session context handler</description> 
 <listener-class>org.zkoss.zkplus.hibernate.HibernateSessionContextListener 
 </listener-class> 
 </listener>

创建 Java 对象

package events; 

 import java.util.Date; 

 public class Event { 
    private Long id; 

    private String title; 
    private Date date; 

    public Event() {} 

    public Long getId() { 
        return id; 
    } 
    private void setId(Long id) { 
        this.id = id; 
    } 
    public Date getDate() { 
        return date; 
    } 
    public void setDate(Date date) { 
        this.date = date; 
    } 
    public String getTitle() { 
        return title; 
    } 
    public void setTitle(String title) { 
        this.title = title; 
    } 
 }

创建你的首个 Java 类 (Event.java), 之后,编译 Java 源文件,然后将类文件放到 Web 部署文件夹的 classes 目录下,要保证包名正确。 ( 例如,$myApp/WEB-INF/classes/event/Event.class)

映射 Java 对象

有两种方式告诉 Hibernate 如何加载和存储持久类对象,第一种方式是使用 Hibernate 映射文件,另一种方式是使用 Java 注释。

使用映射文件

简单的为持久类 Event.java 创建一个 Event.hbm.xml 文件。

<?xml version="1.0"?> 
 <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 
 <hibernate-mapping> 
    <class name="events.Event" table="EVENTS"> 
        <id name="id" column="EVENT_ID"> 
            <generator class="native"/> 
        </id> 
        <property name="date" type="timestamp" column="EVENT_DATE"/> 
        <property name="title"/> 
    </class> 
 </hibernate-mapping>

将这个 Event.hbm.xml 文件放到部署文件夹的目录 src 下,并且保证正确的包名。 ( 例如 ,$myApp/WEB-INF/src/event/Event.hbm.xml)

使用 Java 注释

使用 Java 注释的好处是不用创建额外的 Hibernate 配置文件。为你的 Java 类简单的添加注释来告诉如何关联 Hibernate 。

import javax.persistence.Entity; 
 import javax.persistence.GeneratedValue; 
 import javax.persistence.GenerationType; 
 import javax.persistence.Id; 
 import javax.persistence.Table; 
 
 @Entity 
 
 @Table(name="EVENTS") 
 
 public class Event { 
    private Long id; 
    private String title; 
    private Date date; 
    @Id 
    @GeneratedValue(strategy=GenerationType.SEQUENCE) 
    @Column(name = "EVENT_ID") 	
    public Long getId() { 
       return id; 
    } 
    private void setId(Long id) { 
       this.id = id; 
    } 
    @Column(name = "EVENT_DATE") 
    public Date getDate() { 
       return date; 
    } 
    public void setDate(Date date) { 
       this.date = date; 
    } 
    public String getTitle() { 
       return title; 
    } 
    public void setTitle(String title) { 
       this.title = title; 
    } 
 } 


   1.      @Entity 声明这个类为一个持久对象
   2.      @Table(name = "EVENTS") 注释表示这个实体是和数据库的 EVENTS 表映射的。
   3.      @Column 元素备用与映射实体属性和数据库表的字段。
   4.      @Id 元素定义了映射主键字段的属性。

创建 Hibernate 配置文件

下一步是安装 Hibernate 来使用一个数据库。 HSQL DB , 一个基于 Java 的 SQL 数据库管理系统 (DBMS),可以在 HSQL DB 网站 (http://hsqldb.org/) 下载到。

解压其到一个目录,例如 c:/hsqld 。

打开一个命令框 (command box),切换到 c:/hsqldb 目录。

在命令提示 (command prompt) 下,执行 java -cp lib/hsqldb.jar org.hsqldb.Server 。

安装完数据库之后,我们需要安装 Hibernate 配置文件。在部署文件夹的目录 src 下创建

hibernate.cfg.xml

文件 ( 例如,$myApp/WEB-INF/src/hibernate.cfg.xml) 。将下面的内容复制到你的 hibernate.cfg.xml 。这依赖于你如何映射 Java 对象。


使用映射文件
<?xml version='1.0' encoding='utf-8'?> 
 <!DOCTYPE hibernate-configuration PUBLIC 
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 
 <hibernate-configuration> 
    <session-factory> 
        <!-- Database connection settings --> 
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> 
        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property> 
        <property name="connection.username">sa</property> 
        <property name="connection.password"></property> 
        <!-- JDBC connection pool (use the built-in) --> 
        <property name="connection.pool_size">1</property> 
        <!-- SQL dialect --> 
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property> 
        <!-- Enable Hibernate's automatic session context management --> 
        <property name="current_session_context_class">thread</property> 
        <!-- Disable the second-level cache  --> 
        <property name= 
                   "cache.provider_class">org.hibernate.cache.NoCacheProvider</property> 
        <!-- Echo all executed SQL to stdout --> 
        <property name="show_sql">true</property> 
        <!-- Drop and re-create the database schema on startup --> 
        <property name="hbm2ddl.auto">create</property> 
        <mapping resource="events/Event.hbm.xml"/> 
    </session-factory> 
 </hibernate-configuration>


使用 Java 注释
<?xml version='1.0' encoding='utf-8'?> 
 <!DOCTYPE hibernate-configuration PUBLIC 
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 
    <hibernate-configuration> 
    <session-factory> 
        <!-- Database connection settings --> 
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> 
        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property> 
        <property name="connection.username">sa</property> 
        <property name="connection.password"></property> 
        <!-- JDBC connection pool (use the built-in) --> 
        <property name="connection.pool_size">1</property> 
        <!-- SQL dialect --> 
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property> 
        <!-- Enable Hibernate's automatic session context management --> 
        <property name="current_session_context_class">thread</property> 
        <!-- Disable the second-level cache  --> 
        <property name= 
                  "cache.provider_class">org.hibernate.cache.NoCacheProvider</property> 
        <!-- Echo all executed SQL to stdout --> 
        <property name="show_sql">true</property> 
        <!-- Drop and re-create the database schema on startup --> 
        <property name="hbm2ddl.auto">create</property> 
        <mapping class="events.Event"/> 
    </session-factory> 
 </hibernate-configuration>

创建 DAO 对象

创建 EventDAO.java 
 import java.util.Date; 
 import java.util.List; 
 import org.hibernate.Session; 
 import org.zkoss.zkplus.hibernate.HibernateUtil; 

 public class EventDAO { 
        Session currentSession() { 
        return HibernateUtil.currentSession(); 
    } 
    
    public void saveOrUpdate(Event anEvent, String title, Date date) { 
        Session sess =  currentSession(); 
        anEvent.setTitle(title); 
        anEvent.setDate(date);          
        sess.saveOrUpdate(anEvent); 
    } 

    public void delete(Event anEvent) { 
        Session sess =  currentSession(); 
        sess.delete(anEvent); 
    } 
    
    public Event findById(Long id) { 
        Session sess =  currentSession(); 
      
        return (Event) sess.load(Event.class, id); 
    } 
    
    public List findAll() { 
        Session sess =  currentSession();        
        return sess.createQuery("from Event").list(); 
    } 
 }

编译并部署该文件到 class 目录中。

ZUML 页面访问持久对象

创建 event.zul 文件,其内容如下:

package events; 
 <zk> 
 <zscript><![CDATA[ 
 import java.util.Date; 
 import java.text.SimpleDateFormat; 
 import events.Event; 
 import events.EventDAO; 

 //fetch all allEvents from database 
 List allEvents = new EventDAO().findAll(); 

 ]]></zscript> 
 <listbox id="lbxEvents"> 	
			
   <listhead> 
      <listheader label="Title" width="200px"/> 
      <listheader label="Date" width="100px"/> 
   </listhead> 
   <listitem forEach="${allEvents}" value="${each}"> 
      <listcell label="${each.title}"/> 
      <zscript>String datestr = 
                  new SimpleDateFormat("yyyy/MM/dd").format(each.date);</zscript> 
      <listcell label="${datestr}"/> 
   </listitem> 
 </listbox> 
 </zk>

这样我们就可以在浏览器中访问该 ZUL 了。

至此,我们在文章中已经看到了 ZK 的基本与高级应用。当然,ZK 还有许多强大的功能有待我们去挖掘,希望各位朋友能使用 ZK 开发出美观且功能强大的 web 应用。



参考资料

  • SourceForge 是开源软件的开发者进行开发管理的集中式场所,也是全球最大开源软件开发平台和仓库。

  • 参考 ZK 网站:http://www.zkoss.org/,获取相关资料与 demo。

  • 访问 developerWorks 开放源码专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。


作者简介

杨凡,IBM 软件工程师,具有丰富的 globalization 领域项目开发与管理经验,参与多个产品的全球化开发,也是开源社区的常客。


郑霞锦,IBM 软件工程师 , 五年 IT 从业经验,具有广泛的工程北京,丰富的产品自动化测试经验,对于 opensource 领域一向非常关注。




对本文的评价








IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款