Spring read values for #Bean class from properties file - java

I am trying to create Spring beans using only annotations. I am not able to load values for my #Bean class from properties file.
Here is my code:
This is my main class
public class AnnotationDI {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigurationProvider.class);
ApplicationProperties properties = (ApplicationProperties)context.getBean(ApplicationProperties.class);
System.out.println(properties);
}}
Configuration class
#Configuration
public class ConfigurationProvider {
private ApplicationProperties m_applicationProperties;
#Bean
public ApplicationProperties getApplicationProperties() {
return new ApplicationProperties();
}
}
Bean class
#PropertySource(value = { "classpath:application.properties" })
public class ApplicationProperties {
#Value("${longThreadCount}")
private String m_longProcessThread;
#Value("${routeTimeout}")
private String m_routeTimeout;
#Value("${updateDirectoryPath}")
private String m_updateDirectoryPath;
public String getLongProcessThread() {
return m_longProcessThread;
}
#Override
public String toString() {
return "ApplicationProperties [m_longProcessThread=" +m_longProcessThread"]";
}
}
when i run this program, I get following output
m_longProcessThread=${longThreadCount}
Any idea what am i doing wrong?

To be able to have #Value with placeholders resolved you need to register a PropertySourcesPlaceholderConfigurer. As this is a BeanFactoryPostProcessor it needs to be registered as a static bean so that it can be detected early on in the process.
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

#PropertySource annotation has to be used in conjunction with #Configuration annotation like so,
#Configuration
#PropertySource(value = { "classpath:application.properties" })
public class ApplicationProperties {
...
}
Adding #Configuration annotation would solve the issue in this case.

Related

When exactly is PropertySourcesPlaceholderConfigurer optional?

Spring 5.3.20 - have 2 Demos.
A: The classical Java based JPA hello world.
#Configuration
#EnableTransactionManagement
#PropertySource()
public class JpaAppConfig {
#Value() user;
#Value() password;
#Bean public static PropertySourcesPlaceholderConfigurer...
#Bean public PlatformTransactionManager...
#Bean public LocalContainerEntityManagerFactoryBean...
#Bean public DataSource ... { dataSource.setUsername(user); }
#Bean public PersistenceExceptionTranslationPostProcessor...
}
B: A combined Java/Annotations based properties demo.
#Configuration
#ComponentScan() /* is needed for some other bean used in main */
public class AppConfig {
#Autowired Environment env
#Bean MyObject1 ... { foo = env.getProperty("foo"); }
#Bean MyObject2 ... { /* has #Value as field of MyObject2 */ }
}
A breaks if I omit PropertySourcesPlaceholderConfigurer.
B is doing just fine without.
Why?
What exactly is either:
causing PSPC to be required in A, or
adding PSPC in B without me realizing?

Test on Spring configuration properties reading an external YML file doesn't work

