Debunking The Magic behind Spring Boot 2

Author: Shazin Sadakath


This post is a continuation of Debunking The Magic behind Spring Boot (recommended to read this) which introduced readers on how to write a custom Spring Boot Auto Configuration and using it to initial and inject a GreeterService. In this post however we are going to look at how to use configuration properties to create and inject beans with property values loaded from configuration files. Furthermore we will look at how to Failure Analyzer provided by Spring Boot to display meaningful messages when an exception is thrown. 

Using Configuration Properties

The following GreeterProperties class is used load configuration properties from application.properties file.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("greeter")
public class GreeterProperties {

    private String greeting;

    private String salutation;

    public String getGreeting() {
        return greeting;
    }

    public String getSalutation() {
        return salutation;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    public void setSalutation(String salutation) {
        this.salutation = salutation;
    }
}

And the application.properties file can have following properties

greeter.greeting=Welcome
greeter.salutation=Mr

Loading and Injecting Spring Beans based on Configuration properties

The following GreeterServiceAutoConfiguration can be used to conditional load GreeterService based on configuration properties.

@Configuration
@ConditionalOnClass(GreeterService.class)
@EnableConfigurationProperties(GreeterProperties.class)
public class GreeterServiceAutoConfiguration {

    private final GreeterProperties greeterProperties;

    public GreeterServiceAutoConfiguration(GreeterProperties greeterProperties) {
        this.greeterProperties = greeterProperties;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "greeter", value = "greeting")
    public GreeterService greeterService() {
        return new GreeterService(greeterProperties.getGreeting(), greeterProperties.getSalutation());
    }

}

In the above auto configuration @EnableConfigurationProperties annotation is used to enable the configuration properties loading and @ConditionalOnProperty annotation on greeterService bean is used to load the bean only if the greeter.greeting property is present in application.properties. Furthermore it will use the values specified in the configuration file as greeting and salutation.

Displaying meaningful error messages with Failure Analyzer

The following InvalidSalutationException is throw from GreeterService whenever it is initialized with a salutation which doesn't begin with a Captial letter. 

public class InvalidSalutationException extends RuntimeException {

    private String salutation;

    public InvalidSalutationException(String message, String salutation) {
        super(message);
        this.salutation = salutation;
    }

    public String getSalutation() {
        return salutation;
    }

    public void setSalutation(String salutation) {
        this.salutation = salutation;
    }
}

But without a Failuer Analyzer the error message would look like below in the console when Spring Boot starts.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationRunner' defined in com.github.shazin.app.GreetingApplication: Unsatisfied dependency expressed through method 'applicationRunner' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'greeterService' defined in class path resource [com/github/shazin/greeter/starter/config/GreeterServiceAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.shazin.greeter.service.GreeterService]: Factory method 'greeterService' threw exception; nested exception is com.github.shazin.greeter.service.exception.InvalidSalutationException: Invalid Salutation
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:729) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:470) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1254) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1103) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:541) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
 at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
 at com.github.shazin.app.GreetingApplication.main(GreetingApplication.java:13) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'greeterService' defined in class path resource [com/github/shazin/greeter/starter/config/GreeterServiceAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.shazin.greeter.service.GreeterService]: Factory method 'greeterService' threw exception; nested exception is com.github.shazin.greeter.service.exception.InvalidSalutationException: Invalid Salutation
 at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:587) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1254) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1103) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:541) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:815) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:721) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 ... 18 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.github.shazin.greeter.service.GreeterService]: Factory method 'greeterService' threw exception; nested exception is com.github.shazin.greeter.service.exception.InvalidSalutationException: Invalid Salutation
 at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:579) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 ... 31 common frames omitted
Caused by: com.github.shazin.greeter.service.exception.InvalidSalutationException: Invalid Salutation
 at com.github.shazin.greeter.service.GreeterService.(GreeterService.java:14) ~[classes/:na]
 at com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration.greeterService(GreeterServiceAutoConfiguration.java:26) ~[classes/:na]
 at com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration$$EnhancerBySpringCGLIB$$cae86c7c.CGLIB$greeterService$0() ~[classes/:na]
 at com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration$$EnhancerBySpringCGLIB$$cae86c7c$$FastClassBySpringCGLIB$$32d72113.invoke() ~[classes/:na]
 at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) ~[spring-core-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 at com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration$$EnhancerBySpringCGLIB$$cae86c7c.greeterService() ~[classes/:na]
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_73]
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_73]
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_73]
 at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_73]
 at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
 ... 32 common frames omitted

But using a Failure Analyzer like following 

import com.github.shazin.greeter.service.exception.InvalidSalutationException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

public class InvalidGreeterSalutationFailureAnalyzer extends AbstractFailureAnalyzer {

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, InvalidSalutationException cause) {
        return new FailureAnalysis(String.format("The greeter service could not be auto-configured properly: '%s' is an invalid salutation", cause.getSalutation()),
                             "A valid salutation must begin with an upper-case letter",
                                   cause);
    }
}

and registering it under src/main/resources/META-INF/spring.factories file like following

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.shazin.greeter.starter.config.GreeterServiceAutoConfiguration

org.springframework.boot.diagnostics.FailureAnalyzer=com.github.shazin.greeter.starter.config.InvalidGreeterSalutationFailureAnalyzer

will allow to format the error message in a meaningful and easy to troubleshoot way with the following message being displayed when Spring Boot starts.

***************************
APPLICATION FAILED TO START
***************************

Description:

The greeter service could not be auto-configured properly: 'mr' is an invalid salutation

Action:

A valid salutation must begin with an upper-case letter

This makes it clear and easy to troubleshoot.

Complete source is available in Github



Tags: SpringBoot AutoConfiguration Inner Working
Views: 995
Register for more exciting articles

Comments

Please login or register to post a comment.


There are currently no comments.