Java Web 开发学习手册(四)

原文:Learn Java for Web Development

协议:CC BY-NC-SA 4.0

六、将 JSF 2 用于基于组件的 Web 开发

它在盘子里排列得如此美丽——你就知道有人用手指在上面摸过。

—茱莉亚·切尔德

JavaServer Faces (JSF) 是一个用于开发 web 应用的基于组件的框架。使基于组件的框架与众不同的显著特征是创建和分发可重用 UI 组件的能力*。一般来说,组件代表一种抽象,一种定义良好的契约,对组件的用户隐藏了实现细节。也就是说,组件的用户不需要知道组件的内部机制就能够使用它。Struts 和 Spring Web MVC 减轻了为 Web 构建复杂用户界面的复杂性,但是这些 Web 框架不是以组件为中心的,因此不适合设计真正可重用的 UI 组件。为此,出现了不同的 web 框架,如 Tapestry 和 Wicket,为 web 应用开发提供了一种以组件为中心的方法。然而,由于缺乏以组件为中心的开发的现有标准,这些 web 框架实现可重用 UI 组件的方式对于一个有经验的 web 开发人员来说似乎是乏味的或有限的。*

*JSF 标准化了基于组件的 web 开发,并提供了大量广泛的 UI 组件来降低 web 应用开发的复杂性。JSF 提供开箱即用的可重用 UI 组件,以便应用开发人员可以专注于应用的业务逻辑,而不是努力开发和维护动态和丰富的用户界面。JSF 是 Struts 等一些框架的发展,并受到 Swing 组件模型的启发。JSF 代表并要求一种范式转变,让你从组件而不是请求和响应的角度来考虑问题。它的目标是通过促进和标准化一个生态系统来设计可重用的 UI 组件,从而加快 web 开发。

JSF 的建筑

JSF web 框架使用模型-视图-控制器(MVC)设计模式,就像 Struts 和 SpringMVC 这样基于请求的 web 框架一样。图 6-1 显示了 JSF 框架的高级架构。

9781430259831_Fig06-01.jpg

图 6-1 。JSF 框架的高层架构

图 6-1 展示了 JSF 的几个重要部分,这些部分使其建筑丰富而灵活。该体系结构允许您执行以下操作:

  • 插入任何视图声明语言(VDL ),如 JSP 和 Facelets
  • 在不同的设备上渲染显示,如台式机、平板电脑等
  • 使用组件创建页面

核心控制器

facessservlet 是 MVC 中的控制器,如图图 6-1 所示,它实现了前端控制器模式,拦截 Facelets(视图)和模型之间的每一次交互。FacesServlet 是通过托管 beans、转换器、组件、呈现器和验证器上的注释来配置的,也可以通过 faces-config.xml 描述符文件来配置。

受管 Bean

受管 bean 充当 UI 组件的模型。他们负责以下工作:

  • 将数据与组件同步
  • 处理业务逻辑
  • 处理页面之间的导航

VDL〔??〕

JSF 使用视图声明语言(VDL) 在各种设备上向客户端显示页面,比如台式机、笔记本电脑等等。JavaServer Faces (JSF)的默认 VDL 是 Facelets ,但是 JSF 允许多个 VDL,比如 JSP。

JSF EL(联合王国)

在第五章的 Hello World 应用中,您看到了如何使用 EL 表达式 和分隔符#{和},访问受管 bean 属性和调用受管 bean 操作。JSF 1.0 和 1.1(以及后来的 JSP 版本 1.2 和 2.0)中使用的 EL 是 JSP 标准标记库 (JSTL)的一部分的 EL 的扩展,如第三章中的所述。JSF EL 和 JSP EL 的区别在于评估。在 JSP 中,正如你在第三章中看到的,页面中出现的任何${}表达式都会在页面呈现期间立即被求值。这样的表达式被称为立即表达式

JSF 允许表达式在页面呈现期间和页面再次回发时都可用。这种类型的表达式在 JSF 被称为延迟表达式 ,用分隔符#{}表示。

JSF 标签库

标准的 JSF 库由页面需要访问的四个部分组成,以便使用 JSF 组件。

  • HTML 组件库:定义了表示普通 HTML 用户界面组件的元素。标准的 HTML 库可以在 Facelets 和 JSP 中作为标签库访问,标签库的 URI 是 http://java.sun.com/jsf/html 的 ??,默认前缀是 h
  • JSF 核心库:标准核心库与 f:命名空间相关联,为验证和转换提供通用的应用开发工具。
  • Facelets 库:标准的 Facelets 模板库可以在 Facelets 中作为标签库访问,其 URI 为java.sun.com/jsf/facelets,默认前缀为 ui。
  • 复合库:标准复合组件库可以在 Facelets 中作为标签库访问,其 URI 为java.sun.com/jsf/composite,默认前缀为 Composite。

标准的 JSF 组件库是规范的一部分,也是任何标准 JSF 实现的一部分,比如参考实现或 MyFaces 实现。接下来的部分展示了如何下载和安装 JSF 实现,即 Mojarra,并将其集成到 web 应用中。

UI 组件

JSF 在标准 UI 组件框架中提供了丰富的组件模型,如图 6-2 所示。JSF 组件模型包括以下:

  • 一种呈现模型,它定义了组件可以呈现的各种形式,例如用于桌面应用设备和移动应用设备
  • 定义如何处理组件事件的事件和事件监听器模型
  • 定义将数据转换器注册到组件以进行数据转换的方法的转换模型
  • 一个验证模型,它定义了为服务器端验证向组件注册验证器的方法

9781430259831_Fig06-02.jpg

图 6-2 。JSF 组件模型

UI 组件模型是 JSF 的核心;它允许您从标准的、现成的 UI 组件集合中开发 web 应用的视图。这些 UI 组件负责行为,并根据您想要使用的 UI 组件类型,通过包含 JSF 提供的四个标记库,在 JSF 页面中使用。

渲染器

渲染器负责显示组件,换句话说,将标记呈现给客户端,并将用户的输入转换为组件的值。JSF 支持两种显示组件的编程模型。

  • 直接渲染器模型:当使用直接模型时,组件从视图解码并编码到视图。解码和编码过程将在下一节解释。
  • 委托渲染器模型:使用委托模型时,解码和编码委托给渲染器。

转换器和验证器

JSF 提供了现成的转换器,可以将其 UI 组件的数据转换为托管 bean 中使用的对象,反之亦然。例如,它们将组件的日期值与来自 HTML 标记的字符串值相互转换。

JSF 还提供开箱即用的验证器来验证其 UI 组件,以确保用户输入的值是有效的。例如,这些标签可以验证一个长整型范围或一个字符串的长度。

事件和事件侦听器

当用户单击 JSF 页面上的按钮或链接时,JSF UI 组件会触发一个事件。为了处理这样的事件,在受管 bean 上注册了一个事件监听器。UI 组件调用特定事件的事件侦听器上的事件通知。

正如您所看到的,JSF 页面由组件树组成。这个组件树由 JSF 请求处理生命周期在幕后管理。为了理解 JSF 请求处理生命周期,首先您将创建一个 Hello World web 应用,然后通过这个应用您将了解 JSF 生命周期如何在幕后工作。

JSF 入门

在本节中,您将使用支持 JSF2 的 Eclipse 3.8 或更新版本创建一个简单的 Hello World JSF web 应用。 x 。通过选择文件image新建image项目image web image动态 Web 项目,创建如图图 6-3 所示的动态 Web 项目。在目标运行时中指定 Apache Tomcat v7.0,在配置中选择 JavaServer Faces 项目,然后单击 Next。

9781430259831_Fig06-03.jpg

图 6-3 。创建一个 JSF 项目

配置用于构建 Java 应用的项目,如图 6-4 所示,然后单击 Next。

9781430259831_Fig06-04.jpg

图 6-4 。配置动态 web 项目

配置网络模块设置,如图 6-5 所示,并点击下一步。

9781430259831_Fig06-05.jpg

图 6-5 。配置网络模块设置

你需要选择 JSF 实现库,如图图 6-6 所示。您可以通过单击“下载”来下载库。

9781430259831_Fig06-06.jpg

图 6-6 。下载 JSF 实现库

如图图 6-7 所示,MyFaces 和 Mojarra 被列为 JSF 2.0 的两个开源参考实现。

9781430259831_Fig06-07.jpg

图 6-7 。JSF 实现库

选择 Mojarra,点击 Next,接受许可条款,如图图 6-8 所示。

9781430259831_Fig06-08.jpg

图 6-8 。接受许可条款

单击完成。Mojarra 将被列为选中的实现库,如图图 6-9 所示。

9781430259831_Fig06-09.jpg

图 6-9 。增加 JSF 能力

单击完成。项目创建完成,如图图 6-10 所示。

9781430259831_Fig06-10.jpg

图 6-10 。创建的 JSF 项目的目录结构

您将在项目中创建以下文件:

  • 托管豆:HelloBean.java。
  • form.xhtml :这是一个视图文件,包含 JSF 核心标签和延迟 EL。当应用运行时,form.xhtml 文件看起来像图 6-11 。该屏幕提供了一个输入字段和一个提交按钮。

9781430259831_Fig06-11.jpg

图 6-11 。输入姓名的表单

  • hello.xhtml :当用户在 form.xhtml 中输入姓名并点击提交按钮时,会被问候姓名。hello.xhtml 显示 form.xhtml 中输入的用户名以及问候语,如图图 6-12 所示。

9781430259831_Fig06-12.jpg

图 6-12 。你好屏幕

清单 6-1 展示了 form.xhtml 文件的代码。

清单 6-1 。表单. xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html >
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.    xmlns:h="http://java.sun.com/jsf/html">
5.
6.    <h:head>
7.         <title>First JSF app</title>
8.    </h:head>
9.    <h:body>
10.        <h3>Enter your name:</h3>
11.
12.        <h:form>
13.            <h:inputText value="#{helloBean.name}"></h:inputText>
14.            <h:commandButton value="Submit" action="hello"></h:commandButton>
15.        </h:form>
16.    </h:body>
17.    </html>
  • 第 4 行:xmlns 属性声明了 JSF 名称空间。
  • 第 6、9、12、13、14 行:有些标签有前缀,比如 h:head,h:inputText。这些是 JSF 的标签。h:inputText 和 h:commandButton 标签对应于图 6-11 中的文本字段和提交按钮。
  • 第 13 行:输入字段链接到对象属性。例如,attribute value = " # { hello bean . name } "告诉 JSF 实现将文本字段与用户对象的 name 属性链接起来。
  • 第 14 行:第{…}分隔符将 JSF 表达式语言中的表达式括起来。
  • 第 14 行:当您输入名称并单击 Submit 按钮时,会显示 hello.xhtml 文件,这是在 h:commandButton 标记的 action 属性中指定的。

清单 6-2 展示了 hello.xhtml 文件的代码。

清单 6-2 。hello.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html>
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html">
5.
6.    <h:head>
7.        <title>Hello world</title>
8.    </h:head>
9.    <h:body>
10.        <h2>Hello  #{helloBean.name}</h2>
11.    </h:body>
12.    </html>
  • 第 10 行:当页面提交后,JSF 会找到 helloBean 并通过 setName()方法设置提交的 name 值。当显示 hello.xhtml 时,JSF 将找到 helloBean 并通过其 getName()方法显示 name 属性值。

清单 6-3 展示了 helloBean。

清单 6-3 。受管 Bean

1.    package com.apress.jsf.helloworld;
2.    import javax.faces.bean.ManagedBean;
3.    import javax.faces.bean.SessionScoped;
4.    import java.io.Serializable;
5.
6.    @ManagedBean
7.    @SessionScoped
8.    public class HelloBean implements Serializable {
9.
10.        private static final long serialVersionUID = 1L;
11.
12.        private String name;
13.
14.        public String getName() {
15.            return name;
16.        }
17.        public void setName(String name) {
18.            this.name = name;
19.        }
20.    }
  • 第 6 行:托管 bean 是从 JSF 页面访问的 Java bean。@ManagedBean 注释指定了在 JSF 页面中引用该类的对象的名称。
  • 第 7 行:受管 bean 必须有名称和作用域。会话范围表示 bean 对象对跨多个页面的一个用户可用。

