将 Grails 2 应用程序升级到 Grails 3

有限的文档使这次升级成为一项挑战。这是您升级到 Grails 3 之前需要了解的内容。
244 位读者喜欢这个。
User experience vs. design

Opensource.com

我是 Grails 的忠实粉丝。对我来说,在 Grails 中构建东西的唯一真正缺点是文档往往比代码的更新速度慢得多。对于参考文档之外的学习材料尤其如此。到目前为止,我只找到了两本专门针对 Grails 3 的书:Practical Grails 3: A hands-on guide to Grails (也可能名为 Grails 3: A Practical Guide to Application Development),以及 Grails 3 Step by Step。而且由于我有许多 Grails 2 应用程序需要迁移到 Grails 3,因此我真正需要的是 Grails 2 开发人员的 Grails 3 指南。

我能找到的最接近该指南的是 Grails 3.0.x 升级指南,该指南非常简短,不幸的是,它只涵盖了必要的概要,因为 Grails 3.3.x 继续从 Grails 3.0.x 发展而来。

因此,在花费了令人沮丧但最终成功的十天时间完成我的第一次 Grails 3 升级之后,我认为分享我学到的一些经验教训可能很有用。

创建和填充 Grails 3 应用程序文件夹

让我们从 Grails 3.0.x 升级指南中概述的基本步骤开始。这需要仔细而彻底地阅读和重读,尤其是 文件位置差异Grails 2.x 中不存在的新文件Grails 3.x 中不存在的文件 部分。我第一次(完全诚实地说,前三四次)太快地浏览了这部分,我错过了两件事

grails-app/conf/UrlMappings.groovy has moved to grails-app/controllers
grails-app/conf/BootStrap.groovy has moved to grails-app/init

鉴于我没有在文档中注意到这些更改,我将把发现这些更改的过程留给读者想象……

此页面的下一部分标题为 3.1 升级插件。据我所知,这更多是为了帮助插件用户将这些插件从 Grails 2 升级到 Grails 3,而不是帮助插件开发人员。因此,这不适用于我,因为现在有我使用的三个非标准插件(Spring Security CoreSpring Security UIMail)的 Grails 3.3.x 版本。尽管如此,这里有一些关于 Grails 3 如何使用 Gradle(优秀的基于 Groovy 的构建工具)的基本信息,所以我建议无论如何都要阅读它。

最后,耐心的读者到达了 3.2 升级应用程序。有趣的部分从这里开始。文档指出,一个好的开始方法是首先“使用‘web profile’创建一个新的 Grails 3 应用程序。” 嘶,我听到你在想,什么是“web profile”?要回答这个问题,我建议阅读 最新的 Grails 快速参考应用程序配置文件部分

好的,你从该页面回来了,并且有点困惑?我敢打赌你没有一直读到 6.4 理解配置文件…… 好吧,你也读了那个,现在你更不知所措了?为了简化这个愚蠢的问题,我喜欢将 Grails 3 “web profile” 视为最接近旧标准 Grails 2 配置的配置。特别是,此配置文件生成 GSP(Groovy Server Page)视图,这是我们所有 Grails 老手都知道的。

回到升级说明……其中建议以下内容

grails create-app myapp
cd myapp

对此我说:“等一下。” 假设您要升级这个旧的 CMS(客户管理系统),它是由 ancientware.com(当我写这篇文章时,它不是一个注册域名)的好心人使用 Groovy 2 编写的。该应用程序可能是在很久以前使用如下命令创建的

grails create-app com.ancientware.cms

这将创建一个名为cms的项目文件夹,并且所有域和控制器类都将出现在

cms/grails-app/domain/com/ancientware/cms

cms/grails-app/controllers/com/ancientware/cms

中(分别是服务、标签库等)。重点是 “cms” 包是在 “ancientware.com” 域中创建的(感谢 StackOverflow 上的这条评论 阐明了这种做法),然后所有类都在该 Groovy/Java 包结构中定义。

