使用 Dojo 和 Grails 快速实现数据的增删改查(CRUD)

选择 Grails 和 Dojo 的原因

随着 web 的广泛应用,web application 的开发项目越来越多,而大部分的 web 开发归根结底都是对数据库的增删改查。对于一张数据库表的增删改查,采用基于 MVC(模型 - 视图 - 控制器)设计模式的开发往往需要 Domain class、 Controller、4 个增删改查的页面、form 输入数据的校验等。这样就免不了大量类似功能的复制粘贴修改。 如果你想从重复劳动中解放出来,Grails 是一个很好的选择。 使用 Grails 只需要写一个 domain class 就可以自动生成 CRUD 4 个页面和对应的 controller 方法来实现对数据库的增删改查,并实现服务器端的数据校验,从而大大地提高了开发效率。

类似的 web 快速开发框架还有 Rails、Django 和 TurboGears 等,与它们相比 Grails 有如下优势:

  • Grails 是一套用于快速 Web 应用开发的开源框架,它是由 15% 的 Groovy 代码和 85% 的 Java 代码写成,并建立在千锤百炼的经典框架(Spring,Hibernate,Sitemesh)之上,从而为大家带来一套能实现超高生产力的一站式框架。
  • Grails 学习快速,使用容易,如果你熟悉 Hibernate、Spring、SiteMesh 和 JSP,那么你一个小时之内就能学会 Grails。
  • Grails 运行于 JVM 之上,生成的 war 包可以部署在各种成熟的服务器 Tomcat, JBoss,Weblogic,Webspere 等。
  • 性能与 Rails2(100% Ruby) 相比要快很多。
  • Grails 被 SpringSource 收购,有强大的技术支持。

采用 Grails 的默认模板生成的 CRUD 页面有些简陋,实际项目中往往需要功能更加丰富,外观更加漂亮,使用更加方便的控件。例如页面布局控件,Tab 控件,树,菜单,日历,编辑器,进度条等等。现在有很多 JS 框架致力于 web UI, 例如 Dojo, Extjs, Jquery, YUI 等。我们之所以选择 Dojo, 因为 Dojo 具有以下的优势:

  1. 组件丰富,有强大的 UI(Dijit)。这样一来就可以减轻我们的大量沉重的工作,而且目前的开发非常活跃。
  2. UI 外的功能也很强大,Full Stack 的框架,扩展了 DHTML 的能力,例如:
    • 支持与浏览器 Back/Forward 按钮的集成。
    • Dojo Offline,一个跨平台的离线存储 API。
    • Chart 组件,可以方便地在浏览器端生成图表。
    • 基于 SVG/VML 的矢量图形库。
    • Google Maps、Yahoo! Maps 组件,方便开发 Mashup 应用。
    • Dojox Socket, 基于 WebSocket 或者 XHR 长连接的“服务器推”技术。
  3. Dojo 是一个很好的基础架构,具有面向对象的设计,统一的命名空间,包管理机制(The Package System and Custom Builds)可扩展性。Dojo 其实是一个组件模型,类似于 Spring,用来支持大规模的组件化开发。组件模型的作用就是增强代码的重用,这对于提高开发效率是至关重要的。
  4. Dojo 的背后有强大的支持 IBM、Oracle 等,这是非常重要的优势。Dojo 现在已经是众多开源框架的选择,包括:WebWork、Tapestry、Eclipse ATF、MyFaces。Dojo 的开发团队由 Alex Russell 领军,人数众多,力量非常强大。
  5. 开源,使用 BSD 软件许可。

快速搭建 CRUD 的框架 (MVC)

开发环境配置

首先需要安装 JDK,并设置好 JAVA_HOME。本文使用的是 JDK 1.6。

  1. Grails 官方网站下载并解压 grails.zip。本文使用的是 Grails1.3.7。
  2. 创建一个 GRAILS_HOME 环境变量,将 $GRAILS_HOME/bin 添加到 PATH 中。
  3. 习惯用 IDE 的可以选择 Intellij idea,Netbeans 和 Spring STS。本文以免费的 Netbeans7.0.1 为例,配置很简单,只需要在 Tools->Options->Miscellaneous->Groovy 下设置 Grails Home,就可以进行 Grails 项目的开发了。(见图 1)

图 1. Netbeans 中 Grails 的配置
图 1. Netbeans 中 Grails 的配置  