注意命名一个 bean 有两种注释。@Named 是 Java EE 6 和更新的应用服务器的最佳选择。

像 Struts 和 Spring MVC web 应用一样,当您在应用服务器内部部署 JSF web 应用时,您需要提供一个名为 web.xml 的部署描述符文件。为了简洁起见,web-app 声明和 welcome-file-list 中的文件列表没有完全显示出来。

清单 6-4 。web.xml

1.    <web-app  ..... >
2.    <display-name>helloWorldJSF</display-name>
3.    <welcome-file-list>
4.        ...
5.    </welcome-file-list>
6.    <servlet>
7.    <servlet-name>Faces Servlet</servlet-name>
8.    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
9.    <load-on-startup>1</load-on-startup>
10.    </servlet>
11.    <servlet-mapping>
12.    <servlet-name>Faces Servlet</servlet-name>
13.    <url-pattern>/faces/*</url-pattern>
14.    </servlet-mapping>
15.    <servlet-mapping>
16.    <servlet-name>Faces Servlet</servlet-name>
17.    <url-pattern>*.faces</url-pattern>
18.    </servlet-mapping>
19.    <context-param>
20.    <description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
21.    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
22.    <param-value>client</param-value>
23.    </context-param>
24.    <context-param>
25.    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
26.    <param-value>resources.application</param-value>
27.    </context-param>
28.    <listener>
29.    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
30.    </listener>
31.    </web-app>
  • 第 11 到 14 行:servlet-mapping 元素确保所有带有前缀/faces 的 URL 都由 FacesServlet 处理。
  • 第 15 到 18 行:servlet-mapping 元素确保所有以 faces 扩展名结尾的 URL 都由 FacesServlet 处理。

图 6-13 显示了 Hello World web 应用的目录结构。

9781430259831_Fig06-13.jpg

图 6-13 。Hello World web 应用的目录结构

JSF 应用的生命周期

与 Struts 和 Spring Web MVC 不同,JSF 生命周期在定义明确的阶段中执行以下请求处理任务,在此期间执行特定的任务:

  • 检查输入数据是否有效
  • 触发应用逻辑以满足请求
  • 将字段属性直接绑定到模型的属性,并在表单提交时更新它们的值
  • 将响应呈现给客户端

以下是 JSF 应用生命周期的六个阶段,如图图 6-14 所示:

  • 恢复视图阶段
  • 应用请求值阶段
  • 流程验证阶段
  • 更新模型阶段
  • 调用应用阶段
  • 渲染响应阶段

9781430259831_Fig06-14.jpg

图 6-14 。JSF 生命周期的各个阶段

阶段 1:恢复视图

一旦点击链接或按钮,JSF 收到请求,JSF 就开始恢复视图阶段。在此阶段,JSF 执行以下操作:

  • 从页面构建组件树。此组件树包含与页面所有组件相关的信息。如果页面是第一次被请求,JSF 会创建一个空视图。
  • 将事件处理程序和验证程序连接到 UI 组件。
  • 将视图保存在 FacesContext 实例中。

阶段 2:应用请求值

创建/恢复组件树后,JSF 运行时运行组件树中每个组件的 decode 方法,从请求参数中提取值。如果需要,在执行数据转换之后,由 decode 方法提取的值存储在组件中。如果转换失败,将生成一条错误消息并在 FacesContext 上排队。

阶段 3:流程验证

在这个阶段,JSF 运行时处理在恢复视图阶段在组件树上注册的验证器。如果存在验证错误,JSF 会向 FacesContext 实例添加一条错误消息,跳过第四和第五阶段,进入渲染响应阶段,并显示错误消息。

阶段 4:更新模型

如果在流程验证阶段没有验证错误,JSF 运行时将使用 UI 组件的新值更新绑定到 UI 组件的受管 bean 的属性。如有必要,转换也在此阶段执行。

阶段 5:调用应用

在这个阶段,JSF 运行时通过执行相应的事件侦听器来处理应用事件。当用户提交表单时,JSF facessservlet 生成一个应用事件,该事件返回一个传递给导航处理程序的结果字符串。导航处理程序查找下一个要呈现的页面。

阶段 6:呈现响应

在这个阶段,组件树中的每个组件都会呈现自己,并且保存响应的状态,以便 FacesServlet 可以在 Restore View 阶段访问它,如果对同一个页面发出后续请求,就会发生这种情况。

让我们从请求处理生命周期的角度来看看 Hello World 应用的幕后。

  1. 浏览器首先连接localhost:8080/hello world JSF/form . faces

  2. The JSF implementation initializes the JSF code and reads the form.xhtml page. That page contains tags, such as h:form, h:inputText, and h: commandButton. Each tag has an associated tag handler class. When the page is read, the tag handler class associated with each tag is executed, and a component tree is constructed. This is the first phase: Restore View. Since this is the first request and the component tree does not already exist, a new but empty component tree is created instead of restoring the component tree. Figure 6-15 shows the component tree for the code fragment of the form.xhtml file in Listing 6-1.

    <h:form>
    <h:inputText value="#{helloBean.name}"></h:inputText>
    <h:commandButton value="Submit" action="hello"></h:commandButton>
    </h:form>
    

    9781430259831_Fig06-15.jpg

    图 6-15 。form.xhtml 组件树

  3. 现在,JSF 运行时进入第二阶段:应用请求值。UIForm 对象对应于 h:form,UIInput 对象对应于 h:inputText,UICommand 对象对应于 JSF 文件中的 h:commandButton。由于这是对该页面的第一个请求,并且没有可用的请求参数或要处理的事件,没有要更新模型的内容,没有要转换和验证的内容,也没有应用级别的事件,因此 JSF 运行时跳过第二、第三、第四和第五阶段,进入第六阶段:呈现响应。创建的组件树中的每个组件对象都有一个生成 HTML 的渲染器。这个由组件的渲染器生成 HTML 的过程叫做编码。这个编码后的页面会显示在浏览器中。

  4. 用户现在填写表单中的 name 字段,并单击 Submit 按钮。

  5. 浏览器将表单数据发送到 web 服务器,格式化为 POST 请求。JSF 运行时再次进入第一个阶段,还原视图,组件树被还原以反映用户在表单中输入的值。然后 JSF 运行时进入第二阶段,应用请求值。

  6. 在应用请求值阶段,JSF 运行时执行名为解码 的过程,其中组件树中的每个组件解码表单数据,组件存储该值。如果存储时转换失败,则会生成一条错误消息,并在 FacesContext 上排队。

  7. JSF 运行时进入第三阶段:流程验证。在这一点上,JSF 运行时处理在第一阶段向组件树注册的验证器。如果有任何验证错误,比如 Hello World 应用中的 name 字段为空,JSF 会向 FacesContext 实例添加一条错误消息,跳过其他阶段,进入第六个阶段:呈现响应。它还显示错误消息,如“名称字段不能为空”因为在 Hello World 应用中没有验证,所以 JSF 运行时进入第四个阶段:更新模型。

  8. 在更新模型阶段,JSF 运行时用表单上输入的值更新受管 bean,即 helloBean 的属性名。UIInput 组件更新 value 属性中引用的 helloBean 属性名,并使用用户输入的值调用 setter 方法。在此阶段,如有必要,还会通过向组件注册的转换器执行转换。因为在这种情况下不需要转换,所以 JSF 运行时进入第五个阶段,调用应用,或者触发一个动作事件。

  9. 在调用应用阶段,UICommand 组件检查按钮是否被点击。如果是,它会触发一个 action 事件,即 action 属性中引用的 hello 操作,该操作告诉导航处理程序寻找 hello.xhtml 页面,JSF 运行时进入第六个阶段:呈现响应。

  10. facessservlet 创建一个响应组件树,当 JSF 运行时遍历相应的 JSF 标签时,每个组件都会呈现自己。在这个阶段的最后,保存响应的状态,以便 FacesServlet 可以在对同一页面的后续请求的 Restore View 阶段访问它。

受管 Beans

一个托管 bean 是一个常规的 JavaBean 类,在 JSF 注册,由 JSF 框架管理,充当 UI 组件的模型。受管 bean 包含 getter 和 setter 方法、业务逻辑或支持 bean,即包含表单所有值的 bean。受管 beans 负责以下内容:

  • 将服务器端数据与组件树中的组件同步
  • 处理业务逻辑
  • 处理页面之间的导航

该组件通过 EL 与特定的托管 bean 属性或操作相关联。正如您在 Hello World 应用中看到的,您不需要编写任何代码来构造和操作 HelloBean。JSF 运行时构建 beans 并访问它们。可以很容易地在 JSF 配置文件(即 faces-config.xml)中或使用注释注册受管 beans。

清单 6-5 展示了使用 XML 配置注册一个被管理的 bean。

清单 6-5 。使用 XML 注册受管 Bean

<managed-bean>
<managed-bean-name>helloWorld</managed-bean-name>
<managed-bean-class>com..apress.jsf.helloWorld.HelloBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

清单 6-6 展示了使用注释注册一个受管 bean。

清单 6-6 。使用注释注册受管 Bean

@ManagedBean
@SessionScoped
public class HelloWorld {

}

注意上下文和依赖注入(CDI)bean 比托管 bean 更强大。如果您在 Java EE 应用服务器(比如 Glassfish)中部署应用,那么您应该使用 CDI beans。Java EE 6 和更新的应用服务器自动支持 CDI。CDI Beans 的使用方式与受管 bean 相同,但它们不是用@ManagedBean,而是用@Named 批注声明的,如下所示:

@ Named(" helloBean ")
@ session scoped
公共类 hello bean 实现 Serializable {

}

Facelets

Facelets 被创建来代替 JSP 作为 JSF 的视图声明语言;它是专门为 JSF 设计的,提供了模板和可扩展标记库,以避免在 HTML 页面中使用 script let(Java 代码)。Facelets 和 JSP 之间的显著区别在于,Facelets 提供了用纯 HTML 标记编写页面的能力,并提供了服务器端模板。

使用 Facelets 进行模板化

您在第四章中学习了使用 Tiles 框架进行模板化,在那里您看到了模板是如何封装通用布局以供所有页面使用的,现在您理解了模板是如何工作的。Facelets 类似于 Tiles 框架,用于模板化和组合页面。因此,除了 Facelets 为 ui: tag 库中的模板提供的六个标记之外,模板化是非常相同的。

  • ui:构图
  • ui:装饰
  • ui:定义
  • ui:包含
  • 用户界面:插入
  • ui:停止〔??〕

ui:构图

ui:composition 标记用在充当模板客户端的模板客户端文件中,并指示在页面中的特定点,封装内容应该包含在 UIComponent 层次结构中。清单 6-7 说明了 ui:composition 的语法。

清单 6-7 。ui:合成标签

<ui:composition template="optional">

可选属性声明一个模板,应该使用 template 属性将包含的内容应用于该模板。

ui:装饰

ui:decoration 标记和 ui:composition 标记的区别在于,与 ui:composition 不同,ui:decoration 也将周围的内容包含在页面中。清单 6-8 说明了 ui:decorate 的语法。

清单 6-8 。ui:装饰标签

<ui:decorate template="required">

ui:定义

ui:define 标记用于模板客户端文件中的 ui:composition 标记内,以定义将在 ui:insert 标记提供的点插入到合成中的区域。清单 6-9 说明了 ui:define 的语法。

清单 6-9 。用户界面:定义标签

<ui:define name="required">

用户界面:插入

ui:insert 标记在模板文件中用来指示 ui:define 在模板客户机中应该插入的位置。清单 6-10 说明了 ui:insert 的语法。

清单 6-10 。用户界面:插入标签

<ui:insert name="optional">

如果没有指定名称,那么 ui:insert 标记体中的内容将被添加到视图中。

ui:包含

ui:include 可以在模板文件或模板客户端文件中使用。清单 6-11 展示了 ui:include 的语法。

清单 6-11 。ui:包含标签

<ui:include src="required">

ui:停止〔??〕

ui:param 标记用在 ui:include 标记中,为页面的参数化包含定义名称-值对。清单 6-12 展示了 ui:param 的语法。

清单 6-12 。ui:停止标记

<ui:param name="required" value="required">

接下来,您将使用 Facelets 实现模板化。创建一个类似于前面部分中 Hello World 项目的 JSF 项目。在这个应用中,您将创建将在书店应用中使用的标题和侧栏模板。图 6-16 说明了应用的目录结构。

9781430259831_Fig06-16.jpg

图 6-16 。JSFTemplate 应用的目录结构

清单 6-13 展示了模板文件 common.xhtml.

清单 6-13 。common.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <html fontname">http://www.w3.org/1999/xhtml"
3.        xmlns:h="http://java.sun.com/jsf/html"
4.        xmlns:ui="http://java.sun.com/jsf/facelets">
5.    <h:head>
6.        <link rel="stylesheet" href="css/bookstore.css" type="text/css" />
7.    </h:head>
8.    <h:body>
9.        <div id="centered">
10.            <div>
11.                <ui:insert name="header">
12.                    <ui:include src="/templates/header.xhtml" />
13.                </ui:insert>
14.            </div>
15.            <div>
16.                <ui:insert name="sideBar">
17.                    <ui:include src="/templates/sideBar.xhtml" />
18.                </ui:insert>
19.            </div>
20.            <div>
21.                <ui:insert name="content">
22.                    <ui:include src="/templates/contents.xhtml" />
23.                </ui:insert>
24.            </div>
25.        </div>
26.    </h:body>
27.    </html>
  • 第 3 行:声明 HTML 库的名称空间
  • 第 4 行:声明 Facelet 库的名称空间
  • 第 5 行到第 7 行:显示 h:head 标签的用法,而不是使用标记< head/ >
  • 第 8 行:显示了 h:body 标签的用法,而不是使用标记< body/ >
  • 第 11 行到第 13 行:展示了使用 u:insert 标签来模板化添加到视图中的< ui:insert >标签的主体内容
  • 第 12 行:显示了包含 header.xhtml 的 ui:include 标签的用法
  • 第 17 行:展示了包含 sideBar.xhtml 的 ui:include 标签的用法
  • 第 22 行:显示了包含 contents.xhtml 的 ui:include 标签的用法

清单 6-14 展示了模板客户端 header.xhtml.

清单 6-14 。header.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <html fontname">http://www.w3.org/1999/xhtml"
3.        xmlns:h="http://java.sun.com/jsf/html"
4.        xmlns:ui="http://java.sun.com/jsf/facelets">
5.    <h:body>
6.        <ui:composition>
7.            <div class="header">
8.                <h2>
9.                <span style="margin-left: 15px; margin-top: 15px;" class="label">BOOK
10.                        <span style="color: white;">STORE</span>
11.                    </span>
12.                </h2>
13.            </div>
14.        </ui:composition>
15.    </h:body>
16.    </html>
  • 第 6 行:ui:composition 标签向 Facelets 系统表明,封闭的子元素将被移植到 UIComponent 层次结构中,其中 header.xhtml 被插入到 common.xhtml 中。

清单 6-15 展示了 sideBar.xhtml.

清单 6-15 。sideBar.xhtml

1.    <div class="leftbar">
2.        <ul id="menu">
3.            <li><div>
4.                    <a class="link1" href=""><span class="label"
5.                        style="margin-left: 15px;">Home</span>
6.                    </a>
7.                </div></li>
8.            <li><div>
9.            <a class="link1" href="listOfBooks.xhtml"><span
10.                style="margin-left: 15px;" class="label">All Books</span></a>
11.                </div></li>
12.            <li><div>
13.            <span class="label" style="margin-left: 15px;">Categories</span>
14.                </div>
15.                <ul>
16.                    <li><a class="label" href=""><span class="label"
17.                            style="margin-left: 30px;"></span></a></li>
18.                </ul></li>
19.            <li><div>
20.                    <span class="label" style="margin-left: 15px;">Contact Us</span>
21.                </div></li>
22.        </ul>
23.    </div>

清单 6-15 是在第二章中使用的同一个侧边栏文件。

清单 6-16 展示了模板客户端内容. xhtml.

清单 6-16 。内容. xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <html fontname">http://www.w3.org/1999/xhtml"
3.        xmlns:ui="http://java.sun.com/jsf/facelets">
4.    <body>
5.        <ui:composition>
6.            <h1>Book Store Home</h1>
7.        </ui:composition>
8.    </body>
9.    </html>
  • 第 5 行:ui:composition 标签向 Facelets 系统表明,应该在 contents.xhtml 插入的地方将封闭的子元素移植到 UIComponent 层次结构中。

清单 6-17 展示了定义 content.xhtm 的 home.xhtml 文件。

清单 6-17 。home.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html>
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:ui="http://java.sun.com/jsf/facelets">
6.    <h:body>
7.        <ui:composition template="templates/common.xhtml">
8.            <ui:define name="content">
9.                <ui:include src="/contents.xhtml" />
10.            </ui:define>
11.        </ui:composition>
12.    </h:body>
13.    </html>
  • 第 7 行:ui:composition 标签声明了模板 common.xhtml,应该使用 template 属性将包含的 content contents.xhtml 应用于该模板。

你现在可以使用 URLlocalhost:8080/JSF template/home . faces运行这个应用,如图图 6-17 所示。

9781430259831_Fig06-17.jpg

图 6-17 。使用模板的标题和侧栏

使用 JSF 2 构建书店应用

在本节中,您将使用 JSF 开发书店应用。在第一章中,您开发了书店应用的数据访问层,并通过使用 Java 开发的独立应用对其进行查询。在第五章中,你重构了独立应用,将其与 Spring 框架集成,以便使用 Spring 的 JDBCTemplate 提供的健壮特性。然后你将 Spring JDBCTemplate 用于 Spring Web MVC web 应用。在本章中,您将在一个基于 JSF 的 web 应用中使用相同的 Spring JDBCTemplate,以利用在第五章中讨论的 Spring 模板的优势。为此,您需要将 JSF 与 Spring 框架集成在一起。在接下来的部分中,您将学习如何将 JSF 与 Spring 集成在一起。然后,您将使用 JSF 开发应用的 web 层。您将通过以下四个步骤开发该应用:

  1. 将 JSF 与 Spring 框架集成。
  2. 通过 Spring JDBCTemplate 从 web 层访问数据库。
  3. 重用之前开发的模板来开发模板。
  4. 使用 UI 组件和 JSF EL 开发用户界面。

图 6-18 展示了你将要开发的应用的目录结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-18 。书店 web 应用的目录结构

将 JSF 与 Spring 框架集成

要将 JSF 与 Spring 依赖注入框架集成,您必须做以下事情:

  • 在 web.xml 中添加由 Spring 框架提供的 ContextLoaderListener 和 RequestContextListener 侦听器
  • 在 faces-config.xml 中添加一个 el-resolver 条目,指向 Spring 类 SpringBeansFacesELResolver

清单 6-18 展示了在 web.xml 文件中添加 ContextLoaderListener 和 RequestContextListener。

清单 6-18 。web.xml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" fontname">http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="true" version="3.0">
3.    <display-name>JSFBooks</display-name>
4.    <servlet>
5.    <servlet-name>Faces Servlet</servlet-name>
6.    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
7.    <load-on-startup>1</load-on-startup>
8.    </servlet>
9.    <servlet-mapping>
10.    <servlet-name>Faces Servlet</servlet-name>
11.    <url-pattern>/faces/*</url-pattern>
12.    </servlet-mapping>
13.    <servlet-mapping>
14.    <servlet-name>Faces Servlet</servlet-name>
15.    <url-pattern>*.jsf</url-pattern>
16.    </servlet-mapping>
17.    <servlet-mapping>
18.    <servlet-name>Faces Servlet</servlet-name>
19.    <url-pattern>*.faces</url-pattern>
20.    </servlet-mapping>
21.    <context-param>
22.    <description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
23.    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
24.    <param-value>client</param-value>
25.    </context-param>
26.    <context-param>
27.    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
28.    <param-value>resources.application</param-value>
29.    </context-param>
30.    <listener>
31.    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
32.    </listener>
33.    <listener>
34.    <listener-class>
35.             org.springframework.web.context.ContextLoaderListener
36.    </listener-class>
37.    </listener>
38.    <listener>
39.    <listener-class>
40.             org.springframework.web.context.request.RequestContextListener
41.    </listener-class>
42.    </listener>
43.    </web-app>
  • 第 33 到 37 行:配置 ContextLoaderListener
  • 第 38 到 42 行:配置 RequestContextListener

SpringBeanFacesELResolver 是一个 ELResolver 实现,它委托给 Spring 的 WebApplicationContext 和底层 JSF 实现的默认解析器。清单 6-19 说明了如何添加 el-resolver。

清单 6-19 。faces-config.xml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <faces-config
3.        fontname">http://java.sun.com/xml/ns/javaee"
4.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5.        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
6.        version="2.0">
7.    <application>
8.        <el-resolver>
9.            org.springframework.web.jsf.el.SpringBeanFacesELResolver
10.        </el-resolver>
11.        </application>
12.    </faces-config>
  • 第 7 行到第 11 行:配置弹簧 EL 分解器

JSF 现在与 Spring 框架集成在一起,您应该能够通过 Spring JDBCTemplate 访问数据库。

通过 Spring JDBCTemplate 从 Web 层访问数据库

接下来,您将创建受管 bean 来从 web 层访问数据库。您已经在第五章中创建了 Spring JDBCTemplate。您现在将要创建的受管 bean 将使用先前通过 BookService 创建的 JDBCTemplate,它也是在第五章中创建的。换句话说,我们将通过在第五章中创建的服务层和数据访问层创建托管 bean 来访问数据库。清单 6-20 展示了 BookController 管理的 bean。

清单 6-20 。BookController.java

1.    package com.apress.books.controller;
2.
3.    import javax.faces.bean.ManagedBean;
4.    import javax.faces.bean.RequestScoped;
5.    import com.apress.books.model.Book;
6.    import com.apress.books.service.BookService;
7.    import java.util.List;
8.
9.    @ManagedBean
10.    @RequestScoped
11.    public class BookController {
12.
13.          private BookService bookService ;
14.          private List<Book> bookList;
15.
16.        public String listAllBooks() {
17.            bookList = bookService.getAllBooks();
18.                return "bookList.xhtml";
19.            }
20.
21.        public BookService getBookService() {
22.            return bookService;
23.        }
24.
25.        public void setBookService(BookService bookService) {
26.            this.bookService = bookService;
27.        }
28.
29.        public List<Book> getBookList() {
30.              return bookList;
31.        }
32.        public void setBookList(List<Book> bookList) {
33.            this.bookList = bookList;
34.        }
35.    }
  • 第 17 行:调用 bookService 上的 getAllBooks()方法
  • 第 18 行:返回由模板和 list.xhtml 组成的 booklist.xhtml 文件,显示图书列表,见后文图 6-20

清单 6-21 展示了提供给 Spring IoC 容器的配置元数据。该文件与在第五章中创建的文件相同,只是稍微修改了一下,用 BookService 配置受管 bean BookController。

清单 6-21 。application context . XML

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <beans fontname">http://www.springframework.org/schema/beans"
3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
4.        xmlns:aop="http://www.springframework.org/schema/aop"
5.        xsi:schemaLocation="http://www.springframework.org/schema/beans
6.            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
7.            http://www.springframework.org/schema/context
8.            http://www.springframework.org/schema/context/spring-context-3.2.xsd
9.            http://www.springframework.org/schema/aop
10.            http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
11.
12.        <!-- telling container to take care of annotations stuff -->
13.        <context:annotation-config />
14.
15.        <!-- declaring base package -->
16.        <context:component-scan base-package="com.apress.books" />
17.
18.        <bean id="bookController" class="com.apress.books.controller.BookController">
19.            <property name="bookService" ref="service"></property>
20.        </bean>
21.
22.        <bean id="dao" class="com.apress.books.dao.BookDAOImpl" >
23.        <property name="dataSource" ref="dataSourceBean">
24.            </property>
25.        </bean>
26.
27.        <bean id="service" class="com.apress.books.service.BookServiceImpl">
28.            <property name="bookDao" ref="dao">
29.            </property>
30.        </bean>
31.
32.        <bean id="dataSourceBean"
33.            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
34.            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
35.            <property name="url" value="jdbc:mysql://localhost:3306/books" />
36.            <property name="username" value="root" />
37.            <property name="password" value="password" />
38.        </bean>
39.    </beans>
  • 第 18 到 20 行:用 bookService 配置 bookController

开发模板

您将重用先前为书店应用的标题和侧栏开发的模板和模板客户机文件。但是,您需要修改 sideBar.xhtml 来调用 bookController bean 中的 listOfAllBooks 动作,如清单 6-22 所示。

清单 6-22 。sideBar.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html >
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:ui="http://java.sun.com/jsf/facelets">
6.    <h:form>
7.        <div class="leftbar">
8.            <ul id="menu">
9.                <li><div>
10.                        <a class="link1" href=""><span class="label"
11.                            style="margin-left: 15px;">Home</span>
12.                        </a>
13.                    </div></li>
14.                <li><div>
15.                <h:commandLink class="link1" action="#{bookController.listAllBooks}">
16.                    <span style="margin-left: 15px;" class="label">All Books</span>
17.                </h:commandLink>
18.                    </div></li>
19.                <li><div>
20.                    <span class="label" style="margin-left: 15px;">Categories</span>
21.                    </div>
22.                    <ul>
23.                        <li><a class="label" href=""><span class="label"
24.                        style="margin-left: 30px;"></span></a></li>
25.                    </ul></li>
26.                <li><div>
27.                    <span class="label" style="margin-left: 15px;">Contact Us</span>
28.                    </div></li>
29.            </ul>
30.        </div>
31.    </h:form>
32.
33.    </html>
  • 第 6 行:显示了 h:form 标签的用法,而不是使用标记<表单>
  • 第 15 行:显示了 h:commandlink 标签的用法,该标签使用延迟 EL 触发 bookController 管理的 bean 上的 listAllBooks 操作

使用 UI 组件和 JSF EL 开发用户界面

现在您将开发应用的 UI。 图 6-19 说明了应用的主页。当用户点击侧边栏中的所有书籍时,显示所有书籍的列表,如图图 6-20 所示。

9781430259831_Fig06-19.jpg

图 6-19 。书店 web 应用的主页

9781430259831_Fig06-20.jpg

图 6-20 。图书清单

清单 6-23 说明了图 6-19 的代码。

清单 6-23 。home.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html>
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:ui="http://java.sun.com/jsf/facelets">
6.    <h:body>
7.        <ui:composition template="templates/common.xhtml">
8.            <ui:define name="content">
9.                <ui:include src="/contents.xhtml" />
10.            </ui:define>
11.        </ui:composition>
12.    </h:body>
13.    </html>
  • 第 9 行:包含 contents.xhtml 的 ui:include 标签的用法

当用户点击首页侧边栏中的所有书籍时,显示所有书籍的列表,如图图 6-20 所示。

清单 6-24 和清单 6-25 说明了图 6-20 的代码。

清单 6-24 。bookList.html

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html>
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:ui="http://java.sun.com/jsf/facelets">
6.    <h:body>
7.        <ui:composition template="templates/common.xhtml">
8.            <ui:define name="content">
9.                <ui:include src="/list.xhtml" />
10.            </ui:define>
11.        </ui:composition>
12.    </h:body>
13.    </html>
  • 第 9 行:包含 list.xhtml 的 ui:include 标签的用法

清单 6-25 展示了图 6-20 中显示的图书列表的 list.xhtml 文件。

清单 6-25 。list.xhtml

1.    <?xml version="1.0" encoding="UTF-8"?>
2.    <!DOCTYPE html>
3.    <html fontname">http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:ui="http://java.sun.com/jsf/facelets"
6.        xmlns:f="http://java.sun.com/jsf/core">
7.    <h:head>
8.        <title>List of All books</title>
9.        <link rel="stylesheet" href="css/bookstore.css" type="text/css" />
10.        <script type="text/javascript" src="js/jquery-1.9.1.js"></script>
11.        <script src="js/bookstore.js"></script>
12.    </h:head>
13.    <h:body>
14.        <h:dataTable id="grid" value="#{bookController.bookList}" var="book">
15.            <h:column>
16.                <f:facet name="header" id="th-title">
17.                    <h:outputText value="Title" />
18.                </f:facet>
19.                <h:outputText value="#{book.bookTitle}" />
20.            </h:column>
21.            <h:column>
22.                <f:facet name="header" id="th-author">
23.                    <h:outputText value="Author" />
24.                </f:facet>
25.                <ui:repeat value="#{book.authors}" var="content">
26.                    <h:outputText value="#{content.firstName} #{content.lastName}" />
27.                </ui:repeat>
28.            </h:column>
29.            <h:column>
30.                <f:facet name="header" id="th-price">
31.                    <h:outputText value="Publisher" />
32.                </f:facet>
33.                <h:outputText value="#{book.publisherName}" />
34.            </h:column>
35.        </h:dataTable>
36.    </h:body>
37.    </html>
  • 第 6 行:声明 JSF 核心库的名称空间
  • 第 14 行:显示了 h:dataTable 标签的用法,而不是使用标记< table/ >
  • 第 14 行:显示了使用受管 bean bookController 的 bookList 属性的延迟 EL 表达式
  • 第 15 行:显示 h:column 的用法,不使用标记< td/ >
  • 第 16 行:显示了使用 f:facet 标签来添加 facet
  • 第 17 行:显示显示书名的 h:outputText 的用法

摘要

JSF 是一个基于组件的 MVC 框架,其核心是 UI 组件模型;该模型允许从标准的、现成的、可重用的 UI 组件集合中开发 web 应用的视图。与 Struts 和 Spring Web MVC 不同,JSF 生命周期在定义明确的阶段执行普通的和重复的请求处理任务,允许开发人员专注于 Web 应用的业务逻辑。*

七、使用 Grails 快速开发 Web

不再活在碎片里,只有连接。

埃德加·摩根·福斯特

Grails 将 web 开发带到了下一个抽象层次。Java EE 不是用应用级的抽象编写的,这一事实导致了 Spring、Hibernate 等 Java 框架的开发和随后的流行。但是大多数 Java 框架对 web 开发采取了一种分散的方法。您必须维护每一层的配置。Grails 支持配置之上的约定,并通过 Groovy 语言用一个抽象层包装这些强大的框架,从而提供一个完整的开发平台,允许您充分利用 Java 和 JVM。

本章将深入 Grails 机器的内部,查看它的各个部分:它的轮子和齿轮都在协调运动,它的可工作性,它的前沿引擎,以及它的底层形式。它将进一步研究 Grails 生态系统中的交互。它将展示控制器如何处理、管理、指导和编排应用的逻辑流,以及它们如何处理请求、重定向请求、执行和委托操作,或者根据需要呈现视图。它将探索视图并揭示 Grails 如何使用 SiteMesh(页面装饰框架)为页面提供一致的外观,以及视图如何利用 Grails 的内置标签和标签库中的动态标签来创建格式良好的标记并促进关注点的清晰分离。它是一台相当好的机器。

Grails 特性

Grails 是一个基于请求的、MVC、开源的 web 开发框架。不仅如此,它还是一个完整的开发平台,一切都运行在健壮的 Java 和 Java EE 平台之上,如图 7-1 所示。它利用了现有的流行 Java 框架,并且包含了利用 Groovy 语言动态性的 web 容器、数据库、构建系统和测试工具。

9781430259831_Fig07-01.jpg

图 7-1 。Grails 平台

Grails 提供了最佳实践,例如约定优于配置,以及使用 Spring、Hibernate 和 SiteMesh 等框架进行单元测试。本节将重点介绍一些重要的最佳实践。

约定胜于配置

与配置相比,Grails 优先考虑约定。约定优于配置 ,简单来说,就是只在背离约定的时候才写配置代码。这些巧妙的约定对应于目录结构;Grails 使用文件的名称和位置,而不是通过连接 XML 配置文件来依赖显式配置。这意味着如果你创建一个遵循 Grails 惯例的类,Grails 会把它连接到 Spring 中,或者把它当作一个 Hibernate 实体。如果创建一个名为 book 的新域类,Grails 会自动在数据库中创建一个名为 Book 的表。通过使用约定胜于配置的范例,Grails 可以根据组件的名称及其在目录结构中的位置来设想组件。除了加速应用开发之外,这样做的一个直接后果是,只有当配置偏离标准时,您才必须配置组件的特定方面。

脚手架

Grails 脚手架在运行时或开发时从域类生成应用的 CRUD 功能。生成的应用由与域类相关联的控制器和 GSP 视图组成。脚手架还生成数据库模式,包括每个域类的表。

对象关系映射

Grails 包括一个强大的对象关系映射(ORM) 框架,称为 Grails 对象关系映射(GORM)。像大多数 ORM 框架一样,GORM 将对象映射到关系数据库;但是与其他 ORM 框架不同,GORM 是基于动态语言的。因此,GORM 可以将 CRUD 方法直接注入到类中,而不必实现它们或从持久超类继承它们。

注意 ORM 是一种将面向对象世界中的对象映射到关系数据库中的表的方法,它提供了 SQL 之上的抽象。

外挂程式

Grails 没有为每一个可能的需求提供现成的解决方案,而是提供了一个插件架构,您可以找到大量功能的插件。

单元测试

为了提高可交付成果的质量,Grails 为自动化 web 界面提供了单元测试、集成测试和功能测试。

集成开源

Grails 集成了行业标准和成熟的开源框架,本节将简要介绍其中的几个。

表 7-1 展示了 Grails 利用的框架。

表 7-1 。Grails 利用的框架

|

集成开源技术

|

描述

|
| — | — |
| Ajax 框架 | Grails 附带了 jQuery 库,但也通过插件系统提供了对其他框架的支持,如 Prototype、Dojo、Yahoo UI 和 Google Web Toolkit。 |
| 冬眠 | Hibernate 是一个 ORM 框架,为 GORM 提供了基础。 |
| 氘 | Grails 使用内存中的 H2 1 数据库,并在开发模式下启用 H2 数据库控制台(在 URI /dbconsole),以便可以从浏览器轻松查询内存中的数据库。 |
| 春天 | Spring Framework 在 Java EE API 之上提供了一个应用级的抽象。Grails 开发人员可以构建一个内部使用 Spring 和 Hibernate 的应用,而无需了解这些框架。Grails 从 Grails 开发人员那里抽象出了这些框架的大部分细节。 |
| SiteMesh | SiteMesh 2 是一个布局呈现框架,它实现了 Decorator 设计模式来呈现带有页眉、页脚和导航元素的 HTML。Grails 从 Grails 开发人员那里抽象出了大多数 SiteMesh 细节。 |
| 雄猫 | 默认情况下,Grails 使用嵌入式 Tomcat 容器。 |

安装 Grails

在安装 Grails 之前,您至少需要一个 Java 开发工具包(JDK)版本 1.6 或更新版本。下载适用于您的操作系统的 JDK,运行安装程序,然后设置一个名为 JAVA_HOME 的环境变量,指向这个安装的位置。

注意在您的 Grails 开发环境中需要一个 JDK。一个 JRE 是不够的。

开始使用 Grails 的第一步是安装发行版。为此,请按照下列步骤操作:

  1. grails.org/下载 Grails 的二进制发行版,并将生成的 ZIP 文件解压到您选择的位置。
  2. 将 GRAILS_HOME 环境变量设置为解压缩 ZIP 文件的位置。
    • 在基于 Unix/Linux 的系统上,这通常是在您的概要文件中添加如下内容:export GRAILS _ HOME =/path/to/GRAILS。
    • 在 Windows 上,这通常是在我的电脑/高级/环境变量下设置一个环境变量的问题。
  3. 然后将 bin 目录添加到 PATH 变量中。
    • 在基于 Unix/Linux 的系统上,这可以通过将 export PATH = " $ PATH:$ GRAILS _ HOME/bin "添加到您的概要文件中来完成。
      • 在 Windows 上,这是通过修改我的电脑/高级/环境变量下的 PATH 环境变量来完成的。

如果 Grails 工作正常,您现在应该能够在终端窗口中键入 grails -version,并看到类似如下的输出:

E:\>grails -version
Grails version: 2.2.4

Hello World 应用

在本节中,您将创建您的第一个 Grails web 应用。要创建一个 grails 应用,您需要熟悉 Grails 命令的用法:

grails [command name]

运行 create-app 创建应用。

grails create-app helloworld

这将在包含项目的当前目录中创建一个名为 helloworld 的新目录,换句话说,就是您的工作区。在控制台中导航到此目录:

cd helloworld

进入刚刚创建的 helloworld 目录,通过键入 grails 命令启动 Grails 交互式控制台。

\grails2-workspace\helloworld>grails

这会下载几个资源,然后你应该会看到一个提示,如图图 7-2 所示。

9781430259831_Fig07-02.jpg

图 7-2 。Grails 交互控制台

我们想要的是一个简单的页面,它只是将消息“Hello World”打印到浏览器。在 Grails 中,每当您想要一个新页面时,您就为它创建一个新的控制器动作。由于我们还没有控制器,现在让我们用 create-controller 命令创建一个。

grails> create-controller hello

前面的命令将在 grails-app/controllers/hello world 目录下创建一个名为 HelloController.groovy 的新控制器,如清单 7-1 中的所示。

清单 7-1 。HelloController.groovy

package helloworld

class HelloController {

    def index() { }
}

我们现在有了一个控制器,所以让我们添加一个动作来生成“Hello World”页面。代码看起来像清单 7-2 。

清单 7-2 。修改索引操作

def index() { render "Hello World" } }

动作只是一个方法。在这种特殊情况下,它调用 Grails 提供的特殊方法来呈现页面。

要查看应用的运行情况,您需要使用另一个名为 run-app 的命令启动服务器。

grails> run-app

这将在端口 8080 上启动一个托管您的应用的嵌入式服务器。现在,您应该能够通过 URLlocalhost:8080/hello world/访问您的应用。结果将看起来像图 7-3 。

9781430259831_Fig07-03.jpg

图 7-3 。Grails 的欢迎屏幕

这是 Grails 简介页面,由 grails-app/view/index.gsp 文件呈现。它检测控制器的存在,并提供指向它们的链接。单击 HelloController 链接,查看包含文本“Hello World”的自定义页面您有了第一个工作的 Grails 应用。

注意图 7-3 中的有一个到 Dbdoc 控制器的链接。单击此链接将产生一条错误消息,因为控制器尚未实现。DbdocController 的目的是生成静态 HTML 文件来查看更改日志信息。您可以在 conf/Config.groovy 中通过设置 dbDocController.enabled = true 来启用它

书店应用

在本章中,您将学习如何利用 Grails 约定和搭建来创建一个简单但功能强大的书店应用版本。然而,这个应用的初始版本还不能用于生产;这个应用的目的是向你展示如何使用脚手架,你可以用除了你的域类代码之外几乎没有任何代码来呈现一个 CRUD web 应用。此外,Grails 将生成一个数据库模式,并在应用运行时用该模式填充数据库。

创建书店应用

要创建书店应用,您需要在命令行上使用可选的项目名称来执行 create-app 目标,如下所示:

>grails create-app bookstore

前面命令行中的整行都是命令,其中 create-app 是目标。目标是您希望 Grails 执行的特定任务。

注意使用 help 命令会产生一个可用目标列表:> grails 帮助。

如果您在使用 create-app 时没有提供项目名称,系统会提示您提供项目名称。

在 create-app 目标运行之后,您将拥有一个与项目名称相匹配的新目录。这是新项目的根目录,您必须从这个目录中进行所有后续的 Grails 命令行调用。现在使用 cd 命令进入目录是个好主意,这样你就不会忘记了。在新的项目目录中,您会发现一个与图 7-4 所示目录结构相匹配的结构。

9781430259831_Fig07-04.jpg

图 7-4 。书店应用的目录结构

您可以使用自己选择的 IDE,而不是从命令行创建应用。我们推荐 Groovy/GrailsTool 套件(GGTS),你可以从www.springsource.org/downloads/sts-ggts下载。这本书用的是最新版本,GGTS 3.0。GGTS 为构建 Groovy 和 Grails 应用提供了最好的基于 Eclipse 的开发环境。GGTS 提供了对 Groovy 和 Grails 最新版本的支持,并基于最新的 Eclipse 版本。图 7-5 显示了如何在 GGTS 配置 Grails。在 Preferences 下,单击 Grails,然后单击 Add 按钮。在“配置 Grails 安装”窗口中,通过单击“浏览”按钮浏览 Grails,这将打开“Grails 安装目录”窗口。选择 Grails 安装目录,然后单击 OK。

9781430259831_Fig07-05.jpg

图 7-5 。用 Grails 配置 GGTS

Grails 现在被添加到构建路径中,如图 7-6 所示。单击确定。现在 GGTS 已经配置了 Grails,您可以创建一个 Grails 项目了。

9781430259831_Fig07-06.jpg

图 7-6 。构建路径中的 grails

在 GGTS 新建一个项目,使用菜单选项 File image New image Grails Project,如图图 7-7 所示。

9781430259831_Fig07-07.jpg

图 7-7 。创建新项目

因为您已经从命令行创建了项目,所以您可以在 GGTS 中导入创建的项目。在导入窗口中选择已有的项目到工作区,如图图 7-8 所示。

9781430259831_Fig07-08.jpg

图 7-8 。导入现有项目

单击下一步。选择项目的根目录,如图图 7-9 所示。

9781430259831_Fig07-09.jpg

图 7-9 。选择一个目录来搜索现有的 Eclipse 项目

单击完成。图 7-10 展示了 GGTS 书店应用的目录结构。

9781430259831_Fig07-10.jpg

图 7-10 。书店应用的目录结构

运行应用

此时,您已经有了一个可以通过 web 浏览器运行和访问的功能性应用。它还没有做很多事情,但是现在运行它将使您能够在添加域和控制器类时获得即时反馈。

要运行 Grails 应用,请从项目根目录执行 run-app 目标,如下所示:

> grails run-app

执行 run-app 目标的输出如下所示:

Server running. Browse to http://localhost:8080/bookstore

在 URLlocalhost:8080/book store访问应用,显示欢迎屏幕,如图图 7-11 所示。

9781430259831_Fig07-11.jpg

图 7-11 。书店应用的欢迎屏幕

要在 GGTS 运行应用,请单击 Grails 命令历史,如图 7-12 中突出显示的。在命令窗口中键入 run-app ,并按回车键。或者你也可以在 IDE 中右击项目,选择 Run As 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

9781430259831_Fig07-12.jpg

图 7-12 。Grails 命令历史

创建控制器

控制器处理请求并创建或准备响应。控制器可以直接生成响应或委托给视图。要创建控制器类,请使用 Grails create-controller 目标。这将在 grails-app/controllers 目录中创建新的 Grails 控制器类,并在 test/unit 中为控制器类创建单元测试。如果 grails-app/views/ 目录不存在,它还会创建一个目录。

要创建 BookController 类,需要使用可选的类名执行 create-controller 目标,如下所示:

>grails create-controller book

如果不提供类名,系统会提示您提供一个。执行 create-controller 目标的输出如下所示:

| Created file grails-app/controllers/bookstore/BookController.groovy
| Created file grails-app/views/book
| Created file test/unit/bookstore/BookControllerTests.groovy

注意,当运行带有可选类名的 create-controller 时,您可以让类名保持小写,Grails 会自动将其大写,这样它就遵循了标准的 Groovy 类命名约定。

要使用 GGTS 创建控制器,单击书店项目层次中的控制器,然后使用新的image控制器。在 Grails 命令向导中键入 Book ,如图图 7-13 所示。

9781430259831_Fig07-13.jpg

图 7-13 。使用 GGTS 创建控制器

单击完成。GGTS 将生成控制器并进行测试,如以下输出所示:

Loading Grails 2.2.4
| Environment set to development.....
| Created file grails-app/controllers/bookstore/BookController.groovy
| Created file grails-app/views/book
| Compiling 1 source files.....
| Created file test/unit/bookstore/BookControllerTests.groovy

清单 7-3 展示了生成的控制器。

清单 7-3 。Grails 生成的 BookController

package bookstore

class BookController {

    def index() {
}
}

现在刷新浏览器,您可以在欢迎屏幕上看到该控制器,如图 7-14 所示。

9781430259831_Fig07-14.jpg

图 7-14 。书店应用的欢迎屏幕

修改 index(){}的代码,如清单 7-4 所示。

清单 7-4 。修改索引操作

    def index() {
render "book list"
}

现在你可以点击图 7-14 中的 BookController 链接,你将得到如图图 7-15 所示的简单文本响应。

9781430259831_Fig07-15.jpg

图 7-15 。简单的文字回复

测试控制器

清单 7-5 展示了 Grails 生成的 BookControllerTests 测试。

清单 7-5 。由 Grails 生成的 BookControllerTests

package bookstore

import grails.test.mixin.*
import org.junit.*

/**
 * See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
 */
