返回介绍

5.1.2 通过 Groovy 消除代码噪声

发布于 2025-04-21 21:10:08 字数 6203 浏览 0 评论 0 收藏

Groovy 本身是种优雅的语言。与 Java 不同,Groovy 并不要求有 publicprivate 这样的限定符,也不要求在行尾有分号。此外,归功于 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 方法,没有 publicprivate 修饰符,也没有分号。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 如何解析 JdbcTemplateRequestMapping 的类型?

  • 没有部署应用 ,Web 服务器从何而来?

实际上,我们编写的代码看起来不止缺少分号。这些代码究竟是怎么运行起来的?

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。