分享
 
 
 

使用 AppFuse 快速构建 J2EE 应用

王朝java/jsp·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

示例介绍

本文的示例实现对员工信息的增删查改等基本功能。用 Tapestry 实现表示层,用 Hibernate 开发持久层,用 SPRing 提供事务控制等跨模块服务,并用 Acegi 进行安全管理。本示例只用到一个域模型:Employee,下面是它的 UML 图。

图 1. Employee UML 图

搭建开发环境

本文的代码开发平台采用的是 Windows 操作系统,因此,以下环境设置也是针对 Windows 操作系统的。

从 AppFuse 下载页面 下载 appfuse-tapestry-1.9.3-src.zip,并解压缩在任意目录下。这个 zip 已经定制了使用 Tapestry 作为表现层的实现框架,因而使用起来较为直接。

从 http://java.sun.com 下载最新的 JDK,并安装或解压缩到任意目录下。本文采用 JDK 1.5.0。设置环境变量 JAVA_HOME 指向 JDK 所在的目录,并在 PATH 中添加 %JAVA_HOME%/bin。

从 http://jakarta.apache.org/tomcat 下载最新版的 Tomcat,并安装或解压缩到任意目录下。本文采用 Tomcat 5.5.17。设置环境变量 CATALINA_HOME 指向 Tomcat 的安装目录。

从 http://ant.apache.org下载最新版的 Ant,并解压缩到任意目录下。AppFuse 要求的最低版本是 1.6.2,本文采用的是 1.6.5。设置 ANT_HOME 指向 Ant 所在的目录,并在 PATH 中添加 %ANT_HOME%/bin。另外,要拷贝一个 junit.jar 到 %ANT_HOME%/lib 下,如果 lib 下没有 junit.jar,AppFuse 的脚本在运行时会给出警告信息。junit.jar 可以从 http://www.junit.org 获得,也可以从 %AppFuse%/lib/junit3.8.1 目录下获得。

从 http://www.MySQL.com 下载最新版的 MySQL,并安装或解压缩到任意目录下。本文采用的是 5.0。

从 http://www.eclipse.org 下载 Eclipse 3.1 或 3.2,安装到任意目录下。

AppFuse 的 Ant 脚本可以在命令行中运行,也可以在 Eclipse 里运行。有关如何在 Eclipse 里执行 Ant 脚本,请参考《用Eclipse开发AppFuse应用》。到此,我们已经为 AppFuse 开发应用准备好了环境,下面让我们开始使用 AppFuse 创建项目。

新建项目

AppFuse 的便捷与强大之处在于它已经为我们提供了多种开源框架的集成,并且通过使用 Ant 将所有的构建过程自动化。另外,AppFuse 利用 XDoclet 能够为我们生成绝大多数重要的代码,例如 dao 类、service 类以及测试用例,等等,并且能够将大量的配置文件也一并生成好,从而极大地节省了开发人员的时间。

用 AppFuse 进行开发通常有三种模式:“自上而下”,“自下而上”以及“混合模式”。采用“自上而下”(由 Java 对象向数据库对象创建的过程)的方式固然比较符合“面向对象”的设计思维,但是为此要编写大量的 XDoclet 的 tag 也确是一件痛苦的事情。相比较而言,采用“自下而上”(由数据库对象生成 Java 对象的过程)就显得简单许多 -- 只需要提供数据库表结构。然而,对于较为复杂的系统,尤其是类之间具有大量的关联的情形,仍然需要采用“自上而下”的创建模式。因此,在实际的项目开发中,将两种模式进行混合使用比较常见,这也就是“混合”模式。本文采用“自下而上”的模式。

本文的 AppFuse 安装在 "c:\opt" 下面。打开命令行控制台,进入 "c:\opt\appfuse",运行 “ant new”,为简单起见,所有参数选用默认值,见图 2。

图 2. ant new -- 新建项目

脚本运行成功后,新项目创建在 c:\opt\myapp 下(与 AppFuse 目录同级),myapp 是 AppFuse 默认的项目名称。将该项目导入到 Eclipse 中,并根据 《用Eclipse开发AppFuse应用》 进行必要的设置。以下是两个你可能需要进行的配置:

AppFuse 默认连接 MySQL 的用户名是 root,密码为空。如果你的 root 密码不是空,需要修改 C:\opt\myapp\build.properties 中的 database.admin.passWord 项,记得将注释去掉。