@TestFor(BookController)
class BookControllerTests {

void testSomething() {
       fail "Implement me"
}
}

修改 testSomething(),如清单 7-6 所示。

清单 7-6 。添加断言

1.void testSomething() {
2.controller.index()
3.assert "book list" == response.text
4.}

现在通过 Run as image Grails 命令(test-app)运行测试,如图 7-16 中的所示。

9781430259831_Fig07-16.jpg

图 7-16 。Grails 中的 test-app 命令

在运行 test-app 命令时,Grails 运行测试,如以下输出所示:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to test.....
| Running 1 unit test... 1 of 1
| Completed 1 unit test, 0 failed in 3444ms
| Packaging Grails application.....
| Packaging Grails application.....
| Tests PASSED - view reports in E:\ModernJava\grails2-workspace\bookstore\target\test-reports

您可以从先前输出的最后一行所示的路径生成图 7-17 所示的报告。

9781430259831_Fig07-17.jpg

图 7-17 。测试报告:通过测试

注意通过这种方式,您可以在 Grails 生成的 ControllerTests 中增加控制器的单元测试。

测试通过了,因为在清单 7-6 中,第 3 行的断言是正确的。现在替换清单 7-6 中的第 3 行,如下所示:

assert "xyz" == response.text

这是一个不正确的断言,因为在图 7-15 中显示的简单文本响应是“书单”而不是“xyz”所以这个测试应该会失败。要查看 Grails 如何报告失败的测试,通过用清单 7-7 中的替换 testSomething()来使测试失败。

