Chapter 3. Customizing configuration

This chapter covers

  • Overriding auto-configured beans
  • Configuring with external properties
  • Customizing error pages

Freedom of choice is an awesome thing. If you’ve ever ordered a pizza (who hasn’t?) then you know that you have full control over what toppings are placed on the pie. If you ask for sausage, pepperoni, green peppers, and extra cheese, then you’re essentially configuring the pizza to your precise specifications.

On the other hand, most pizza places also offer a form of auto-configuration. You can ask for the meat-lover’s pizza, the vegetarian pizza, the spicy Italian pizza, or the ultimate example of pizza auto-configuration, the supreme pizza. When ordering one of these pizzas, you don’t have to explicitly specify the toppings. The type of pizza ordered implies what toppings are used.

But what if you like all of the toppings of the supreme pizza, but also want jalapenos and would rather not have mushrooms? Does your taste for spicy food and aversion to fungus mean that auto-configuration isn’t applicable and that you must explicitly configure your pizza? Absolutely not. Most pizzerias will let you customize your pizza, even if you started with a preconfigured option from the menu.

Working with traditional Spring configuration is much like ordering a pizza and explicitly specifying all of the toppings. You have full control over what goes into your Spring configuration, but explicitly declaring all of the beans in the application is non-optimal. On the other hand, Spring Boot auto-configuration is like ordering a specialty pizza from the menu. It’s easier to let Spring Boot handle the details than to declare each and every bean in the application context.

Fortunately, Spring Boot auto-configuration is flexible. Like the pizzeria that will leave off the mushrooms and add jalapenos to your pizza, Spring Boot will let you step in and influence how it applies auto-configuration.

In this chapter, we’re going to look at two ways to influence auto-configuration: explicit configuration overrides and fine-grained configuration with properties. We’ll also look at how Spring Boot has provided hooks for you to plug in a custom error page.

3.1. Overriding Spring Boot auto-configuration

Generally speaking, if you can get the same results with no configuration as you would with explicit configuration, no configuration is the no-brainer choice. Why would you do extra work, writing and maintaining extra configuration code, if you can get what you need without it?

Most of the time, the auto-configured beans are exactly what you want and there’s no need to override them. But there are some cases where the best guess that Spring Boot can make during auto-configuration probably isn’t going to be good enough.

A prime example of a case where auto-configuration isn’t good enough is when you’re applying security to your application. Security is not one-size-fits-all, and there are decisions around application security that Spring Boot has no business making for you. Although Spring Boot provides some basic auto-configuration for security, you’ll certainly want to override it to meet your specific security requirements.

To see how to override auto-configuration with explicit configuration, we’ll start by adding Spring Security to the reading-list example. After seeing what you get for free with auto-configuration, we’ll then override the basic security configuration to fit a particular situation.

3.1.1. Securing the application

Spring Boot auto-configuration makes securing an application a piece of cake. All you need to do is add the security starter to the build. For Gradle, the following dependency will do:

compile("org.springframework.boot:spring-boot-starter-security")

Or, if you’re using Maven, add this <dependency> to your build’s <dependencies> block:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

That’s it! Rebuild your application and run it. It’s now a secure web application! The security starter adds Spring Security (among other things) to the application’s classpath. With Spring Security on the classpath, auto-configuration kicks in and a very basic Spring Security setup is created.

If you try to open the application in your browser, you’ll be immediately met with an HTTP Basic authentication dialog box. The username you’ll need to enter is “user”. As for the password, it’s a bit trickier. The password is randomly generated and written to the logs each time the application is run. You’ll need to look through the logging messages (written to stdout by default) and look for a line that looks something like this:

Using default security password: d9d8abe5-42b5-4f20-a32a-76ee3df658d9

I can’t say for certain, but I’m guessing that this particular security setup probably isn’t ideal for you. First, HTTP Basic dialog boxes are clunky and not very user-friendly. And I’ll bet that you don’t develop too many applications that have only one user who doesn’t mind looking up their password from a log file. Therefore, you’ll probably want to make a few changes to how Spring Security is configured. At very least, you’ll want to provide a nice-looking login page and specify an authentication service that operates against a database or LDAP-based user store.

Let’s see how to do that by writing some explicit Spring Security configuration to override the auto-configured security scheme.

3.1.2. Creating a custom security configuration

Overriding auto-configuration is a simple matter of explicitly writing the configuration as if auto-configuration didn’t exist. This explicit configuration can take any form that Spring supports, including XML configuration and Groovy-based configuration.

