Spring Bootification Process

Overview

The following guide will focus on the Bootification of an existing spring based Java application. Every application has the potential to take a different Bootification journey depending on a number of different factors:

  • The application's dependency and build management tool i.e. Ant, Maven, Gradle
  • Executable Jar/War vs Standalone War
  • Customized Spring application context configuration
  • Appetite to refactor xml configuration to java configuration
  • Appetite to update dependency versions
  • If there is a frontend component

Why Bootify

  • Highly opinionated; highly customizable
  • Build a runnable application with minimal upfront configuration
  • Take advantage of Spring Boot Starter modules
  • Leverage Spring Boot's parent pom for dependency and plugin management
  • Excellent test support for each vector of the application
  • Highly portable across containers
  • Great for microservices and monoliths
  • Multiple configuration options for logging
  • Easy to build an configuration friendly application
  • Local development made easy
  • Low development setup overhead
  • The Spring development team!
  • Production ready administration endpoints
  • Awesome IDE support from IntelliJ, STS, NetBeans, VSCode
  • Spring Boot dev tools included
  • Great support and integration with Pivotal Cloud Foundry - try it at run.pivotal.io facade Image by Rohit Kelapure via http://cloud.rohitkelapure.com/2017/10/pcf-is-best-place-to-run-spring-apps.html

Resources

Bootification Process

Backup Project

  1. Before you begin the Bootification process you should backup and/or create a branch of your project so that you can always view differences between the original and the bootified application.
    • Backup and/or create branch of application code

Project Structure

  1. Review the best practices for structuring the packages for Spring Boot project and make updates accordingly:
  2. Review the code structure and ensure that it aligns with Maven default code structure

Dependency Management

  1. If a project is not using a build tool, then the recommendation is to use Gradle as the build tool, however if the project already uses either Maven or Gradle then it may be better to continue with the existing tool.

  2. For Maven based projects, Spring Boot uses a Parent pom.xml and Spring Boot Starter modules to provide necessary Spring dependencies and known versions of 3rd party dependencies that work well together. Maven will bring in transitive dependencies for you so it is always good to baseline the application's dependency tree before and after bootification. See Spring Boot Parent Pom v1.5.x for more detail: GitHub - spring-boot/pom.xml

    • If the application is using ant, then make sure all the build dependencies are in one folder and you're following the Spring Boot standards for project structure setup.
    • If the application is already using maven then you can output the dependency graph through the following:
      • From a bash shell (git bash), navigate to the root application directory that contains the pom.xml
      • Execute the following mvn dependency:tree > mvn-dependency-tree-output.log
      • Commit the dependency tree output file mvn-dependency-tree-output.log to the code repository
  3. For Gradle based projects, Spring Boot brings in a plugin which manages the dependencies of third party libraries. Dependency management plugin will bring in transitive dependencies. See Spring Boot Parent Pom v1.5.x for more detail: GitHub - spring-boot/pom.xml

    • If the application is already using gradle then you can output the dependency graph through the following:
      • From a bash shell (git bash), navigate to the root application directory that contains the build.gradle
      • Execute the following gradle dependencies > gradle-dependency-tree-output.log
      • Commit the dependency tree output file gradle-dependency-tree-output.log to the code repository
  4. If you are teasing out a slice of functionality from a monolith application then you should begin your journey by creating a new Spring Boot project with Spring Initializr. Spring Initializr is like a shopping cart for all the dependencies that you might need for you application.

    • Navigate to Spring Initializr, select dependencies and create new project
    • Migrate classes with packages, configuration, and static content to the Spring Initialized shell project
      • Java classes -> src/main/java
      • Configuration -> src/main/resources/config
      • Static Content -> src/main/webapp/WEB-INF/
  5. For Maven based projects, Spring Boot uses a parent pom.xml for dependency and plugin management.

    • If the application is a stand alone project with no parent pom.xml then add the Spring Boot Starter Parent section near the top of your pom.xml

      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>1.5.X.RELEASE</version>       
      </parent>
      
- If the application already contains a parent `pom.xml` and it will be the go-forward parent `pom.xml` then you can add a `<dependencyManagement>` section to your `pom.xml` and include the `spring-boot-dependencies` to use Spring Boot's dependency management.
  - See: [Using Spring Boot without the Parent Pom](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-maven-without-a-parent)

    ```
    <dependencyManagement>
         <dependencies>
            <dependency>            
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.5.X.RELEASE</version>
                <type>pom</type>            
            </dependency>
        </dependencies>
    </dependencyManagement>
    ```
  1. For Gradle based projects, Spring Boot brings in a plugin which manages the dependencies of third party libraries. Dependency management plugin will bring in transitive dependencies

    • If the application is a has just one module then the Spring Boot plugin can be added to the root build.gradle file
        buildscript {
            ext {
                springBootVersion = '1.5.10.RELEASE'
            }
            repositories {
                maven {  
                    credentials {
                        username mavenUsername
                        password mavenPassword
                    }
                    url 'https://repo.web.xyz.com/artifactory/Maven-Releases/'
                }
            }
            dependencies {
                classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
            }
        }
    
        apply plugin: 'org.springframework.boot'
    
- If the application has [multiple projects](https://www.petrikainulainen.net/programming/gradle/getting-started-with-gradle-creating-a-multi-project-build/) at the same level, then a root build.gradle describes elements that are common to all the projects. Since Spring Boot is likely to be managing the dependencies of all the child projects, it can be added in as a plugin to the child projects.

```
    buildscript {
        ext {
            springBootVersion = '1.5.10.RELEASE'
        }
        repositories {
            maven {  
                credentials {
                    username mavenUsername
                    password mavenPassword
                }
                url 'https://repo.web.xyz.com/artifactory/Maven-Releases/'
            }
        }
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    subprojects {
        ...
        apply plugin: 'org.springframework.boot'
        ...
    }
```
-  Gradle Spring Boot plugin also tends to [repackage a jar](https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-gradle-plugin.html#build-tool-plugins-gradle-repackage-configuration) file. This is useful only for end applications and not for intermediate jar files. To prevent repacking for intermediate jars disable to boot repackage step the following way:
```
    bootRepackage {
        enabled = false
    }       
```
  1. At this point you should be ready to add your first Spring Boot starter dependency and test case to your application.

    • For Maven, Add the spring-boot-starter-test artifact:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
      
    • For Gradle: testCompile('org.springframework.boot:spring-boot-starter-test')

- Create a parent package in `src/test/java` matching your package structure from `src/main/java` i.e. `com.companyname.appname`

- Add a Spring Application Context load test case and run it. You can use the following test as a baseline for ensuring the application has the necessary dependencies and is configured correctly. The test will attempt to fire up the application and create the Spring Application Context. The test case should fail at this point.

    ```java
    package com.companyname.appname;

    import static org.junit.Assert.assertNotNull;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.context.ApplicationContext;
    import org.springframework.test.context.junit4.SpringRunner;

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ApplicationTests {
        @Autowired
        ApplicationContext context;

        @Test
        public void contextLoads() {
            assertNotNull(context);
        }
    }
    ```
  1. One approach to creating a Spring Boot application is to add a java main method inside of a class annotated with @SpringBootApplication. The @SpringBootApplication convenience annotation encapsulates the @ComponentScan, @Configuration, @EnableAutoConfiguration annotations. Additional information about @SpringBootApplication can be found here: 18. Using the @SpringBootApplication annotation.

    • If the application does not have 1 common parent package in src/main/java then create that now. For example, com.companyname.appname
    • Create a Spring Boot application class in the package created in the prior step.

      package com.companyname.appname;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.builder.SpringApplicationBuilder;
      
      @SpringBootApplication
      public class Application {
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
      }
      
  2. The application will probably have many individual Spring dependencies i.e. spring-core, spring-jdbc, spring-context etc. The next step is to start removing explicit dependency versions and rely on Spring Boot’s starter modules and dependency management to import dependencies and include versions. One of the advantages of relying on Spring Boot as parent POM is that acts as a Bill of Materials for all the dependencies for that version of Spring Boot that work will together. Here is the catalog of Spring Boot starter modules: Spring Boot Reference Guide - Using Spring Boot Starter

    • NOTE: You can use the test case above to iterate quickly on updating your dependency graph i.e. Run Test to failure, add dependency, repeat
    • For Maven projects, If you rely on a parent pom already you can leverage Spring Boot as the Bill of Materials by adding a <dependencyManagement> section to your pom.xml
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>1.5.10.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>                   
                </dependency>
            </dependencies>
        </dependencyManagement>    
    
    • Comment out spring dependencies and begin replacing with spring-boot-starter-* modules
    • Comment out explicit versions <version>x.x.x</version> NOTE: A compromise will most likely need to be made for some 3rd party dependencies that are not managed by Spring Boot's dependency management.
    • Repeat for each No Class Def Found Exception NOTE: If the application is a couple years old there is a good chance that it has accumulated some dependency debt. This is a good opportunity to only pull in dependencies that the application needs.

Auto-Configuration

Auto Configuration is one of the core tenants of Spring Boots opinionated framework. Spring Boot auto-configuration inspects the jar dependencies and attempts to configure the Spring application context on your behalf.

Exclude Auto-Configuration

During the Bootification process you might find that by adding a jar dependency you cause an issue within your Spring Application context. The exception stack trace might indicate that there are multiple beans of the same type and Spring is unable to decipher which one to use.

If this occurs, then you should review the types of beans to see if they are related to JMS, DataSource, Web Services, etc.

  • Enable debug=true and restart the application. Spring Boot will log an Auto-Configuration report which might provide some more insight into the root cause of the issue.
  • If you discover that you have an unwanted Auto-Configuration class running, then attempt to exclude like so:
@SpringBootApplication(exclude = JmsAutoConfiguration.class)

or via the following property: spring.autoconfigure.exlude=JmsAutoConfiguration

WAR Configuration

Spring Boot allows you to configure an executable war, executable jar or standalone war artifact.

  1. Review the following "How To Guide" to create a deployable War file: How to create a deployable War file
  2. Review the following "How To Guide" to convert an existing application to Spring Boot: How to Create an existing application to Spring Boot
  3. Review and implement the following recipe: Create Spring Boot WAR
  4. Replace the web.xml with a Java configuration class which includes @Bean definitions for Servlets and Filters via ServletRegistrationBean and FilterRegistrationBean, respectively.
    • TODO - How test the web.xml to java config conversion

Environment Configuration

Spring Boot has multitude of options to configure the applications environment variables. To reduce configuration drift, you should aim to reduce the number of environment specific files/profiles to the least common denominator.

  1. Review Spring Boot Configuration
  2. Review How to guide for properties and configuration
  3. If there are any Spring Application Context xml files then use the @ImportResource annotation to import it. If the xml file is small then this a good opportunity to replace with Java configuration.
- If there are bean definitions in the Spring application context file that need to be environmentally friendly then this is good time to extract them into a Java Configuration class. For example, a `DataSource` bean should be extracted into Java configuration class annotated with `@Confguration`. This will provide an easy hook later to configure an embedded db locally and a PCF user provided service when operating in the cloud profile. See the Spring-Music CF example application: [Spring Music](https://github.com/cloudfoundry-samples/spring-music)
  • If there are groupings of properties that belong together then you should create @ConfigurationProperties annotated class to house them.

Logging Configuration

Spring Boot provides an easy to use spring-boot-starter-logging module which will attempt to wire up your logging implementation based on the class path.

  1. Review How to guide for Spring Boot logging

  2. If your explicit definition of log level's is small then you may choose to get rid of your logback.xml or log4j.properites file all together and add the log levels to the application.[properties|yml file

    • Add log level definitions to application.[properties|yml]. For example, logging.level.org.apache.cxf=INFO

Spring Boot Maven Plugin

The spring-boot-maven-plugin will, by default, add jars for running an embedded container in the lib-provided folder within your jar or war artifact. If you are unable to deploy an executable artifact using an embedded servlet container then you must take an extra step to either remove the plugin completely or add the maven-war-plugin to your pom.xml explicitly.

Actuator Endpoints

Spring Boot combined with the Spring Boot Actuator Starter module enriches the telemetry capabilities of your application by providing a set of production ready endpoints to give you more insight into the health, performance, and environment configuration of your application. It also provides you with troubleshooting endpoints that allows you to trace http endpoints and generate heap dumps, emit custom metrics, and much more.

お問い合わせ