I'm working with Spring Boot 2.4.8, and I'm reading into a bean the information read from an external YML file:
#Component
#ConfigurationProperties(prefix = "my.conf")
#PropertySource(value = "classpath:ext.yml", factory = YamlPropertySourceFactory.class)
public class MyExternalConfProp {
private String property;
public void setProperty(String property) {
this.property = property;
}
public String getProperty() {
return property;
}
}
I defined a custom factory to read external YML files, as stated here in the article #PropertySource with YAML Files in Spring Boot:
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(
Objects.requireNonNull(encodedResource.getResource().getFilename()),
Objects.requireNonNull(properties));
}
}
The content of the YML file is the following:
my.conf.property: yeyeye
The problem is that I cannot find a proper slice to test the configuration property in isolation. In fact, the following test fails:
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
As we said, the test fails with the following message:
java.lang.AssertionError:
Expecting
<in.rcard.externalconfprop.MyExternalConfProp#4cb40e3b>
to have a property or a field named <"property"> with value
<"yeyeye">
but value was:
<null>
Whereas, if I don't use any slice, the test succeeds:
#SpringBootTest
class ExternalConfPropApplicationTests {
#Autowired
private MyExternalConfProp confProp;
#Test
void contextLoads() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
How can I resolve this? Is it some initializer or something similar that I can add to the slice to make the test succeed?
Here you can find the whole project on GitHub.
Add #EnableConfigurationProperties to your test or start the spring boot application on your test will solve the problem
#EnableConfigurationProperties
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
or
#SpringBootTest(classes = {YourSpringBootApplication.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}

BeanCreationException error when referencing configuration class inside a service class

I am trying to #Autowire a #Configuration class inside a #Service class. basically my #Configuration class contains mapping to my custom .properties file. When i try to autowire my configuration class inside my service class, BeanCreationException occurs. I am not sure what happen. Just followed the guide on creating Property classes from spring. There must be something i missed out.
Also, when i try to autowire #Configuration class to another #Configuration class, it runs smoothly
Currently, i know that, prop is always null because when i remove prop.getUploadFileLocation() call, everything will be fine. There must be something wrong during autowiring.
Here is my Service class
#Service
public class ImageService {
public static Logger logger = Logger.getLogger(ImageService.class.getName());
#Autowired
MyProperties prop;
private final String FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";
public void upload(String base64ImageFIle) throws IOException {
logger.info(FILE_UPLOAD_LOCATION);
}
}
Here is my Configuration class
#Data
#Configuration
#ConfigurationProperties (prefix = "my")
public class MyProperties {
private String resourceLocation;
private String resourceUrl;
public String getUploadFileLocation() {
return getResourceLocation().replace("file:///", "");
}
public String getBaseResourceUrl() {
return getResourceUrl().replace("**", "");
}
}
And here is where i can successfully use MyProperties
#Configuration
public class StaticResourceConfiguration implements WebMvcConfigurer {
#Autowired
MyProperties prop;
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(prop.getResourceUrl())
.addResourceLocations(prop.getResourceLocation());
}
}
The issue is that you are trying to use an autowired field to set the value in an inline field assignment.
That means
private final String FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";
is executed before the prop is autowired, meaning it will always be null
The way to mitigate this would be to use constructor injection instead.
#Service
public class ImageService {
//Fine since you are using static method
public static Logger logger = Logger.getLogger(ImageService.class.getName());
//Not needed if you are only using it to set FILE_UPLOAD_LOCATION
//Allows field to be final
private final MyProperties prop;
//Still final
private final String FILE_UPLOAD_LOCATION;
//No need for #Autowired since implicit on component constructors
ImageService(MyProperties prop){
//Again not needed if you aren't going to use anywhere else in the class
this.prop = prop;
FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";
}
public void upload(String base64ImageFIle) throws IOException {
logger.info(FILE_UPLOAD_LOCATION);
}
}
See this question for why constructor is preferred over #autowired in general
If you need MyProperties bean to be created before StaticResourceConfiguration bean, you can put #ConditionalOnBean(MyProperties.class) as following. Spring will make sure MyProperties is there before processing StaticResourceConfiguration.
#Configuration
#ConditionalOnBean(MyProperties.class)
public class StaticResourceConfiguration implements WebMvcConfigurer {

Spring Data Cassandra config change on the fly

I am trying to do a similar thing with my application. I am using following versions of Spring boot and Cassandra:
spring-data-cassandra - 2.0.8.RELEASE
spring-boot-starter-parent - 2.0.4.RELEASE
I need to change some properties(mostly hostnames) of Cassandra on the fly and want it to make a new connection with the application. For config change we have internal Cloud Config Change Management and it runs fine on changes and listens to it.
This is my class :
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#RefreshScope
#EnableCassandraRepositories(basePackages = {"com.*.*.*.dao.repo"})
public class AppConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(AppConfig.class);
#Value("${application['cassandraPort']}")
private String cassandraPort;
#Value("${application['cassandraEndpoint']}")
private String cassandraEndpoint;
#Value("${application['keyspaceName']}")
private String keyspaceName;
#Value("${application['cassandraConsistency']}")
private String cassandraConsistency;
#Value("${application['cassandraUserName']}")
private String cassandraUserName;
#Autowired
private AppConfig appConfig;
public AppConfig() {
System.out.println("AppConfig Constructor");
}
public String getCassandraPort() {
return cassandraPort;
}
public void setCassandraPort(String cassandraPort) {
this.cassandraPort = cassandraPort;
}
public String getCassandraEndpoint() {
return cassandraEndpoint;
}
public void setCassandraEndpoint(String cassandraEndpoint) {
this.cassandraEndpoint = cassandraEndpoint;
}
public String getKeyspaceName() {
return keyspaceName;
}
public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}
public String getCassandraConsistency() {
return cassandraConsistency;
}
public void setCassandraConsistency(String cassandraConsistency) {
this.cassandraConsistency = cassandraConsistency;
}
public String getCassandraUserName() {
return cassandraUserName;
}
public void setCassandraUserName(String cassandraUserName) {
this.cassandraUserName = cassandraUserName;
}
#Bean
// #RefreshScope
public CassandraConverter converter() {
return new MappingCassandraConverter(this.mappingContext());
}
#Bean
// #RefreshScope
public CassandraMappingContext mappingContext() {
return new CassandraMappingContext();
}
#Bean
//#RefreshScope
public CassandraSessionFactoryBean session() {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(this.cluster().getObject());
session.setKeyspaceName(appConfig.getKeyspaceName());
session.setConverter(this.converter());
session.setSchemaAction(SchemaAction.NONE);
return session;
}
#Bean
//#RefreshScope
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(appConfig.getCassandraEndpoint());
cluster.setPort(Integer.valueOf(appConfig.getCassandraPort()));
cluster.setUsername(appConfig.getCassandraUserName());
cluster.setPassword("password");
cluster.setQueryOptions(new QueryOptions().setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM));
return cluster;
}
}
However, when I try to use #RefreshScope with that Configuration class, the application fails to start. This is what it shows in console :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 2 of constructor in org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration required a bean of type 'com.datastax.driver.core.Cluster' that could not be found.
- Bean method 'cassandraCluster' not loaded because auto-configuration 'CassandraAutoConfiguration' was excluded
Action:
Consider revisiting the entries above or defining a bean of type 'com.datastax.driver.core.Cluster' in your configuration.
Is there some guidelines on using #RefreshScope with Cassandra Bean? If anyone has done that earlier can you share the same?
You're mixing a couple of things here.
The config carries properties and bean definitions.
#RefreshScope on AppConfig causes some interference with Spring Boot's auto-configuration and the declared beans aren't used (that's why you see Parameter 2 of constructor…).
To clean up, we will reuse what Spring Boot provides as much as possible, and only declare what's really needed.
Follow these steps to solve the issue (based on your code above):
Create a #ConfigurationProperties bean that encapsulates your properties, or better, reuse CassandraProperties.
Re-enable CassandraAutoConfiguration and remove your own MappingContext and CassandraConverter beans, keep only Cluster and Session bean definitions
Declare Cluster and Session beans as needed and make them use #RefreshScope. Your #Configuration class should look like:
Example Configuration:
#Configuration
public class MyConfig {
#Bean(destroyMethod = "close")
#RefreshScope
public Cluster cassandraCluster(CassandraProperties properties) {
Cluster.Builder builder = Cluster.builder().addContactPoints(properties.getContactPoints().toArray(new String[0]))
.withoutJMXReporting();
return builder.build();
}
#Bean(destroyMethod = "close")
#RefreshScope
public Session cassandraSession(CassandraProperties properties, Cluster cluster) {
return cluster.connect(properties.getKeyspaceName());
}
}

