级别: 中级 Ryan Shillington (shillington@us.ibm.com), 软件工程师, IBM
2008 年 5 月 15 日
这篇入门文章将说明如何在 IBM® WebSphere® Application Server V6.1 内部署 Ruby on Rails 应用程序并将其与 WebSphere Application Server 的现有功能进行集成——以及为何希望考虑将二者组合在一起的原因。
来自 IBM WebSphere Developer Technical Journal。
引言
由于采用 Java™ 的 IBM WebSphere Application Server 环境具有出色的可伸缩性、安全性和技术支持,因此很多组织都将其作为标准配置。不过,使用 Java 开发动态的网页经常非常单调乏味,而且极为费时。Ruby on Rails 提供了值得编程社区为之一振的快速开发和测试功能,简化了动态网页的创建工作。
本系列包括两个部分,本文是其中的第一部分,将演示如何在 WebSphere Application Server V6.1 内部署 Ruby on Rails 应用程序以及如何将 Ruby on Rails 应用程序与 WebSphere Application Server 的现有功能集成。这样的结合同时具有二者的优点:快速开发和测试,而且同时充分利用在 WebSphere Application Server 中的投资。
什么是 Ruby on Rails?
Ruby on Rails 有时候很难予以简洁地描述,因为其中包括如此多的东西。Ruby on Rails(以下称为 Rails)是:
- 模型-视图-控制器(Model, View, Controller,MVC)框架,拆分为两个部分:ActionController 用于控制器,而 ActionView 用于模型和视图。
- 对象关系映射(Object Relational Mapping,ORM)框架,称为 ActiveRecord。
- 一组命令行工具,用于进行设置目录结构和创建初始生产及测试代码的修改工作。
Rails 的动态性让我们惊叹,它是在不需要严格的构造和类型安全性的编程语言 (Ruby) 的基础上构建的。由于这个原因,从代码行数量而言,其他希望开发人员采用 Java 进行编码的框架很难与 Rails 竞争。使用 Ruby on Rails 的主要优势在于,它的简单简直让人着迷。
例如,Ruby 通过解释方式执行,这意味着您可以采用迭代方式工作:编写代码,单击浏览器中的“刷新”按钮查看更改,编写更多的代码,再次刷新,如此往复。通过使用随 Rails 提供的内置 WEBrick Web 服务器,您不需要等待重新部署 WAR 和 EAR 文件。当然,对于长期的 Java 开发,可以设置 WebSphere Application Server 来对特定的 WAR 和 EAR 进行此工作,但对于快速而简单的迭代开发,基本的 WEBrick Web 服务器就可以完成此工作,不会带来额外的设置成本——而且真的相当方便。
作为老牌 Java 开发人员,在完成了一些 Rails 教程的学习之后,我很惊讶地发现只需要很少的代码行就可以开发可正常工作的 Web 应用程序,而且不需要进行大量的 XML 配置。事实上,本文依赖于随 Rails 提供的命令行工具来帮助您创建整个示例“愿望列表”应用程序。(其中唯一的例外是迁移中用于创建数据库表的代码,但事实上这也可以通过数据库中的简单 SQL 完成。)
本文将指导您在 WebSphere Application Server 中充分利用 Rails 应用程序的功能。如果您有希望迁移到 WebSphere Application Server 上运行的现有 Rails 应用程序,或希望开始使用 Rails 作为现有 Java EE 应用程序的前端,或对使用 Rails 感兴趣,但不确定此新技术是否能集成到您当前的环境中,此信息将为您提供所需的帮助。这里的目标不是简单地提供完成重要任务的步骤,而且还要说明采用此方法的原因以及将 Ruby on Rails 与 WebSphere Application Server 结合使用所带来的价值。
先决条件和设置
为了按照本文列出的步骤进行操作,您需要安装此软件:
Ruby 提供了一个方便的程序包管理工具,称为 gem,其功能与 Perl 的 CPAN 类似。向您的 Rudy 安装添加库将非常有用,而且可以使用这个方法安装 Rails 和 ActiveRecord-JDBC:
清单 1
C:\>gem install rails -y --no-rdoc --no-ri
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed rails-1.2.5
Successfully installed activesupport-1.4.4
Successfully installed activerecord-1.15.5
Successfully installed actionpack-1.13.5
Successfully installed actionmailer-1.3.5
Successfully installed actionwebservice-1.2.5
C:\projects\wishlist\WEB-INF\gems\specifications>
gem install activerecord-jdbc-adapter --no-rdoc --no-ri
Successfully installed activerecord-jdbc-adapter-0.6 |
Rails 中包含 ActiveRecord,可支持各种数据库但并不直接支持 JDBC。ActiveRecord-JDBC gem 提供了连接适配器,使 ActiveRecord 看起来似乎直接支持 JDBC。ActiveRecord-JDBC 支持 IBM DB2®、MySQL、Oracle、Microsoft® SQL Server 和其他数据库,但本文示例中使用的是 Derby,这主要是因为此数据库可免费使用,而且可以方便地迁移到 DB2,这在以后需要承担较为繁重的任务时非常方便。
Ruby 和 Jruby:有什么区别?
JRuby 是原始 Rudy 解释程序(最初使用 C 编写的)的纯 Java 实现,其相对于 Rudy 的主要优势似乎是,您可以在 Java 代码和 Rudy 代码之间进行相互调用。尽管这非常有用,但并不是考虑使用 JRuby 替代 Ruby 的唯一原因。
Rudy 的标准 C 实现使用绿色线程,即某个用户长期运行的进程可以阻塞所有其他用户。假定某个用户请求信息量大的报告,需要一分钟才能完成,这将阻塞所有其他用户,而其他用户可能只是尝试查看通常显示所需时间少于一秒钟的网页。尽管这一点可能在发布 Ruby 2.0 时发生变化,但在这种情况下标准 Rudy 实现的所有用户可能会被阻塞长达一分钟,等待这个用户长时间跨度的请求完成。
对于 JRuby 并不会这样,因为 JRudy 基于 Java 线程实现 Rudy 线程,而 Java 线程使用的是本机操作系统线程。JRuby 几乎可以随时切换上下文。因此,在处理负载较重的情况下,在 JRudy 上运行的 Rails 应用程序的响应能力通常比使用标准 Ruby 实现时更好。
部署 Rails 项目
您要进行的第一个大步骤将是,详细了解在 WebSphere Application Server 上部署包含 Rails 项目的 WAR 的基本步骤。在此部分,您将首先部署其中仅包含 Rails 欢迎页面的空白 Rails 应用程序。具体来说,您将设置目录结构、将其打包为 WAR 文件,然后将其部署到正在运行的 WebSphere Application Server 上。
-
创建 Rails 项目
通过 Rails 可以非常方便地设置项目的基本目录结构。运行 rails 命令,以创建所有必要的初始文件:
清单 2
C:\>mkdir projects
C:\>cd \projects
C:\projects>jruby -S rails wishlist
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
<continued...> |
您可能注意到了,在上面的命令中并不是调用标准 Ruby rails,而是 jruby -S rails。-S 标志告知 JRuby 在 Jruby 的 bin 目录中查找脚本。否则将在用户的当前目录中查找脚本。
-
安装 Goldspike
Goldspike 是 Rails 插件,提供充实构建系统所需的任务和模板,以便从新创建的 Rails 项目创建 WAR 文件。Rake 是 Ruby 的构建工具,用于自动执行类似于 shell 的命令,如打包和部署 Ruby 代码以及执行数据库迁移。对于 Java 开发人员,基本上可以将其视为与 Ant 等效的工具;而对于 C/C++ 开发人员,基本上可以将其视为与 make 等效的工具。它和二者都相似,不过 Rake 实际上是 Rudy 代码。对于本文,您只需要了解 Rake 用于创建构建系统即可。
- 运行清单 3 中所示的命令,以安装 Goldspike。
清单 3
C:\projects\wishlist>jruby script\plugin install
http://jruby-extras.rubyforge.org/svn/trunk/rails-integration/plugins/goldspike
+ ./goldspike/README
+ ./goldspike/Rakefile
+ ./goldspike/generators/goldspike/goldspike_generator.rb
+ ./goldspike/generators/goldspike/templates/war.rb
+ ./goldspike/generators/goldspike/templates/web.xml.erb
+ ./goldspike/init.rb
+ ./goldspike/install.rb
+ ./goldspike/lib/create_war.rb
+ ./goldspike/lib/java_library.rb
+ ./goldspike/lib/packer.rb
+ ./goldspike/lib/run.rb
+ ./goldspike/lib/util.rb
+ ./goldspike/lib/war_config.rb
+ ./goldspike/tasks/war.rake
+ ./goldspike/test/test_create_war.rb
+ ./goldspike/test/test_java_library.rb
+ ./goldspike/test/test_maven_library.rb
+ ./goldspike/test/war_config_test_config.rb
exists config
create config/war.rb
create WEB-INF
create WEB-INF/web.xml.erb |
Goldspike 将在 source 树中创建两个新文件:
-
config/war.rb:通过此文件可以限制将打包在 WAR 文件中的 Jruby、Rails 和其他插件的版本。
-
WEB-INF/web.xml.erb:这是 WAR 中最终生成的 WEB-INF/web.xml 文件的模板。
- 创建 lib\java 目录,并将此目录用于存储在 JRudy 代码中将引用的任何 JAR 文件。lib\java 中 JAR 内的任何类都将打包到 Goldspike 所创建的 WAR 中。这些新目录将在首次运行 Rake 任务来创建 WAR 时创建:
-
WEB-INF/classes:Rudy 代码所引用的任何 Java 类都包含在此目录中。
-
WEB-INF/gems:Goldspike 将自动对安装到此目录中的所有 gems 进行捆绑,以便在 WebSphere Application Server 内提供。
不要在这里添加新 JAR;将自动从 lib\java 复制此类文件。
-
创建 WAR 文件
为了确保基础结构正常工作,请使用 JRuby 和 Goldspike 创建您的第一个 WAR 文件:
清单 4
C:\projects\wishlist>jruby -S rake war:standalone:create
(in C:/projects/wishlist)
info: Assembling web application
info: Packing needed Java libraries ...
info: Packing needed Ruby gems ...
info: adding Ruby gem ActiveRecord-JDBC version 0.5
info: Packing needed files ...
info: Creating web archive |
新创建的 WAR 文件的大小应该略微大于 7 MB。不要看到空的 Rails 项目这么大而感到惊慌:Goldspike 还将 JRuby 和 Rails 打包到了 WAR 文件中。(或者,您可以让 Goldspike 通过 web.xml 将 JRudy 和 Rails 的安装位置告知 WebSphere Application Server,以避免将其包含在这些包中,但这不在本文的讨论范围之内。)
-
部署 WAR 文件
要部署新 WAR:
- 导航到 http://localhost:9060/admin,打开 WebSphere 管理控制台。
- 在左侧导航菜单中,展开 Applications 并单击 Install New Application。
- 单击 Browse,以选择最近创建的 WAR 文件:C:\projects\wishlist\wishlist.war(图 1)。
图 1. 准备应用程序安装
- 将 Context root 设置为
wishlist,然后单击 Next。
- 在 Deployment 向导中,接受缺省设置,并单击 Next,直到进入步骤 4,然后单击 Finish。
- 搜索所显示的 Installing... 面板上的消息,确定没有报警和错误,然后单击 Save。
- 在导航窗格中的 Applications 下选择 Enterprise Applications。
- 选中 wishlist_war 旁边的复选框,然后单击 Start 按钮。
- 或者,如果您计划在整个开发周期中进行重新部署,则可以考虑设置一个 Jython wsadmin 脚本。尽管脚本的详细信息不在本文的讨论范围内,但我在下面给出了一个示例。不过,要注意,只需要针对您的环境修改
# Customize these: 注释之后的六行代码。
清单 5
import re
import os
# Customize these:
WAR_FILE = "C:/projects/wishlist/wishlist.war"
WAR_NAME = "wishlist_war"
WAR_CONTEXT_ROOT = "/wishlist"
SERVER_NAME = "server1";
NODE_NAME = "rshillington-ltNode01"
CELL_NAME = "rshillington-ltNode01Cell"
# No need to customize the rest.
def log(text):
print "---- " + text;
appList = AdminApp.list()
log("Apps installed before uninstall: \n" + appList + "\n\n")
log("Find the appManager...")
appManager = AdminControl.queryNames
("cell=" + CELL_NAME + ",node=" + NODE_NAME +
",type=ApplicationManager,process=" + SERVER_NAME + ",*")
containsApp = re.compile("\\b" + WAR_NAME + "\\b", 0).search(appList) != None
if containsApp:
log("Stop the application...")
try:
AdminControl.invoke(appManager, "stopApplication", WAR_NAME)
except:
# This is okay - it just wasn't started.
log("\tApplication not started.");
log("Uninstall the application...")
AdminApp.uninstall(WAR_NAME)
log("Save the changes...")
AdminConfig.save()
log("Install the application...")
AdminApp.install(WAR_FILE, "-appname " + WAR_NAME + " -server " + SERVER_NAME +
" -contextroot " + WAR_CONTEXT_ROOT + " -usedefaultbindings")
AdminConfig.save()
log("Start the application...")
AdminControl.invoke(appManager, "startApplication", WAR_NAME)
log("Save the changes...");
AdminConfig.save()
appList = AdminApp.list()
log("Apps installed after install: \n" + appList + "\n\n") |
- 将脚本保存为 deployWishlist.py,并使用 wsadmin 工具运行。您应该看到与下面所示类似的内容:
清单 6
C:\WAS\bin>wsadmin.bat -f c:\moveit\deployWishlist.py
-user admin -password <your password>
WASX7209I: Connected to process "server1" on node rshillington-ltNode01
using SOAP
connector; The type of process is: UnManagedProcess
---- Apps installed before uninstall:
DefaultApplication
ivtApp
---- Find the appManager...
---- Install the application...
WASX7327I: Contents of was.policy file:
//
// Template policy file for enterprise application.
// Extra permissions can be added if required by the enterprise application.
//
// NOTE: Syntax errors in the policy files will cause the enterprise
// application FAIL to start. Extreme care should be taken when
// editing these policy files. It is advised to use the policytool
// editing the policy files provided by the JDK for
// (WAS_HOME/java/jre/bin/policytool).
grant codeBase "file:${application}" {
};
grant codeBase "file:${jars}" {
};
grant codeBase "file:${connectorComponent}" {
};
grant codeBase "file:${webComponent}" {
};
grant codeBase "file:${ejbComponent}" {
};
ADMA5016I: Installation of wishlist_war started.
ADMA5058I: Application and module versions are validated with versions
of deployment targets.
ADMA5062W: An error occurred while reusing the existing deployment.xml file.
The original deployment settings are not reused."
ADMA5005I: The application wishlist_war is configured in the WebSphere
Application Server repository.
ADMA5053I: The library references for the installed optional package
are created.
ADMA5005I: The application wishlist_war is configured in the WebSphere
Application Server repository.
ADMA5001I: The application binaries are saved in
C:\WAS\profiles\AppSrv01\wstemp\Script116c5288827\workspace\cells\rshillington-
ltNode01Cell\applications\wishlist_war.ear\wishlist_war.ear
ADMA5005I: The application wishlist_war is configured in the WebSphere
Application Server repository.
SECJ0400I: Successfuly updated the application wishlist_war with the
appContextIDForSecurity information.
ADMA5011I: The cleanup of the temp directory for application wishlist_war
is complete.
ADMA5013I: Application wishlist_war installed successfully.
---- Start the application...
---- Save the changes...
---- Apps installed after install:
DefaultApplication
ivtApp
wishlist_war |
- 现在将您的浏览器指向 http://locahost:9080/wishlist。您将看到与图 2 所示类似的网页。
图 2. 初始应用程序欢迎页面

 |