AppFuse 默认不是用 utf-8 创建数据库,如果你需要支持多语言,需要修改 C:\opt\myapp\metadata\sql\mysql-create.sql 中的创建数据库的语句,修改如下:

清单 1. 创建数据库语句

create database if not exists @DB-NAME@ CHARACTER SET utf8 COLLATE utf8_general_ci;

注:AppFuse 会在构建期将 @DB-NAME@ 替换成你指定的数据库名(本文中为“mydb”)。

在 c:\opt\myapp 下运行“ant setup test-all”。“setup” 完成了很多“设置”工作:创建数据库、构建 dao 和 serive 类、加载样本数据、创建 war 文件并部署到 tomcat,等等。“test-all” 运行所有的测试用例:对 dao,service 以及页面的测试。如果这个脚本运行成功,说明开发环境一切就绪。这时,启动 Tomcat,通过访问 http://localhost:8080/myapp 就能够看到 AppFuse 的登录界面了。AppFuse 预定义了两个用户:mraible 和 tomcat,密码都是 tomcat。mraible 属于管理员角色(能够管理用户信息),tomcat 属于普通用户角色。用 mraible 登录可以看到 图 3的界面。

图 3. AppFuse 的初始界面

或许此时,你已惊奇地发现,自己不过只运行了一次 Ant 脚本,但是系统已经拥有“用户管理”、“邮件”、“文件上传” 等功能 -- 这就是 AppFuse “开箱即用”的优势。接下来让我们开始开发前述的应用示例。

创建数据库表

在 mydb 数据库中执行如下语句创建 employee 表:

清单 2. 创建 employee 语句