Spring Context Initialization fails

I'm pretty much stucked and I hope you guys can help me out. Somehow I can't manage to get my spring context initialized.
I have these nice Bean Configuration classes:
#Configuration
public class CoreConfig {
#Bean
public TeamService createService(TeamPersistenceService teamPersistenceService) {
return new TeamEventHandler(teamPersistenceService);
}
}
And this one:
#Configuration
#EnableJpaRepositories(basePackages = "de.ktv.persistence.repository", //
includeFilters = #ComponentScan.Filter(value = { TeamsRepository.class }, type = FilterType.ASSIGNABLE_TYPE))
#EnableTransactionManagement
public class PersistenceConfig {
#Bean
public TeamPersistenceService createService(TeamsRepository repository) {
return new TeamPersistenceEventHandler(repository);
}
}
And in this test I want to use them:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { CoreConfig.class, PersistenceConfig.class })
public class CoreIntegrationTest {
#Autowired
TeamService teamService;
#Test
public void addNewTeamToTheSystem() {
//some test
}
The PersistenceConfig.class I am using in a different test and it works fine. But somehow here combined with CoreConfig.class it fails to initialize.
That is the error I get:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [de.ktv.core.services.TeamService] found for
dependency: expected at least 1 bean which qualifies as autowire
candidate for this dependency. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I would really appreciate any help/hint.Thanks!
SpringContext cannot bind the #autowired if you don't indicate the same name. By default, the bean name will be the same as the method name, in this case, he is different, 2 options : change the method name or add attribut name !
Option 1
#Bean(name = "teamService")
public TeamService createService(TeamsRepository repository) {
return new TeamPersistenceEventHandler(repository);
}
Option 2
#Bean
public TeamService teamService(TeamsRepository repository) {
return new TeamPersistenceEventHandler(repository);
}
Enjoy \o/
#Configuration
public class CoreConfig {
#Autowired
private TeamPersistenceService teamPersistenceService;
#Bean
public TeamService teamService() {
return new TeamEventHandler(teamPersistenceService);
}
}
And this one:
#Configuration
#EnableJpaRepositories(basePackages = "de.ktv.persistence.repository", //
includeFilters = #ComponentScan.Filter(value = { TeamsRepository.class }, type = FilterType.ASSIGNABLE_TYPE))
#EnableTransactionManagement
public class PersistenceConfig {
#Autowired
private TeamsRepository repository:
#Bean
public TeamPersistenceService teamPersistenceService() {
return new TeamPersistenceEventHandler(repository);
}
}
And in this test I want to use them:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { CoreConfig.class, PersistenceConfig.class })
public class CoreIntegrationTest {
#Autowired
TeamService teamService;
#Test
public void addNewTeamToTheSystem() {
//some test
}

Categories