Grails4.0.11入门

Grails4.0.11

介绍

Grails 是一个全栈框架,它建立在已存在的 Java 技术(如 Spring 和 Hibernate)之上。
Grails核心技术及其相关插件解决了很多 Web 开发难题,降低了在 Java 平台上构建 Web 应用程序的复杂性。
Grails提供了开箱即用的功能:

  • GORM - 一个易于使用的对象映射库,支持 SQL、MongoDB、Neo4j 等。
  • 查看用于呈现 HTML 和 JSON 的技术
  • 基于 Spring Boot 构建的控制器层
  • 一个包含数百个插件的插件系统
  • 使用 AngularJS、React 等创建应用程序的灵活配置文件
  • 基于 Gradle 的交互式命令行环境和构建系统
  • 一个嵌入式 Tomcat 容器,配置为即时重新加载

相关依赖

GroovyGORMHibernateSpringFrameworkSpringBootGradleSpock
2.5.1475.45.1.202.1.185.6.41.3

入门

Java环境搭建

在安装 Grails 4.0.11 之前,至少需要安装JDK1.8版本才行,并且设置JAVA_HOME 的环境变量。

JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home"
export PATH=".:$PATH:$JAVA_HOME/bin"
export JAVA_HOME

Grails环境搭建

  1. 下载 Grails4.0.11 的二进制发行版并将生成的 zip 文件解压缩到/opt目录。
  2. 设置GRAILS_HOME环境变量,编辑~/.bash_profile
    export GRAILS_HOME=/opt/grails-4.0.11
    
  3. 然后将 bin 目录添加到您的 PATH 变量中:
    export PATH="$PATH:$GRAILS_HOME/bin"
    
  4. 打开新的命令行窗口,输入命令 grails -version查看输出内容
    ➜ /Users/xiaosan > grails -version
    | Grails Version: 4.0.11
    | JVM Version: 1.8.0_171
    

如何创建grails应用

进入要创建项目的目录
➜ /Users/xiaosan >cd Documents/work
使用create-app命令创建应用

➜ /Users/xiaosan/Documents/work >grails create-app helloworldgrails4
| Application created at /Users/xiaosan/Documents/work/helloworldgrails4

该命令会创建一个helloworldgrails4的项目目录,进入项目目录
➜ /Users/xiaosan/Documents/work >cd helloworldgrails4
输入grails命令,启动 Grails 交互式控制台
➜ /Users/xiaosan/Documents/work >grails
等待几秒会看到如下提示:

| Resolving Dependencies. Please wait...
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
grails>

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

grails> create-controller hello
| Created grails-app/controllers/helloworldgrails4/HelloController.groovy
| Created src/test/groovy/helloworldgrails4/HelloControllerSpec.groovy

创建hello时没有指定包名,默认以项目名称helloworldgrails4作为包名。
在输入命令时可以使用tab键,会自动补全命令或者提示命令。
查看HelloController.groovy文件:

package helloworldgrails4

class HelloController {
    def index() { }
}

修改这个控制器,添加一个动作来生成“Hello World”页。

package helloworldgrails4

class HelloController {
    def index() {
        render "Hello World!"
    }
}

动作只是一种方法。在这种特殊情况下,它调用 Grails 提供的特殊方法来呈现页面。
在grails交互控制台中启动项目:

grails> run-app
| Running application...
The Class-Path manifest attribute in /Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/jaxb-runtime-2.3.1.jar referenced one or more files that do not exist: file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/jaxb-api-2.3.1.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/txw2-2.3.1.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/istack-commons-runtime-3.0.7.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/stax-ex-1.8.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/FastInfoset-1.2.15.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.1/dd6dda9da676a54c5b36ca2806ff95ee017d8738/javax.activation-api-1.2.0.jar
The Class-Path manifest attribute in /Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/jaxb-impl-2.3.1.jar referenced one or more files that do not exist: file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/jaxb-runtime-2.3.1.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/txw2-2.3.1.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/istack-commons-runtime-3.0.7.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/stax-ex-1.8.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/FastInfoset-1.2.15.jar,file:/Users/xiaosan/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-impl/2.3.1/a1a12b85ba1435b4189e065f7dafcc3fb9410d38/javax.activation-api-1.2.0.jar
Grails application running at http://localhost:8080 in environment: development
<=======<==========---> 83% EXECUTING [2m 11s]
> :bootR> :bootRun
grails>