CREATE TABLE `employee` (

`id` bigint(20) NOT NULL auto_increment,

`code` varchar(10) NOT NULL,

`dept` varchar(50) NOT NULL,

`name` varchar(20) NOT NULL,

`status` varchar(10) NOT NULL,

`telephone` varchar(20) default NULL,

`title` varchar(50) NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用 AppGen 生成代码

AppFuse 自带了一个代码生成工具 -- AppGen,它位于 c:\opt\myapp\extras\appgen 目录下面。AppGen 可以生成绝大部分我们需要的代码,比如 dao 类,service 类,菜单、增删改的 web 页面、配置文件、样本数据,等等。AppGen 利用 XDoclet 生成代码,因此可以在 extras/appgen/src 看到很多 .xdt 文件,这些就是 XDoclet 的模版定义文件。如果你希望自己编写 dao 和 service 类,就运行“install”这个 target,否则就用 “install-detailed” ,它可以帮你搞定一切。下面就让我们来运行 “install-detailed” 生成代码。在 c:\opt\myapp\extras\appgen 下运行 “ant install-detailed”。

清单 3. 运行 install-detailed

...

[input] Would you like to generate code from a table or POJO? (table,pojo)

table

[input] What is the name of your table (i.e. person)?

employee

[input] What is the name, if any, of the module for your table (i.e. organization)?

hr

...

前两个问题都很直观:选择从 table 生成代码,表名是 employee。第三个问题是让用户输入使用的模块名,如果你希望 AppFuse 帮你按模块生成代码的话,就需要输入一个模块名称。这里,我们输入“hr”。如果运行成功,在 Eclipse 中会看到如下的目录结构:

图 4. “install-detailed” 执行后的 Eclipse

表 2 列出了 "install-detailed" 生成的主要文件。

表 2. "install-detailed" 生成的主要文件列表

文件

说明

myapp/src/dao/org/appfuse/dao/hibernate/applicationContext-hibernate.xml

在其中增加了 employeeDao 的声明

myapp/src/dao/org/appfuse/hr/model/Employee.java

Employee 类 -- Java Bean

myapp/build/dao/gen/org/appfuse/hr/model/Employee.hbm.xml

Employee 类的 Hibernate 映射文件

myapp/src/dao/org/appfuse/hr/dao/EmployeeDao.java

定义关于 employee 的 dao 层的操作

myapp/src/dao/org/appfuse/hr/dao/hibernate/EmployeeDaoHibernate.java

EmployeeDao 的 Hibernate 实现类

myapp/src/service/org/appfuse/service/applicationContext-service.xml

在其中增加了employeeManager的声明

myapp/src/service/org/appfuse/hr/service/EmployeeManager.java

定义关于 employee 的 service 层的操作

myapp/src/service/org/appfuse/hr/service/impl/EmployeeManagerImpl.java

EmployeeManager 的实现类

myapp/src/web/org/appfuse/hr/webapp/action/EmployeeForm.java

employee 的添加/修改页面对应的 tapestry 类

myapp/src/web/org/appfuse/hr/webapp/action/EmployeeList.java

employee 的列表页面对应的 tapestry 类

myapp/test/dao/org/appfuse/hr/dao/EmployeeDaoTest.java

employee dao 类的测试用例

myapp/test/service/org/appfuse/hr/dao/EmployeeManagerTest.java

employee service 类的测试用例

myapp/test/web/org/appfuse/hr/webapp/action/EmployeeFormTest.java

employee 添加/修改页面类的测试用例

myapp/test/web/org/appfuse/hr/webapp/action/EmployeeFormTest.java

employee 列表页面类的测试用例

myapp/web/pages/hr/employeeForm.Html

employee 添加/修改页面 html 模版文件

myapp/web/pages/hr/employees.html

employee 列表页面 html 模版文件

myapp/web/pages/hr/employeeForm.page

employee 添加/修改页面规格文件

myapp/web/pages/hr/employees.page

employee 列表页面规格文件

不过,AppFuse 并不知道开发者需要加载哪些 hbm 文件,所以要手工将 Employee.hbm.xml 文件添加到配置文件中:打开 applicationContext-hibernate.xml,在 “sessionFactory” 的 bean 声明中,找到 “mappingResources” 属性的定义,增加 “<value>org/appfuse/hr/model/Employee.hbm.xml</value>”。

清单 4. applicationContext-hibnerate.xml 中添加 Employee.hbm.xml

...

<beans>

<!-- Hibernate SessionFactory -->

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

-- XML error: The previous line is longer than the max of 90 characters --

<property name="dataSource" ref="dataSource"/>

<property name="mappingResources">

<list>

<value>org/appfuse/hr/model/Employee.hbm.xml</value>

<value>org/appfuse/model/Role.hbm.xml</value>

<value>org/appfuse/model/User.hbm.xml</value>

</list>

</property>

...

在 c:\opt\myapp 下运行 “ant deploy”。打开 “http://localhost:8080/myapp”,用 mraible/tomcat 登录,“Employee List” 已经被添加到菜单里了。

图 5. myapp 的原始主页面

点击 “Employee List” 链接,进入“员工信息列表”页面。

图 6. myapp 的原始员工信息列表页面

点击“添加”按钮或点击任意一行数据,进入“员工信息添加/修改/删除”页面。

图 7. myapp 的原始员工信息添加/修改/删除页面

不难看出,虽然 AppFuse 帮我们生成了页面,但是这些页面并非那么“理想”,我们仍然需要根据实际的需求做些调整。

根据项目需求调整代码

在本文中,做了如下代码修改:

将所有页面文字翻译成中文:AppFuse 中用到的所有 Resource Bundle 文件位于 myapp/web/WEB-INF/classes 目录下(以ApplicationResources开头的properties文件)。更改 ApplicationResources_zh_CN.properties 的文件编码方式为“UTF-8”。然后,把 ApplicationResources.properties 中 “# -- Employee-START” 和 “# -- Employee-END” 之间的项拷贝到 ApplicationResources_zh_CN.properties 中,并逐项翻译成中文。AppFuse 会在脚本运行的时候自动用 native2ascii 进行编码转换。另外,AppFuse 默认对 “button.done” 的翻译是“做”,这不太合适,所以改为“完成”。

在“员工信息列表页面”去掉了 id 列,并调整了列的顺序:只要修改 employees.html 就可以。

在“员工信息添加/修改/删除页面”,将“所在部门”、“职位”、“状态”改为下拉列表:需要修改 employeeForm.html、employeeForm.page、EmployeeForm.java。用 PropertySelection 组件实现下拉列表,用 Resource Bundle 文件定义真正显示的选项文本。

增加了一个“人事管理”的角色,用来执行员工信息管理的权限控制:具体介绍见“系统安全”。

添加了一个新的主题 “mytheme”(只是更改了界面的颜色):具体介绍见“页面布局和样式”。

应用了上述修改后,在 c:\opt\myapp 中运行 “ant deploy” 重新打包整个项目并发布。以下是修改后的界面截图:

图 8. 修改后的 myapp 主页面

图 9. 修改后的 myapp 员工信息列表页面

图 10. 修改后的 myapp 员工信息添加/修改/删除页面

图 11. 修改后的 myapp 用户管理页面

其他功能

一个系统除了包含核心逻辑之外,还有其他一些辅助功能,它们也是非常重要的。下面,让我们来看看如何在 AppFuse 中开发这些功能。

语言国际化

如果你的系统不仅仅支持一种语言,那么就需要考虑这个问题。在 AppFuse 中,Resource Bundle 文件是位于 web\WEB-INF\classes 目录下的以 ApplicationResources 开头的 properties 文件。Tapestry 有自己的国际化文本机制。但是在 AppFuse 中,并不全是 Tapestry 页面,仍有些地方使用 jsp,而这些页面使用 JSTL 的 fmt 标签显示国际化文本。不过,AppFuse 已经将这二者的“源头”进行了整合,因此,对用户而言,只需要在 ApplicationResources*.properties 中定义需要国际化的文本。

但是,在 Eclipse 中可以看到,AppFuse 的 properties 文件默认的编码不是 UTF-8,而是 ISO-8859-1,这样会导致最后通过 native2ascii 转换后的文件都是 “???”,所以用户需要自己把这些文件转成 UTF-8。转换的方法很简单:在 properties 文件上点右键,在右键菜单上选择 Properties,打开属性窗口后,更改 “Text file encoding” 为 UTF-8。在修改编码前,最好先把已有的文字拷贝出来,转换好之后再粘贴回去,否则会导致原先翻译好的文字变成乱码。

图 12. ApplicationResources_zh_CN.properties 的属性窗口

AppFuse 在发布项目的时候,会自动用 native2ascii 转换这些资源文件。如果你想使用其他资源文件名,可以修改 web\WEB-INF\web.xml 中的 “javax.servlet.jsp.jstl.fmt.localizationContext” 的参数值。

页面布局和样式

使用 AppFuse,能够很方便的修改系统的整体布局和样式,因为 AppFuse 使用了一种强大的 “CSS框架”。项目创建好之后,在 web\styles 目录下,有三个目录:andreas01,puzzlewithstyle 和 simplicity。这些是 AppFuse 自带的三种主题,目录名即 CSS 框架的主题名。属于“管理员”角色的用户可以在登录后通过在 url 后面添加形如 "?theme=andreas01" 的参数更改系统使用的主题。如下图:

图 13. 应用了 “puzzlewithstyle” 的 myapp

系统默认使用的主题由 web\WEB-INF\web.xml 中的 “theme” 参数指定,AppFuse 默认使用的主题是 “simplicity”。更改或创建新的主题也很简单,只要在 web\styles 目录下,新建一个自己的目录,并参照已有主题的编写规范定义自己的主题。本文中,拷贝了 simplicity 目录,更名为 “mytheme”,然后将里面的字体颜色从“蓝色”基调改成了“绿色”基调,并修改 web.xml 中的 theme 参数值为 “mytheme”,这样 myapp 默认使用的就是 mytheme 的主题了,如图 8所示。你也可以从 http://css.appfuse.org/themes/ 得到更多关于 “CSS框架” 的信息。

系统安全

AppFuse 使用 Acegi 进行安全管理。Acegi 的配置信息位于 web\WEB-INF\classes\security.xml。事实上,Acegi 是被集成到 Spring 当中的,因此这个文件是 Spring 的配置文件格式。在 web\WEB-INF\web.xml 中,该文件被指定在应用启动前会被加载:

清单 5. web.xml 关于 Spring 配置文件的定义

...

<!-- Context Configuration locations for Spring XML files -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/applicationContext-*.xml,/WEB-INF/security.xml</param-value>

</context-param>

...

本文关于系统安全的实现如下:

在数据库中增加新的角色“hr”:编辑 myapp\metadata\sample-data.xml 文件,增加如下黑体的部分:

清单 6. sample-data.xml 中角色 “hr” 的记录

...

<table name='role'>

<column>id</column>

<column>name</column>

<column>description</column>

<row>

<value>1</value>

<value>admin</value>

<value><![CDATA[Administrator role (can edit Users)]]></value>

</row>

<row>

<value>2</value>

<value>user</value>

<value><![CDATA[Default role for all Users]]></value>

</row>

<row>

<value>3</value>

<value>hr</value>

<value><![CDATA[Role for employee mangement]]></value>

</row>

</table>

...

AppFuse 使用 dbunit 加载样本数据到数据库中,sample-data.xml 为 dbunit 提供样本数据定义。修改完 sample-data.xml,在 c:\opt\myapp\ 下运行 “ant db-load”,样本数据被重新加载。这样,我们就在数据库中定义了一个新的角色记录 “hr”。

定义角色名称的中文显示文本:在 myapp/sr/web/webapp/action/UserForm.java 的方法 pageBeginRender 中找到如下代码:

// initialize drop-downs

if (getAvailableRoles() == null) {

List roles =

(List) getServletContext().getAttribute(Constants.AVAILABLE_ROLES);

setAvailableRoles(new RoleModel(roles));

}

将其做如下修改:

// initialize drop-downs

if (getAvailableRoles() == null) {

List roles =

(List) getServletContext().getAttribute(Constants.AVAILABLE_ROLES);

for(int i=0;i<roles.size();i++){

LabelValue role=(LabelValue) roles.get(i);

role.setLabel(getText("rolelabel_"+role.getValue()));

}

setAvailableRoles(new RoleModel(roles));

}

并在 web\WEB-INF\classes\ApplicationResources_zh_CN.properties 中增加角色名称的定义:

rolelabel_admin=系统管理员

rolelabel_user=普通用户

rolelabel_hr=人事管理

AppFuse 默认在用户管理界面上显示的角色的名称是表 role 中的名称,这样无论切换到何种语言,角色名称都是 “admin”、"user"、“hr” 等等,角色名称不能根据 Locale 用相应的语言显示。因此,本文将角色的名称用 Resource Bundle 文件定义,数据库中存储 “key” 值。修改后的效果见 图 10。

配置“安全策略”:在 web\WEB-INF\security.xml 的 bean "filterInvocationInterceptor" 声明中增加如下“黑体”的一行:

<bean id="filterInvocationInterceptor"

class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">

<property name="authenticationManager" ref="authenticationManager"/>

<property name="accessDecisionManager" ref="accessDecisionManager"/>

<property name="objectDefinitionSource">

<value>

PATTERN_TYPE_APACHE_ANT

/clickstreams.jsp*=admin

/flushCache.*=admin

/passwordHint.html*=ROLE_ANONYMOUS,admin,user

/reload.*=admin

/signup.html*=ROLE_ANONYMOUS,admin,user

/users.html*=admin

/employees.html*=hr

/**/*.html*=admin,user

</value>

</property>

</bean>

“/employees.html*=hr” 的意思是:只有 hr 这个角色可以访问形如 “/employees.html*” 的 url。

将“员工信息维护”菜单关联到指定角色 hr:在 web\WEB-INF\menu-config.xml 中在 “EmployeeMenu” 的定义中增加 “roles='hr'”:

<!--Employee-START-->

<Menu name="EmployeeMenu" title="employeeList.title"

page="/employees.html" roles="hr"/>

<!--Employee-END-->

于是,“员工信息维护”的菜单入口只对属于“人事管理”角色的用户显示,对其他用户则隐藏。

分配角色 “hr” 给 tomcat:将“人事管理”角色分配给某一用户,例如 tomcat。则 tomcat能够看见并访问“员工信息维护”相关页面,而其他用户的界面上则没有“员工信息维护”这个菜单入口。并且,如果用户试图通过url访问employees.html的时候会看到如下页面:

图 14. “访问被拒绝”页面

图 14是 AppFuse 提供的默认“访问被拒绝”页面,你可以通过修改 web\403.jsp 把它定制成自己喜欢的页面。

事务控制

AppFuse 利用 Spring 的事务管理机制。Spring 可以以声明的方式,对方法进行事务控制,并且可以根据实际的需要,调整控制粒度。“声明方式”的好处在于:核心代码只需要关注业务逻辑,而将事务控制完全交由配置文件管理,一方面是核心代码简洁清晰,另一方面也便于进行集中配置管理。

事务控制一般是定义在 service 类的方法上的。AppFuse 的所有 service 类都声明在 src\service\applicationContext-service.xml 中,该文件中包含有一个 “tXProxyTemplate” bean 的声明,它定义了基本事务策略。其它的 service 类从 “txProxyTemplate” 继承,并可以“重写”事务策略。例如,AppFuse 对 userManager 的声明如下:

<!-- Transaction template for Managers, from:

http://blog.exis.com/colin/archives/2004/07/31/

concise-transaction-definitions-spring-11/ -->

-- XML error: The previous line is longer than the max of 90 characters --

<bean id="txProxyTemplate" abstract="true"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<property name="transactionManager" ref="transactionManager"/>

<property name="transactionAttributes">

<props>

<prop key="save*">PROPAGATION_REQUIRED</prop>

<prop key="remove*">PROPAGATION_REQUIRED</prop>

<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

</props>

</property>

</bean>

<!-- Transaction declarations for business services.

To apply a generic transaction proxy to

-- XML error: The previous line is longer than the max of 90 characters --

all managers, you might look into using the BeanNameAutoProxyCreator -->

<bean id="userManager" parent="txProxyTemplate">

<property name="target">

<bean class="org.appfuse.service.impl.UserManagerImpl">

<property name="userDao" ref="userDao"/>

</bean>

</property>

<!-- Override default transaction attributes b/c of UserExistsException -->

<property name="transactionAttributes">

<props>

<prop key="save*">PROPAGATION_REQUIRED,-UserExistsException</prop>

<prop key="remove*">PROPAGATION_REQUIRED</prop>

<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

</props>

</property>

<!-- This property is overriden in applicationContext-security.xml to add

method-level role security -->

<property name="preInterceptors">

<list>

<ref bean="userSecurityInterceptor"/>

</list>

</property>

</bean>

Spring 提供了大量的参数和选项使开发者能够灵活地管理事务。有关 Spring 使用方面的知识,请参阅 Spring 的文档。另外,《Spring in Action》也是一个不错的选择。

日志

AppFuse 集成了 Log4j 进行日志管理,log4j.properties 位于 web\WEB-INF\classes 目录下。AppFuse 已经在绝大多数基类(诸如,BasePage.java、BaseDaoHibernate.java 以及 BaseManager.java 等)中加入了如下用于输出日志的成员变量:

protected final Log log = LogFactory.getLog(getClass());

因此,开发者只需要在自己的代码中调用 log 的方法就可以了,例如:“log.debug("entered 'delete' method");”。

邮件

AppFuse 集成了 Spring 的发送邮件的功能。发送邮件需要用的参数,如主机、端口等信息在 web\WEB-INF\classes\mail.properties 中进行配置。和发送邮件相关的 bean 已经在 applicationContext-service.xml 中声明:mailEngine、mailSender、velocityEngine 以及 mailMessage。用户只需要在自己的类中 “注入” mainSender 的实例,就可以发送邮件了。具体使用方法,请参阅Spring的文档。

缓存

AppFuse 对缓存机制的支持源自 Hibernate 对缓存的支持。Hibernate 提供了对五种缓存机制的集成,AppFuse 默认提供了其中的两种:Ehcache 和 Oscache。开发者也可以根据需要自行添加和配置。Acegi 默认提供了对 Ehcache 支持的实现,所以 Ehcache 是较好的选择。ehcache.xml 和 oscache.properties 位于 web\WEB-INF\classes 中。

结束语

使用 AppFuse 创建 Web 应用,步骤非常简单,你只需要了解如何运行 Ant 就能够使用 AppFuse;使用 AppFuse 创建 Web 应用,非常快速,因为 AppFuse 已经帮我们完成大部分代码生成/集成/配置的工作;使用 AppFuse 创建 Web 应用,非常省力,因为 AppFuse 已经提供了很多“开箱即用”的功能。体验快速开发,从 AppFuse 开始。

关于作者

沈锐在 J2EE 项目开发方面有多年的经验,目前在 IBM CSDL 从事 IBM Workplace Dashboard Framework 产品的开发工作。他对 Java 的开源技术有着浓厚的兴趣,欢迎使用 shenrui@cn.ibm.com 与他交流。

(出处:http://www.knowsky.com)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有