清单 7-7 。用不正确的断言替换测试

void testSomething() {
controller.index()
assert "xyz" == response.text
}

在运行 test-app 命令时,Grails 运行测试,如以下输出所示:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to test.....
| Compiling 1 source files.
| Running 1 unit test... 1 of 1
| Failure:  testSomething(bookstore.BookControllerTests)
|  Assertion failed:

assert "xyz" == response.text
             |  |        |
             |  |        book list
             |  org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse@14cf61d
             false

at bookstore.BookControllerTests.testSomething(BookControllerTests.groovy:16)
| Completed 1 unit test, 1 failed in 3210ms
| Packaging Grails application.....
| Packaging Grails application.....
| Tests FAILED  - view reports in E:\ModernJava\grails2-workspace\bookstore\target\test-reports

您可以从先前输出的最后一行所示的路径生成图 7-18 所示的报告。

9781430259831_Fig07-18.jpg

图 7-18 。测试报告:测试失败

创建域类

此时,我们创建的应用实际上并不做任何事情;它只是呈现一个简单的文本响应。我们将继续创建一个域类。要创建域类,请使用 Grails create-domain-class 目标。这将在 grails-app/domain 目录中创建一个新的 Grails 域类,并在 test/unit 中为该域类创建一个单元测试。

要创建 Book domain 类,您需要使用可选的类名执行 create-domain-class 目标,如下所示:

> grails createdomain-class book

如果不提供类名,系统会提示您提供一个。

请注意,当运行带有可选类名的 create-domain-class 目标时,您可以将类名保留为小写,Grails 会自动将其大写,以便它遵循标准的 Groovy 类命名约定。

要使用 GGTS 创建域类,在项目层次中点击“域”,然后使用新的image域类,如图 7-19 中的所示。

9781430259831_Fig07-19.jpg

图 7-19 。使用 GGTS 创建域类

当你点击图 7-19 中的域类时,会显示 Grails 命令向导窗口,如图图 7-20 所示。

9781430259831_Fig07-20.jpg

图 7-20 。使用 GGTS 创建域类

在名称字段中输入域类的名称,然后单击完成。Grails 创建 Book domain 类和 BookTests,如以下输出所示:

Loading Grails 2.2.4
| Environment set to development.....
| Created file grails-app/domain/bookstore/Book.groovy
| Compiling 1 source files.....
| Created file test/unit/bookstore/BookTests.groovy

清单 7-8 展示了 Grails 生成的图书类。

清单 7-8 。Grails 生成的图书领域类

package bookstore

class Book {

    static constraints = {
    }
}

清单 7-8 中的 Book 类为空。现在你可以完成这个域类,如清单 7-9 所示。

清单 7-9 。图书领域类

1.package bookstore
2.
3.class Book {
4.String bookTitle
5.Long price
6.Long isbn
7.
8.static constraints = {
9.bookTitle(blank:false)
10.price(blank:false)
11.}
12.String toString() {
13.bookTitle
14.}
15.}

清单 7-9 中的第 8 到 11 行的约束为 Grails 提供了定义验证规则的声明性机制。表 7-2 展示了 Grails 可用的约束。

表 7-2 。Grails 可用的约束

|

限制

|

描述

|
| — | — |
| 空白的 | 验证字符串值不为空 |
| 信用卡呢 | 验证字符串值是有效的信用卡号 |
| 电子邮件 | 验证字符串值是有效的电子邮件地址 |
| 在列表中 | 验证值是否在受约束值的范围或集合内 |
| 比赛 | 验证字符串值是否与给定的正则表达式匹配 |
| 最大 | 验证值不超过给定的最大值 |
| maxSize(最大值) | 验证值的大小没有超过给定的最大值 |
| 部 | 验证值不低于给定的最小值 |
| 最小尺寸 | 验证值的大小不低于给定的最小值 |
| 不等 | 验证属性不等于指定的值 |
| 可空的 | 允许将属性设置为 null 默认为假 |
| 范围 | 使用 Groovy 范围来确保属性值出现在指定的范围内 |
| 规模 | 设置浮点数所需的小数位数(即小数点右边的位数) |
| 大小 | 使用 Groovy 范围来限制集合或数字的大小或字符串的长度 |
| 独一无二的 | 将属性约束为数据库级别的唯一属性 |
| 全球资源定位器(Uniform Resource Locator) | 验证字符串值是有效的 URL |
| 验证器 | 向字段添加自定义验证 |

脚手架

Scaffolding 允许您为给定的域类自动生成整个应用,包括 CRUD 操作的视图和控制器动作。脚手架可以是静态的也可以是动态的;这两种类型生成相同的代码。主要区别在于,在静态搭建中,生成的代码在编译之前就可供用户使用,因此如果需要的话可以很容易地修改。然而,在动态搭建中,代码是在运行时在内存中生成的,对用户是不可见的。在接下来的部分中,您将学习动态和静态搭建。

动态脚手架

如前所述,动态脚手架在运行时为 CRUD 应用生成控制器动作和视图。要动态搭建一个域类,您需要一个控制器。您在清单 7-3 的中创建了一个控制器(BookController)。要使用动态脚手架,将索引动作更改为脚手架属性,并为其分配域类,如清单 7-10 中的所示。这导致为指定的域类生成列表页面、创建页面、编辑页面、显示页面视图以及删除功能。

清单 7-10 。启用动态脚手架的记账员

package bookstore

class BookController {

    static scaffold = Book
}

在将 BookController 更改为类似于清单 7-10 中的之后,执行 run-app 目标。

执行 run-app 目标的输出如下所示:

| Loading Grails 2.2.4
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Running Grails application
| Server running. Browse tohttp://localhost:8080/bookstore

单击欢迎页面上的 BookController 链接。

点击 BookController 链接,进入图 7-21 所示的图书列表视图。

9781430259831_Fig07-21.jpg

图 7-21 。书单查看

您可以通过点按“新书”来创建或添加新书。图 7-22 显示了创建新书的屏幕。图 7-22 也显示了验证 的运行,你不必为其编写任何代码。这个验证的代码包含在域类手册中,如清单 7-9 第 8 到 11 行的所示。

9781430259831_Fig07-22.jpg

图 7-22 。创建视图并进行验证

图 7-23 显示了通过完成所有验证来创建一本新书。

9781430259831_Fig07-23.jpg

图 7-23 。创建视图

图 7-24 显示了新创建的图书。

9781430259831_Fig07-24.jpg

图 7-24 。显示视图

您可以编辑、删除或添加新创建的图书。您可以通过点按“更新”来编辑创建的图书。图 7-25 说明了编辑视图。

9781430259831_Fig07-25.jpg

图 7-25 。编辑视图

图 7-26 说明了更新后的图书。

9781430259831_Fig07-26.jpg

图 7-26 。显示带有更新消息的视图

您可以通过点按“新书”来添加新书。图 7-27 显示了以这种方式添加的图书列表。

9781430259831_Fig07-27.jpg

图 7-27 。带有已添加图书列表的列表视图

静态脚手架

Static scaffolding 提供了一个优秀的学习工具,帮助您熟悉 Grails 框架以及一切是如何组合在一起的。现在,是时候将静态脚手架作为一种学习工具来使用了。动态和静态搭建的域类没有区别。为了快速参考,Book 类如清单 7-11 所示。

清单 7-11 。图书领域类

package bookstore

class Book {
String bookTitle
Long price
Long isbn

static constraints = {
bookTitle(blank:false)
price(blank:false)
}
String toString() {
bookTitle
}
}

静态搭建与动态搭建的不同之处在于视图和控制器的生成方式。在这两种情况下,域类保持不变。然而,在动态搭建中,您需要控制器向 Grails 表明您需要动态搭建来为您生成应用。如果你想让 Grails 通过静态搭建生成应用,你必须使用清单 7-12 中的命令。

清单 7-12 。通过静态搭建生成应用的命令

>grails generate-all bookstore.Book

从命令行或 GGTS 运行该命令后,Grails 会生成如下所示的应用:

Loading Grails 2.2.4
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Packaging Grails application.....
| Generating views for domain class bookstore.Book
| Generating controller for domain class bookstore.Book
| Finished generation for domain class bookstore.Book

如果我们现在运行应用,我们将拥有一个完整的 CRUD 应用。这个 generate-all 命令为我们的域类 Book 生成一个控制器(BookController)和四个视图,并为我们的控制器 BookControllerTest 生成一个单元测试。这些文件为我们提供了一个完整的 CRUD 应用,充当了我们可以添加定制代码的存根。让我们仔细看看我们生成的代码。我们从图 7-28 中的所示的图书控制器开始。

9781430259831_Fig07-28.jpg

图 7-28 。BookController(帐簿控制器)

在图 7-28 所示的 BookController 代码中,你会注意到的第一件事是 Grails 控制器是一个普通的 Groovy 类,它不扩展任何类,也不实现任何接口。接下来您会注意到 BookController 有八个动作。

  • 创建行动
  • 删除操作
  • 编辑操作
  • 索引操作
  • 列表操作
  • 保存操作
  • 显示动作
  • 更新操作

这些动作是控制器的闭包属性。控制器中的所有工作都在动作中完成。控制器中声明的每个闭包都是一个动作,可以通过 URL 访问,默认情况下,URL 映射到控制器动作。URL 的第一部分代表控制器名称,第二部分代表操作名称。在接下来的几节中,您将更深入地了解这些操作,但在此之前,有必要了解如何退出控制器操作。正确退出控制器动作有三个选项。

  • 调用 render()方法
  • 调用 redirect()方法
  • 返回模型或空值

在接下来的章节中,在探究图 7-28 中的所示的 BookController 中的每个动作之前,您将查看这三个选项。

调用 render()方法

退出控制器动作的第一个选项是调用 render()方法来呈现视图或文本响应。为了理解 render()方法是如何工作的,清单 7-13 展示了一个简单的 Grails 控制器,当它被调用时,会向您问候。

清单 7-13 。呈现文本响应

package chapter5
class HelloController {
def index() {
render 'hello'
}
defshow(){}
def someOtherAction(){}
}

如清单 7-13 所示,控制器在被/hello/index 请求调用时,将执行控制器中定义的 index()动作,index()动作将使用 render()方法呈现文本响应。在 HelloController 中调用 index()动作的完整 URL 是localhost:8080/chapter 5/hello/index。如清单所示,控制器中可以有任意数量的动作。

如果您在 render()方法中指定视图名称,如清单 7-14 中的所示,Grails 会假设您指的是位于 Grails-app/views/hello/hello . gsp 位置的视图,并呈现一个名为 hello 的视图。

清单 7-14 。渲染视图

class HelloController {
...
def show() {
render view: "hello"
}
...
}

调用 redirect()方法

退出控制器操作的第二个选项是调用 redirect()方法来发出一个到另一个 URL 的 HTTP 重定向。Grails 为所有控制器提供了一个接受 Map 作为参数的 redirect()方法。该映射应该包含 Grails 执行重定向所需的所有信息,包括重定向到的动作的名称。

此外,映射可以包含要重定向到的控制器的名称。清单 7-15 显示了在同一个控制器中从第一个动作到第二个动作的标准重定向。

清单 7-15 。重定向到同一控制器中的动作

class HelloController {
def first() {
redirect action: "second"
}
def second() {
...
}
}

如果重定向是针对另一个控制器中的操作,则必须指定另一个控制器的名称。清单 7-16 展示了如何重定向到另一个控制器中的动作。

清单 7-16 。重定向到另一个控制器中的操作

class HelloController {
def first() {
redirect action: "second", controller: "other"
}
}

在清单 7-16 中,HelloController 中的第一个()动作重定向到另一个控制器中的第二个()动作。

返回模型

退出控制器动作的第三个选项是返回一个模型,它是一个包含数据的地图,如清单 7-17 中的所示。

清单 7-17 。归还模型

class HelloController {
def show() {
    [user: User.get(params.id)]
}
}

Grails 将尝试呈现与动作同名的视图。它将在以控制器的基本名称命名的目录中查找这个视图。在清单 7-17 中,从 HelloController 的 show()操作返回将导致 Grails 呈现 view /views/hello/show.gsp。

现在你知道如何调用控制器的动作,以及如何退出。有了这些知识,看一看图 7-28 ,让我们开始逐一研究每个动作。首先让我们把 allowedMethods 属性去掉。

static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

allowedMethods 属性提供了一个简单的声明性语法来指定控制器操作允许哪些 HTTP 方法。默认情况下,所有控制器操作都允许所有请求方法。allowedMethods 属性是可选的,只有当控制器的操作需要限制为某些请求方法时,才需要定义该属性。BookController 中的这个属性指定只有 save、update 和 delete 可以是 POST 方法。

索引操作

index()动作是导航到 BookController 时调用的默认动作。默认情况下,这个动作只是使用之前解释的 redirect()方法重定向到 list()动作,如清单 7-18 所示。

清单 7-18 。图书管理员的索引操作

def index() {
        redirect(action: "list", params: params)
    }

redirect()方法将 HTTP 重定向到由这些参数构造的 URL。如果未指定操作,将使用 index()操作。params 保存请求参数,如果有的话。

列表操作

清单 7-19 展示了 BookController 的 list()动作。

清单 7-19 。BookController 的列表操作

def list(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        [bookInstanceList: Book.list(params), bookInstanceTotal: Book.count()]
    }

列表闭包的第一行使用 params 属性,这是一个包含传入请求的所有参数的映射。

最后一行返回一个包含两个元素的映射:bookInstanceList 和 bookInstanceTotal。bookInstanceList 是通过对 Book.list()的调用加载的。向 list()传递参数映射,它将从该映射中提取任何可以使用的参数。bookInstanceTotal 是用 Book.count()加载的。bookInstanceTotal 的使用将在后面的“列表视图”部分提到。list()操作使用从该操作返回的地图中的数据呈现列表视图。

创建行动

清单 7-20 展示了 BookController 的 create()动作。

清单 7-20 。BookController 的创建操作

def create() {
        [bookInstance: new Book(params)]
    }

create()动作创建一个新的 Book 实例,然后将参数分配给 Book instance 的属性,因为稍后会用到它,如下面在清单 7-21 中讨论的“保存动作”部分所述。然后它用 bookInstance 的键在 Map 中返回该实例。最后,它呈现 create 视图。

保存操作

清单 7-21 展示了 BookController 的 save()动作。

清单 7-21 。BookController 的保存操作

def save() {
        def bookInstance = new Book(params)
        if (!bookInstance.save(flush: true)) {
            render(view: "create", model: [bookInstance: bookInstance])
            return
        }

        flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), bookInstance.id])
        redirect(action: "show", id: bookInstance.id)
    }

如果有错误,用户将被重定向到 create()操作。对于这个重定向到 create()动作的操作,params 被分配给 bookInstance 的属性,正如前面在清单 7-20 中的 create()动作所提到的。如果没有错误,将使用新创建的实例呈现 show 视图。

显示动作

清单 7-22 展示了 BookController 的 show()动作。

清单 7-22 。BookController 的显示操作

def show(Long id) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }

        [bookInstance: bookInstance]
    }

show()操作需要一个 id 参数。show()操作的第一行调用 Book.get()方法来检索 id 参数所引用的图书。如果不存在带有传入 id 的 Book 实例,则在 flash 范围内会存储一条错误消息,用户会被重定向到列表视图。

如果找到一个带有传入 id 的 Book 实例,它将在一个 Map 中返回,键为 Book instance,show()操作将呈现 show 视图。

编辑操作

清单 7-23 展示了 BookController 的 edit()动作。

清单 7-23 。BookController 的编辑操作

def edit(Long id) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }

        [bookInstance: bookInstance]
    }

edit()动作加载将在编辑过程中使用的必要数据,并将其传递给 edit 视图。edit()操作与 show()操作非常相似。edit()动作的名称 edit 用于呈现编辑视图。

更新操作

当提交来自编辑视图的更改时,将调用 update()操作。清单 7-24 展示了 BookController 的 update()动作。

清单 7-24 。BookController 的更新操作

def update(Long id, Long version) {
        def bookInstance = Book.get(id)
        if (!bookInstance) {
            flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
            redirect(action: "list")
            return
        }

        if (version != null) {
            if (bookInstance.version > version) {
                bookInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
                          [message(code: 'book.label', default: 'Book')] as Object[],
                          "Another user has updated this Book while you were editing")
                render(view: "edit", model: [bookInstance: bookInstance])
                return
            }
        }

        bookInstance.properties = params

        if (!bookInstance.save(flush: true)) {
            render(view: "edit", model: [bookInstance: bookInstance])
            return
        }

        flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), bookInstance.id])
        redirect(action: "show", id: bookInstance.id)
    }

update()操作试图用 id 参数检索一个 Book 实例。id 是从编辑视图中提供的。如果找到实例,将执行开放式并发检查。如果没有错误,edit 视图中的所有值都被分配给 Book 实例的适当属性,包括任何必要的数据转换。

bookInstance.properties = params

如果这两个步骤都成功,flash 中会存储一条“成功”消息,用户会被定向到 show 视图。如果任一步骤失败,flash 中会存储一条“失败”消息,用户将返回到编辑视图。

删除操作

默认情况下,delete()操作在编辑和显示视图中可用。清单 7-25 展示了 BookController 的 delete()动作。

清单 7-25 。BookController 的删除操作

def delete(Long id) {
     def bookInstance = Book.get(id)
     if (!bookInstance) {
         flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "list")
         return
     }

     try {
         bookInstance.delete(flush: true)
         flash.message = message(code: 'default.deleted.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "list")
     }
     catch (DataIntegrityViolationException e) {
         flash.message = message(code: 'default.not.deleted.message', args: [message(code: 'book.label', default: 'Book'), id])
         redirect(action: "show", id: id)
     }
}

delete()操作试图检索一个 Book 实例,如果找不到,就重定向到列表视图。如果找到一个实例,则进入 try/catch 块,尝试删除该实例。如果删除成功,消息将存储在闪存中,并重定向到列表视图。如果出现异常,不同的消息将存储在 flash 中,并重定向到 show 视图。

现在,您已经看到了 BookController 中为 Book 类生成的所有操作,让我们检查一下由静态搭建为 Book 类生成的视图。

Grails 视图

Grails 使用 Groovy 服务器页面(GSP) 作为其视图层。Grails 还使用页面装饰框架 SiteMesh 来帮助页面布局。SiteMesh 合并每个。gsp 文件合并到一个名为 main.gsp 的文件中,以使所有页面具有一致的外观。您将从 main.gsp 开始生成视图,它可以在\views\layouts 中找到,然后是为 Book 类生成的四个视图:list.gsp、show.gsp、create.gsp 和 edit . gsp。清单 7-26 展示了 main.gsp

清单 7-26 。main .普惠制

1.<!DOCTYPE html>
2.<!--[if lt IE 7 ]><html lang="en" class="no-js ie6"><![endif]-->
3.<!--[if IE 7 ]><html lang="en" class="no-js ie7"><![endif]-->
4.<!--[if IE 8 ]><html lang="en" class="no-js ie8"><![endif]-->
5.<!--[if IE 9 ]><html lang="en" class="no-js ie9"><![endif]-->
6.<!--[if (gt IE 9)|!(IE)]><!--><html lang="en" class="no-js"><!--<![endif]-->
7.<head>
8.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9.<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10.<title><g:layoutTitle default="Grails"/></title>
11.<meta name="viewport" content="width=device-width, initial-scale=1.0">
12.<link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
13.<link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
14.<link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
15.<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
16.<link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
17.<g:layoutHead/>
18.<r:layoutResources />
19.</head>
20.<body>
21.<div id="grailsLogo" role="banner"><a href="http://grails.org"><imgsrc="${resource(dir: 'images', file: 'grails_logo.png')}" alt="Grails"/></a></div>
22.<g:layoutBody/>
23.<div class="footer" role="contentinfo"></div>
24.<div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
25.<g:javascript library="application"/>
26.<r:layoutResources />
27.</body>
28.</html>
  • 第 1 行到第 6 行:main . gsp 页面以<开头!doctype html >。这是一个 HTML5 文档类型。Grails 支持现成的 HTML5。
  • 第 10 行 : < g:layoutTitle >在布局中用于呈现装饰页面的 Title 标签的内容。< g:layoutTitle >标签替换了正在被合并的视图中的<标题>,并将它链接到所有视图都将使用的样式表和 favicon 中。
  • 第 17 行:<g:layout head>标签合并到目标视图的< head >部分的内容中。< g:layoutHead >在布局中用于呈现装饰页面的 Head 标签的内容。
  • 第 22 行 : < g:layoutBody >在 layouts 中用来输出装饰页面的 Body 标签的内容。< g:layoutBody >标签合并到目标视图的<主体>内容中。
  • 第 25 行 : < g:javascript >包括 javascript 库和脚本,并提供了内联 JavaScript 的简写。指定一个库告诉 Ajax 标签使用哪个 JavaScript 提供者。

列表视图

列表视图如清单 7-27 所示。

清单 7-27 。list.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.list.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#list-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
15.</ul>
16.</div>
17.<div id="list-book" class="content scaffold-list" role="main">
18.<h1><g:message code="default.list.label" args="[entityName]" /></h1>
19.<g:if test="${flash.message}">
20.<div class="message" role="status">${flash.message}</div>
21.</g:if>
22.<table>
23.<thead>
24.<tr>
25.
26.<g:sortableColumn property="bookTitle" title="${message(code: 'book.bookTitle.label', default: 'Book Title')}" />
27.
28.<g:sortableColumn property="price" title="${message(code: 'book.price.label', default: 'Price')}" />
29.
30.<g:sortableColumn property="isbn" title="${message(code: 'book.isbn.label', default: 'Isbn')}" />
31.
32.</tr>
33.</thead>
34.<tbody>
35.<g:each in="${bookInstanceList}" status="i" var="bookInstance">
36.<tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
37.
38.<td><g:link action="show" id="${bookInstance.id}">${fieldValue(bean: bookInstance, field: "bookTitle")}</g:link></td>
39.
40.<td>${fieldValue(bean: bookInstance, field: "price")}</td>
41.
42.<td>${fieldValue(bean: bookInstance, field: "isbn")}</td>
43.
44.</tr>
45.</g:each>
46.</tbody>
47.</table>
48.<div class="pagination">
49.<g:paginate total="${bookInstanceTotal}" />
50.</div>
51.</div>
52.</body>
53.</html>
  • 第 14 行:<g:link>标签创建了一个到 BookController 的 create 动作的链接。
  • 第 19 行:<g:if>标签检查存储在动作中的 flash.message 是否存在,如果存在,就显示出来。
  • 第 26 到 31 行:<g:sortable column>标签用于在我们的列表视图中提供排序。
  • 第 35 到 45 行:<g:each>标签遍历 bookInstanceList。列表中的每一项都被分配给 bookInstance 变量。< g:each >标签的主体用 bookInstance 的属性填充表格行。在第<行 tr class=“${(i % 2) == 0?even’ : ‘odd’}” >,用 Groovy 表达式确定< tr >的样式类,用 fieldValue()方法渲染每个 Book 属性的值。
  • 第 49 行:<g:paginate>标签显示列表视图中是否有足够的元素的分页控件。如前所述,bookInstanceTotal 是从清单 7-19 中使用的。

创建视图

创建视图如清单 7-28 中的所示。

清单 7-28 。create.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.create.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#create-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.</ul>
16.</div>
17.<div id="create-book" class="content scaffold-create" role="main">
18.<h1><g:message code="default.create.label" args="[entityName]" /></h1>
19.<g:if test="${flash.message}">
20.<div class="message" role="status">${flash.message}</div>
21.</g:if>
22.<g:hasErrors bean="${bookInstance}">
23.<ul class="errors" role="alert">
24.<g:eachError bean="${bookInstance}" var="error">
25.<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
26.</g:eachError>
27.</ul>
28.</g:hasErrors>
29.<g:form action="save" >
30.<fieldset class="form">
31.<g:render template="form"/>
32.</fieldset>
33.<fieldset class="buttons">
34.<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
35.</fieldset>
36.</g:form>
37.</div>
38.</body>
39.</html>
  • 第 22 行到第 28 行:<g:has errors>标签检查分配给其 bean 属性的 Book 实例,如果发现错误,则呈现其主体。
  • 第 29 行到第 36 行:<g:form>标签建立了一个 HTML 表单。这个标签有一个动作,这个动作将导致表单提交到的 URL。
  • Line 31 : < g:render >对模型应用内置或用户定义的 Groovy 模板,这样模板就可以被共享和重用。在这种情况下,模板称为 form,位于 views 目录中,名为 _form.gsp。gsp 文件是一个模板。

显示视图

展示视图如清单 7-29 中的所示。

清单 7-29 。show.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.show.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#show-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
16.</ul>
17.</div>
18.<div id="show-book" class="content scaffold-show" role="main">
19.<h1><g:message code="default.show.label" args="[entityName]" /></h1>
20.<g:if test="${flash.message}">
21.<div class="message" role="status">${flash.message}</div>
22.</g:if>
23.<ol class="property-list book">
24.
25.<g:if test="${bookInstance?.bookTitle}">
26.<li class="fieldcontain">
27.<span id="bookTitle-label" class="property-label"><g:message code="book.bookTitle.label" default="Book Title" /></span>
28.
29.<span class="property-value" aria-labelledby="bookTitle-label"><g:fieldValue bean="${bookInstance}" field="bookTitle"/></span>
30.
31.</li>
32.</g:if>
33.
34.<g:if test="${bookInstance?.price}">
35.<li class="fieldcontain">
36.<span id="price-label" class="property-label"><g:message code="book.price.label" default="Price" /></span>
37.
38.<span class="property-value" aria-labelledby="price-label"><g:fieldValue bean="${bookInstance}" field="price"/></span>
39.
40.</li>
41.</g:if>
42.
43.<g:if test="${bookInstance?.isbn}">
44.<li class="fieldcontain">
45.<span id="isbn-label" class="property-label"><g:message code="book.isbn.label" default="Isbn" /></span>
46.
47.<span class="property-value" aria-labelledby="isbn-label"><g:fieldValue bean="${bookInstance}" field="isbn"/></span>
48.
49.</li>
50.</g:if>
51.
52.</ol>
53.<g:form>
54.<fieldset class="buttons">
55.<g:hiddenField name="id" value="${bookInstance?.id}" />
56.<g:link class="edit" action="edit" id="${bookInstance?.id}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
57.<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
58.</fieldset>
59.</g:form>
60.</div>
61.</body>
62.</html>
  • 第 55 行到第 56 行:这个?bookInstance 引用后是一个安全的导航运算符。计算该表达式时,如果 bookInstance 为 null,则整个表达式的计算结果为 null,并且不会引发异常。
  • Line 57:<g:action submit>标签生成一个提交按钮,该按钮映射到一个特定的动作,这使得在一个表单中可以有多个提交按钮。可以使用与 HTML 中相同的参数名添加 JavaScript 事件处理程序。

编辑视图

编辑视图如清单 7-30 中的所示。

清单 7-30 。edit.gsp

1.<%@ page import="bookstore.Book" %>
2.<!DOCTYPE html>
3.<html>
4.<head>
5.<meta name="layout" content="main">
6.<g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
7.<title><g:message code="default.edit.label" args="[entityName]" /></title>
8.</head>
9.<body>
10.<a href="#edit-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
11.<div class="nav" role="navigation">
12.<ul>
13.<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
14.<li><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
15.<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
16.</ul>
17.</div>
18.<div id="edit-book" class="content scaffold-edit" role="main">
19.<h1><g:message code="default.edit.label" args="[entityName]" /></h1>
20.<g:if test="${flash.message}">
21.<div class="message" role="status">${flash.message}</div>
22.</g:if>
23.<g:hasErrors bean="${bookInstance}">
24.<ul class="errors" role="alert">
25.<g:eachError bean="${bookInstance}" var="error">
26.<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
27.</g:eachError>
28.</ul>
29.</g:hasErrors>
30.<g:form method="post" >
31.<g:hiddenField name="id" value="${bookInstance?.id}" />
32.<g:hiddenField name="version" value="${bookInstance?.version}" />
33.<fieldset class="form">
34.<g:render template="form"/>
35.</fieldset>
36.<fieldset class="buttons">
37.<g:actionSubmit class="save" action="update" value="${message(code: 'default.button.update.label', default: 'Update')}" />
38.<g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" formnovalidate="" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
39.</fieldset>
40.</g:form>
41.</div>
42.</body>
43.</html>
  • 第 31 到 32 行:在清单 7-24 中,提到了 update()动作中的 id 是从这个编辑视图中提供的。id 来自于< g:隐藏字段>标签,如代码所示。

至此,我们已经完成了 Book 类的静态搭建所生成的所有视图。

H2 控制台

如前所述,Grails 在开发模式下启用了 H2 数据库控制台(在 URI /dbconsole 处),这样就可以从浏览器轻松地查询内存中的数据库。要查看 dbconsole 的运行情况,请浏览到localhost:8080/book store/dbconsole。默认的登录参数应该与 grails-app/conf/data source . groovy 中的默认参数相匹配,如图图 7-29 所示。

9781430259831_Fig07-29.jpg

图 7-29 。H2 的登录界面

你可以从 Datasource.groovy 中获取用户名和密码图 7-30 说明了 H2 控制台 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-30 。H2 控制台

现在,在我们的书店应用中添加一本书,然后在数据库控制台中输入 SELECT * from BOOK 。我们在应用中创建的用户将会出现,如图图 7-31 所示。

9781430259831_Fig07-31.jpg

图 7-31 。查询 H2 表

创建域关系 s

在应用域中,类与类之间有关系。域关系定义了域类如何交互。Grails 支持领域类之间的几种关系。一对多关系是指一个类 Author 拥有 Book 类的多个实例。使用 Grails,你可以用 hasMany 设置定义这样的关系,如清单 7-31 所示。

清单 7-31 。作者和书之间的一对多关系

class Author {
    static hasMany = [books: Book]

String name }

class Book {
    String title
}

在清单 7-31 中有一个单向的一对多。默认情况下,Grails 会用一个连接表来映射这种关系。Grails 会根据 hasMany 设置自动将 java.util.Set 类型的属性注入到域类中。Grails 通过在关系的两端定义 hasMany 并在关系的拥有方定义 belongsTo 来支持多对多关系。

您将创建一个在作者和图书之间具有多对多域关系的应用。您可以在源代码档案的第七章中的 books 项目中找到这个应用的源代码,您可以从 Apress 网站下载。清单 7-32 说明了图书类。

清单 7-32 。创建图书和作者之间的域关系

1.package books
2.
3.class Book {
4.    static belongsTo = Author
5.    static hasMany = [authors:Author]
6.    String title
7.Long isbn
8.String publisher
9.static constraints = {
10.title(blank:false)
11.}
12.
13.String toString() {
14.title
15.}
16.}
  • 第 4 行:这一行通知 Grails Book 类属于它自己的作者。
  • 第 5 行:这一行通知 Grails Book 类有许多 Author 实例。

清单 7-33 说明了作者类。

清单 7-33 。作者类。

1.package books
2.
3.class Author {
4.
5.    static hasMany = [books:Book]
6.    String firstName
7.String lastName
8.static constraints = {
9.firstName(blank:false)
10.lastName(blank:false)
11.}
12.String toString() {
13."$lastName, $firstName"
14.}
15.}
  • 第 5 行:这一行告诉 Grails 一个作者有许多 Book 的实例。

Grails 使用数据库级别的连接表映射多对多关系。关系的拥有方(本应用中的作者)负责保持关系,因为 Grails 使用 Hibernate 作为 ORM 框架,而在 Hibernate 中,多对多关系中只有一方负责管理关系。

创建了域类之间的关系后,您需要做的就是创建 BookController 和 AuthorController,并设置它们的 scaffold 属性。在您创建的 BookController 中,用设置为 Book 的 scaffold 属性替换 index()动作,如清单 7-34 所示。

清单 7-34 。图书管理员

class BookController {

    static scaffold = Book
}

在 AuthorController 中,将 scaffold 属性设置为 Author,如清单 7-35 所示。

清单 7-35 。AuthorController

class AuthorController {

    static scaffold = Author
}

现在您可以运行应用了。脚手架将为您生成应用,并将您指向 URL。图 7-32 显示了应用的欢迎界面。

9781430259831_Fig07-32.jpg

图 7-32 。图书应用的欢迎界面

点击 AuthorController,显示作者列表界面,如图图 7-33 所示。

9781430259831_Fig07-33.jpg

图 7-33 。作者列表屏幕

在图 7-33 中,您可以通过点击新作者链接来创建新作者。图 7-34 显示了这样创建的作者列表。

9781430259831_Fig07-34.jpg

图 7-34 。作者名单

现在你可以通过点击欢迎界面上的图书控制器链接来查看图书列表,如图图 7-35 所示。

9781430259831_Fig07-35.jpg

图 7-35 。图书列表屏幕

您可以通过单击新书链接来添加新书。单击“新建图书”时,将显示“创建图书”屏幕。如果你试图创建一个标题和 Isbn 字段为空的书,你会得到如图图 7-36 所示的确认信息。在清单 7-32 中,您没有为 ISBN 编写任何约束,但是 ISBN 字段仍然有验证,因为 Grails 默认为一些字段提供了约束,ISBN 就是其中之一(如本章表 7-2 中所列)。

9781430259831_Fig07-36.jpg

图 7-36 。验证错误

现在,您可以通过为这些字段提供值来创建图书。图 7-37 显示了创建了两本图书的图书列表屏幕。

9781430259831_Fig07-37.jpg

图 7-37 。图书列表屏幕

现在你可以进入作者列表界面,点击编辑按钮,如图图 7-38 所示。

9781430259831_Fig07-38.jpg

图 7-38 。编辑作者

点击编辑按钮,会看到有两本书可用,如图图 7-39 所示。您可以选择这两本书,然后单击“更新”按钮。

9781430259831_Fig07-39.jpg

图 7-39 。编辑作者屏幕

这样,您可以为您创建的所有作者添加图书。现在,当你进入图书列表界面并点击其中一个书名链接时,显示图书界面会显示该书所有作者的名字,如图图 7-40 所示。

9781430259831_Fig07-40.jpg

图 7-40 。显示图书屏幕

本章到此结束,它概述了 Grails 2 的概况。对于更详细的报道,我推荐 Vishal Layka、Christopher M. Judd、Joseph Faisal Nusairat 和 Jim shiller(2012 年出版)的 Beginning Groovy、Grails和 Griffon ,以及 Jeff 斯科特·布朗和 Graeme Rocher(2012 年出版)的Grails 2权威指南。

摘要

在本章中,您了解了 Grails 是一个快速的 web 开发框架,它结合了 Java 开源、约定、Groovy 动态语言和 Java 平台的优势。您看到了使用 Grails 脚手架来完成大部分工作,开发一个全功能的应用是多么容易。您使用静态搭建作为学习工具,为一个领域类(书籍)生成控制器和视图。然后浏览控制器,看到控制器中的所有工作都在动作中完成了。然后,您学习了负责相应视图的每个动作的代码。最后,您浏览了视图,看到了视图如何利用 Grails 标记库来促进关注点的清晰分离。

1【www.h2database.com/html/main.html】??

2【http://wiki.sitemesh.org/display/sitemesh/Home】??

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值