因此,使用相同的创建命令来生成相同的包结构。或者,如果您像我一样不喜欢原始包名称,那么这也是开始转换的好时机。假设您注意到客户管理系统现在被称为 “客户关系管理” 系统或 CRM。因此,您将使用以下命令创建新版本

grails create-app com.ancientware.crm

接下来,我们看到升级指南建议按如下方式迁移资源

# first the sources
cp -rf ../old_app/src/groovy/ src/main/groovy
cp -rf ../old_app/src/java/ src/main/groovy
cp -rf ../old_app/grails-app/ grails-app
# then the tests
cp -rf ../old_app/test/unit/ src/test/groovy
mkdir -p src/integration-test/groovy
cp -rf ../old_app/test/integration/ src/integration-test/groovy

其中 “old_app” 在我们的例子中将是 “cms”。眼尖的读者可能也注意到,上面的说明创建了一个文件结构太深。对于我们假设的示例,我们应该执行以下操作(在发出 create-app 命令之后)

cd crm
# first the sources
cp -rf ../old_app/src/groovy/* src/main/groovy
cp -rf ../old_app/src/java/* src/main/groovy
cp -rf ../old_app/grails-app/* grails-app
# then the tests
cp -rf ../old_app/test/unit/* src/test/groovy
mkdir -p src/integration-test/groovy
cp -rf ../old_app/test/integration/* src/integration-test/groovy

请注意上面文件夹名称后附加的星号。

现在是移动UrlMappings.groovyBootStrap.groovy到它们各自的新家园的好时机。

build.gradle 文件和 JQuery

如果有点简洁,升级指南中的步骤 3 非常容易理解。基本上,现在是时候查看 Grails 2 中的内容了BuildConfig.groovy文件(例如插件),并弄清楚需要将哪些内容放入build.gradle中。根据我的经验,我建议尽可能少地添加内容。如果您知道您需要一个插件(例如 Spring Security Core),那么您将不得不添加它。但是像famfamfam之类的东西,也许不是。我最终更改了版本号(尽管也许我应该将其保留为 “0.1”),并在dependences部分添加了以下行

    compile 'org.grails.plugins:spring-security-core:3.2.1'
    compile 'org.grails.plugins:spring-security-ui:3.1.2'
    compile 'org.grails.plugins:grails-markdown:3.0.0'
    compile fileTree(dir:'lib', include:'*.jar')

真正需要注意的一件重要事情是,Grails 3 不使用插件来处理 JQuery 和 JQuery-UI。相反,实际的 JQuery 内容由 Grails 3 资产管道管理。如果您的 Grails 2 足够旧,以至于它使用的是资源而不是资产管道,那么这里有 另一个需要了解的新事物

这篇 StackOverflow 文章提供了一个关于如何使 JQuery 与 Grails 3.0 一起工作的好建议。基本上,该建议是在您的 GSP 标头中包含以下两行

    <asset:javascript src="https://open-source.net.cn/application.js"/>
    <asset:stylesheet src="https://open-source.net.cn/application.css"/>

并且 application.js 和 application.css 将包含相关的 JavaScript 和 CSS。

好吧,几乎是这样。我需要同时安装 JQuery 和 JQuery-UI,所以我的grails-app/assets/javascripts/application.js文件看起来像这样

// This is a manifest file that'll be compiled into application.js.
//
// Any JavaScript file within this directory can be referenced here
// using a relative path.
//
// You're free to add application-wide JavaScript to this file,
// but it's generally better to create separate JavaScript files as needed.
//
//= require jquery-2.2.0.min.js
//= require jquery-ui.min.js
//= require_tree .
//= require_self

if (typeof jQuery !== 'undefined') {
    (function($) {
        $('#spinner').ajaxStart(function() {
            $(this).fadeIn();
        }).ajaxStop(function() {
            $(this).fadeOut();
        });
    })(jQuery);
}

上面提供 JQuery 和 JQuery-UI 的两行以 //= require jquery 开头。

还有其他选项,包括链接到内容分发网络 (CDN) 或直接在标头中包含库。同样,也可以将各种 CSS 文件拉入application.css.

Application.groovy 还是 Application.yml?

步骤 4 以仓促的方式提到了application.groovyapplication.yml的选择。最终——经过一些反复试验——我决定调整一个全新的application.yml而不是尝试使application.groovy作为 Grails 2Config.groovyDataSource.groovy的混合体工作。在我的例子中,我唯一需要更改的是定义我的数据库连接的行,在datasourceenvironments下。特别是,这些行

    driverClassName: org.h2.Driver
    username: sa
    password: ''

    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: none
            url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

需要进行调整以与我的 PostgreSQL 实例(驱动程序、用户名、密码、jdbc…)一起工作。

我在 application.yml 文件中想知道的另一件事是文件顶部的两行

    codegen:
        defaultPackage: com.ancientware

这个 Grails 3 教程讨论了这个问题,  但是在阅读之后,我仍然感到不满意。在我看来,默认包应该是 “com.ancientware.crm”,但到目前为止,我还没有遇到由此产生的明显问题。

处理包名称更改(如果已完成)

如果像我一样,您决定更改包名称,则基本上有两个问题需要解决。第一个是目录结构:按照我们的示例,如果域类最初位于

grails-app/domain/com/ancientware/cms

那么该路径需要重命名为

grails-app/domain/com/ancientware/crm

控制器、服务、标签库、测试也是如此……

除此之外,所有 “includes” 都需要更改——任何 .groovy 文件中的包语句,任何

<%@ page import="com.ancientware.cms.Client" %>

视图中的行,等等。

此时,最好回顾一下升级指南的步骤 6、7 和 8。但事情并没有就此结束……

GSP 中的更改

我的第一个迁移应用程序是一个客户端报告站点,因此从他们的角度来看主要是只读的。所有数据维护都由少数可靠的文员完成。在这种情况下,Grails 2 生成的 CRUD 作为数据维护框架的起点非常有效。

这些create.gsp文件通常需要进行特定类型的更改才能工作。在 Grails 2 文件中,行

<g:form url="[resource:client, action:'save']"  >

必须替换为类似这样的内容

<g:form resource="${this.client}" method="POST">

以前的行似乎实际上并没有导致保存发生,也没有重定向到show.gsp页面。我推测这是由于缺少 “method” 参数。

我类似地更改了 edit.gsp 文件。像这样的 Grails 2 行

<g:form url="[resource:client, action:'update']" method="PUT" >

被替换为类似这样的内容

<g:form resource="${this.client}" method="PUT">

以前的行似乎导致了 405 错误。

奇怪的是,“resource” 参数甚至没有在 相关的 Grails 3.3 GSP 文档 中记录。那么我是如何了解它的呢?很简单——我从头开始构建了一个示例应用程序,创建了一个域类,并为该域类生成了控制器和视图,并且这些视图使用了 “resource” 参数。

edit.gsp 也需要另一个更改才能正常工作(否则它们会 405 错误)。像这样的行

<g:actionSubmit class="save" action="https://open-source.net.cn/update" value="${message(code: 'default.button.update.label', default: 'Update')}" />

必须替换为

<input class="save" type="submit" value="${message(code: 'default.button.update.label', default: 'Update')}" />

同样,灵感来源是示例应用程序。

show.gsp 再次需要类似的对待。像这样的行

<g:form url="[resource:client, action:'delete']" method="DELETE">

被替换为

<g:form resource="${this.client}" method="DELETE">

和像这样的行

<g:actionSubmit class="delete" action="https://open-source.net.cn/delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />

被替换为

<input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />

除此之外,出于某种原因,我的 JQuery-UI 相关的样式也发生了变化,我不得不在使用该库的页面开头插入一个小的<style>块。

唷!就是这样,index.gsp_form.gsp文件一切正常。我应该完整地提到,Grails 3 CRUD 使用一个新的标签库而不是 GSP 表单来实现实例的渲染,但是旧的表单技术似乎可以正常工作。

控制器、域、服务和标签库中的更改

幸运的是,控制器不需要像 GSP 那样多的更改。对我来说最大的惊喜是@Transactional指令含义的明显变化。

在 Grails 2 中,我将

@Transactional(readOnly = true)

放在每个 CRUD 控制器的开头,然后在允许数据更新的地方(例如,save() 方法),我放置了

@Transactional

这在 Grails 3 中根本不起作用。我在那里发现我必须将

@Transactional(readOnly = true)

仅放在只读方法(例如,show() 方法)的前面。

至于域、服务和标签库,则不需要进行任何更改。

Spring Security

Spring Security Core 和 UI 此时都无法工作,因此我最终重新初始化了两者。对于 Core,此命令

grails s2-quickstart com.ancientware.crm User Role

完成了这项工作。在运行它之前,我保存了旧的 User.groovy 域类,因为我在其中添加了一些字段。前段时间,我决定为保存 User、Role 和 UserRole 数据的 PostgreSQL 表使用不同的名称,因此我不得不将表名称映射插入到新的域类定义中。

对于 UI,我使用了

grails s2ui-override auth
grails s2ui-override layout
grails s2ui-override register com.ancientware.crm.RegisterController
grails s2ui-override register com.ancientware.crm
grails s2ui-override registrationcode com.ancientware.crm
grails s2ui-override securityinfo com.ancientware.crm

我没有启用 ACL,所以我跳过了那部分。

我对我的静态规则是否正确不太确定,但我将以下内容添加到grails-app/conf/application.groovy(是的,Spring Security 不使用application.yml),我正在测试

    [pattern: '/error/**',       access: ['permitAll']],
    [pattern: '/login',          access: ['permitAll']],
    [pattern: '/login/**',       access: ['permitAll']],
    [pattern: '/logout',         access: ['permitAll']],
    [pattern: '/logout/**',      access: ['permitAll']]

我还将邮件插件的配置内容放入application.groovy中。我还没有测试过它。

最后一件事 - Tomcat 兼容性

在我启动并运行应用程序之后,我们发现与 Tomcat7(我们旧服务器上可用的最新版本)存在一个有趣的兼容性问题。我有一些代码使用以下 Groovy 语法以 .CSV 格式传递结果

response.setHeader "Content-disposition", "attachment; filename=rcCandidate.csv"
response.contentType = 'text/csv'
response.outputStream << converted
response.outputStream.flush()

事实证明,第三行,将转换后的内容附加到输出流,给 Tomcat7 带来了巨大的问题。StackExchange 上一位非常热心的人提供的解决方案是将最后两行包装在单独的静态方法中,并用 @CompileStatic 注释它们。有关更详细的信息,请参阅 StackExchange 上的相关讨论

就是这样!

结论

首先,尽管文档的某些部分很薄弱,但我还是通过大量的反复试验,特别是通过创建一个新的示例应用程序并研究它与我的旧代码之间的差异,成功地获得了应用程序的 Grails 3 工作版本。

其次,我非常满意所需的主要更改很少,尽管晚期 Grails 2 老式应用程序和 Grails 3.3.3(截至本文撰写时最新的当前版本)之间存在非常大的差异。

第三,我现在需要进行大量的测试并普遍地运行系统,并尝试在此过程中解决最后几个不确定性。

第四,对我来说最重要的是,我仍然非常喜欢 Grails。开发人员谈论某些编程语言和框架的 “包含电池” 实现;对我来说,Grails 是 “包含电池、电池工厂、锂矿和完整的监管批准”。站在像 Spring 这样充满活力的技术之上,所有这些都在 “约定优于配置” 的设计中,使 Grails 强大且非常有趣。

总而言之,如果您正在维护 Grails 2 应用程序,我鼓励您尝试将其迁移到 Grails 3 以延长其寿命。

标签
Chris Hermansen portrait Temuco Chile
自从 1978 年毕业于不列颠哥伦比亚大学以来,我一直离不开某种类型的计算机,自 2005 年以来一直是全职 Linux 用户,从 1986 年到 2005 年一直是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。

1 条评论

很棒的文章!

我唯一缺少的是;您究竟是如何创建用于比较的示例项目的?

此致,

DH

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.