The fabulous https://start.spring.io website created a tiny class, LearningSpringBootApplication, as shown here:
package com.greglturnquist.learningspringboot; import org.springframework.boot.SpringApplication; import
org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LearningSpringBootApplication { public static void main(String[] args) { SpringApplication.run( LearningSpringBootApplication.class, args); } }
This preceding tiny class is actually a fully operational web application!
- The @SpringBootApplication annotation tells Spring Boot, when launched, to scan recursively for Spring components inside this package and register them. It also tells Spring Boot to enable autoconfiguration, a process where beans are automatically created based on classpath settings, property settings, and other factors. We'll see more of this throughout the book. Finally, it indicates that this class itself can be a source for Spring bean definitions.
- It holds public static void main(), a simple method to run the application. There is no need to drop this code into an application server or servlet container. We can just run it straight up, inside our IDE. The amount of time saved by this feature, over the long haul, adds up fast.
- SpringApplication.run() points Spring Boot at the leap-off point--in this case, this very class. But it's possible to run other classes.
This little class is runnable. Right now! In fact, let's give it a shot:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.0.M5) 2017-08-02 15:34:22.374: Starting LearningSpringBootApplication
on ret... 2017-08-02 15:34:22.377: Running with Spring Boot
v2.0.0.BUILD-SNAPSHO... 2017-08-02 15:34:22.378: No active profile set, falling back
to defaul... 2017-08-02 15:34:22.433: Refreshing
org.springframework.boot.web.react... 2017-08-02 15:34:23.717: HV000184: ParameterMessageInterpolator
has be... 2017-08-02 15:34:23.815: HV000184: ParameterMessageInterpolator
has be... 2017-08-02 15:34:23.953: Cannot find template location:
classpath:/tem... 2017-08-02 15:34:24.094: Mapped URL path [/webjars/**] onto
handler of... 2017-08-02 15:34:24.094: Mapped URL path [/**] onto handler of
type [c... 2017-08-02 15:34:24.125: Looking for @ControllerAdvice:
org.springfram... 2017-08-02 15:34:24.501: note: noprealloc may hurt performance
in many... 2017-08-02 15:34:24.858: 2017-08-02T15:34:24.858-0500 I
NETWORK [init... 2017-08-02 15:34:24.858: start
de.flapdoodle.embed.mongo.config.Mongod... 2017-08-02 15:34:24.908: Cluster created with settings
{hosts=[localho... 2017-08-02 15:34:24.908: Adding discovered server
localhost:65485 to c... 2017-08-02 15:34:25.007: 2017-08-02T15:34:25.006-0500 I
NETWORK [init... 2017-08-02 15:34:25.038: Opened connection
[connectionId{localValue:1,... 2017-08-02 15:34:25.040: Monitor thread successfully
connected to serv... 2017-08-02 15:34:25.041: Discovered cluster type of STANDALONE 2017-08-02 15:34:25.145: Cluster created with settings
{hosts=[localho... 2017-08-02 15:34:25.145: Adding discovered server
localhost:65485 to c... 2017-08-02 15:34:25.153: Opened connection
[connectionId{localValue:2,... 2017-08-02 15:34:25.153: Monitor thread successfully connected
to serv... 2017-08-02 15:34:25.153: Discovered cluster type of STANDALONE 2017-08-02 15:34:25.486: Registering beans for JMX exposure
on startup 2017-08-02 15:34:25.556: Started HttpServer on
/0:0:0:0:0:0:0:0:8080 2017-08-02 15:34:25.558: Netty started on port(s): 8080 2017-08-02 15:34:25.607: Started in 3.617 seconds (JVM
running for 4.0...
Scrolling through the preceding output, we can see these several things:
- The banner at the top gives us a read-out of the version of Spring Boot. (By the way, you can create your own ASCII art banner by creating either banner.txt or banner.png and putting it in the src/main/resources/ folder.)
- Embedded Netty is initialized on port 8080, indicating that it's ready for web requests.
- It's slightly cut off, but there are signs that Flapdoodle, our embedded MongoDB data store, has come up.
- And the wonderful Started LearningSpringBootApplication in 3.617 seconds message can be seen too.
Spring Boot uses embedded Netty, so there's no need to install a container on our target machine. Non-web apps don't even require that. The JAR itself is the new container that allows us to stop thinking in terms of old-fashioned servlet containers. Instead, we think in terms of apps. All these factors add up to maximum flexibility in application deployment.
How does Spring Boot use embedded Netty among other things? As mentioned earlier, it has autoconfiguration, which means that it defines Spring beans based on different conditions. When Spring Boot sees Netty on the classpath, it creates an embedded Netty instance along with several beans to support it.
When it spots Spring WebFlux on the classpath, it creates view resolution engines, handler mappers, and a whole host of other beans needed to help us write a web application. This lets us focus writing routes, not doddling around configuring infrastructure.
With Flapdoodle on the classpath as well as the Reactive MongoDB drivers, it spins up an in-memory, embedded MongoDB data store and connects to it with its state-of-the-art drivers.
Spring Data MongoDB will cause Spring Boot to craft a MongoOperations bean along with everything else needed to start speaking Mongo Query Language and make it available if we ask for it, letting us focus on defining repositories.
At this stage, we have a running web application, albeit an empty one. There are no custom routes, and no means to handle data. But we can add some real fast.
Let's start by drafting a simple REST controller as follows:
package com.greglturnquist.learningspringboot; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class HomeController { @GetMapping public String greeting(@RequestParam(required = false, defaultValue = "") String name) { return name.equals("") ? "Hey!" : "Hey, " + name + "!"; } }
Let's examine this tiny REST controller in detail:
- The @RestController annotation indicates that we don't want to render views, but write the results straight into the response body instead.
- @GetMapping is Spring's shorthand annotation for @RequestMapping(method = RequestMethod.GET). In this case, it defaults the route to /.
- Our greeting() method has one argument--@RequestParam(required=false, defaultValue="") String name. It indicates that this value can be requested via an HTTP query (?name=Greg)--the query isn't required, and in case it's missing, it will supply an empty string.
- Finally, we return one of two messages depending on whether or not the name is an empty string, using Java's ternary operator.
If we relaunch LearningSpringBootApplication in our IDE, we'll see this new entry in the console:
2017-08-02 15:40:00.741: Mapped "{[],methods=[GET]}" onto
public java....
We can then ping our new route in the browser at http://localhost:8080 and http://localhost:8080?name=Greg. Try it out!
(By the way, it sure would be handy if the system could detect this change and relaunch automatically, right? Check out Chapter 5, Developer Tools for Spring Boot Apps to find out how.)
That's nice, but since we picked Spring Data MongoDB, how hard would it be to load some sample data and retrieve it from another route? (Spoiler alert--Not hard at all.)
We can start out by defining a simple Chapter entity to capture book details, as follows:
package com.greglturnquist.learningspringboot; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Data @Document public class Chapter { @Id private String id; private String name; public Chapter(String name) { this.name = name; } }
This preceding little POJO lets us look at the details about the chapter of a book as follows:
- The @Data annotation from Lombok generates getters, setters, a toString() method, an equals() method, a hashCode() method, and a constructor for all required (that is, final) fields
- The @Document annotation flags this class as suitable for storing in a MongoDB data store
- The id field is marked with Spring Data's @Id annotation, indicating this is the primary key of our Mongo document
- Spring Data MongoDB will, by default, create a collection named chapters with two fields, id and name
- Our field of interest is name, so let's create a constructor call to help insert some test data
To interact with this entity and its corresponding collection in MongoDB, we could dig in and start using the autoconfigured MongoOperations supplied by Spring Boot. But why do that when we can declare a repository-based solution?
To do this, we'll create an interface defining the operations we need. Check out this simple interface:
package com.greglturnquist.learningspringboot; import org.springframework.data.repository
.reactive.ReactiveCrudRepository; public interface ChapterRepository extends ReactiveCrudRepository<Chapter, String> { }
This last declarative interface creates a Spring Data repository as follows:
- ReactiveCrudRepository extends Repository, a Spring Data Commons marker interface that signals Spring Data to create a concrete implementation based on the reactive paradigm while also capturing domain information. It also comes with some predefined CRUD operations (save, delete, deleteById, deleteAll, findById, findAll, and more).
- It specifies the entity type (Chapter) and the type of the primary key (String).
- We could also add custom finders, but we'll save that for Chapter 3, Reactive Data Access with Spring Boot.
Spring Data MongoDB will automatically wire up a concrete implementation of this interface.
With Chapter and ChapterRepository defined, we can now preload the database, as shown in the following code:
package com.greglturnquist.learningspringboot; import reactor.core.publisher.Flux; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LoadDatabase { @Bean CommandLineRunner init(ChapterRepository repository) { return args -> { Flux.just( new Chapter("Quick Start with Java"), new Chapter("Reactive Web with Spring Boot"), new Chapter("...and more!")) .flatMap(repository::save) .subscribe(System.out::println); }; } }
This preceding class will be automatically scanned by Spring Boot and run in the following way:
- @Configuration marks this class as a source of beans.
- @Bean indicates that the return value of init() is a Spring Bean--in this case, a CommandLineRunner (utility class from Spring Boot).
- Spring Boot runs all CommandLineRunner beans after the entire application is up and running. This bean definition requests a copy of ChapterRepository.
- Using Java 8's ability to coerce the args → {} lambda function into CommandLineRunner, we are able to gather a set of Chapter data, save all of them and then print them out, preloading our data.
With all this in place, the only thing left is to write a REST controller to serve up the data!
package com.greglturnquist.learningspringboot; import reactor.core.publisher.Flux; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ChapterController { private final ChapterRepository repository; public ChapterController(ChapterRepository repository) { this.repository = repository; } @GetMapping("/chapters") public Flux<Chapter> listing() { return repository.findAll(); } }
This preceding controller is able to serve up our data as follows:
- @RestController indicates that this is another REST controller.
- Constructor injection is used to automatically load it with a copy of ChapterRepository. With Spring, if there is only one constructor call, there is no need to include an @Autowired annotation.
- @GetMapping tells Spring that this is the place to route /chapters calls. In this case, it returns the results of the findAll() call found in ReactiveCrudRepository. Again, if you're curious what Flux<Chapter> is, we'll tackle that at the top of the next chapter. For now, think of it being like a Stream<Chapter>.
If we relaunch our application and visit http://localhost:8080/chapters, we can see our preloaded data served up as a nicely formatted JSON document, as seen in this screenshot:
This may not be very elaborate, but this small collection of classes has helped us quickly define a slice of functionality. And, if you'll notice, we spent zero effort configuring JSON converters, route handlers, embedded settings, or any other infrastructure.
Spring Boot is designed to let us focus on functional needs, not low-level plumbing.