|
编写“愿望列表”应用程序
接下来,您将开发简洁的生日愿望列表应用程序,可以在其中输入想要的礼物的名称,让其他人买来送给您:
- Rails 需要知道如何连接到数据库。对于本文,您将使用基于文件的 Derby 数据库,其具体路径以硬编码的形式提供。更改 config\database.yml,以包含以下代码:
清单 7
development:
adapter: jdbc
driver: org.apache.derby.jdbc.EmbeddedDriver
url: jdbc:derby:c:\projects\wishlist\db\wishlist_dev;create=true
test:
adapter: jdbc
driver: org.apache.derby.jdbc.EmbeddedDriver
url: jdbc:derby:c:\projects\wishlist\db\wishlist_test;create=true
production:
adapter: jdbc
driver: org.apache.derby.jdbc.EmbeddedDriver
url: jdbc:derby:c:\projects\wishlist\db\wishlist_prod;create=true
|
测试和生产部分与开发部分完全相同,不过它们配置为在 URL 中使用数据库 wishlist_test 和 wishlist_prod,而不是 wishlist_dev。测试数据库将用于运行您的测试,以避免影响开发环境。生产环境最好指向承载应用程序的位置。对于本文,生产环境用于将应用程序部署到 WebSphere Application Server。就目前而言,数据库为空也无大碍。Derby URL 上的 create=true 标志告知 Derby,如果目录和数据库不存在,则进行创建。
- 为了识别 ActiveRecord JDBC,请将清单 8 中的
if RUBY_PLATFORM 块中的前五个语句添加到 environment.rb 中 Rails::Initializer.run 块之前的位置,从而加载 activerecord-jdbc-adapter gem。您的 config\environment.rb 将与以下所示类似:
清单 8
# Be sure to restart your web server when you modify this file.
# Uncomment below to force Rails into production mode when
# you don't control web/app server and can't set it the proper way
# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '1.2.5' unless defined? RAILS_GEM_VERSION
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
if RUBY_PLATFORM =~ /java/
require 'rubygems'
gem 'activerecord-jdbc-adapter'
require 'jdbc_adapter'
end
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here
... |
- 要在 JRuby 类路径和 WAR 中包括 Derby JDBC JAR,请将其添加到系统 CLASSPATH 中。要将其包括到 WAR 中,请将 derby.jar 复制到 lib\java 中
清单 9
C:\projects\wishlist>cd lib\java
C:\projects\wishlist\lib\java>copy C:\WAS\derby\lib\derby.jar derby-10.1.3.jar
1 file(s) copied.
C:\projects\wishlist\lib\java>set CLASSPATH=C:\projects\wishlist\lib\java\
derby-10.1.3.jar |
- 请特别注意所添加的版本号。Goldspike 仅仅识别 Maven 样式的依赖关系,因此不能识别没有版本号的 JAR 文件。不过,Goldspike 并不会扫描 lib\java 目录并选取其中的所有 JAR 文件,因此您需要将 config\war.rb 修改为与以下所示类似的样子:
清单 10
# Goldspike configuration
# Set the version of JRuby and GoldSpike to use:
maven_library 'org.jruby', 'jruby-complete', '1.0.2'
# Add the Derby library to the WAR
include_library 'derby', '10.1.3';
# Add the ActiveDirectory-JDBC adapter to the WAR
add_gem 'activerecord-jdbc-adapter', '0.6';
# Uncomment this to have the war run in the rails 'development' environment
(against wishlist_dev) when run in WebSphere
#@result.rails_env= 'development'; |
- Goldspike 允许有一系列很大程度上未公开的选项,可以在 vendor\plugins\goldspike\lib\war_config.rb 中找到这些选项。例如,要更改所生成的 WAR 文件的路径和名称,请将以下代码行添加到 config/war.rb 结束的位置:
清单 11
@result.war_file= 'c:/temp/funnyName.war'; |
- 运行 Rails 生成的脚本,以创建“愿望列表”编辑器的代码。Rails 提示问题时,请回答
Y(代表是)。
清单 12
C:\projects\wishlist>jruby script\generate controller Editor
exists app/controllers/
exists app/helpers/
create app/views/editor
create test/functional/
create app/controllers/editor_controller.rb
create test/functional/editor_controller_test.rb
create app/helpers/editor_helper.rb
C:\projects\wishlist>jruby script\generate model Wish
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/wish.rb
create test/unit/wish_test.rb
create test/fixtures/wishes.yml
exists db/migrate
create db/migrate/001_create_wishes.rb
C:\projects\wishlist>jruby script\generate scaffold Wish Editor
exists app/controllers/
exists app/helpers/
exists app/views/editor
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/wish.rb
identical test/unit/wish_test.rb
identical test/fixtures/wishes.yml
create app/views/editor/_form.rhtml
create app/views/editor/list.rhtml
create app/views/editor/show.rhtml
create app/views/editor/new.rhtml
create app/views/editor/edit.rhtml
overwrite app/controllers/editor_controller.rb? [Ynaqd]
force app/controllers/editor_controller.rb
overwrite test/functional/editor_controller_test.rb? [Ynaqd]
force test/functional/editor_controller_test.rb
identical app/helpers/editor_helper.rb
create app/views/layouts/editor.rhtml
create public/stylesheets/scaffold.css |
- 上面脚本中的第一个命令
jruby script\generate controller Editor 创建将包含编辑器操作的后端代码的控制器。
- 第二个命令
jruby script\generate model Wish 将创建负责将数据写入数据库的模型。
- 第三个命令
jruby script\generate scaffold Wish Editor 提供基本列表和针对存储在数据库中的 wish 对象的编辑器控制器操作(创建、编辑和删除)。
- 迁移是在生成模型期间创建的文件之一,位于 db/migrate/001_create_wishes.rb 中。迁移用于将数据库从其现有状态(在本例中为空白数据库)迁移到新状态(通常包括新表、新列等)。在这里,迁移将用于在数据库中创建表(名为 wishes),用于保存愿望列表中的各个项目。编辑新迁移 db/migrate/001_create_wishes.rb,在其中添加您认为必要的列。无论向 wishes 表添加什么列,都会按照相同的顺序显示在您的应用程序中。
清单 13
class CreateWishes < ActiveRecord::Migration
def self.up
create_table :wishes do |t|
t.column :name, :string, :limit => 255, :null => false
t.column :url, :string, :limit => 1024, :null => true
end
end
def self.down
drop_table :wishes
end
end |
- 运行
rake,以让 Rails 在您的 wishlist_dev 开发数据库中创建表。
清单 14
C:\projects\wishlist>jruby -S rake db:migrate
(in C:/projects/wishlist)
== CreateWishesTable: migrating ===============================================
-- create_table(:wishes)
-> 0.8440s
== CreateWishesTable: migrated (0.8440s) ====================================== |
- 对生产数据库进行相同的处理,WebSphere Application Server 将指向此数据库:
清单 15
C:\projects\wishlist>jruby -S rake db:migrate RAILS_ENV=production
(in C:/projects/wishlist)
== CreateWishes: migrating ====================================================
-- create_table(:wishes)
-> 0.1400s
== CreateWishes: migrated (0.1400s) =========================================== |
- 最后,再次打包 WAR,并确保其中包括 Derby JAR。
清单 16
C:\projects\wishlist>jruby -S rake war:standalone:create
(in C:/projects/wishlist)
(in C:/projects/wishlist)
info: Assembling web application
info: Packing needed Java libraries ...
info: adding Java library jruby-complete-1.0.2
info: adding Java library derby-10.1.3
info: Packing needed Ruby gems ...
info: Packing needed files ...
info: Creating web archive |
- 从 WebSphere 管理控制台卸载旧 WAR,并重新安装在 C:\projects\wishlist\wishlist.war 中生成的新 WAR(按照与之前相同的目录进行安装)。首次访问 http://localhost:9080/wishlist/editor/ 时,可能收到以下错误:
清单 17
Error 503: The server is currently overloaded, please try again later. |
这与预期的情况相符。尽管 WebSphere Application Server 认为 WAR 已经启动,但 JRuby 仍然需要时间来启动自己的解释程序,然后再启动 Rails。在您服务器的 SystemOut.log 文件中,最终将看到与以下所示类似的内容:
清单 18
WebApp A SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist]
[Servlet.LOG]: JRuby init time: 6344ms
WebApp A SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist]
[Servlet.LOG]: Rails init time: 14531ms
WebApp A SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist]
[Servlet.LOG]: Runtime 0 loaded
WebApp A SRVE0180I: [wishlist.war1165c5b3058#wishlist.war] [/wishlist]
[Servlet.LOG]: Requests can now be processed |
- 看到最终“现在可以处理请求了(Requests can now be processed)”消息后,就可以开始在您的新“愿望列表”应用程序中添加和编辑愿望了。如果您在 WebSphere Application Server SystemOut.log 中看到与此类似的内容:
清单 19
WebApp A SRVE0180I: [wishlist_war#wishlist.war] [/wishlist] [Servlet.LOG]:
Failed to load Rails: No such file or directory - C:/WAS/profiles/AppSrv01/C: |
,则请确保在使用 JRuby 1.0.2 而不是更早的版本。由于在设置 war.rb 文件前创建了初始 WAR 文件,因此请确保 WEB-INF\lib 中唯一以“jruby”开头的文件名为 jruby-complete-1.0.2.jar。如果一切正常,则在转到 http://localhost:9080/wishlist/editor/list 时应该看到与图 3 所示类似的面板。
图 3. “愿望列表”应用程序面板

 |

|
测试用例
如果不能确保质量,快速开发软件能力就没有用。Rails 提供了强大的测试框架,包括自动化单元测试(用于模型)、功能测试用例(用于控制器)和集成测试(跨多个控制器的端到端集成)。Rails 真正强大的地方在于,您可以像编写代码一样快速编写测试。
为编辑器控制器生成代码时,会在 test\functional\editor_controller_test.rb 中创建测试。还记得在本练习开始时如何设置三个数据库的情况吗?Rails 框架将复制开发环境中的表结构,并使用 fixture(位于 test\fixtures)中的数据填充这些表。由于前面在迁移中已将数据库中的 Name 列标记为 NOT NULL,因此需要编辑 Rails 在 test\fixtures\wishes.yml 中创建的测试数据,以包括您的愿望名称。您的 wishes.yml fixture 文件可能与以下所示类似:
清单 20
first:
id: 1
name: A new bike
second:
id: 2
name: A great programming language
url: http://www.rubyonrails.org |
您还需要在 editor_controller_test.rb 中修改第 55 行 test_create 方法的内容,因为其中尝试不使用名称创建新 wish 对象——这样应该行不通,会导致测试失败。将该行代码改为与以下所示类似的内容:
清单 21
post :create, :wish =>
{:name => "Gift certificate to my favorite store."} |
现在测试用例将能够正常地运行:
清单 22
C:\projects\wishlist>jruby -S rake
(in C:/projects/wishlist)
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb"
"test/unit/wish_test.rb"
Loaded suite C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader
Started
.
Finished in 0.375 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb"
"test/functional/editor_controller_test.rb"
Loaded suite C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader
Started
.....DEPRECATION WARNING: paginate is deprecated and will be removed from Rails 2.0
(Pagination is moving to a plugin in Rails 2.0: script/plugin install
svn://errtheblog.com/svn/plugins/classic_pagination) See http://www.rubyonrails.org/
deprecation for details. (called from list at C:/projects/wishlist/app/controllers/
editor_controller.rb:12)
DEPRECATION WARNING: paginate is deprecated and will be removed from Rails 2.0 (Pagination
is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/
plugins/classic_pagination) See http://www.rubyonrails.org/deprecation for details.
(called from list at C:/projects/wishlist/app/controllers/editor_controller.rb:12)
...
Finished in 1.344 seconds.
8 tests, 26 assertions, 0 failures, 0 errors
C:/jruby-1.0.2/bin/jruby.bat -Ilib;test
"C:/jruby-1.0.2/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb" |
 | | 您所收到的任何弃用警告都是告诉您在 Rails 2.0 发布之后,您的代码将失效。与 Java 不同,Rails 社区删除了以后版本中的已弃用功能。 |
|
既然测试成功,就可以开发 Rails 应用程序了:首先编写测试,然后添加 Ruby 代码让测试用例工作——不用定期将其部署到 WebSphere Application Server。如果您在 Rudy 代码中调用任何 Java 方法,请记住将支持 JAR 添加到系统 CLASSPATH 变量和 lib\java 中。在持续对应用程序进行更改的过程中,您还可以运行 WEBrick 服务器来快速测试您所进行的更改:
清单 23
C:\projects\wishlist>jruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-11-20 01:05:25] INFO WEBrick 1.3.1
[2007-11-20 01:05:25] INFO ruby 1.8.5 (2007-11-01) [java]
[2007-11-20 01:05:25] INFO WEBrick::HTTPServer#start: pid=1881829418 port=3000 |
WEBrick 和 WebSphere Application Server 可以同时运行。它们各自位于侦听不同端口的不同 JVM 中,不应该彼此存在连接。
安全性
尽管让公众都能查看您的愿望列表的做法可以接受,但您希望能够控制对其进行编辑的人员。在 Ruby on Rails 的标准 C 版本中,处理用户身份验证有两个方法:通过安装十多个不同的用户身份验证包中的一个,或尝试通过创建用户表(或类似的东西)来创建自己身份验证包。当然,与此相对,WebSphere Application Server 可以处理 LDAP、ActiveDirectory、Kerberos 和很多第三方身份验证提供程序。当然,它还支持标准 Java EE 安全性,因此您可以找到大量相关文档(请参见参考资料)。
为了使用 WebSphere Application 安全性限制对您愿望列表的功能访问,请执行以下步骤:
- 创建查看器控制器,该控制器与编辑器平行,用于显示愿望列表,然后将编辑器访问限制为一个特定的用户:
清单 24
C:\projects\wishlist>jruby script\generate controller Viewer
exists app/controllers/
exists app/helpers/
create app/views/viewer
exists test/functional/
create app/controllers/viewer_controller.rb
create test/functional/viewer_controller_test.rb
create app/helpers/viewer_helper.rb |
- 将 EditorController 的代码复制出来作为 ViewerController 唯一的方法。完成此操作后,app\controllers\viewer_controller.rb 应该与以下所示类似:
清单 25
class ViewerController < ApplicationController
def list
@wish_pages, @wishes = paginate :wishes, :per_page => 10
end
end |
- 在编辑器中,将列出愿望的模板复制到 app\views\viewer\list.rhtml,然后删除 Show、Edit、Destroy 和 New Wish 的链接。这里的目的是为了设置两个独立的控制器,以便演示如何在其中之一上启用 Java EE 安全性。
清单 26
<h1>Listing wishes</h1>
<table>
<tr>
<% for column in Wish.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>
<% for wish in @wishes %>
<tr>
<% for column in Wish.content_columns %>
<td><%=h wish.send(column.name) %></td>
<% end %>
</tr>
<% end %>
</table>
<%= link_to 'Previous page', { :page => @wish_pages.current.previous }
if @wish_pages.current.previous %>
<%= link_to 'Next page', { :page => @wish_pages.current.next }
if @wish_pages.current.next %>
<br /> |
- 在 public\login.html 创建简单的登录页面:
清单 27
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="j_security_check" method="post">
Username: <input type="text" name="j_username"><br>
Password: <input type="password" name="j_password"><br>
<br>
<input type="submit"><br>
</form>
</body>
</html> |
- 在 public\login-error.html 创建简单的登录错误页面:
清单 28
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Login Failed</title>
</head>
<body>
<h1>Login Failed.</h1><br>
<br>
<a href="login.html">Please try again.</a>
</body>
</html> |
- 为了对编辑器控制下(即位于相对 URL editor/* 下)的所有内容进行限制,请将下面的 security section 部分添加到 web.xml.erb 中。这就是 WebSphere Application Server 所需的全部,可以将 /editor/* 中的所有对象限制为仅供 Administrator 组的用户访问。请记住,Goldspike
war:standalone:create Rake 任务将使用 web.xml.erb 作为模板创建 web.xml 文件。下面是在 WEB-INF/web.xml.erb 中添加的 <servlet-mapping> 元素:
清单 29
<security-constraint>
<display-name>Editor</display-name>
<web-resource-collection>
<web-resource-name>Editor</web-resource-name>
<url-pattern>/editor/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>Administrator</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/login-error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>Administrator</role-name>
</security-role> |
- 最后,您需要在 WebSphere Application Server 中启用应用程序安全性,并将您的用户添加到愿望列表的 Administrator 组:
- 通过 http://localhost:9060/admin 登录到管理控制台。
- 在左侧的菜单中,展开 Security 并选择 Secure administration, applications, and infrastructure。
- 选中 Enable application security 复选框,然后单击 Apply。
- 直接保存到主配置。
- 在左侧菜单中,展开 Applications 并选择 Enterprise Applications。
- 选择 wishlist_war。
- 在 Detail Properties 部分下,选择 Security role to user/group mapping。
- 单击 Administrator 旁边的复选框,然后单击 Look up users 按钮。
- 单击 Search。
- 选择用户,并单击 > >,以将用户移动到 Selected: 列。(如果 Available: 列没有可选用户,则可以通过在左侧菜单中展开 Users and Groups 并选择 Manage Users 来创建新用户。)
- 单击 OK。
- 重新启动 WebSphere Application Server。(如果应用程序安全性已经启用,则将不需要重新启动 WebSphere Application Server。)
- 重新启动 WebSphere Application Server(如果有必要进行此步骤)后,请在查看器控制台中查看列表操作 (http://localhost:9080/wishlist/viewer/list)。在尝试从编辑器控制器导航到页面时(如 http://localhost:9080/wishlist/editor/),应该会提示出现登录错误。
在以后为其他控制器或特定 URL 添加安全性的操作就像修改 WEB-INF/web.xml.erb 模板一样简单。
指定默认用户和组
现在,每次卸载和重新安装 WAR 文件时,您需要将用户添加回 WAR 的 Administrator 组。为了避免进行此操作,可以将您的 WAR 文件打包在 EAR 内,并在 EAR 的 META-INF 配置文件内指定缺省组和用户。要完成此任务,请执行以下步骤:
- 在靠近 WEB-INF 的 root rails 目录中创建 META-INF 目录。
- 在 META-INF 中创建描述 WAR 文件和角色的 application.xml 文件:
清单 30
<?xml version="1.0" encoding="UTF-8"?>
<application id="Application_ID" version="1.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
<display-name>Wishlist_EAR</display-name>
<module id="wishlist">
<web>
<web-uri>wishlist.war</web-uri>
<context-root>/wishlist</context-root>
</web>
</module>
<security-role id="Administrator">
<role-name>Administrator</role-name>
</security-role>
</application> |
- 上述文件的目的是为了实现 Web 应用程序的上下文根的自动化,以免手动输入。如果您在使用前面提到的 Jython 脚本,则将需要修改第 40 行的 AdminApp.install 命令,以不再包括上下文根。如果您手动部署,则不需要在初始安装页上键入上下文根。即使对提供默认上下文根不感兴趣,但下面的步骤要正常工作,必须进行此步骤。
- 在 META-INF 中创建 ibm-application-bnd.xml 文件,此文件详细描述映射到应用程序的 Administrator 组的 WebSphere Application Server 用户和组:
清单 31
<?xml version="1.0" encoding="UTF-8"?>
<applicationbnd:ApplicationBinding xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI"
xmlns:applicationbnd="applicationbnd.xmi"
xmi:id="ApplicationBinding">
<authorizationTable xmi:id="AuthorizationTable">
<authorizations xmi:id="RoleAssignment">
<users xmi:id="admin_user" name="admin"/>
<role href="META-INF/application.xml#Administrator"/>
<groups xmi:id="Administrators_Group" name="Administrators"/>
</authorizations>
</authorizationTable>
<application href="META-INF/application.xml#Application_ID"/>
</applicationbnd:ApplicationBinding> |
users 元素指定将用户“admin”放入此角色。此文件中相关的行是第 4 行、第 5 行和第 6 行的 users、role 和 groups 元素:
- users 元素指定将用户“admin”放入此角色。
- role 元素描述 web.xml 中 users 和 groups 元素引用的角色。
- groups 元素指定此角色中将涉及哪些组。
其中可以包含任意多的 users 和 groups 元素,但每个 authorizations 只有一个对应的 role 元素。
- 创建自定义 Rails 任务,以将 WAR 文件和 META-INF 中的文件打包为 EAR:
清单 32
namespace :ear do
desc 'Create an ear file to wrap the war created by goldspike'
task :create do
unless system("jar -cf wishlist.ear META-INF\* wishlist.war")
raise "Error: failed to create archive, error code #{$?}"
end
puts "Ear was successfully created."
end
end |
这个最后一步实际上会在命令行执行 jar -cf wishlist.ear META-INF\* wishlist.war。请记住,Rails 任务只是 Rudy 代码;前三行在“ear”命名空间中创建“create”。将任务放入命名空间的唯一目的是为了方便维护。下一行代码指定执行您的 JAR 命令,如果不成功则引发错误。最后,发出了用于向用户提供反馈的语句,告知用户命令成功完成。以下是应有的输出情况:
清单 33
C:\projects\wishlist>jruby -S rake ear:create
(in C:/projects/wishlist)
Ear was successfully created. |
其结果是,根 rails 目录现在将包含 wishlist.ear。

 |

|
使用数据源
到目前为止,此示例依赖于 ActiveRecord-JDBC 创建和管理 JDBC 数据库连接池。在生产系统中,经常最好首选可通过应用服务器的管理控制台控制的 Java EE JNDI 数据源。这样,可以通过应用服务器监视数据库连接,而且不需要开发人员的帮助。
ActiveRecord-JDBC 的优势在于,在正确配置的情况下,它将尝试使用 JNDI 数据源(如果能找到),如果找不到,将退而采用传统 JDBC。这样做的目的是为了让您能继续使用 Rake 命令迁移数据库、运行测试等等。
要创建新数据源(如果之前未创建),请执行以下操作:
- 登录到 WebSphere Application Server 管理控制台。
- 选择 Resources => JDBC => Data sources. => New。
- 给数据源指定一个有意义的名称,如
Derby Wishlist。
- JNDI 名称应该与目录路径类似,如
jdbc/wishlist。
- 假定您使用的是 Derby,请将组件管理身份验证别名和 XA 恢复身份验证别名集保留为 none。
- 单击 Next。
- 选择 Select an existing JDBC provider 单选按钮,然后从下拉列表中选择 Derby JDBC Provider。
- 单击 Next。
- 将数据库名称设置为 Derby 数据库在文件系统中驻留的目录路径,例如:
C:\projects\wishlist\db\wishlist_prod。(如果您的数据库在此步骤之前不存在,请确保将 create=true 追加到命令末尾。)
- 依次单击 Next、Finish 和 Save。
- 您现在已经有了指向 wishlist_prod 数据库的数据源,而且拥有了名为 jdbc/wishlist 的 JNDI。修改 config/database.yml,以添加 JNDI 名称,并将实际数据库 URL 参数注释掉:
清单 34
adapter: jdbc
driver: org.apache.derby.jdbc.EmbeddedDriver
jndi: jdbc/wishlist
# url: jdbc:derby:c:/projects/wishlist/db/wishlist_prod;create=true |
正如前面提到的,test 和 db:migrate Rake 任务并不会使用已经注释掉的 JDBC URL 属性工作。您仅仅注释掉了用于指定所使用的数据源的 url 行。确定应用服务器在使用 JNDI 名称查找数据源后,将再次取消注释 URL 属性。
- 重新部署“愿望列表”应用程序。这一次,在编辑器控制器之后,您应该看到在 WebSphere Application Server SystemOut.log 中与以下所示类似的内容:
清单 35
InternalGener I DSRA8203I: Database product name : Apache Derby
InternalGener I DSRA8204I: Database product version : 10.1.3.2
InternalGener I DSRA8205I: JDBC driver name : Apache Derby Embedded JDBC Driver
InternalGener I DSRA8206I: JDBC driver version : 10.1.3.2 |
要使用 Rails Rake 命令行任务执行进一步的迁移,请执行以下步骤:
- 在 config\database.yml 中取消注释 URL。
- 停止 WebSphere Application Server,因为其中包含对数据库的锁定。
- 运行
jruby -S rake db:migrate RAILS_ENV=production,验证您的数据库处于最新状态。
在从命令行运行 Rake 或脚本时,您可能会看到与以下所示类似的警告:
清单 36
JNDI data source unavailable: javax.naming.NoInitialContextException: Need to specify
class name in environment or system property, or as an applet parameter, or in an
application resource file: java.naming.factory.initial; trying straight JDBC |
这是正常的,也是预期的情况。Rails 警告您无法查找 JNDI 信息,将按照预期的方式返回采用 JDBC 的方式。
结束语
无论其特性和功能如何先进,新编程语言总是需要一定的时间才能成熟。就其本质而言,企业应用程序需要一系列相对较为普通的要素:最好的安全性、强大的管理界面以及由 IT 人员(非程序员)在运行时进行维护的能力,诸如此类。通过将 WebSphere Application Server 的成熟和 Ruby on Rails 的快速开发能力结合,您可以同时获得二者的竞争优势。
本文是在 WebSphere Application Server 上开发 Rails 应用程序的入门篇。第 2 部分将介绍如何将现有自定义 Java API 和 EJB 组件与 Rails 一起使用,WebSphere Application Server 上的 Rails 是否可以用于创建 SOAP 和 REST Web 服务,在 Ruby/JRuby 上运行的 Rails 和常规 Java Servlet 及 JSP 在性能和内存使用率方面有何差异,等等。
参考资料
关于作者  | 
|  |
Ryan Shillington 是在德克萨斯州奥斯汀工作的一位 IBM 软件工程师。在 2007 年 5 月加入 IBM 前,他在全力进行一个 Rails 电子商务网站的创建工作。Ryan 目前在从事 WebSphere Business Services Fabric 方面的工作。 |
对本文的评价
|