For our purposes, we’re going to focus on Java configuration when writing explicit configuration. In the case of Spring Security, this means writing a configuration class that extends WebSecurityConfigurerAdapter. SecurityConfig in listing 3.1 is the configuration class we’ll use.

Listing 3.1. Explicit configuration to override auto-configured security

SecurityConfig is a very basic Spring Security configuration. Even so, it does a lot of what we need to customize security of the reading-list application. By providing this custom security configuration class, we’re asking Spring Boot to skip security auto-configuration and to use our security configuration instead.

Configuration classes that extend WebSecurityConfigurerAdapter can override two different configure() methods. In SecurityConfig, the first configure() method specifies that requests for “/” (which ReadingListController’s methods are mapped to) require an authenticated user with the READER role. All other request paths are configured for open access to all users. It also designates /login as the path for the login page as well as the login failure page (along with an error attribute).

Spring Security offers several options for authentication, including authentication against JDBC-backed user stores, LDAP-backed user stores, and in-memory user stores. For our application, we’re going to authenticate users against the database via JPA. The second configure() method sets this up by setting a custom user details service. This service can be any class that implements UsersDetailsService and is used to look up user details given a username. The following listing has given it an anonymous inner-class implementation that simply calls the findOne() method on an injected ReaderRepository (which is a Spring Data JPA repository interface).

Listing 3.2. A repository interface for persisting readers

As with BookRepository, there’s no need to write an implementation of ReaderRepository. Because it extends JpaRepository, Spring Data JPA will automatically create an implementation of it at runtime. This affords you 18 methods for working with Reader entities.

Speaking of Reader entities, the Reader class (shown in listing 3.3) is the final piece of the puzzle. It’s a simple JPA entity type with a few fields to capture the username, password, and full name of the user.

Listing 3.3. A JPA entity that defines a Reader

As you can see, Reader is annotated with @Entity to make it a JPA entity. In addition, its username field is annotated with @Id to designate it as the entity’s ID. This seemed like a natural choice, as the username should uniquely identify the Reader.

You’ll also notice that Reader implements the UserDetails interface and several of its methods. This makes it possible to use a Reader object to represent a user in Spring Security. The getAuthorities() method is overridden to always grant users READER authority. The isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), and isEnabled() methods are all implemented to return true so that the reader account is never expired, locked, or revoked.

Rebuild and restart the application and you should be able to log in to the application as one of the readers.

Keeping it simple

In a larger application, the authorities granted to a user might themselves be entities and be maintained in a separate database table. Likewise, the boolean values indicating whether an account is non-expired, non-locked, and enabled might be fields drawn from the database. For our purposes, however, I’ve decided to keep these details simple so as not to distract from what it is we’re really discussing ... namely, overriding Spring Boot auto-configuration.

There’s a lot more we could do with regard to security configuration,[1] but this is all we need here, and it does demonstrate how to override the security auto-configuration provided by Spring Boot.

1

For a deeper dive into Spring Security, have a look at chapters 9 and 14 of my Spring in Action, Fourth Edition (Manning, 2014).

Again, all you need to do to override Spring Boot auto-configuration is to write explicit configuration. Spring Boot will see your configuration, step back, and let your configuration take precedence. To understand how this works, let’s take a look under the covers of Spring Boot auto-configuration to see how it works and how it allows itself to be overridden.

3.1.3. Taking another peek under the covers of auto-configuration

As we discussed in section 2.3.3, Spring Boot auto-configuration comes with several configuration classes, any of which can be applied in your application. All of this configuration uses Spring 4.0’s conditional configuration support to make runtime decisions as to whether or not Spring Boot’s configuration should be used or ignored.

For the most part, the @ConditionalOnMissingBean annotation described in table 2.1 is what makes it possible to override auto-configuration. The JdbcTemplate bean defined in Spring Boot’s DataSourceAutoConfiguration is a very simple example of how @ConditionalOnMissingBean works:

@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
  return new JdbcTemplate(this.dataSource);
}

The jdbcTemplate() method is annotated with @Bean and is ready to configure a JdbcTemplate bean if needed. But it’s also annotated with @ConditionalOnMissingBean, which requires that there not already be a bean of type JdbcOperations (the interface that JdbcTemplate implements). If there’s already a JdbcOperations bean, then the condition will fail and the jdbcTemplate() bean method will not be used.

