The Magic behind Spring Boot 2
The Magic behind Spring Boot 2
This post is a continuation of 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.GreeterServiceAutoConfigurationorg.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 salutationAction:A valid salutation must begin with an upper-case letter
This makes it clear and easy to troubleshoot.
Complete source is available in Github