默认使用内嵌Tomcat8服务器,默认端口8080,在浏览器中输入http://localhost:8080/ 访问grails启动界面
这是Grails提供的介绍页面,页面位置在 grails-app/view/index.gsp。显示有多少个控制器,并提供指向它们的链接。
单击“HelloController”链接就可以看到包含文本“Hello World!”的自定义页面。
在这里插入图片描述

现在第一个 Grails 应用程序已经成功运行起来了。

为应用程序设置上下文路径,在 grails-app/conf/application.yml 中添加一个配置属性:

server:
    servlet:
        context-path: /helloworld

添加后的代码:

---
server:
    servlet:
        context-path: /helloworld
grails:
    profile: web
    codegen:
    后面代码省略···
···    

保存之后,系统会自动将项目运行在helloworld上,不需要重启

Grails application running at http://localhost:8080/helloworld in environment: development
<==========---> 83% EXECUTING [28m 43s]
> :bootRun

这个时候就不能使用http://localhost:8080访问项目了
要使用http://localhost:8080/helloworld访问项目
在这里插入图片描述
访问"HelloWorld"页面
在这里插入图片描述
注意:

  • 控制器(Controller)可以包含许多动作(Action),每个动作对应一个不同的页面(此时忽略 AJAX)。
  • 每个页面都可以通过一个唯一的 URL 访问。
  • 该 URL 由控制器名称和操作名称组成:
    /上下文路径/控制器/动作
    /appname/controller/action
    这意味着您可以通过 /helloworld/hello/index 访问 Hello World 页面,其中
    hello 是控制器名称(从类名中删除 ‘Controller’ 后缀和小写首字母),
    index 是控制器动作名称。
    因为“index”是默认操作,可以省略。例如
    http://localhost:8080/helloworld/hello/index
    http://localhost:8080/helloworld/hello
    都可以访问到HelloWorld页面
    在这里插入图片描述

可以在启动项目时,通过命令参数指定上下文路径,这样就不用在applicatioin.yml中配置了

grails> run-app -Dgrails.server.servlet.context-path=/helloworld
| Running application...
...
...
Grails application running at http://localhost:8080/helloworld in environment: development
<=======<==========---> 83% EXECUTING [24s]
> :bootR> :bootRun
grails>

也可以在命令行中指定端口号,端口号范围在1024 ~ 49151 。

grails> run-app -Dgrails.server.servlet.context-path=/helloworld -port=9999
| Running application...
...
...
Grails application running at http://localhost:9999/helloworld in environment: development
<=======<==========---> 83% EXECUTING [43s]
> :bootR> :bootRun
grails>

在这里插入图片描述
可以在grails交互模式中,输入quit命令来退出交互模式并停止项目

也可以不在grails交互模式中启动项目,直接在外面使用命令启动:
➜ /Users/xiaosan/Documents/work/helloworldgrails4 >grails run-app -Dgrails.server.servlet.context-path=/helloworld -port=9999

官方说最好以交互模式启动应用程序,因为容器重启要快得多,但实际使用时没感觉快多少。

如何选择开发工具

推荐使用IDEA
其他可选开发工具:

IDEA如何导入Grails项目

选择 File / Open,
IDEA选择文件
选择grails项目的 build.gradle文件。
选择build.gradle文件述
以项目形式打开。
以项目形式打开
信任项目
选择信任项目
等待导入配置
等待导入配置
导入完成
导入完成

目录结构,约定优于配置