What circumstances would result in there already being a JdbcOperation bean? Spring Boot is designed to load application-level configuration before considering its auto-configuration classes. Therefore, if you’ve already configured a JdbcTemplate bean, then there will be a bean of type JdbcOperations by the time that auto-configuration takes place, and the auto-configured JdbcTemplate bean will be ignored.

As it pertains to Spring Security, there are several configuration classes considered during auto-configuration. It would be impractical to go over each of them in detail here, but the one that’s most significant in allowing us to override Spring Boot’s auto-configured security configuration is SpringBootWebSecurityConfiguration. Here’s an excerpt from that configuration class:

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({ EnableWebSecurity.class })
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication
public class SpringBootWebSecurityConfiguration {

...

}

As you can see, SpringBootWebSecurityConfiguration is annotated with a few conditional annotations. Per the @ConditionalOnClass annotation, the @EnableWebSecurity annotation must be available on the classpath. And per @ConditionalOnWebApplication, the application must be a web application. But it’s the @ConditionalOnMissingBean annotation that makes it possible for our security configuration class to be used instead of SpringBootWebSecurityConfiguration.

The @ConditionalOnMissingBean requires that there not already be a bean of type WebSecurityConfiguration. Although it may not be apparent on the surface, by annotating our SecurityConfig class with @EnableWebSecurity, we’re indirectly creating a bean of type WebSecurityConfiguration. Therefore, by the time auto-configuration takes place, there will already be a bean of type WebSecurityConfiguration, the @ConditionalOnMissingBean condition will fail, and any configuration offered by SpringBootWebSecurityConfiguration will be skipped over.

Although Spring Boot’s auto-configuration and @ConditionalOnMissingBean make it possible for you to explicitly override any of the beans that would otherwise be auto-configured, it’s not always necessary to go to that extreme. Let’s see how you can set a few simple configuration properties to tweak the auto-configured components.

3.2. Externalizing configuration with properties

When dealing with application security, you’ll almost certainly want to take full charge of the configuration. But it would be a shame to give up on auto-configuration just to tweak a small detail such as a server port number or a logging level. If you need to set a database URL, wouldn’t it be easier to set a property somewhere than to completely declare a data source bean?

As it turns out, the beans that are automatically configured by Spring Boot offer well over 300 properties for fine-tuning. When you need to adjust the settings, you can specify these properties via environment variables, Java system properties, JNDI, command-line arguments, or property files.

To get started with these properties, let’s look at a very simple example. You may have noticed that Spring Boot emits an ascii-art banner when you run the reading-list application from the command line. If you’d like to disable the banner, you can do so by setting a property named spring.main.show-banner to false. One way of doing that is to specify the property as a command-line parameter when you run the app:

$ java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.main.show-banner=false

Another way is to create a file named application.properties that includes the following line:

spring.main.show-banner=false

Or, if you’d prefer, create a YAML file named application.yml that looks like this:

spring:
  main:
    show-banner: false

You could also set the property as an environment variable. For example, if you’re using the bash or zsh shell, you can set it with the export command:

$ export spring_main_show_banner=false

Note the use of underscores instead of periods and dashes, as required for environment variable names.

