Suppose I have a jar with a Spring Component called MyComponent. This jar is a Spring Boot "autoconfigured" jar, meaning that it has a Configuration class (annotated with #Configuration), and additionally, a META-INF/spring.factories file on the classpath. This jar is not an executable jar by itself; it is a library that is meant for inclusion in a Spring Boot application.
These files look as follows:
MyComponent.java, in package com.mine.components:
#Component
public class MyComponent {
private static final Logger logger = LoggerFactory.getLogger(MyComponent.class);
#PostConstruct
public void init() {
logger.info("MyComponent inited");
}
}
MyConfiguration.java, in package com.mine.config:
#Configuration
#ComponentScan(basePackages = "com.mine.components")
public class MyConfiguration {
}
spring.factories, in META-INF under src/main/resources:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.mine.config.MyConfiguration
If I include this jar in a Spring Boot project with the above three files, the MyComponent component is NOT detected (the log message never prints).
But if I instead remove the #ComponentScan and declare MyComponent using the #Bean annotation as follows, it is detected:
#Bean
public MyComponent myComponent() {
return new MyComponent();
}
Why?
Difference beetween ComponentScan and declared Bean inside #Configuration class:
#ComponentScan: You enable auto-scanning (default using current folder path), optionally you can specify an basePackage where spring will found yours beans.
#ComponentScan(basePackages = "com.mine.components")
You're saying to Spring that in this package("com.mine.components"), you'll define yours beans typically using annotations (#Component, #Entity, #Controller, #Service, #Repository or more).
#Bean: This way you define your beans manually inside #Configuration class, but Spring has to discover your configuration class, usually using #ComponentScan, #SpringBootApplication.
META-INF/spring.factories: you define an custom autoconfiguration
I am using spring boot: 2.5.7
I have a starter in my application that is not working.
I opened the code in order to understand the reason and notice a weird behavior:
The starter code is:
AutoConfiguration:
#Configuration
#EnableConfigurationProperties(MyConfigProperties.class)
public class MyCompanyAutoConfiguration {
#Bean
#ConditionalOnBean(MyConfigProperties.class)
public MeterRegistry defaultNewRelicMeterRegistry(MyConfigProperties config) {
System.out.println("called method");
return MyCompanyNewRelicRegistry.builder(config).build();
}
}
PropertyBean
#Configuration
#ConfigurationProperties(prefix = "company.config.newrelic")
#ConditionalOnProperty(prefix = "company.config.newrelic", name = "apiKey")
#ConditionalOnClass(NewRelicRegistryConfig.class)
public class MyConfigProperties implements NewRelicRegistryConfig {
// getters and setters
...
}
Factory file (spring.factories)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.company.MyCompanyAutoConfiguration
When I start my application, MyCompanyNewRelicRegistry bean is not been created.
I find out that "ConditionalOnBean" is the problem, the code does not print "called method" when use this annotation. It is weird because MyConfigProperties bean is initialized (I saw calling the applicationContext.getBean after container have started).
When I remove the annotation #ConditionalOnBean, everything works well and the method is called.
Why ConditionalOnBean is not finding MyConfigProperties that time?
Note: all properties and classes used in other conditions are ok.
# UPDATE
I changed to:
#Configuration
#EnableConfigurationProperties
#ComponentScan
public class MyCompanyAutoConfiguration {
#Bean
#ConditionalOnBean(MyConfigProperties.class)
public MeterRegistry defaultNewRelicMeterRegistry(MyConfigProperties config) {
System.out.println("called method");
return MyCompanyNewRelicRegistry.builder(config).build();
}
}
The above example works.
I saw in log that spring try to load "defaultNewRelicMeterRegistry" first, when evaluate the conditionalBean, its not created yet.
When I remove the ConditionalOnBean the spring will load the property in order to inject it.
May the componentScan "force" spring to load all other beans before the declared bean in autoconfiguration class, make sense?
I want to test small parts of the application that rely on properties loaded with #Autowired and #ConfigurationProperties. I am looking for a solution loading only the required properties and not always the whole ApplicationContext.
Here as reduced example:
#TestPropertySource(locations = "/SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestSettings.class, TestConfiguration.class})
public class SettingsTest {
#Autowired
TestConfiguration config;
#Test
public void testConfig(){
Assert.assertEquals("TEST_PROPERTY", config.settings().getProperty());
}
}
Configuration Class:
public class TestConfiguration {
#Bean
#ConfigurationProperties(prefix = "test")
public TestSettings settings (){
return new TestSettings();
}
}
Settings Class:
public class TestSettings {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
The properties file in the resource folder contains the entry:
test.property=TEST_PROPERTY
In my current setup config is not null, but no fields are available.
The reason the fields are not field should have something to do with the fact that I am not using Springboot but Spring.
So what would be the Springboot way to get this running?
edit:
The reason why I want to do this is: I have a parser that parses Textfiles, the regular expressions used are stored in a properties file.
To test this I would like to load only the properties needed for this parser which are in the exaple above the TestSettings.
While reading the comments I already noticed that this are no Unit tests anymore. However using the full Spring boot configuration for this small test seems a bit too much to me. That's why I asked if there is a posibilty to load only the one class with properties.
You need to annotate your TestConfiguraion with #EnableConfigurationProperties as follows:
#EnableConfigurationProperties
public class TestConfiguration {
#Bean
#ConfigurationProperties(prefix = "test")
public TestSettings settings (){
return new TestSettings();
}
}
Also you only need to include TestConfiguration.class in #ContextConfiguration of you SettingsTest class:
#TestPropertySource(locations = "/SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class SettingsTest {
...
A couple points:
You don't need a "TestConfiguration" class in your main package, because all it's doing is configuring the "TestSettings" bean. You can do this simply by annotating the TestSettings class itself.
Normally you would load the context you need for the test using the #SpringApplicationConfiguration annotation, passing the name of your Application class. However, you said you don't want to load the whole ApplicationContext (though it's not clear why), so you need to create a special configuration class to do the loading only for tests. Below I call it "TestConfigurationNew" to avoid confusion with the TestConfiguration class that you had originally.
In the Spring Boot world, all properties are generally kept in the "application.properties" file; but it is possible to store them elsewhere. Below, I have specified the "SettingsTest.properties" file that you proposed. Note that you can have two copies of this file, the one in the main/resources folder, and the one in the test/resources folder for testing.
Change the code as follows:
TestSettings.java (in main package)
#Configuration
#ConfigurationProperties(prefix="test", locations = "classpath:SettingsTest.properties")
public class TestSettings {
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
SettingsTest.java (in test package)
#TestPropertySource(locations="classpath:SettingsTest.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfigurationNew.class)
public class SettingsTest {
#Autowired
TestSettings settings;
#Test
public void testConfig(){
Assert.assertEquals("TEST_PROPERTY", settings.getProperty());
}
}
TestConfigurationNew.java (in test package):
#EnableAutoConfiguration
#ComponentScan(basePackages = { "my.package.main" })
#Configuration
public class TestConfigurationNew {
}
This should now work the way you wanted.
you can actually just add #EnableConfigurationProperties to your #SpringBootTest directly.
eg:
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestConfiguration.class)
#EnableConfigurationProperties
...
If you use Spring Boot, now you only need:
#RunWith(SpringRunner.class)
#SpringBootTest
No extra #ContextConfiguration, no extra class only for tests to EnableAutoConfiguration and EnableConfigurationProperties. You don't have to specify the configuration class to load, they will all be loaded.
But, ensure the properties entries you want to read in main/resources/application.yml is also present in test/resources/application.yml. Repetition is unavoidable.
Another way is:
Define a class of configuration only for tests, along with MyApplicationTest.java, at the same level. This class can be empty.
Like:
#EnableAutoConfiguration
#EnableConfigurationProperties(value = {
ConnectionPoolConfig.class
})
public class MyApplicationTestConfiguration {
}
And, in the class to load the autowired configuration.
Like:
#RunWith(SpringRunner.class)
//#SpringBootTest // the first, easy way
#ContextConfiguration(classes = MyApplicationTestConfiguration.class,
initializers = ConfigFileApplicationContextInitializer.class)
public class ConnectionPoolConfigTest {
#Autowired
private ConnectionPoolConfig config;
Basically, you:
use a specific configuration to #EnableConfigurationProperties and #EnableAutoConfiguration, listing all the #ConfigurationProperties files you want to load
in the test class, you load this configuration file of tests, with an initializer class defined by Spring to load application.yml file.
And, put the values to load in test/resources/application.yml. Repetition is unavoidable. If you need load another file, use #TestProperties() with a location. Note: #TestProperties only supports .properties files.
Both way works for configuration class loading values
either from application.yml/application.properties
or from another properties file, specified by PropertySource, like #PropertySource(value = "classpath:threadpool.properties")
Important
Last notes from Spring doc, as per here
Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.
Finally, only standard Java Bean properties are considered and binding on static properties is not supported.
That means, if you have lombok.#Builder without #NoArgsConstructor nor #AllArgsConstructor, properties injection will not happen because it only sees the invisible constructor created by #Builder. So, be sure to use none, or all of these annotations!
Unit test
To avoid having to load a Spring context, we can use the Binder class, which is also used internally by Spring anyway.
// A map of my properties.
Map<String, String> properties = new HashMap<>();
properties.put("my-prefix.first-property", "foo");
properties.put("my-prefix.second-property", "bar");
// Creates a source backed by my map, you can chose another type of source as needed.
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties)
// Binds my properties to a class that maps them.
Binder binder = new Binder(source);
BindResult<MyConfiguration> result = binder.bind("my-prefix", MyConfiguration.class);
// Should return true if bound successfully.
Assertions.assertTrue(result.isBound);
// Asserts configuration values.
MyConfiguration config = result.get();
Assertions.assertEquals("foo", config.getFirstProperty());
Assertions.assertEquals("bar", config.getSecondProperty());
I have a SpringBoot main application, as well as a separate Maven module project that compiles as a separate Jar. The module has a Spring config class annotated with #Configuration, which I want to get loaded, when the main app loads.
Apparently, this does not happen out of the box (by just including the module to the main app). What else do I need to do, to get the module configuration class also get loaded by the main app?
The easiest way is to scan the package that the #Configuration class is in.
#ComponentScan("com.acme.otherJar.config")
or to just load it as a spring bean:
#Bean
public MyConfig myConfig() {
MyConfig myConfig = new MyConfig ();
return myConfig;
}
Where MyConfig is something like:
#Configuration
public class MyConfig {
// various #Bean definitions ...
}
See docs
#ComponentScan annotation will scan all classes with #Compoment or #Configuration annotation.
Then spring ioc will add them all to spring controlled beans.
If you want to only add specific configurations, you can use #import annotation.
example:
#Configuration
#Import(NameOfTheConfigurationYouWantToImport.class)
public class Config {
}
#Import Annotation Doc
I am using spring framework 4.1.4
I have JBoss5XmlWebApplicationContext as my contextClass reading xml configuration.
I want to add #Configuration class to be read aside from the xml.
Is this possible? How to do it?
Yes, Its possible. One way I know is using springboot classes. I am using MySpringConfiguration class to declare beans using #Configuration and MySpringConfig.xml for xml based bean configuration. I am able to load beans from the context coming from both sources.
Check below code:
public static void main(String[] args) {
Resource res = new FileSystemResource("E:\\MySpringConfig.xml");
SpringApplication app = new SpringApplication(MySpringConfiguration.class,res);
ApplicationContext ctx = app.run("");
MyXMLBean myXMLBean = (MyXMLBean)ctx.getBean("myNewBean"); // From XML file...
GeData geData= (GeData)ctx.getBean("geData"); // From Java Configuration ...
....
Configuration class goes like this
#Configuration
public class MySpringConfiguration {
#Bean
public GeData geData(){
return new GeData();
}
Hope this helps...