Grails 使用“约定优于配置”来配置自身。意味着Grails的文件名称和位置不需要显示的去配置,按照Grails的默认约定就可以了,因此需要熟悉 Grails 提供的目录结构。

  • grails-app:Groovy 源码的顶级目录
    • conf:存放配置文件的目录,可以配置日志,数据源,Spring bean配置等。
    • controllers:Web控制器,MVC中的C。负责处理请求并创建或准备响应,控制器可以直接生成响应或委托给视图。
    • doamin:领域类,MVC中的M。定义业务流程的状态和行为,配置属性约束,配置一对一、一对多或多对多关系。
    • i18n:支持开箱即用的国际化(i18n)。Grails底层利用SpringMVC的国际化支持。
    • services:业务层。用于存放应用程序逻辑,让控制器负责处理带有重定向等的请求。
    • taglib:标签库。
    • utils:Grails 特定的实用程序。
    • views:Groovy 服务器页面或 JSON 视图 - MVC 中的 V。
      - src/main/scripts:代码生成脚本,用于创建脚本,类似月create-app、run-app脚本。 grails4.0.11中没有该目录
  • src/main/groovy:存放groovy代码
  • src/test/groovy:存放单元测试

如何部署Grails项目

1. 使用默认内嵌的Tomcat8部署
  • 先打包成war文件
    ➜ /Users/xiaosan/Documents/work/helloworldgrails4 >grails war
    Processing File 2 of 43 - bootstrap.bundle.js
    ...
    Processing File 43 of 43 - bootstrap.min.css.map
    > Task :assetCompile
    Finished Precompiling Assets
    
    BUILD SUCCESSFUL in 15s
    7 actionable tasks: 5 executed, 2 up-to-date
    | Built application to build/libs using environment: production
    
    默认打包环境为生产环境。
    打包文件默认生成在build/libs/helloworldgrails4-0.1.war 。
    默认情况下,Grails 将在 WAR 文件中包含可嵌入版本的 Tomcat
  • 使用java命令启动内嵌Tomcat的war文件
    /Users/xiaosan/Documents/work/helloworldgrails4 >java -Dgrails.env=prod -jar build/libs/helloworldgrails4-0.1.war
    Grails application running at http://localhost:8080 in environment: production
    
    
    启动可以添加一些参数:
    -Dgrails.env参数可以指定启动的环境,不加这个参数默认是生产环境。
    例如:
    -Dgrails.env=prod:生产环境
    -Dgrails.env=dev:开发环境
    -Dgrails.env=test:测试环境
    使用 -server 参数给JVM容器分配内存。
    例如:
    -server -Xmx768M -XX:MaxPermSize=256m
    参数一起使用:
    ➜ /Users/xiaosan/Documents/work/helloworldgrails4 >java -Dgrails.env=prod -server -Xmx768M -XX:MaxPermSize=256m -jar build/libs/helloworldgrails4-0.1.war
    Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
    Grails application running at http://localhost:8080 in environment: production
    

启动成功访问http://localhost:8080,注意这里我将application.yml中的上下文路径给删除了,没有配置上下文路径。 在这里插入图片描述
由于MaxPermSize参数在JDK8中已经删除了,所以这里可以去掉这个参数。

jvm参数含义
-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配

2. 使用外部Tomtcat8部署
  • 打包前修改build.gradle 文件,在依赖项中将 Tomcat 依赖的范围更改为provided
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    改为
    provided "org.springframework.boot:spring-boot-starter-tomcat"
    如果使用IDEA工具修改,改后后记得gradle图标,重新加载依赖
    重新加载gradle
  • 开始打包,进入项目所在目录,执行grails war命令。
    | Resolving Dependencies. Please wait...
    CONFIGURE SUCCESSFUL in 1s
    
    BUILD SUCCESSFUL in 6s
    7 actionable tasks: 6 executed, 1 up-to-date
    | Built application to build/libs using environment: production
    
  • 下载Tomcat8,解压到目录/opt/apache-tomcat-8.5-grails4
  • 删除/opt/apache-tomcat-8.5-grails4/webapps/ROOT目录
  • 将war包复制到/opt/apache-tomcat-8.5-grails4/webapps/,并改名为ROOT.war
    cp build/libs/helloworldgrails4-0.1.war /opt/apache-tomcat-8.5-grails4/webapps/ROOT.war
    Tomcat8项目目录
  • 修改Tomcat的端口号。修改/opt/apache-tomcat-8.5-grails4/conf/server.xml,将8080端口改为8880
  • 启动Tomcat。进入/opt/apache-tomcat-8.5-grails4/bin目录,执行
    /opt/apache-tomcat-8.5-grails4/bin >./startup.sh
    Using CATALINA_BASE:   /opt/apache-tomcat-8.5-grails4
    Using CATALINA_HOME:   /opt/apache-tomcat-8.5-grails4
    Using CATALINA_TMPDIR: /opt/apache-tomcat-8.5-grails4/temp
    Using JRE_HOME:        /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home
    Using CLASSPATH:       /opt/apache-tomcat-8.5-grails4/bin/bootstrap.jar:/opt/apache-tomcat-8.5-grails4/bin/tomcat-juli.jar
    Tomcat started.
    
  • 查看tomcat日志输出
    ➜ /opt/apache-tomcat-8.5-grails4/bin >tail -f ../logs/catalina.out
    ...
    ...
    04-Aug-2021 16:02:03.911 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8880"]
    04-Aug-2021 16:02:03.918 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["ajp-nio-8809"]
    04-Aug-2021 16:02:03.920 信息 [main] org.apache.catalina.startup.Catalina.start Server startup in 13480 ms
    
  • 访问项目http://localhost:8080
    在这里插入图片描述
    注意编译的时候依赖的是Tomcat8,打包后的war只能在Tomcat8中运行,不能放在Tomcat7中运行。
3. 使用外部Tomcat7部署
  • 修改build.gradle文件,修改Tomcat的依赖版本,例如改为7.0.88,在dependencies{}上面添加ext['tomcat.version'] = '7.0.88'

  • 打包前修改build.gradle 文件,在依赖项中将 Tomcat 依赖的范围更改为provided
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    改为
    provided "org.springframework.boot:spring-boot-starter-tomcat"

  • 如果IDEA修改的build.gradle,修改后注意点击gradle图标重新加载依赖重新加载gradle

  • 开始打包,进入项目所在目录,执行grails war命令。

    | Resolving Dependencies. Please wait...
    CONFIGURE SUCCESSFUL in 1s
    Processing File 7 of 43 - application.js
    ...
    ...
    > Task :assetCompile
    Finished Precompiling Assets
    
    BUILD SUCCESSFUL in 9s
    7 actionable tasks: 5 executed, 2 up-to-date
    | Built application to build/libs using environment: production
    
  • 下载Tomcat7.0.88,解压到目录/opt/apache-tomcat-7.0.88

  • 删除/opt/apache-tomcat-7.0.88/webapps/ROOT目录

  • 将war包复制到/opt/apache-tomcat-7.0.88/webapps/,并改名为ROOT.war
    cp build/libs/helloworldgrails4-0.1.war /opt/apache-tomcat-7.0.88/webapps/ROOT.warTomcat7项目目录

  • 修改Tomcat7的端口号。修改/opt/apache-tomcat-7.0.88/conf/server.xml,将8005端口改为8705,将8080改为8780,将8009改为8709。该端口主要是为了避免多个Tomcat端口冲突,如果只有一个Tomcat就可以不用修改

  • 启动Tomcat。进入/opt/apache-tomcat-7.0.88/bin目录,执行./startup.sh

    /opt/apache-tomcat-7.0.88/bin >./startup.sh
    Using CATALINA_BASE:   /opt/apache-tomcat-7.0.88
    Using CATALINA_HOME:   /opt/apache-tomcat-7.0.88
    Using CATALINA_TMPDIR: /opt/apache-tomcat-7.0.88/temp
    Using JRE_HOME:        /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home
    Using CLASSPATH:       /opt/apache-tomcat-7.0.88/bin/bootstrap.jar:/opt/apache-tomcat-7.0.88/bin/tomcat-juli.jar
    Tomcat started.
    
  • 查看tomcat日志输出

      八月 04, 2021 7:59:54 下午 org.apache.catalina.loader.WebappClassLoaderBase validateJarFile
      信息: validateJarFile(/opt/apache-tomcat-7.0.88/webapps/ROOT/WEB-INF/lib/el-api-2.1.2-b03.jar) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/el/Expression.class
      八月 04, 2021 7:59:54 下午 org.apache.catalina.loader.WebappClassLoaderBase validateJarFile
      信息: validateJarFile(/opt/apache-tomcat-7.0.88/webapps/ROOT/WEB-INF/lib/javax.el-3.0.0.jar) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/el/Expression.class
      八月 04, 2021 7:59:54 下午 org.apache.catalina.loader.WebappClassLoaderBase validateJarFile
      信息: validateJarFile(/opt/apache-tomcat-7.0.88/webapps/ROOT/WEB-INF/lib/javax.el-api-3.0.1-b06.jar) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/el/Expression.class
      八月 04, 2021 7:59:54 下午 org.apache.catalina.loader.WebappClassLoaderBase validateJarFile
      信息: validateJarFile(/opt/apache-tomcat-7.0.88/webapps/ROOT/WEB-INF/lib/javax.servlet-api-4.0.1.jar) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/servlet/Servlet.class
      八月 04, 2021 7:59:56 下午 org.apache.catalina.startup.TldConfig execute
      信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
      2021-08-04 20:00:02.899 ERROR --- [ost-startStop-1] o.s.boot.SpringApplication               : Application run failed
      
      org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter' defined in org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]: Factory method 'requestMappingHandlerAdapter' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mvcValidator' defined in org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
    

    报错nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
    因为Grails4.0.11对el-api的版本要求比较高,Tomcat7.0.88/lib/目录里自带的el-api.jar是2.1版本的,版本比较底,启动tomcat时先加载自己的底el-api.jar,后面就不会再去加载ROOT/WEB-INF/lib/目录下的高版本el-api.jar了。
    执行➜ /opt/apache-tomcat-7.0.88/bin >./shutdown.sh停止tomcat
    复制/opt/apache-tomcat-7.0.88/webapps/ROOT/WEB-INF/lib/javax.el-api-3.0.1-b06.jar
    /opt/apache-tomcat-7.0.88/lib
    重新启动Tomcat7

    八月 04, 2021 8:08:40 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deployment of web application directory /opt/apache-tomcat-7.0.88/webapps/examples has finished in 149 ms
    八月 04, 2021 8:08:40 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deploying web application directory /opt/apache-tomcat-7.0.88/webapps/host-manager
    八月 04, 2021 8:08:40 下午 org.apache.catalina.startup.TldConfig execute
    信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
    八月 04, 2021 8:08:40 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deployment of web application directory /opt/apache-tomcat-7.0.88/webapps/host-manager has finished in 35 ms
    八月 04, 2021 8:08:40 下午 org.apache.coyote.AbstractProtocol start
    信息: Starting ProtocolHandler ["http-bio-8780"]
    八月 04, 2021 8:08:40 下午 org.apache.coyote.AbstractProtocol start
    信息: Starting ProtocolHandler ["ajp-bio-8709"]
    八月 04, 2021 8:08:40 下午 org.apache.catalina.startup.Catalina start
    信息: Server startup in 12017 ms
    

    启动成功,访问http://localhost:8780
    grails4-tomcat7启动成功
    注意高版本编译的代码,不要使用底版本的servlet容器运行,很容易出现由于jar包不兼容导致冲突,最好使用默认的servlet容器来运行。

