《Spring Boot in Action》【5. Groovy】

5. Groovy

Spring Boot CLI可以很方便地使用Groovy编写Spring应用程序。

5.2 创建CLI项目

首先创建一个项目目录:

mkdir readinglist

进去创建静态资源目录和Thymeleaf模板目录:

$ cd readinglist
$ mkdir static
$ mkdir templates

分别将之前的style.css和readingList.html放进去。

在根目录下添加Boot.groovy,无需访问修饰符、行末分号、getter和setter:

class Book {
  Long id
  String reader
  String isbn
  String title
  String author
  String description
}

还有一个区别是这里没有Spring Data JPA的注解,事实上我们将使用Spring的JdbcTemplate,而不是Spring Data JPA,因为JPA需要.class文件来实现repository接口,当你通过命令行运行Groovy脚本的时候,CLI在内存中编译脚本且不会生成.class文件,所以使用CLI开发的时候JPA不太好用。不过你可以使用CLI的jar命令打包成JAR文件,JAR包里面就有.class文件了,不过开发的时候为了方便,就用JdbcTemplate了。

接下来写ReadingListRepository接口:

interface ReadingListRepository {
  List<Book> findByReader(String reader)
  void save(Book book)
}

写对应的接口实现:

@Repository
class JdbcReadingListRepository implements ReadingListRepository {

  @Autowired
  JdbcTemplate jdbc

  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,
        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)
  }
}

创建schema.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
);

创建ReadingListController.groovy:

@Controller
@RequestMapping("/")
class ReadingListController {

  String reader = "Craig"

  @Autowired
  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:/"
  }
}

创建Grabs.groovy:

@Grab("h2")
@Grab("spring-boot-starter-thymeleaf")
class Grabs {}

下面就可以运行了,到项目根目录:

$ spring run .

发生了什么?

当你用CLI运行应用的时候,CLI试图使用内置的Groovy编译器编译Groovy代码,不过因为有些类型不认识(如JdbcTemplate, Controller, RequestMapping),所以失败了,不过它没放弃,它知道JdbcTemplate可以通过Spring Boot JDBC starter得到,Spring MVC的类型可以通过Spring Boot web starter得到,所以它会从Maven仓库(默认是Maven Central)获取这些依赖。此时还是失败,因为没有import,不过CLI知道许多常用类型所在的包,它会添加到Groovy编译器的默认包列表,这时候如果没有别的语法错误,编译通过,并且CLI通过内部的启动方法来运行应用,此时,Spring Boot自动配置介入。

5.2 获取依赖

@Grab注解来自于Groovy’s Grape(Groovy Adaptable Packaging Engine or Groovy Advanced Packaging Engine),用法:

@Grab(group="com.h2database", module="h2", version="1.4.190")
@Grab("com.h2database:h2:1.4.185")

Spring Boot CLI扩展了@Grab,使它用起来更简洁,许多依赖都不用指定版本(版本根据CLI的版本来确定),对一些常用的依赖,甚至都不用指定group:

@Grab("com.h2database:h2")
@Grab("h2")

覆盖默认依赖版本

Spring Boot引入了@GrabMetadata注解:

@GrabMetadata(“com.myorg:custom-versions:1.0.0”)

如上,它会从Maven仓库的com/myorg目录加载一个名为custom-versions.properties的属性文件,每行的键是group ID和module ID,值是版本号:

com.h2database:h2=1.4.186

Spring IO platform提供了一套兼容的依赖版本集,它是Spring Boot依赖版本集的超集:

@GrabMetadata('io.spring.platform:platform-versions:1.0.4.RELEASE')

添加依赖仓库

@GrabResolver注解可以添加依赖仓库,比如:

@GrabResolver(name='jboss', root='https://repository.jboss.org/nexus/content/groups/public-jboss')

5.3 运行测试

CLI提供了test命令来运行测试,测试可以放在项目的任何位置,不过建议放在一起,比如tests目录下:

$ mkdir tests

创建ReadingListControllerTest.groovy:

import org.springframework.test.web.servlet.MockMvc
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import static org.mockito.Mockito.*

class ReadingListControllerTest {

  @Test
  void shouldReturnReadingListFromRepository() {
    List<Book> expectedList = new ArrayList<Book>()
    expectedList.add(new Book(
        id: 1,
        reader: "Craig",
        isbn: "9781617292545",
        title: "Spring Boot in Action",
        author: "Craig Walls",
        description: "Spring Boot in Action is ..."
    ))

    def mockRepo = mock(ReadingListRepository.class)
    when(mockRepo.findByReader("Craig")).thenReturn(expectedList)

    def controller = new ReadingListController(readingListRepository: mockRepo)
    MockMvc mvc = standaloneSetup(controller).build()
    mvc.perform(get("/"))
        .andExpect(view().name("readingList"))
        .andExpect(model().attribute("books", expectedList))
  }
}

运行测试:

$ spring test tests/ReadingListControllerTest.groovy

如果有多个测试,你也可以只提供一个目录:

$ spring test tests

如果你更倾向于写Spock测试,而不是JUnit,test命令也可以执行Spock测试:

import org.springframework.test.web.servlet.MockMvc
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import static org.mockito.Mockito.*

class ReadingListControllerSpec extends Specification {

  MockMvc mockMvc
  List<Book> expectedList

  def setup() {
    expectedList = new ArrayList<Book>()
    expectedList.add(new Book(
      id: 1,
      reader: "Craig",
      isbn: "9781617292545",
      title: "Spring Boot in Action",
      author: "Craig Walls",
      description: "Spring Boot in Action is ..."
    ))

    def mockRepo = mock(ReadingListRepository.class)
    when(mockRepo.findByReader("Craig")).thenReturn(expectedList)

    def controller = new ReadingListController(readingListRepository: mockRepo)
    mockMvc = standaloneSetup(controller).build()
  }

  def "Should put list returned from repository into model"() {
    when:
      def response = mockMvc.perform(get("/"))

    then:
      response.andExpect(view().name("readingList"))
              .andExpect(model().attribute("books", expectedList))
  }
}

5.4 创建可部署的包

在项目根目录下,执行:

$ spring jar ReadingList.jar .

然后就可以运行了:

$ java -jar ReadingList.jar
Summary A developer-focused guide to writing applications using Spring Boot. You'll learn how to bypass the tedious configuration steps so that you can concentrate on your application's behavior. Purchase of the print book includes a free eBook in PDF, Kindle, and ePub formats from Manning Publications. About the Technology The Spring Framework simplifies enterprise Java development, but it does require lots of tedious configuration work. Spring Boot radically streamlines spinning up a Spring application. You get automatic configuration and a model with established conventions for build-time and runtime dependencies. You also get a handy command-line interface you can use to write scripts in Groovy. Developers who use Spring Boot often say that they can't imagine going back to hand configuring their applications. About the Book Spring Boot in Action is a developer-focused guide to writing applications using Spring Boot. In it, you'll learn how to bypass configuration steps so you can focus on your application's behavior. Spring expert Craig Walls uses interesting and practical examples to teach you both how to use the default settings effectively and how to override and customize Spring Boot for your unique environment. Along the way, you'll pick up insights from Craig's years of Spring development experience. What's Inside Develop Spring apps more efficiently Minimal to no configuration Runtime metrics with the Actuator Covers Spring Boot 1.3 About the Reader Written for readers familiar with the Spring Framework. About the Author Craig Walls is a software developer, author of the popular book Spring in Action, Fourth Edition, and a frequent speaker at conferences.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值