This is the first Post in my series Developing a web application with Spring Boot, AngularJS and Java 8 and has been featured in This Week in Spring – April 15, 2014.
Spring Boot makes it very easy to get you up and running with the Spring ecosystem without the need for XML configuration and the like. The nice thing about Boot is: It doesn’t add aspects and doesn’t generate code. Instead it uses several conditional beans to configure sensible defaults that disappear as soon as you add your own beans. This document helps a lot to discover the defaults and properties to customize everything you need: How Do I Do That With Spring Boot?.
So we want to create a simple MVC app with a hand full of (REST) controllers. Therefor i use the “spring-boot-starter-parent” as my parent pom:
and then use “spring-boot-starter-web” as the dependencies that include everything that is needed for Spring MVC to work:
Another goodie i wanted to use is Spring Boots ability to create executable jar files with an embedded web container, Tomcat 7 by default, so i need the spring-boot-maven-plugin as well:
Given my Application.java as the “start-class” (defined in the properties of my pom), i can run this application via “mvn spring-boot:run” or create an executable jar file with mvn package.
So what is this Application class? It looks like this:
You’ll see that it is a Spring @Configuration class that tells Spring Boot to enable automatic configuration (@EnableAutoConfiguration) as well as scheduling (@EnableScheduling) and finally tells Spring to look for more components in this and all child packages (@ComponentScan). The main method initializes an application context via a factory method inside SpringApplication. The configure method inherited from SpringBootServletInitializer does essentially the same but only when deployed as a war file. The one additional bean “home” just didn’t fit anywhere else, but you see a nice @Value annotation around the two parameters that reads “take the required value ‘biking2.home.longitude’ from the environment and used id as a parameter”. Methods annotated with @Bean are subject to dependency injection for their parameters, either beans or values. The values here are taken from application.properties at the root of the classpath.
With just a bunch of code you’ll have a fully configured application including scheduled jobs.
I want to keep track of my milage so i need some kind of persistence. I opted for H2 and added com.h2database:h2 as a dependency. Without any version number as a lot of possible dependencies are already versioned and managed by the parent pom. As i added org.springframework:orm and org.springframework.data:spring-data-jpa i could stop here. Spring Boot would automatically create a data source for me. I want some control so i added the following to the application properties:
This is also a nice example of using SpEL and nested properties inside property files managed by spring. The colon inside spring.datasource.username means “take the value of biking2.database-username if exists, otherwise use biking2”. Nice detail. Given this data source, i wanted a little more control over JPA, see PersistenceConfig.java. For example, i wanted the Spring Data repository inside a given package (@EnableJpaRepositories(basePackages = “ac.simons.biking2.persistence.repositories”)) (otherwise they would have been initialized as well, but only in the default level), have some control over sql statements and adding the hibernateExceptionTranslator.
So, data source and repositories are ready? Nearly. I have an additional application-test.properties. This properties file is only evaluated with profile test being active. I’m configuring a test in-memory db. This would happen automatically as well but with the added DB_CLOSE_DELAY=-1 parameter to the database url which means: “Keep the content of this db inside memory until the VM closes, not only until the last connection closes.” I had problems with my tests. The builder automatically adds schema.sql and data.sql and that fails if a new tests forks and the database is reused. I’m not sure wether it’s a problem with my configuration or with the Spring test runner.
So i’ve created a bunch of entities together with a repository if needed. The basic idea behind the bike –< milage entities is to record the milage of my trip recorder each month and to be done. All calculations happens inside the app. JpaRepositories are just great. The following interface
will be instantiated at runtime and together with basic CRUD functionality, it maps two named queriers (“Bike.findActive” as #findActive(final @Param(“cutoffDate”) Calendar cutoffDate) and “Bike.getDateOfFirstRecord” as #getDateOfFirstRecord() both defined inside Bike and a dynamic query build by parsing “findByDecommissionedOnIsNull”.
So we have an application context, a data source including entities and repositories, we could throw in some services, but just keep it simple, i use the repository inside the following @RestController:
What do we see? First, an auto wired constructor. I opted for all dependency injection through constructors as i’m currently observing in another project when getter/setter injection get’s out of hands. People usually start to feel uncomfortable having a constructor more than 15 parameters (at least i hope). As an added benefits, most objects are immutable.
The repository is than used, depending on a request parameter, to retrieve a list of all bikes or of all active bikes and this list is returned. As jackson-core is on the class path, this list is automatically converted to a valid JSON representation. Given ” @RequestMapping(value = “/api/bikes”, method = GET)”, the method is exposed at the given endpoint.
Testing this method is an easy task. Just instantiate the controller with a mock repository, run your tests directly on the instance and be done. You can alternatively boot an application context or parts of a context to test methods with MockMvc, have a look at BikesControllerTest.java (More on that topic in a later post).
This method is actually used here for the list of bikes at the bottom of the page.
With a minimum effort, i’ve developed an API without nearly zero maintenance cost. I like this a lot.
Here is a short list of hints, that maybe help you:
Running behind a proxy
The app runs behind a proxy and needs to generate some links and therefore must know the proxy name.
Apart from seeing a Java 8 lambda, you’ll find a static Condition that checks the presence of a given property in the environment of the application context.
The following @Bean “embeddedServletContainerCustomizer” is only instantiated when the @Conditional matches and the property is present. The parameter proxyName uses this property name, the parameter proxyPort has a default of 80. The following just sets those two values on a TomcatEmbeddedServletContainerFactory.
To enable the Servlet 3.0 Multipart support, the Spring Boot Guides show here a bean of type MultipartConfigElement is enough. That’s true and works very well. But as many people just copy & paste code, the note the default configuration imposes no limits on the file size for an upload should really be a warning if you ever plan to run this in public. An authentication doesn’t protect you from big uploads. Those are send first, than the authentication is processed. So be sure the bean reads something like
This restricts uploads to 5Mb.
Multipart/form-data and UTF-8
To support UTF-8 in Multipart/form-data, add the following bean to your config:
The ServletContextInitializer is a special form of ServletContainerInitializer that works with Spring and Spring Boot. Here it’s used to add the CharacterEncodingFilter before all other filters. The before all other part is important, otherwise it doesn’t work for Multipart/form-data.
At the moment it is not possible to get hold of the default RequestMappingHandlerMapping and customize things like “useRegisteredSuffixPatternMatch” and the like. It’s not a conditional bean and adding your own will lead to double mappings.
So i’m using a classic beanPostProcessor to achieve this with Spring Boot 1.0.0.RC3:
Next post will be about Java 8 in the context of this application.