Developing a Spring Boot CLI application

Một phần của tài liệu Manning spring boot in action (Trang 110 - 117)

Most development projects that target the JVM platform are developed in the Java lan- guage and involve a build system such as Maven or Gradle to produce a deployable artifact. In fact, the reading-list application we created in chapter 2 follows this model.

The Java language has seen great improvements in recent versions. Even so, Java has a few strict rules that add noise to the code. Line-ending semicolons, class and method modifiers (such as public and private), getter and setter methods, and import statements serve a purpose in Java, but they distract from the essentials of the code. From a developer’s perspective, code noise is friction—friction when writing code and even more friction when trying to read it. If some of this code noise could be eliminated, it’d be easier to develop and read the code.

Likewise, build systems such as Maven and Gradle serve a purpose in a project.

But build specifications are also one more thing that must be developed and main- tained. If only there were a way to do away with the build, projects would be that much simpler.

When working with the Spring Boot CLI, there is no build specification. The code itself serves as the build specification, providing hints that guide the CLI in resolving dependencies and producing deployment artifacts. Moreover, by teaming up with Groovy, the Spring Boot CLI offers a development model that eliminates almost all code noise, producing a friction-free development experience.

In the very simplest case, writing a CLI-based application could be as easy as writing a single standalone Groovy script like the one we wrote in chapter 1. But when writing a more complete application with the CLI, it makes sense to set up a basic project structure to house the project code. That’s where we’ll get started with rewriting the reading-list application.

5.1.1 Setting up the CLI project

The first thing we’ll do is create a directory structure to house the project. Unlike Maven- and Gradle-based projects, Spring Boot CLI projects don’t have a strict project structure. In fact, the simplest Spring Boot CLI app could be a single Groovy script liv- ing in any directory in the filesystem. For the reading-list project, however, you should create a fresh, clean directory to keep the code separate from anything else you keep on your machine:

$ mkdir readinglist

Here I’ve named the directory “readinglist”, but feel free to name it however you wish.

The name isn’t as important as the fact that the project will have a place to live.

We’ll need a couple of extra directories to hold the static web content and the Thymeleaf template. Within the readinglist directory, create two new directories named “static” and “templates”:

$ cd readinglist

$ mkdir static

$ mkdir templates

It’s no coincidence that these directories are named the same as the directories we created under src/main/resources in the Java-based project. Although Spring Boot doesn’t enforce a project structure like Maven and Gradle do, Spring Boot will still auto-configure a Spring ResourceHttpRequestHandler that looks for static content in a directory named “static” (among other locations). It will also still configure Thyme- leaf to resolve templates from a directory named “templates”.

Speaking of static content and Thymeleaf templates, those files will be exactly the same as the ones we created in chapter 2. So that you don’t have to worry about remembering them later, go ahead and copy style.css into the static directory and readingList.html into templates.

At this point the reading-list project should be structured like this:

.

static

? style.css

??? templates

readingList.html

Now that the project is set up, we’re ready to start writing some Groovy code.

5.1.2 Eliminating code noise with Groovy

On its own, Groovy is a very elegant language. Unlike Java, Groovy doesn’t require qualifiers such as public and private. Nor does it demand semicolons at the end of each line. Moreover, thanks to Groovy’s simplified property syntax (“GroovyBeans”), the JavaBean standard accessor methods are unnecessary.

Consequently, writing the Book domain class in Groovy is extremely easy. Create a new file at the root of the reading-list project named Book.groovy and write the follow- ing Groovy class in it.

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

As you can see, this Groovy class is a mere fraction of the size of its Java counterpart.

There are no setter or getter methods, no public or private modifiers, and no semi- colons. The code noise that is so common in Java is squelched, and all that’s left is what describes the essence of a book.

95 Developing a Spring Boot CLI application

Now that we’ve defined the Book domain class, let’s write the repository. First, let’s write the ReadingListRepository interface (in ReadingListRepository.groovy):

interface ReadingListRepository {

List<Book> findByReader(String reader) void save(Book book)

}

Aside from a clear lack of semicolons and no public modifier on the interface, the Groovy version of ReadingListRepository isn’t much different from its Java counter- part. The most significant difference is that it doesn’t extend JpaRepository because we’re not working with Spring Data JPA in this chapter. And since we’re not using Spring Data JPA, we’re going to have to write the implementation of ReadingListRepository ourselves. The following listing shows what JdbcReadingListRepository.groovy should look like.

@Repository

class JdbcReadingListRepository implements ReadingListRepository {

@Autowired

Listing 5.1 A Groovy and JDBC implementation of ReadingListRepository JDBC vs. JPA in the Spring Boot CLI

One difference you may have noticed between this Groovy implementation of Book and the Java implementation in chapter 2 is that there are no JPA annotations. That’s because this time we’re going to use Spring’s JdbcTemplate to access the database instead of Spring Data JPA.

There are a couple of very good reasons why I chose JDBC instead of JPA for this ex- ample. First, by mixing things up a little, I can show off a few more auto-configuration tricks that Spring Boot performs when working with Spring’s JdbcTemplate. But per- haps the most important reason I chose JDBC is that Spring Data JPA requires a .class file when generating on-the-fly implementations of repository interfaces. When you run Groovy scripts via the command line, the CLI compiles the scripts in memory and doesn’t produce .class files. Therefore, Spring Data JPA doesn’t work well when running scripts through the CLI.

That said, the CLI isn’t completely incompatible with Spring Data JPA. If you use the CLI’s jar command to package your application into a JAR file, the resulting JAR file will contain compiled .class files for all of your Groovy scripts. Building and running a JAR file from the CLI is a handy option when you want to deploy an application devel- oped with the CLI, but it isn’t as convenient during development when you want to see the results of your work quickly.

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

}

For the most part, this is a typical JdbcTemplate-based repository implementation. It’s injected, via autowiring, with a reference to a JdbcTemplate object that it uses to query the database for books (in the findByReader() method) and to save books to the database (in the save() method).

By writing it in Groovy, we’re able to apply some Groovy idioms in the implementation.

For example, in findByReader(), a Groovy closure is given as a parameter in the call to query() in place of a RowMapper implementation.1 Also, within the closure, a new Book object is created using Groovy’s support for setting object properties at construction.

While we’re thinking about database persistence, we also need to create a file named schema.sql that will contain the SQL necessary to create the Book table that the repository issues queries against:

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 );

1 In fairness to Java, we can do something similar in Java 8 using lambdas (and method references).

Inject JdbcTemplate

RowMapper closure

97 Developing a Spring Boot CLI application

I’ll explain how schema.sql is used later. For now, just know that you need to create it at the root of the classpath (at the root directory of the project) so that there will actu- ally be a Book table to query against.

All of the Groovy pieces are coming together, but there’s one more Groovy class we must write to make the Groovy-ified reading-list application complete. We need to write a Groovy implementation of ReadingListController to handle web requests and serve the reading list to the browser. At the root of the project, create a file named ReadingListController.groovy with the following content.

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

} }

Comparing this version of ReadingListController with the one from chapter 2, it’s easy to see that there’s a lot in common. The main difference, once again, is that Groovy’s syntax does away with class and method modifiers, semicolons, accessor methods, and other unnecessary code noise.

You’ll also notice that both handler methods are declared with def rather than String and both dispense with an explicit return statement. If you prefer explicit typ- ing on the methods and explicit return statements, feel free to include them—

Groovy won’t mind.

Listing 5.2 ReadingListController handles web requests for displaying and adding

Inject

ReadingListRepository

Fetch reading list

Populate model

Return view name

Save book Redirect after POST

There’s one more thing we need to do before we can run the application. Create a new file named Grabs.groovy and put these three lines in it:

@Grab("h2")

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

We’ll talk more about what this class does later. For now, just know that the @Grab annotations on this class tell Groovy to fetch a few dependency libraries on the fly as the application is started.

Believe it or not, we’re ready to run the application. We’ve created a project direc- tory, copied a stylesheet and Thymeleaf template into it, and filled it with Groovy code.

All that’s left is to run it with the Spring Boot CLI (from within the project directory):

$ spring run .

After a few seconds, the application should be fully started. Open your web browser and navigate to http://localhost:8080. Assuming everything goes well, you should see the same reading-list application you saw in chapter 2.

Success! In just a few pages of this book, you’ve written a complete (albeit simple) Spring application!

At this point, however, you might be wondering how it works, considering that…

There’s no Spring configuration. How are the beans created and wired together?

Where does the JdbcTemplate bean come from?

There’s no build file. Where do the library dependencies like Spring MVC and Thymeleaf come from?

There are no import statements. How can Groovy resolve types like JdbcTemplate and RequestMapping if there are no import statements to specify what packages they’re in?

We never deployed the app. Where’d the web server come from?

Indeed, the code we’ve written seems to be missing more than just a few semicolons.

How does this code even work?

5.1.3 What just happened?

As you’ve probably surmised, there’s more to Spring Boot’s CLI than just a convenient means of writing Spring applications with Groovy. The Spring Boot CLI has several tricks in its repertoire, including the following:

■ The CLI is able to leverage Spring Boot auto-configuration and starter depen- dencies.

■ The CLI is able to detect when certain types are in use and automatically resolve the appropriate dependency libraries to support those types.

■ The CLI knows which packages several commonly used types are in and, if those types are used, adds those packages to Groovy’s default packages.

99 Developing a Spring Boot CLI application

■ By applying both automatic dependency resolution and auto-configuration, the CLI can detect that it’s running a web application and automatically include an embedded web container (Tomcat by default) to serve the application.

If you think about it, these are the most important features that the CLI offers. The Groovy syntax is just a bonus!

When you run the reading-list application through the Spring Boot CLI, several things happen under the covers to make this magic work. One of the very first things the CLI does is attempt to compile the Groovy code using an embedded Groovy compiler.

Without you knowing it, however, the code fails to compile due to several unknown types in the code (such as JdbcTemplate, Controller, RequestMapping, and so on).

But the CLI doesn’t give up. The CLI knows that JdbcTemplate can be added to the classpath by adding the Spring Boot JDBC starter as a dependency. It also knows that the Spring MVC types can be found by adding the Spring Boot web starter as a dependency. So it grabs those dependencies from the Maven repository (Maven Cen- tral, by default).

If the CLI were to try to recompile at this point, it would still fail because of the missing import statements. But the CLI also knows the packages of many commonly used types. Taking advantage of the ability to customize the Groovy compiler’s default package imports, the CLI adds all of the necessary packages to the Groovy compiler’s default imports list.

Now it’s time for the CLI to attempt another compile. Assuming there are no other problems outside of the CLI’s abilities (such as syntax errors or types that the CLI doesn’t know about), the code will compile cleanly and the CLI will run it via an inter- nal bootstrap method similar to the main() method we put in Application for the Java- based example.

At this point, Spring Boot auto-configuration kicks in. It sees that Spring MVC is on the classpath (as a result of the CLI resolving the web starter), so it automatically con- figures the appropriate beans to support Spring MVC, as well as an embedded Tomcat bean to serve the application. It also sees that JdbcTemplate is on the classpath, so it automatically creates a JdbcTemplate bean, wiring it with a DataSource bean that was also automatically created.

Speaking of the DataSource bean, it’s just one of several other beans that are cre- ated via Spring Boot auto-configuration. Spring Boot also automatically configures beans that support Thymeleaf views in Spring MVC. This happens because we used

@Grab to add H2 and Thymeleaf to the classpath, which triggers auto-configuration for an embedded H2 database and Thymeleaf.

The @Grab annotation is an easy way to add dependencies that the CLI isn’t able to automatically resolve. In spite of its ease of use, however, there’s more to this little annotation than meets the eye. Let’s take a closer look at @Grab to see what makes it tick, how the Spring Boot CLI makes it even easier by requiring only an artifact name for many commonly used dependencies, and how to configure its dependency- resolution process.

Một phần của tài liệu Manning spring boot in action (Trang 110 - 117)

Tải bản đầy đủ (PDF)

(266 trang)