There are, in fact, several ways to set properties for a Spring Boot application. Spring Boot will draw properties from several property sources, including the following:

  1. Command-line arguments
  2. JNDI attributes from java:comp/env
  3. JVM system properties
  4. Operating system environment variables
  5. Randomly generated values for properties prefixed with random.* (referenced when setting other properties, such as `${random.long})
  6. An application.properties or application.yml file outside of the application
  7. An application.properties or application.yml file packaged inside of the application
  8. Property sources specified by @PropertySource
  9. Default properties

This list is in order of precedence. That is, any property set from a source higher in the list will override the same property set on a source lower in the list. Command-line arguments, for instance, override properties from any other property source.

As for the application.properties and application.yml files, they can reside in any of four locations:

  1. Externally, in a /config subdirectory of the directory from which the application is run
  2. Externally, in the directory from which the application is run
  3. Internally, in a package named “config”
  4. Internally, at the root of the classpath

Again, this list is in order of precedence. That is, an application.properties file in a /config subdirectory will override the same properties set in an application.properties file in the application’s classpath.

Also, I’ve found that if you have both application.properties and application.yml side by side at the same level of precedence, properties in application.yml will override those in application.properties.

Disabling an ascii-art banner is just a small example of how to use properties. Let’s look at a few more common ways to tweak the auto-configured beans.

3.2.1. Fine-tuning auto-configuration

As I said, there are well over 300 properties that you can set to tweak and adjust the beans in a Spring Boot application. Appendix C gives an exhaustive list of these properties, but it’d be impossible to go over each and every one of them here. Instead, let’s examine a few of the more commonly useful properties exposed by Spring Boot.

Disabling template caching

If you’ve been tinkering around much with the reading-list application, you may have noticed that changes to any of the Thymeleaf templates aren’t applied unless you restart the application. That’s because Thymeleaf templates are cached by default. This improves application performance because you only compile the templates once, but it’s difficult to make changes on the fly during development.

You can disable Thymeleaf template caching by setting spring.thymeleaf.cache to false. You can do this when you run the application from the command line by setting it as a command-line argument:

$ java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.thymeleaf.cache=false

Or, if you’d rather have caching turned off every time you run the application, you might create an application.yml file with the following lines:

spring:
  thymeleaf:
    cache: false

You’ll want to make sure that this application.yml file doesn’t follow the application into production, or else your production application won’t realize the performance benefits of template caching.

As a developer, you may find it convenient to have template caching turned off all of the time while you make changes to the templates. In that case, you can turn off Thymeleaf caching via an environment variable:

$ export spring_thymeleaf_cache=false

Even though we’re using Thymeleaf for our application’s views, template caching can be turned off for Spring Boot’s other supported template options by setting these properties:

  • spring.freemarker.cache (Freemarker)
  • spring.groovy.template.cache (Groovy templates)
  • spring.velocity.cache (Velocity)

By default, all of these properties are true, meaning that the templates are cached. Setting them to false disables caching.

Configuring the embedded server

When you run a Spring Boot application from the command line (or via Spring Tool Suite), the application starts an embedded server (Tomcat, by default) listening on port 8080. This is fine for most cases, but it can become problematic if you find yourself needing to run multiple applications simultaneously. If all of the applications try to start a Tomcat server on the same port, there’ll be port collisions starting with the second application.

If, for any reason, you’d rather the server listen on a different port, then all you need to do is set the server.port property. If this is a one-time change, it’s easy enough to do this as a command-line argument:

$ java -jar readinglist-0.0.1-SNAPSHOT.jar --server.port=8000

But if you want the port change to be more permanent, you could set server.port in one of the other supported locations. For instance, you might set it in an application.yml file at the root of the application’s classpath:

server:
  port: 8000

Aside from adjusting the server’s port, you might also need to enable the server to serve securely over HTTPS. The first thing you’ll need to do is create a keystore using the JDK’s keytool utility:

$ keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA

You’ll be asked several questions about your name and organization, most of which are irrelevant. But when asked for a password, be sure to remember what you choose. For the sake of this example, I chose “letmein” as the password.

Now you just need to set a few properties to enable HTTPS in the embedded server. You could specify them all at the command line, but that would be terribly inconvenient. Instead, you’ll probably set them in application.properties or application.yml. In application.yml, they might look like this:

server:
  port: 8443
  ssl:
    key-store: file:///path/to/mykeys.jks
    key-store-password: letmein
    key-password: letmein

Here the server.port property is being set to 8443, a common choice for development HTTPS servers. The server.ssl.key-store property should be set to the path where the keystore file was created. Here it’s shown with a file:// URL to load it from the filesystem, but if you package it within the application JAR file, you should use a classpath: URL to reference it. And both the server.ssl.key-store-password and server.ssl.key-password properties are set to the password that was given when creating the keystore.

With these properties in place, your application should be listening for HTTPS requests on port 8443. (Depending on which browser you’re using, you may encounter a warning about the server not being able to verify its identity. This is nothing to worry about when serving from localhost during development.)

Configuring logging

Most applications provide some form of logging. And even if your application doesn’t log anything directly, the libraries that your application uses will certainly log their activity.

By default, Spring Boot configures logging via Logback (http://logback.qos.ch) to log to the console at INFO level. You’ve probably already seen plenty of INFO-level logging as you’ve run the application and other examples.

Swapping out Logback for another logging implementation

Generally speaking, you should never need to switch logging implementations; Logback should suit you fine. However, if you decide that you’d rather use Log4j or Log4j2, you’ll need to change your dependencies to include the appropriate starter for the logging implementation you want to use and to exclude Logback.

For Maven builds, you can exclude Logback by excluding the default logging starter transitively resolved by the root starter dependency:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

In Gradle, it’s easiest to place the exclusion under the configurations section:

configurations {
  all*.exclude group:'org.springframework.boot',
               module:'spring-boot-starter-logging'
}

With the default logging starter excluded, you can now include the starter for the logging implementation you’d rather use. With a Maven build you can add Log4j like this:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j</artifactId>
</dependency>

In a Gradle build you can add Log4j like this:

compile("org.springframework.boot:spring-boot-starter-log4j")

If you’d rather use Log4j2, change the artifact from “spring-boot-starter-log4j” to “spring-boot-starter-log4j2”.

For full control over the logging configuration, you can create a logback.xml file at the root of the classpath (in src/main/resources). Here’s an example of a simple logback.xml file you might use:

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
      </pattern>
    </encoder>
  </appender>

  <logger name="root" level="INFO"/>

  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Aside from the pattern used for logging, this Logback configuration is more or less equivalent to the default you’ll get if you have no logback.xml file. But by editing logback.xml you can gain full control over your application’s log files. The specifics of what can go into logback.xml are outside the scope of this book, so refer to Logback’s documentation for more information.

Even so, the most common changes you’ll make to a logging configuration are to change the logging levels and perhaps to specify a file where the logs should be written. With Spring Boot configuration properties, you can make those changes without having to create a logback.xml file.

To set the logging levels, you create properties that are prefixed with logging.level, followed by the name of the logger for which you want to set the logging level. For instance, suppose you’d like to set the root logging level to WARN, but log Spring Security logs at DEBUG level. The following entries in application.yml will take care of it for you:

logging:
  level:
    root: WARN
    org:
      springframework:
        security: DEBUG

Optionally, you can collapse the Spring Security package name to a single line:

logging:
  level:
    root: WARN
    org.springframework.security: DEBUG

Now suppose that you want to write the log entries to a file named BookWorm.log at /var/logs/. The logging.path and logging.file properties can help with that:

logging:
  path: /var/logs/
  file: BookWorm.log
  level:
    root: WARN
    org:
      springframework:
        security: DEBUG

Assuming that the application has write permissions to /var/logs/, the log entries will be written to /var/logs/BookWorm.log. By default, the log files will rotate once they hit 10 megabytes in size.

Similarly, all of these properties can be set in application.properties like this:

logging.path=/var/logs/
logging.file=BookWorm.log
logging.level.root=WARN
logging.level.root.org.springframework.security=DEBUG

If you still need full control of the logging configuration, but would rather name the Logback configuration file something other than logback.xml, you can specify a custom name by setting the logging.config property:

logging:
  config:
    classpath:logging-config.xml

Although you usually won’t need to change the configuration file’s name, it can come in handy if you want to use two different logging configurations for different runtime profiles (see section 3.2.3).

Configuring a data source

At this point, we’re still developing our reading-list application. As such, the embedded H2 database we’re using is perfect for our needs. But once we take the application into production, we may want to consider a more permanent database solution.

Although you could explicitly configure your own DataSource bean, it’s usually not necessary. Instead, simply configure the URL and credentials for your database via properties. For example, if you’re using a MySQL database, your application.yml file might look like this:

spring:
  datasource:
    url: jdbc:mysql://localhost/readinglist
    username: dbuser
    password: dbpass

You usually won’t need to specify the JDBC driver; Spring Boot can figure it out from the database URL. But if there is a problem, you can try setting the spring.datasource.driver-class-name property:

spring:
  datasource:
    url: jdbc:mysql://localhost/readinglist
    username: dbuser
    password: dbpass
    driver-class-name: com.mysql.jdbc.Driver

Spring Boot will use this connection data when auto-configuring the DataSource bean. The DataSource bean will be pooled, using Tomcat’s pooling DataSource if it’s available on the classpath. If not, it will look for and use one of these other connection pool implementations on the classpath:

  • HikariCP
  • Commons DBCP
  • Commons DBCP 2

Although these are the only connection pool options available through auto-configuration, you are always welcome to explicitly configure a DataSource bean to use whatever connection pool implementation you’d like.

You may also choose to look up the DataSource from JNDI by setting the spring.datasource.jndi-name property:

spring:
  datasource:
    jndi-name: java:/comp/env/jdbc/readingListDS

If you set the spring.datasource.jndi-name property, the other datasource connection properties (if set) will be ignored.

There are many ways to influence the components that Spring Boot auto-configures by just setting a property or two. But this style of externalized configuration is not limited to the beans configured by Spring Boot. Let’s look at how you can use the very same property configuration mechanism to fine-tune your own application components.

3.2.2. Externally configuring application beans

Suppose that we wanted to show not just the title of a book on someone’s reading list, but also provide a link to the book on Amazon.com. And, not only do we want to provide a link to the book, but we also want to tag the book to take advantage of Amazon’s associate program so that if anyone purchases a book through one of the links in our application, we’d receive a small payment for the referral.

This is simple enough to do by changing the Thymeleaf template to render the title of each book as a link:

<a th:href="'http://www.amazon.com/gp/product/'
            + ${book.isbn}
            + '/tag=habuma-20'"
   th:text="${book.title}">Title</a>

This will work perfectly. Now if anyone clicks on the link and buys the book, I will get credit for the referral. That’s because “habuma-20” is my Amazon Associate ID. If you’d rather receive credit, you can easily change the value of the tag attribute to your Amazon Associate ID in the Thymeleaf template.

Even though it’s easy enough to change the Amazon Associate ID in the template, it’s still hard-coded. We’re only linking to Amazon from this one template, but we may later add features to the application where we link to Amazon from several pages. In that case, changes to the Amazon Associate ID would require changes to several places in the application code. That’s why details like this are often better kept out of the code so that they can be managed in a single place.

Rather than hard-code the Amazon Associate ID in the template, we can refer to it as a value in the model:

<a th:href="'http://www.amazon.com/gp/product/'
            + ${book.isbn}
            + '/tag=' + ${amazonID}"
   th:text="${book.title}">Title</a>

In addition, ReadingListController will need to populate the model at the key “amazonID” to contain the Amazon Associate ID. Again, we shouldn’t hard-code it, but instead refer to an instance variable. And that instance variable should be populated from the property configuration. Listing 3.4 shows the new ReadingListController, which populates the model from an injected Amazon Associate ID.

Listing 3.4. ReadingListController modified to accept an Amazon ID

As you can see, the ReadingListController now has an associateId property and a corresponding setAssociateId() method through which the property can be set. And readersBooks() now adds the value of associateId to the model under the key “amazonID”.

Perfect! Now the only question is where associateId gets its value.

Notice that ReadingListController is now annotated with @Configuration-Properties. This specifies that this bean should have its properties injected (via setter methods) with values from configuration properties. More specifically, the prefix attribute specifies that the ReadingListController bean will be injected with properties with an “amazon” prefix.

Putting this all together, we’ve specified that ReadingListController should have its properties injected from “amazon”-prefixed configuration properties. ReadingListController has only one property with a setter method—the associateId property. Therefore, all we need to do to specify the Amazon Associate ID is to add an amazon.associateId property in one of the supported property source locations.

For example, we could set that property in application.properties:

amazon.associateId=habuma-20

Or in application.yml:

amazon:
  associateId: habuma-20

Or we could set it as an environment variable, specify it as a command-line argument, or add it in any of the other places where configuration properties can be set.

Enabling configuration properties

Technically, the @Configuration-Properties annotation won’t work unless you’ve enabled it by adding @EnableConfigurationProperties in one of your Spring configuration classes. This is often unnecessary, however, because all of the configuration classes behind Spring Boot auto-configuration are already annotated with @EnableConfigurationProperties. Therefore, unless you aren’t taking advantage of auto-configuration at all (and why would that ever happen?), you shouldn’t need to explicitly use @EnableConfigurationProperties.

It’s also worth noting that Spring Boot’s property resolver is clever enough to treat camel-cased properties as interchangeable with similarly named properties with hyphens or underscores. In other words, a property named amazon.associateId is equivalent to both amazon.associate_id and amazon.associate-id. Feel free to use the naming convention that suits you best.

Collecting properties in one class

Although annotating ReadingListController with @ConfigurationProperties works fine, it may not be ideal. Doesn’t it seem a little odd that the property prefix is “amazon” when, in fact, ReadingListController has little to do with Amazon? Moreover, future enhancements might present the need to configure properties unrelated to Amazon in ReadingListController.

Instead of capturing the configuration properties in ReadingListController, it may be better to annotate a separate bean with @ConfigurationProperties and let that bean collect all of the configuration properties. AmazonProperties in listing 3.5, for example, captures the Amazon-specific configuration properties.

Listing 3.5. Capturing configuration properties in a bean

With AmazonProperties capturing the amazon.associateId configuration property, we can change ReadingListController (as shown in listing 3.6) to pull the Amazon Associate ID from an injected AmazonProperties.

Listing 3.6. ReadingListController injected with AmazonProperties

ReadingListController is no longer the direct recipient of configuration properties. Instead, it obtains the information it needs from the injected AmazonProperties bean.

As we’ve seen, configuration properties are useful for tweaking both auto-configured components as well as the details injected into our own application beans. But what if we need to configure different properties for different deployment environments? Let’s take a look at how to use Spring profiles to set up environment-specific configuration.

3.2.3. Configuring with profiles

When applications are deployed to different runtime environments, there are usually some configuration details that will differ. The details of a database connection, for instance, are likely different in a development environment than in a quality assurance environment, and different still in a production environment. The Spring Framework introduced support for profile-based configuration in Spring 3.1. Profiles are a type of conditional configuration where different beans or configuration classes are used or ignored based on what profiles are active at runtime.

For instance, suppose that the security configuration we created in listing 3.1 is for production purposes, but the auto-configured security configuration is fine for development. In that case, we can annotate SecurityConfig with @Profile like this:

@Profile("production")
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

...

}

The @Profile annotation used here requires that the “production” profile be active at runtime for this configuration to be applied. If the “production” profile isn’t active, this configuration will be ignored and, for lack of another overriding security configuration, the auto-configured security configuration will be applied.

Profiles can be activated by setting the spring.profiles.active property using any of the means available for setting any other configuration property. For example, you could activate the “production” profile by running the application at the command line like this:

$ java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.profiles.active=production

Or you can add the spring.profiles.active property to application.yml:

spring:
  profiles:
    active: production

Or you could set an environment variable and put it in application.properties or use any of the other options mentioned at the beginning of section 3.2.

But because Spring Boot auto-configures so much for you, it would be very inconvenient to write explicit configuration just so that you can have a place to put @Profile. Fortunately, Spring Boot supports profiles for properties set in application.properties and application.yml.

To demonstrate profiled properties, suppose that you want a different logging configuration in production than in development. In production, you’re only interested in log entries at WARN level or higher, and you want to write the log entries to a log file. In development, however, you only want things logged to the console and at DEBUG level or higher.

All you need to do is create separate configurations for each environment. How you do that, however, depends on whether you’re using a properties file configuration or YAML configuration.

Working with profile-specific properties files

If you’re using application.properties to express configuration properties, you can provide profile-specific properties by creating additional properties files named with the pattern “application-{profile}.properties”.

For the logging scenario, the development configuration would be in a file named application-development.properties and contain properties for verbose, consolewritten logging:

logging.level.root=DEBUG

But for production, application-production.properties would configure logging to be at WARN level and higher and to write to a log file:

logging.path=/var/logs/
logging.file=BookWorm.log
logging.level.root=WARN

Meanwhile, any properties that aren’t specific to any profile or that serve as defaults (in case a profile-specific configuration doesn’t specify otherwise) can continue to be expressed in application.properties:

amazon.associateId=habuma-20
logging.level.root=INFO
Configuring with multi-profile YAML files

If you’re using YAML for configuration properties, you can follow a similar naming convention as for properties files. That is, you can create YAML files whose names follow a pattern of “application-{profile}.yml” and continue to put non-profiled properties in application.yml.

But with YAML, you also have the option of expressing configuration properties for all profiles in a single application.yml file. For example, the logging configuration we want can be declared in application.yml like this:

logging:
  level:
    root: INFO

---

spring:
  profiles: development

logging:
  level:
    root: DEBUG

---

spring:
  profiles: production

logging:
  path: /tmp/
  file: BookWorm.log
  level:
    root: WARN

As you can see, this application.yml file is divided into three sections by a set of triple hyphens (---). The second and third sections each specify a value for spring.profiles. This property indicates which profile each section’s properties apply to. The properties defined in the middle section apply to development because it sets spring.profiles to “development”. Similarly, the last section has spring.profiles set to “production”, making it applicable when the “production” profile is active.

The first section, on the other hand, doesn’t specify a value for spring.profiles. Therefore, its properties are common to all profiles or are defaults if the active profile doesn’t otherwise have the properties set.

Aside from auto-configuration and external configuration properties, Spring Boot has one other trick up its sleeve to simplify a common development task: it automatically configures a page to be displayed when an application encounters any errors. To wrap up this chapter, we’ll take a look at Spring Boot’s error page and see how to customize it to fit our application.

3.3. Customizing application error pages

Errors happen. Even some of the most robust applications running in production occasionally run into trouble. Although it’s important to reduce the chance that a user will encounter an error, it’s also important that your application still present itself well when displaying an error page.

In recent years, creative error pages have become an art form. If you’ve ever seen the Star Wars–inspired error page at GitHub.com or DropBox.com’s Escher-like error page, you have an idea of what I’m talking about.

I don’t know if you’ve encountered any errors while trying out the reading-list application, but if so you’ve probably seen an error page much like the one in figure 3.1.

Figure 3.1. Spring Boot’s default whitelabel error page.

Spring Boot offers this “whitelabel” error page by default as part of auto-configuration. Even though it’s slightly more attractive than a stack trace, it doesn’t compare with some of the great works of error art available on the internet. In the interest of presenting your application failures as masterpieces, you’ll probably want to create a custom error page for your applications.

The default error handler that’s auto-configured by Spring Boot looks for a view whose name is “error”. If it can’t find one, it uses its default whitelabel error view shown in figure 3.1. Therefore, the easiest way to customize the error page is to create a custom view that will resolve for a view named “error”.

Ultimately this depends on the view resolvers in place when the error view is being resolved. This includes

  • Any bean that implements Spring’s View interface and has a bean ID of “error” (resolved by Spring’s BeanNameViewResolver)
  • A Thymeleaf template named “error.html” if Thymeleaf is configured
  • A FreeMarker template named “error.ftl” if FreeMarker is configured
  • A Velocity template named “error.vm” if Velocity is configured
  • A JSP template named “error.jsp” if using JSP views

Because we’re using Thymeleaf for the reading-list application, all we must do to customize the error page is create a file named “error.html” and place it in the templates folder along with our other application templates. Listing 3.7 shows a simple, yet effective replacement for the default whitelabel error page.

Listing 3.7. Custom error page for the reading-list application

This custom error template should be named “error.html” and placed in the templates directory for the Thymeleaf template resolver to find. For a typical Maven or Gradle build, that means putting it in src/main/resources/templates so that it’s at the root of the classpath during runtime.

For the most part, this is a simple Thymeleaf template that displays an image and some error text. There are two specific pieces of information that it also renders: the request path of the error and the exception message. These aren’t the only details available to an error page, however. By default, Spring Boot makes the following error attributes available to the error view:

  • timestamp—The time that the error occurred
  • status—The HTTP status code
  • error—The error reason
  • exception—The class name of the exception
  • message—The exception message (if the error was caused by an exception)
  • errors—Any errors from a BindingResult exception (if the error was caused by an exception)
  • trace—The exception stack trace (if the error was caused by an exception)
  • path—The URL path requested when the error occurred

Some of these attributes, such as path, are useful when communicating the problem to the user. Others, such as trace, should be used sparingly, be hidden, or be used cleverly on the error page to keep the error page as user-friendly as possible.

You’ll also notice that the template references an image named MissingPage.png. The actual content of the image is unimportant, so feel free to flex your graphic design muscles and come up with an image that suits you. But be sure to put it in src/main/resources/static or src/main/resources/public so that it can be served when the application is running.

Figure 3.2 shows what the user will see when an error occurs. It may not quite be a work of art, but I think it raises the aesthetics of the application’s error page a notch or two.

Figure 3.2. A custom error page exhibits style in the face of failure

3.4. Summary

Spring Boot eliminates much of the boilerplate configuration that’s often required in Spring applications. But by letting Spring Boot do all of the configuration, you’re relying on it to configure components in ways that suit your application. When auto-configuration doesn’t fit your needs, Spring Boot allows you to override and fine-tune the configuration it provides.

Overriding auto-configuration is a simple matter of writing explicit Spring configuration as you would in the absence of Spring Boot. Spring Boot’s auto-configuration is designed to favor application-provided configuration over its own auto-configuration.

Even when auto-configuration is suitable, you may need to adjust a few details. Spring Boot enables several property resolvers that let you tweak configuration by setting properties as environment variables, in properties files, in YAML files, and in several other ways. This same property-based configuration model can even be applied to application-defined components, enabling value-injection into bean properties from external configuration sources.

Spring Boot also auto-configures a simple whitelabel error page. Although it’s more user-friendly than an exception and stack trace, the whitelabel error page still leaves a lot to be desired aesthetically. Fortunately, Spring Boot offers several options for customizing or completely replacing the whitelabel error page to suit an application’s specific style.

Now that we’ve written a complete application with Spring Boot, we should verify that it actually does what we expect it to do. That is, instead of poking at it in the web browser manually, we should write some automated and repeatable tests that exercise the application and prove that it’s working correctly. That’s exactly what we’ll do in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset