Book Image

Learning Spring Boot 3.0 - Third Edition

By : Greg L. Turnquist
Book Image

Learning Spring Boot 3.0 - Third Edition

By: Greg L. Turnquist

Overview of this book

Spring Boot 3 brings more than just the powerful ability to build secure web apps on top of a rock-solid database. It delivers new options for testing, deployment, Docker support, and native images for GraalVM, along with ways to squeeze out more efficient usage of existing resources. This third edition of the bestseller starts off by helping you build a simple app, and then shows you how to secure, test, bundle, and deploy it to production. Next, you’ll familiarize yourself with the ability to go “native” and release using GraalVM. As you advance, you’ll explore reactive programming and get a taste of scalable web controllers and data operations. The book goes into detail about GraalVM native images and deployment, teaching you how to secure your application using both routes and method-based rules and enabling you to apply the lessons you’ve learned to any problem. If you want to gain a thorough understanding of building robust applications using the core functionality of Spring Boot, then this is the book for you. By the end of this Spring Boot book, you’ll be able to build an entire suite of web applications using Spring Boot and deploy them to any platform you need.
Table of Contents (17 chapters)
1
Part 1: The Basics of Spring Boot
3
Part 2: Creating an Application with Spring Boot
8
Part 3: Releasing an Application with Spring Boot
12
Part 4: Scaling an Application with Spring Boot

Customizing the setup with configuration properties

So, we’ve decided to pick up Spring Boot and we started adding some of its magical starters. As discussed earlier in this chapter, this will activate a handful of Spring beans.

Assuming we were building a web app and selected Spring MVC’s spring-boot-starter-web, it would activate embedded Apache Tomcat as the servlet container of choice. And with that, Spring Boot is forced to make a lot of assumptions.

For example, what port should it listen on? What about the context path? Secure Sockets Layer (SSL)? Threads? There are a dozen other parameters to fire up a Tomcat servlet container.

And Spring Boot will pick them. So, where does that leave us? Are we stuck with them? No.

Spring Boot introduces configuration properties as a way to plug property settings into any Spring bean. Spring Boot may load certain properties with default values, but we have the opportunity to override them.

The simplest example is the first property mentioned earlier in this section – the server port.

Spring Boot launches with a default port in mind, but we can change it. This can be done by first adding an application.properties file to our src/main/resources folder. Inside that file, we must merely add the following:

server.port=9000

This Java property file, a file format supported since the early days of Java 1.0, contains a list of key-value pairs separated by an equals sign (=). The left-hand side contains the key (server.port) and the right-hand side contains the value (9000).

When a Spring Boot application launches, it will look for this file and scan in all its property entries, and then apply them. And with that, Spring Boot will switch from its default port of 8080 to port 9000.

Note

The server port property is really handy when you need to run more than one Spring Boot-based web application on the same machine.

Spring Boot is not limited to the handful of properties that can be applied to embedded with Apache Tomcat. Spring Boot has alternative servlet container starters, including Jetty and Undertow. We’ll learn how to pick and choose servlet containers in Chapter 2, Creating a Web Application with Spring Boot.

What’s important is knowing that no matter which servlet container we use, the servlet.port property will be applied properly to switch the port the servlet will serve web requests on.

Perhaps you’re wondering why? Having a common port property between servlet containers eases choosing servlet containers.

Yes, there are container-specific property settings if we needed that level of control. But generalized properties make it easy for us to select our preferred container and move to a port and context path of choice.

But we’re getting ahead of ourselves. The point of Spring Boot property settings isn’t about servlet containers. It’s about creating opportunities to make our applications flexible at runtime. And the next section will show us how to create configuration properties.

Creating custom properties

At the beginning of this section, I mentioned that configuration properties can be applied to any Spring bean. This applies not just to Spring Boot’s autoconfigured beans, but to our own Spring beans!

Look at the following code:

@Component
@ConfigurationProperties(prefix = "my.app")
public class MyCustomProperties {
  // if you need a default value, assign it here or the 
     constructor
  private String header;
  private String footer;
  // getters and setters
}

The preceding code can be described as follows:

  • @Component is Spring Framework’s annotation to automatically create an instance of this class when the application starts and register it with the application context.
  • @ConfigurationProperties is a Spring Boot annotation that labels this Spring bean as a source of configuration properties. It indicates that the prefix of such properties will be my.app.

The class itself must adhere to standard Java bean property rules (described earlier in this chapter). It will create various fields and include proper getters and setters – in this case, getHeader() and getFooter().

With this class added to our application, we can include our own custom properties, as follows:

application.properties:
my.app.header=Learning Spring Boot 3
my.app.footer=Find all the source code at https://github.com/PacktPublishing/Learning-Spring-Boot-3.0

These two lines will be read by Spring Boot and injected into the MyCustomProperties Spring bean before it gets injected into the application context. We can then inject that bean into any relevant component in our app.

But a much more tangible concept would be including properties that should never be hardcoded into an application, as follows:

@Component
@ConfigurationProperties(prefix = "app.security")
public class ApplicationSecuritySettings {
  private String githubPersonalCode;
  public String getGithubPersonalCode() {
    return this.githubPersonalCode;
  }
  public void setGithubPersonalCode
    (String githubPersonalCode) {
      this.githubPersonalCode = githubPersonalCode;
  }
}

The preceding code is quite similar to the earlier code but with the following differences:

  • The prefix for this class’s properties is app.security
  • The githubPersonalCode field is a string used to store an API passcode used to presumably interact with GitHub through its OAuth API

An application that needs to interact with GitHub’s API will need a passcode to get in. We certainly do not want to bake that into the application. What if the passcode were to change? Should we rebuild and redeploy the whole application just for that?

No. It’s best to delegate certain aspects of an application to an external source. How can we do that? The next section will show how!

Externalizing application configuration

Did I mention an external source in the previous section? Yes. That’s because while you can put properties into an application.properties file that gets baked into the application, that isn’t the only way to do things. There are more options when it comes to providing Spring Boot with application properties that aren’t solely inside the deliverable.

Spring Boot not only looks for that application.properties tucked inside our JAR file upon startup. It will also look directly in the folder from where we run the application to find any application.properties files there and load them.

We can deliver our JAR file along with an application.properties file right beside it as an immediate way to override pre-baked properties (ours or Spring Boot’s!).

But wait, there’s more. Spring Boot also supports profiles.

What are profiles? We can create profile-specific property overrides. A good example would be one configuration for the development environment, but a different one for our test bed, or production.

In essence, we can create variations of application.properties, as shown here:

  • application-dev.properties is a set of properties applied when the dev profile is activated
  • application-test.properties is applied when the test profile is applied
  • application.properties is always applied, so it could be deemed the production environment

Perhaps an example is in order?

Imagine having our database connection details captured in a property named my.app.databaseUrl, as shown here:

application.properties:
my.app.databaseUrl=https://user:[email protected]:1234/prod/

The test bed of our system surely won’t be linked to the same production server. So, instead, we must provide an application-test.properties with the following override:

application-test.properties:
my.app.databaseUrl=http://user:[email protected]:1234/test/

To activate this override, simply include -Dspring.profiles.active=test as an extra argument to the Java command to run our app.

It’s left as an exercise for you to think up overrides for a development environment.

Note

Since production is the end state of an application, it’s usually best practice to let application.properties be the production version of property settings. Use other profiles for other environments or configurations.

Notice earlier how we said Spring Boot will scan either application.properties files embedded inside our JAR as well as outside the JAR? The same goes for profile-specific property files.

So far, we’ve mentioned internal and external properties, both default and profile-specific. In truth, there are many more ways to bind property settings into a Spring Boot application.

Several are included in this list, ordered from lowest priority to highest priority:

  • Default properties provided by Spring Boot’s SpringApplication.setDefaultProperties() method.
  • @PropertySource-annotated @Configuration classes.
  • Config data (such as application.properties files).
  • A RandomValuePropertySource that has properties only in random.*.
  • OS environment variables.
  • Java system properties (System.getProperties()).
  • JNDI attributes from java:comp/env.
  • ServletContext init parameters.
  • ServletConfig init parameters.
  • Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  • Command-line arguments.
  • The properties attribute on your tests. This is available with the @SpringBootTest annotation and also slice-based testing (which we’ll cover later in Chapter 5, Testing with Spring Boot).
  • @TestPropertySource annotations on your tests.
  • DevTools global settings properties (the $HOME/.config/spring-boot directory when Spring Boot DevTools is active).

Config files are considered in the following order:

  • Application properties packaged inside your JAR file.
  • Profile-specific application properties inside your JAR file.
  • Application profiles outside your JAR file.
  • Profile-specific application properties outside your JAR file.

It’s a bit of a tangent, but we can also ensure certain beans are only activated when certain profiles are activated.

And properties aren’t confined to injecting data values. The following section will show you how to make property-based beans.

Configuring property-based beans

Properties aren’t just for providing settings. They can also govern which beans are created and when.

The following code is a common pattern for defining beans:

@Bean 
@ConditionalOnProperty(prefix="my.app", name="video")
YouTubeService youTubeService() {
    return new YouTubeService();
}

The preceding code can be explained as follows:

  • @Bean is Spring’s annotation, signaling that the following code should be invoked when creating an application context and the created instance is added as a Spring bean
  • @ConditionalOnProperty is Spring Boot’s annotation to conditionalize this action based on the existence of the property

If we set my.app.video=youtube, then a bean of the YouTubeService type will be created and injected into the application context. Actually, in this scenario, if we define my.app.video with any value, it will create this bean.

If the property does not exist, then the bean won’t be created. This saves us from having to deal with profiles.

It’s possible to fine-tune this even further, as shown here:

@Bean 
@ConditionalOnProperty(prefix="my.app", name="video", havingValue="youtube")
YouTubeService youTubeService() {
    return new YouTubeService();
}
@Bean 
@ConditionalOnProperty(prefix="my.app", name="video", havingValue="vimeo")
VimeoService vimeoService() {
    return new VimeoService();
}

This preceding code can be explained as follows:

  • @Bean, like before, will define Spring beans to be created and added to the application context
  • @ConditionalOnProperty will conditionalize these beans to only be created if the named property has the stated values

This time, if we set my.app.video=youtube, a YouTubeService will be created. But if we were to set my.app.video=vimeo, a VimeoService bean would be created instead.

All of this presents a rich way to define application properties. We can create all the configuration beans we need. We can apply different overrides based on various environments. And we can also conditionalize which variants of various services are created based on these properties.

We can also control which property settings are applicable in a given environment, be it a test bed, a developer’s work environment, a production setting, or a backup facility. We can even apply additional settings based on being in different cloud providers!

And as a bonus, most modern IDEs (IntelliJ IDEA, Spring Tool Suite, Eclipse, and VS Code) offer autocompletion inside application.properties files! We will cover this in more detail throughout the rest of this book.

Now, the last thing we need to craft a powerful application is the means to maintain it. This will be covered in the next section.