5.1.2 通过 Groovy 消除代码噪声
Groovy 本身是种优雅的语言。与 Java 不同,Groovy 并不要求有 public
和 private
这样的限定符,也不要求在行尾有分号。此外,归功于 Groovy 的简化属性语法(GroovyBeans),JavaBean 的标准访问方法没有存在的必要了。
随之而来的结果是,用 Groovy 编写 Book
领域类相当简单。如果在阅读列表项目的根目录里创建一个新的文件,名为 Book.groovy,那么在这里编写如下 Groovy 类。
class Book {
Long id
String reader
String isbn
String title
String author
String description
}
如你所见,Groovy 类与它的 Java 类相比,大小完全不在一个量级。这里没有 setter 和 getter 方法,没有 public
和 private
修饰符,也没有分号。Java 中常见的代码噪声不复存在,剩下的内容都在描述书的基本信息。
Spring Boot CLI 中的 JDBC 与 JPA
你也许已经注意到了, Book
的 Groovy 实现与第 2 章里的 Java 实现有所不同,上面没有添加 JPA 注解。这是因为这里要用 Spring 的 JdbcTemplate
,而非 Spring Data JPA 访问数据库。
有好几个不错的理由能解释这个例子为什么选择 JDBC 而非 JPA。首先,在使用 Spring 的 JdbcTemplate
时,我可以多用几种不同的方法,展示 Spring Boot 的更多自动配置技巧。选择 JDBC 的最主要原因是,Spring Data JPA 在生成仓库接口的自动实现时要求有一个.class 文件。当你在命令行里运行 Groovy 脚本时,CLI 会在内存里编译脚本,并不会产生.class 文件。因此,当你在 CLI 里运行脚本时,Spring Data JPA 并不适用。
但 CLI 和 Spring Data JPA 并非完全不兼容。如果使用 CLI 的 jar
命令把应用程序打包成一个 JAR 文件,结果文件里就会包含所有 Groovy 脚本编译后的.class 文件。当你想部署一个用 CLI 开发的应用程序时,在 CLI 里构建并运行 JAR 文件是一个不错的选择。但是如果你想在开发时快速看到开发内容的效果,这种做法就没那么方便了。
既然我们定义好了 Book
领域类,就开始编写仓库接口吧。首先,编写 ReadingListRepository
接口(位于 ReadingListRepository.groovy):
interface ReadingListRepository {
List<Book> findByReader(String reader)
void save(Book book)
}
除了没有分号,以及接口上没有 public
修饰符, ReadingListRepository
的 Groovy 版本和与之对应的 Java 版本并无二致。最显著的区别是它没有扩展 JpaRepository
。本章我们不用 Spring Data JPA,既然如此,我们就不得不自己实现 ReadingListRepository
。代码清单 5-1 就是 JdbcReadingListRepository.groovy 的内容。
代码清单 5-1 ReadingListRepository
的 Groovy JDBC 实现
@Repository
class JdbcReadingListRepository implements ReadingListRepository {
@Autowired
JdbcTemplate jdbc ←---注入 JdbcTemplate
List<Book> findByReader(String reader) {
jdbc.query(
"select id, reader, isbn, title, author, description " +
"from Book where reader=?",
{ rs, row ->
new Book(id: rs.getLong(1),
reader: rs.getString(2),
isbn: rs.getString(3),
title: rs.getString(4),
author: rs.getString(5),
description: rs.getString(6))
} as RowMapper, ←---RowMapper 闭包
reader)
}
void save(Book book) {
jdbc.update("insert into Book " +
"(reader, isbn, title, author, description) " +
"values (?, ?, ?, ?, ?)",
book.reader,
book.isbn,
book.title,
book.author,
book.description)
}
}
以上代码的大部分内容在实现一个典型的基于 JdbcTemplate
的仓库。它自动注入了一个 JdbcTemplate
对象的引用,用它查询数据库获取图书(在 findByReader()
方法里),将图书保存到数据库(在 save()
方法里)。
因为编写过程采用了 Groovy,所以我们在实现中可以使用一些 Groovy 的语法糖。举个例子,在 findByReader()
里,调用 query()
时可以在需要 RowMapper
实现的地方传入一个 Groovy 闭包。2 此外,闭包中创建了一个新的 Book
对象,在构造时设置对象的属性。
2 为了公平对待 Java,在 Java 8 里我们可以用 Lambda(和方法引用)做类似的事情。
在考虑数据库持久化时,我们还需要创建一个名为 schema.sql 的文件。其中包含创建 Book
表所需的 SQL。仓库在发起查询时依赖这个数据表:
create table Book (
id identity,
reader varchar(20) not null,
isbn varchar(10) not null,
title varchar(50) not null,
author varchar(50) not null,
description varchar(2000) not null
);
稍后我会解释如何使用 schema.sql。现在你只需要知道,把它放在 Classpath 的根目录(即项目的根目录),就能创建出查询用的 Book
表了。
Groovy 的所有部分差不多都齐全了,但还有一个 Groovy 类必须要写。这样 Groovy 化的阅读列表应用程序才完整。我们需要编写一个 ReadingListController
的 Groovy 实现来处理 Web 请求,为浏览器提供阅读列表。在项目的根目录,要创建一个名为 ReadingListController.groovy 的文件,内容如代码清单 5-2 所示。
代码清单 5-2 处理展示和添加 Web 请求的 ReadingListController
@Controller
@RequestMapping("/")
class ReadingListController {
String reader = "Craig"
@Autowired
ReadingListRepository readingListRepository ←---注入 ReadingListRepository
@RequestMapping(method=RequestMethod.GET)
def readersBooks(Model model) {
List<Book> readingList =
readingListRepository.findByReader(reader) ←---获取阅读列表
if (readingList) {
model.addAttribute("books", readingList) ←---设置模型
}
"readingList" ←---返回视图名称
}
@RequestMapping(method=RequestMethod.POST)
def addToReadingList(Book book) {
book.setReader(reader)
readingListRepository.save(book) ←---保存图书
"redirect:/" ←---POST 后重定向
}
}
这个 ReadingListController
和第 2 章里的版本有很多相似之处。主要的不同在于,Groovy 的语法消除了类和方法的修饰符、分号、访问方法和其他不必要的代码噪声。
你还会注意到,两个处理器方法都用 def
而非 String
来定义。两者都没有显式的 return
语句。如果你喜欢在方法上说明类型,喜欢显式的 retrun
语句,加上就好了 - Groovy 并不在意这些细节。
在运行应用程序之前,还要做一件事。那就是创建一个新文件,名为 Grabs.groovy,内容包括如下三行:
@Grab("h2")
@Grab("spring-boot-starter-thymeleaf")
class Grabs {}
稍后我们再来讨论这个类的作用,现在你只需要知道类上的 @Grab
注解会告诉 Groovy 在启动应用程序时自动获取一些依赖的库。
不管你信还是不信,我们已经可以运行这个应用程序了。我们创建了一个项目目录,向其中复制了一个样式表和 Thymeleaf 模板,填充了一些 Groovy 代码。接下来,用 Spring Boot CLI(在项目目录里)运行即可:
$ spring run .
几秒后,应用程序完全启动。打开浏览器,访问 http://localhost:8080 。如果一切正常,你应该就能看到和第 2 章一样的阅读列表应用程序。
成功啦!只用了几页纸的篇幅,你就写出了简单而又完整的 Spring 应用程序!
此时此刻你也许会好奇这是怎么办到的。
没有Spring 配置 ,Bean 是如何创建并组装的?
JdbcTemplate
Bean 又是从哪来的?没有构建文件 ,Spring MVC 和 Thymeleaf 这样的依赖库是哪来的?
没有
import
语句 。如果不通过import
语句来指定具体的包,Groovy 如何解析JdbcTemplate
和RequestMapping
的类型?没有部署应用 ,Web 服务器从何而来?
实际上,我们编写的代码看起来不止缺少分号。这些代码究竟是怎么运行起来的?
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论