命令行创建 web application

  1. 首先,在一个空白目录下,输入 grails create-app DojoGrails。稍后,可以看到一个名为 DojoGrails 的目录 , 会建立一个标准的目录结构(如图 2)。很明显这是层次清晰的 MVC 模式。 

    图 2. Grails 的目录结
    图 2. Grails 的目录结 

  2. 进入 DojoGrails 目录,并输入 grails create-domain-class com.shuo.Employee,将得到两个新的文件:域类 grails-app\domain\com\shuo\Employee.groovy 和一个单元测试类。这里我们主要关注域类。一开始,域类里面什么都没有,我们为它添加一些字段和约束,见清单 1: 

    清单 1. 域类 Employee
    				
     class Employee { 
        String employeeNumber 
        String name 
        Integer age 
        Date onboardDate 
        
        static constraints = { 
            employeeNumber blank: false, unique: true 
            name blank: false 
            
        } 
        String toString(){ 
            return "${name}(${employeeNumber})"
        } 
     } 
    

    类似地我们创建一个与 Employee 关联的域类 Product。Product 的负责人 owner 是 Employee。见清单 2:



    清单 2. 域类 Product
    				
     class Product { 
        String name 
        String description 
        Float price    
        Employee owner   
    
        static constraints = { 
        } 
     } 
    

    这两个域类覆盖了所有常见的数据库数据类型(Date,String,Integer,Float)。

  3. 在命令行输入 grails create-controller com.shuo.Employee 生成域类对应的 Controller, 使用 grails 强大的脚手架 scaffold,见清单 3。运行时在内存中将动态地生成所有 CRUD 的 method 以及 CRUD 的页面。同样地生成 ProductController。 

    清单 3. 控制器代码
    				
     class EmployeeController { 
        def scaffold = true 
     } 
     class ProductController { 
        def scaffold = true 
     } 
    

  4. 到这里对两张表的 CRUD 功能全部完成,是时候看看效果了,在命令行输入 grails run-app。通过浏览器 http://localhost:8080/DojoGrails, 可以看到如图 3 所示的 Employee 的 List 页面。 

    图 3. Scaffold 默认的 List 页面
    图 3. Scaffold 默认的 List 页面 

    可以看出这个 list 页面已经具备分页和排序功能了。

    再来看看 Employee 的 Create 页面,如图 4 所示:



    图 4. Scaffold 默认的 create 页面
    图 4. Scaffold 默认的 create 页面 

    从图中可以看出添加后,服务器端会返回数据验证的结果,比如员工号必须唯一,名字不能为空,年龄必须是数字等。

    再来看看 Product 的 Edit 页面 , 如图 5:



    图 5. Product 的 Edit 页面
    图 5. Product 的 Edit 页面 

    这里我们主要关注 owner 字段 , scaffold 自动生成一个列出所有员工的下拉框,让用户从中选择出产品的负责人。

    另外值得一提的是,这个 web application 已经拥有了国际化多语言的支持,只需要对 grails-app/i18n/messages_zh_CN.properties 文件进行修改,就可以汉化整个 web 界面了。

    Grails 的 Scaffold 脚手架是不是很强大呢,一句 def scaffold = true 就实现了如此丰富的功能。

  5. 在命令行输入 grails generate-all com.shuo.Employee,grails 会根据默认模板在 EmployeeController 中生成 CRUD 的对应的 method 代码,并在 grails-app/views/employee 下生成 4 个 CRUD 的 gsp 页面。这些就是 scaffold 背后的代码。后面我们将对这些代码进行修改,引入 Dojo,来增强 CRUD 的用户体验。

使用 Dojo 增强 CRUD 用户体验

引入 Dojo

到 dojotoolkit.org 网站下载最新版本的 Dojo Toolkit Release。 本文用的是 dojo-release-1.6.1。

解压后将其拷入 web-app/js 中,目录结构如图 6:


图 6. Dojo 的目录结构
图 6. Dojo 的目录结构  

在 grails-app/views/layouts/main.gsp 中添加以下代码,见清单 4:


清单 4. 导入 Dojo
				
<link rel="stylesheet" 
href="${resource(dir:'js/dojo-release-1.6.1/dijit/themes/claro',file:'claro.css')}" 
/>    
<script src="${resource(dir:'js/dojo-release-1.6.1/dojo',file:'dojo.js')}" 
data-dojo-config="isDebug: true,parseOnLoad: true,locale:'zh'"></script> 
…
 <body class="claro"> 

使用 EnhancedGrid 增强 List 表格

Grails scaffold 默认的 List 表格(如图 3)功能较为简单,例如它不能像 excel 那样的跨行列合并单元格、冻结表头、单击单元格转成编辑状态等。而 Dojo 为我们提供了这样的控件:Dojox 的 DataGrid 像一个基于 Web 的 Excel 组件,足可以应付非常复杂的数据展示及数据操作。下面列出了 DataGrid 的一些特性:

  • 可以任意的增加和删除单元格、行、或者列;
  • 对行进行统计摘要,Grid 可以生成类似于 OLAP 分析的报表;
  • Grid 超越了二维表格的功能,它可以跨行或跨列合并单元格以满足不同的数据填充的需求;
  • 行列冻结功能,使得浏览数据更加灵活方便;
  • Grid 事件采用了钩子机制,我们可以通过 onStyle 钩子完成对样式的更改;
  • 单元格具备富操作,所有的 dijit 部件都可以在单元格中使用,并且单元格可以通过单击转换为编辑状态;
  • 可以为不同的单元格设置不同的上下文菜单;
  • Grid 嵌套,也就是说 Grid 可以在单元格中嵌套其他的 Grid,从而组成更为复杂的应用
  • 支持 Dnd 拖放和键盘 navigation,提高了 Accessibility。
  • 除此之外,Grid 还有具有其他很多特性,例如,非常实用的偶数行上色、灵活的选取功能、自动调整列宽、数据的展开/合闭等。

其中 DataGrid 有一个非常重要的特性就是虚拟滚动(Virtual Scroll),DataGrid 对付大数据源的时候,在滚动事件触发后才请求后面的数据并创建 DOM 结点,因此每次只需要显示很少的几行,从而加快了 Grid 的加载。 不得不承认这是一种很精巧的做法,在用户体验上也很自然,就好像所有数据本来就在那里一样。但它也有缺点,当数据源真的比较大的时候,滚动条就会非常小,再加上行高参差不齐的情况,要精确地滚动到某个位置就比较困难。这时候简单直观的分页机制就足够了。最新的 Dojo 1.6 版里,继承自 DataGrid 的 EnhancedGrid 引入了几个新的插件,其中就有 Pagination(分页浏览)插件。

首先在 list.gsp 中使用 EnhancedGrid, 见清单 5:


清单 5. Dojo 的 EnhancedGrid 代码
				
 <script type="text/javascript"> 
 dojo.require("dojox.grid.EnhancedGrid"); 
 dojo.require("dojox.grid.enhanced.plugins.Pagination"); 
 dojo.require("dojox.data.QueryReadStore"); 

 var myStore = new dojox.data.QueryReadStore({url:"listJson"}); 

 function onRowDblClick(e){ 
  var itemid = grid.getItem(e.rowIndex).i.id; 
  document.location.href="edit/"+itemid; 
  } 
 </script> 
 <style type="text/css"> 
@import "${resource(dir:'js/dojo-release-1.6.1/dojox/grid/enhanced/resources/claro',
file:'EnhancedGrid.css')}";    
 </style> 
…
    <table dojoType="dojox.grid.EnhancedGrid" jsId="grid" store="myStore" 
           rowsPerPage="5" clientSort="true" style="width: 100%; height: 280px;" 
           onRowDblClick= "onRowDblClick"
           rowSelector="20px" plugins="{ pagination: {  
           pageSizes:['5','10','20'], 
           maxPageStep: 5,  
           descTemplate: '${message(code: 'default.paginateDescTemplate')}', 
           description: true,  
           sizeSwitch: true, 
           pageStepper: true , 
           gotoButton: true 
           }}"> 
      <thead> 
        <tr> 
          <th width="50px" field="id" >ID</th> 
          <th width="100px" field="employeeNumber"> 员工号 </th> 
          <th width="100px" field="name"> 姓名 </th> 
          <th width="100px" field="age"> 年龄 </th> 
          <th width="100px" field="onboardDate"> 入职日期 </th> 
        </tr> 
      </thead> 
    </table> 

在 employeeController 中加入方法,见清单 6:


清单 6. EnhancedGrid 对应的控制器代码
				
    def listJson = {              
        if(params.start?.isInteger()){ 
            params.put("offset",params.int('start'))            
        } 
        params.put("max",params.count?params.int('count'):5) 
        if(params.sort?.startsWith("-")){ 
            params.put("sort",params.sort.substring(1)) 
            params.put("order","desc") 
        } 
        def total = Employee.count();      
        def results = Employee.list(params) 
        def jsonData = [identifier:"id",numRows: total,items: results] 
        render jsonData as JSON   
    } 

这样就完成了 EnhancedGrid 和服务器,数据库的交互。以上代码实现了分页、排序、双击表中一行进入修改界面,效果如图 7:


图 7. EnhancedGrid 界面
图 7. EnhancedGrid 界面  

以上代码有两点需要注意:

  1. 从图 3 可以看出默认的日期格式是 yyyy-MM-dd HH:mm:ss z,这是由 messages.properties 中的 default.date.format 定义的。而对于 JSON 中的日期数据,格式化需要在 config.groovy 中加入如清单 7 的代码: 

    清单 7. JSON 的日期格式化代码
    				
     import grails.converters.JSON; 
     class BootStrap { 
        def init = { servletContext -> 
            JSON.registerObjectMarshaller(Date) { 
                return it?.format("yyyy-MM-dd") 
            } 
        } 
        ... 
    

  2. 这段代码有个小技巧。因为 ${} 在 gsp 文件中是保留字,如果把 pagination 插件的 descTemplate 属性值“${2} - ${3} 共 ${1}${0}” 直接写在 gsp 页面上会出错,所以写到 messages 文件里。
     default.paginateDescTemplate=${2} - ${3}  共 ${1}${0} 
    

使用 FilteringSelect 增强下拉框

Grails scaffold 为外键关联的对象做了下拉框供用户选择,而这个下拉框是个简单的 HTML select 控件(如图 5),用户只能从一堆下拉选项中肉眼找出要选的关联对象。本文例子中,用户就需要从一大堆的企业员工中肉眼找出要选的产品负责人。企业员工常常是成百上千人,要从 select 下拉框中找出一个很难,而且一次性从数据库读取所有员工的姓名到下拉框也很耗时。Dojo 为我们提供了一个类似于 HTML 的 select 控件:FilteringSelect,但它可以动态输入,并且按照输入值列出匹配的可选项,甚至可以按需设置加载选项的数量,下拉列表的选项可以从数据库动态获取。这样用户就能通过输入关键字,让程序帮我们找出要选的对象。

Dojo 的 ComboBox 跟 FilteringSelect 非常类似,不过 FilteringSelect 不允许用户输入可选项之外的值,而 ComboBox 可以输入任意值。所以这里我们选择 FilteringSelect。

首先对 create.gsp 和 edit.gsp 做修改,如清单 8:


清单 8. Dojo 的 FilteringSelect 代码
				
 <script type="text/javascript"> 
 dojo.require("dijit.form.FilteringSelect"); 
 dojo.require("dojox.data.QueryReadStore"); 
 var employeeSelector= new dojox.data.QueryReadStore({
    url:"${resource(dir:'employee')}/combo"}); 
 </script> 
…
 <input name="owner.id" dojoType="dijit.form.FilteringSelect" 
 store="employeeSelector"  placeHolder="请选择员工" pageSize="5" 
 autocomplete="false" value="${productInstance?.owner?.id}" 
 queryExpr="${message(code: 'default.queryExpr')}"></input> 

再为 employeeController 增加方法,从数据库获取下拉选项,如清单 9:


清单 9. FilteringSelect 对应的控制器代码
				
 def combo ={ 
        def criteria='',results 
        if(params.id){ 
            results = Employee.get(params.id) 
        } 
        if(params.name){ 
            criteria = params.name.replace('*','%') 
            results = 
            Employee.findAllByNameLikeOrEmployeeNumberLike(
 criteria,criteria, [max:params.count?(params.count+1):1,offset:params.start]) 
        }  
        render(contentType: "text/json") { 
            identifier = "id"
            label = "name"
            items = array{ 
                results.each {w -> 
                    item("id":w.id,"name":w.name) 
                } 
            } 
        }  
    } 

以上代码实现了按照员工姓名模糊查找选择员工(如图 8 左上),按照员工号模糊查找选择员工(如图 8 中),查找结果分页(如图 8 右),如果输入框的值不在员工中,有出错提示(如图 8 左下)。


图 8. FilteringSelect 界面
图 8. FilteringSelect 界面  

使用 DatePicker 增强日期选择

Grails scaffold 提供下拉框让用户选择年月日(如图 4),从下拉框中选较为麻烦。常常我们喜欢直接输入日期或者是查看日历找到想填的日期。如图 9 的日期选择控件就满足了我们的需要。这个界面漂亮操作简单的日期选择控件就是 Dojo 的 DatePicker,我们可以通过在页面加入以下代码来实现,见清单 10:


图 9. DatePicker 界面
图 9. DatePicker 界面  

清单 10. Dojo 的 DateTextBox 代码
				
 <input type="text" name="onboardDate" 
 dojoType="dijit.form.DateTextBox" required="true" 
 value="<g:formatDate date='${employeeInstance?.onboardDate}'/>" /> 

因为默认的 scaffold 时间控件的年月日是分 3 个字段分别上传,和我们用一个字段上传的数据结构是不同的,所以 controller 中需要对其做特殊转换,见清单 11:


清单 11. 使用 DateTextBox 后,save 方法添加的日期转换代码
				
    def save = {        
        if(params.onboardDate!=null&&!params.onboardDate.isEmpty()){ 
            try{ 
                params.onboardDate =Date.parse("yyyy-MM-dd",params.onboardDate) 
            }catch(Exception e){ 
                employeeInstance.errors.rejectValue("onboardDate", 
                "typeMismatch.java.util.Date", 
                [message(code: 'employee.label', default: 'Employee')] as Object[],
                 "日期格式不对") 
                render(view: "create", model: [employeeInstance: employeeInstance]) 
            } 
        } 
        def employeeInstance = new Employee(params) 
        ... 
    } 

客户端数据校验

Grails 的脚手架已经具备了服务器端数据校验的功能,但是每次都将数据提交到服务器再进行校验,性能太低了。为减轻服务器的负载,在浏览器端做 Javascript 校验,就非常必要。Dojo 正好提供了一个很好的数据检验框架。

用户输入数据的同时 Dojo 就会进行数据检验,一旦出错立即高亮显示出错信息。

例如用 dijit.form.NumberTextBox 可以进行数字的校验,下面的代码要求价格是大于等于 0 的数字,见清单 12:


清单 12. NumberTextBox 的数据验证
				
 <g:textField name="price" 
 value="${fieldValue(bean: productInstance, field: 'price')}" 
 dojoType="dijit.form.NumberTextBox" constraints="{min:0}" 
 required="true" invalidMessage="大于 0 的数字 ." /> 

另外,当表单提交时可以判断表单是否有效,如果包含无效数据将不提交表单。见清单 13:


清单 13. 表单的数据验证
				
 function validateData(form1){ 
  if(!form1.validate()) { 
          alert('请先纠正无效数据,然后再提交!'); 
          return false; 
  } 
  return true; 
 } 
…
 <g:form method="post" dojoType="dijit.form.Form" οnsubmit="return validateData(this);" > 

以上代码的效果类似于图 8(左下)中的红边框及出错提示。

结束语

从前面的文章我们已经能窥见 Dojo 结合 Grails 开发 web application 的强大能力以及效率。此外 Dojo 还有很多优秀的布局控件,对话框,图表,菜单,多文件上传控件等等,能够实现非常复杂的企业级应用。即将发布的 Grails2.0 也添加了很多很好的特性。我们有理由相信 Dojo 结合 Grails 的前途非常广阔。如果你们觉得文中的代码有更好的实现方法,欢迎与我联系(zhyjzhan@cn.ibm.com)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 简介 2. 起步 2.1 下载并安装Grails 2.2 创建一个Grails应用 2.3 Hello World示例 2.4 使用IDE 2.5 规约配置 2.6 运行Grails应用 2.7 测试Grails应用 2.8 部署Grails应用 2.9 所支持的Java EE容器 2.10 创建工件 2.11 生成Grails应用 3. 配置 3.1 基本配置 3.1.1 内置选项 3.1.2 日志 3.2 环境 3.3 数据源 3.3.1 数据源和环境 3.3.2 JNDI数据源 3.3.3 自动数据库移植 3.4 外部配置 3.5 定义版本 4. 命令行 4.1 创建Gant脚本 4.2 可复用的Grails脚本 4.3 脚本中的事件 4.4 Ant和Maven 5. 对象关系映射(GORM) 5.1 快速指南 5.1.1 基本的CRUD 5.2 在GORM中进行领域建模 5.2.1 GORM中的关联 5.2.1.1 一对一 5.2.1.2 一对多 5.2.1.3 多对多 5.2.2 GORM的组合 5.2.3 GORM的继承 5.2.4 集合、列表和映射 5.3 持久化基础 5.3.1 保存和更新 5.3.2 删除对象 5.3.3 级联更新和删除 5.3.4 立即加载和延迟加载 5.3.4 悲观锁和乐观锁 5.4 GORM查询 5.4.1 动态查找器 5.4.2 条件查询 5.4.3 Hibernate查询语言 5.5 高级GORM特性 5.5.1 事件和自动实现时间戳 5.5.2 自定义ORM映射 5.5.2.1 表名和列名 5.5.2.2 缓存策略 5.5.2.3 继承策略 5.5.2.4 自定义数据库标识符 5.5.2.5 复合主键 5.5.2.6 数据库索引 5.5.2.7 乐观锁和版本定义 5.5.2.8 立即加载和延迟加载 5.6 事务编程 5.7 GORM和约束 6. Web层 6.1 控制器 6.1.1 理解控制器和操作 6.1.2 控制器和作用域 6.1.3 模型和视图 6.1.4 重定向和链 6.1.5 控制器拦截器 6.1.6 数据绑定 6.1.7 XML和JSON响应 6.1.8 上传文件 6.1.9 命令对象 6.2 Groovy Server Pages 6.2.1 GSP基础 6.2.1.1 变量和作用域 6.2.1.2 逻辑和迭代 6.2.1.3 页面指令 6.2.1.4 表达式 6.2.2 GSP标签 6.2.2.1 变量和作用域 6.2.2.2 逻辑和迭代 6.2.2.3 搜索和过滤 6.2.2.4 链接和资源 6.2.2.5 表单和字段 6.2.2.6 标签作为方法调用 6.2.3 视图和模板 6.2.4 使用Sitemesh布局 6.3 标签库 6.3.1 简单标签 6.3.2 逻辑标签 6.3.3 迭代标签 6.3.4 标签命名空间 6.4 URL映射 6.4.1 映射到控制器和操作 6.4.2 嵌入式变量 6.4.3 映射到视图 6.4.4 映射到响应代码 6.4.5 映射到HTTP方法 6.4.6 映射通配符 6.4.7 自动重写链接 6.4.8 应用约束 6.5 Web Flow 6.5.1 开始和结束状态 6.5.2 操作状态和视图状态 6.5.3 流执行事件 6.5.4 流的作用域 6.5.5 数据绑定和验证 6.5.6 子流程和会话 6.6 过滤器 6.6.1 应用过滤器 6.6.2 过滤器的类型 6.6.3 过滤器的功能 6.7 Ajax 6.7.1 用Prototype实现Ajax 6.7.1.1 异步链接 6.7.1.2 更新内容 6.7.1.3 异步表单提交 6.7.1.4 Ajax事件 6.7.2 用Dojo实现Ajax 6.7.3 用GWT实现Ajax 6.7.4 服务端的Ajax 6.8 内容协商 7. 验证 7.1 声明约束 7.2 验证约束 7.3 客户端验证 7.4 验证和国际化 8. 服务层 8.1 声明式事务 8.2 服务的作用域 8.3 依赖注入和服务 8.4 使用Java的服务 9. 测试 9.1 单元测试 9.2 集成测试 9.3 功能测试 10. 国际化 10.1 理解信息绑定 10.2 改变Locales 10.3 读取信息 11. 安全 11.1 预防攻击 11.2 字符串的编码和解码 11.3 身份验证 11.4 关于安全的插件 11.4.1 Acegi 11.4.2 JSecurity 12 插件 12.1 创建和安装插件 12.2 理解插件的结构 12.3 提供基础的工件 12.4 评估规约 12.5 参与构建事件 12.6 参与运行时配置 12.7 运行时添加动态方法 12.8 参与自动重载 12.9 理解插件加载的顺序 13. Web服务 13.1 REST 13.2 SOAP 13.3 RSS和Atom 14. Grails和Spring 14.1 Grails的支柱 14.2 配置其他Bean 14.3 通过Beans DSL运行Spring 14.4 配置属性占位 14.5 配置属性重载 15. Grails和Hibernate 15.1 通过Hibernate注释进行映射 15.2 深入了解 16. 脚手架

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值