Grails4支持的JavaEE容器有哪些

Grails 可以在任何支持 Servlet 3.0 及更高版本的容器上运行,并且可以在以下特定的容器产品上运行:

  • Tomcat 7
  • GlassFish 3 或以上版本
  • Resin 4 或以上版本
  • JBoss 6 或以上版本
  • Jetty 8 或以上版本
  • Oracle Weblogic 12c 或以上版本
  • IBM WebSphere 8.0 或以上版本

Grails4.0.11默认是使用Tomcat8作为内嵌servlet容器,部署时也最好使用Tomcat8。

如何快速创建控制器和领域类

1. 创建领域类

Grails提供了一些create-*命令,用于快速创建应用。当然使用IDEA创建更快。
例如:创建领域类命令create-domain-class
grails create-domain-class book
这将会创建一个helloworldgrails4.Book.groovy领域类
和对应的测试类helloworldgrails4.BookSpec.groovy
创建时没有给book指定包名,默认使用项目名作为包名。

 ➜ /Users/xiaosan/Documents/work/helloworldgrails4 >grails create-domain-class book
| Created grails-app/domain/helloworldgrails4/Book.groovy
| Created src/test/groovy/helloworldgrails4/BookSpec.groovy

查看创建的领域类

package helloworldgrails4
class Book {
    static constraints = {
    }
}

修改领域类,添加两个字段

package helloworldgrails4

class Book {
    String title    //书名
    Date releaseDate = new Date()   //发布日期
    
    static constraints = {
    }
}
2. 使用脚手架创建控制器和视图

Grails提供了一些脚手架功能用来快速生成应用程序的骨架。
命令以 generate-* 开头,例如 generate-all,它将生成控制器(及其单元测试)和相关视图:

grails generate-all Book -force
-force 参数强制生成覆盖已有文件。

➜ /Users/xiaosan/Documents/work/helloworldgrails4 >grails generate-all Book -force
| Rendered template Controller.groovy to destination grails-app/controllers/helloworldgrails4/BookController.groovy
| Rendered template Service.groovy to destination grails-app/services/helloworldgrails4/BookService.groovy
| Rendered template Spec.groovy to destination src/test/groovy/helloworldgrails4/BookControllerSpec.groovy
| Rendered template ServiceSpec.groovy to destination src/integration-test/groovy/helloworldgrails4/BookServiceSpec.groovy
| Scaffolding completed for grails-app/domain/helloworldgrails4/Book.groovy
| Rendered template show.gsp to destination grails-app/views/book/show.gsp
| Rendered template index.gsp to destination grails-app/views/book/index.gsp
| Rendered template create.gsp to destination grails-app/views/book/create.gsp
| Rendered template edit.gsp to destination grails-app/views/book/edit.gsp
| Views generated for grails-app/domain/helloworldgrails4/Book.groovy

自动生成的有:

  • 控制器:grails-app/controllers/helloworldgrails4/BookController.groovy
  • 业务层:grails-app/services/helloworldgrails4/BookService.groovy
  • 控制器测试:src/test/groovy/helloworldgrails4/BookControllerSpec.groovy
  • 业务层测试:src/integration-test/groovy/helloworldgrails4/BookServiceSpec.groovy
  • 详情页面:grails-app/views/book/show.gsp
  • 列表页面:grails-app/views/book/index.gsp
  • 添加页面:grails-app/views/book/create.gsp
  • 修改页面:grails-app/views/book/edit.gsp

grails-app/controllers/helloworldgrails4/BookController.groovy代码

package helloworldgrails4

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*

class BookController {

    BookService bookService

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

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond bookService.list(params), model:[bookCount: bookService.count()]
    }

    def show(Long id) {
        respond bookService.get(id)
    }

    def create() {
        respond new Book(params)
    }

    def save(Book book) {
        if (book == null) {
            notFound()
            return
        }

        try {
            bookService.save(book)
        } catch (ValidationException e) {
            respond book.errors, view:'create'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), book.id])
                redirect book
            }
            '*' { respond book, [status: CREATED] }
        }
    }

    def edit(Long id) {
        respond bookService.get(id)
    }

    def update(Book book) {
        if (book == null) {
            notFound()
            return
        }

        try {
            bookService.save(book)
        } catch (ValidationException e) {
            respond book.errors, view:'edit'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), book.id])
                redirect book
            }
            '*'{ respond book, [status: OK] }
        }
    }

    def delete(Long id) {
        if (id == null) {
            notFound()
            return
        }

        bookService.delete(id)

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'book.label', default: 'Book'), id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}

grails-app/services/helloworldgrails4/BookService.groovy代码:

package helloworldgrails4

import grails.gorm.services.Service

@Service(Book)
interface BookService {

    Book get(Serializable id)

    List<Book> list(Map args)

    Long count()

    void delete(Serializable id)

    Book save(Book book)

}

grails-app/views/book/show.gsp代码:

<!DOCTYPE html>
<html>
    <head>
        <meta name="layout" content="main" />
        <g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
        <title><g:message code="default.show.label" args="[entityName]" /></title>
    </head>
    <body>
        <a href="#show-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
        <div class="nav" role="navigation">
            <ul>
                <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
                <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
                <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
            </ul>
        </div>
        <div id="show-book" class="content scaffold-show" role="main">
            <h1><g:message code="default.show.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
            <div class="message" role="status">${flash.message}</div>
            </g:if>
            <f:display bean="book" />
            <g:form resource="${this.book}" method="DELETE">
                <fieldset class="buttons">
                    <g:link class="edit" action="edit" resource="${this.book}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
                    <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?')}');" />
                </fieldset>
            </g:form>
        </div>
    </body>
</html>

grails-app/views/book/index.gsp代码

<!DOCTYPE html>
<html>
    <head>
        <meta name="layout" content="main" />
        <g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
        <title><g:message code="default.list.label" args="[entityName]" /></title>
    </head>
    <body>
        <a href="#list-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
        <div class="nav" role="navigation">
            <ul>
                <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
                <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
            </ul>
        </div>
        <div id="list-book" class="content scaffold-list" role="main">
            <h1><g:message code="default.list.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
                <div class="message" role="status">${flash.message}</div>
            </g:if>
            <f:table collection="${bookList}" />

            <div class="pagination">
                <g:paginate total="${bookCount ?: 0}" />
            </div>
        </div>
    </body>
</html>

grails-app/views/book/create.gsp代码:

<!DOCTYPE html>
<html>
    <head>
        <meta name="layout" content="main" />
        <g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
        <title><g:message code="default.create.label" args="[entityName]" /></title>
    </head>
    <body>
        <a href="#create-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
        <div class="nav" role="navigation">
            <ul>
                <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
                <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
            </ul>
        </div>
        <div id="create-book" class="content scaffold-create" role="main">
            <h1><g:message code="default.create.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
            <div class="message" role="status">${flash.message}</div>
            </g:if>
            <g:hasErrors bean="${this.book}">
            <ul class="errors" role="alert">
                <g:eachError bean="${this.book}" var="error">
                <li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
                </g:eachError>
            </ul>
            </g:hasErrors>
            <g:form resource="${this.book}" method="POST">
                <fieldset class="form">
                    <f:all bean="book"/>
                </fieldset>
                <fieldset class="buttons">
                    <g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
                </fieldset>
            </g:form>
        </div>
    </body>
</html>

grails-app/views/book/edit.gsp代码:

<!DOCTYPE html>
<html>
    <head>
        <meta name="layout" content="main" />
        <g:set var="entityName" value="${message(code: 'book.label', default: 'Book')}" />
        <title><g:message code="default.edit.label" args="[entityName]" /></title>
    </head>
    <body>
        <a href="#edit-book" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
        <div class="nav" role="navigation">
            <ul>
                <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
                <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
                <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
            </ul>
        </div>
        <div id="edit-book" class="content scaffold-edit" role="main">
            <h1><g:message code="default.edit.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
            <div class="message" role="status">${flash.message}</div>
            </g:if>
            <g:hasErrors bean="${this.book}">
            <ul class="errors" role="alert">
                <g:eachError bean="${this.book}" var="error">
                <li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
                </g:eachError>
            </ul>
            </g:hasErrors>
            <g:form resource="${this.book}" method="PUT">
                <g:hiddenField name="version" value="${this.book?.version}" />
                <fieldset class="form">
                    <f:all bean="book"/>
                </fieldset>
                <fieldset class="buttons">
                    <input class="save" type="submit" value="${message(code: 'default.button.update.label', default: 'Update')}" />
                </fieldset>
            </g:form>
        </div>
    </body>
</html>

3. 使用脚手架生成的代码为我们实现了哪些功能

启动项目,看看Grails脚手架生成的代码为我们提供了哪些功能。
在这之前,将grails项目改为使用内嵌Tomcat方式启动,修改build.gradle
注释掉ext['tomcat.version'] = '7.0.88'
provided "org.springframework.boot:spring-boot-starter-tomcat"
改为compile "org.springframework.boot:spring-boot-starter-tomcat"
使用grails run-app命令启动项目
默认使用8080端口,没有配置上下文路径,打来浏览器访问http://localhost:8080
grails脚手架提供的功能

3.1 - 书籍列表页面

点击helloworldgrails4.BookConroller跳转到书籍列表页面http://localhost:8080/book/index
Book列表页面

3.2 - 添加书籍页面

点击NewBook跳转到添加书籍页面http://localhost:8080/book/create添加书籍页面
默认提供了领域类的属性表单,并且提供了非空验证。
添加一个书籍
添加一个书籍
点击Create保存跳转到书籍详情页面

3.3 - 书籍详情页面

点击Create后跳转到书籍详情页面http://localhost:8080/book/show/1
/book/show/1
/控制器/动作/ID
书籍详情页面
默认展示刚刚添加的属性信息,有添加成功提示信息
提供导航到书籍页面,添加书籍,修改书籍,删除书籍的链接。

3.4 - 修改书籍页面

点击Edit跳转到修改书籍页面http://localhost:8080/book/edit/1
修改书籍页面
默认提供了领域类属性表单,同样有领域类属性校验。
提供导航到书籍页面,添加书籍的链接。
点击Update跳转到书籍详情页面。
修改成功跳转到详情页面
点击Create Book再次添加一本书籍
添加第二本书籍
点击Book List跳转到书籍列表页面
书籍列表页面2

3.5 - 删除书籍页面

先在列表页面点击要删除的书籍,进入书籍详情页面
要删除的书籍详情页面
确认是否删除
点击“好” ,确认删除,删除成功后跳转到书籍列表页面
删除成功跳转到列表页面
在列表页面显示删除成功的提示信息。

3.6 - 小结

脚手架为我们提供的功能有:

  • 包含全部属性表单的添加页面
  • 包含属性的列表页面,点击属性标题可以排序
  • 包含全部属性表单的修改页面
  • 包含全部属性表单的详情页面
  • 包含删除数据功能
  • 列表页面-详情页面-添加页面-修改页面的导航链接
  • 操作数据后的提示信息
  • 属性表单的校验(非空等)

基本上为我们提供了所有的数据操作功能,我们只需要修改默认的css样式就可以了。如果不喜欢默认的排版,就需要自己写页面布局,将属性一个一个的写出来了。

完成入门

到目前为止,我们完成了从创建grails项目,到创建领域类,创建控制器,创建视图,完成增删改查,最后发布的所有流程。这只是一个简单的入门教程,主要先了解grails项目开发的几个大概步骤。Grails默认使用的H2内存数据库,数据库配置每次重启项目都会清空数据。后面会讲连接其他数据库,并持久化数据。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值