I'm trying to learn how to read properties file using spring. After an internet searching I found that I can use #value and #PropertySource annotations to achieve that. I created a project which has the following structure and classes codes:
Structure of the project:
AppConfigMongoDB.java implementation:
package com.mongodb.properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
#PropertySource("classpath:config/config.properties")
public class AppConfigMongoDB {
#Value("#{mongodb.url}")
private String mongodbUrl;
#Value("#{mongodb.db}")
private String defaultDb;
public String getMongoDb()
{
return defaultDb;
}
public String getMongoDbUrl()
{
return mongodbUrl;
}
}
SpringConfiguration.java implementation:
package com.mongodb.properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class SpringConfiguration {
#Bean
public AppConfigMongoDB getAppConfigMongoDB(){
return new AppConfigMongoDB();
}
}
Main.java
package com.mongodb.properties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AppConfigMongoDB mongo = applicationContext.getBean(AppConfigMongoDB.class);
System.out.println("db= "+mongo.getMongoDb());
System.out.println("URL= "+mongo.getMongoDbUrl());
}
}
The properties file that I'm reading from called config.properties, it contains the following lines:
mongodb.url=1.2.3.4
mongodb.db=dataBase
I tested this small project and I got a stack trace that contains the following exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'getAppConfigMongoDB': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.mongodb.properties.AppConfigMongoDB.mongodbUrl; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Property or field 'mongodb' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public?
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1202)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at com.mongodb.properties.Main.main(Main.java:9)
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Property or field 'mongodb' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:226)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:93)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:81)
at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:51)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:87)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:120)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:242)
at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:161)
... 18 more
Is it a problem of Spring calling beans? Or maybe is it a problem of properties file path or something else?
I can see several issues in the code.
1) Your place holders for values should be in the form ${mogodb.url}, not #{mongodb.url}. The "#" has a different meaning (See Spring Expressions).
2) You are going to need a PropertySourcesPlaceholderConfigurer bean to do the injection of the values
3) Sooner or later you are going to have a number of Beans floating around, and in I would use #ComponentScan to allow the context to know these without you having to mention them one by one
4) If you use ComponentScan to get the beans, you are going to have to provide AppConfigMongoDB bean once
I end up with these classes after doing all that:
Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AppConfigMongoDB mongo = applicationContext.getBean(AppConfigMongoDB.class);
System.out.println("db= "+mongo.getMongoDb());
System.out.println("URL= "+mongo.getMongoDbUrl());
}
}
SpringConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#ComponentScan
public class SpringConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
AppConfigMongoDB.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#PropertySource("classpath:config/config.properties")
public class AppConfigMongoDB {
#Value("${mongodb.url}")
private String mongodbUrl;
#Value("${mongodb.db}")
private String defaultDb;
public String getMongoDb() {
return defaultDb;
}
public String getMongoDbUrl() {
return mongodbUrl;
}
}
Nice reply given by #Ian Sparkes . Adding some of my inputs here. Configuring the PropertySourcesPlaceholderConfigurer is not mandatory. I am able to get the value from my properties file and set it to the filed variable of my main Configuration class without it.
There is also another way to get the values of your keys in the properties file. Use org.springframework.core.env.Environment class . This class automatically loads all the key-value pairs in properties file that is in class path.
Example :
#Configuration
#ComponentScan(basePackages="com.easy.buy")
#PropertySource({"classpath:config.properties","classpath:props/db-${env}-config.properties",
"classpath:props/app-${env}-config.properties"})
public class CatalogServiceConfiguration {
Logger logger = LoggerFactory.getLogger(CatalogServiceConfiguration.class);
//This object loads & holds all the properties in it as key-pair values
#Autowired
private Environment env;
/**
* Instantiate the DataSource bean & initialize it with db connection properties
* #return
*/
#Bean(name="basicDataSource")
public BasicDataSource basicDataSource() {
String dbUrl = env.getProperty("db.url");
String dbUser = env.getProperty("db.user");
String dbPwd = env.getProperty("db.pwd");
String driver = env.getProperty("db.driver");
logger.info("Initializing CatalogServiceConfiguration");
logger.info("dbUrl=" + dbUrl);
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(driver);
ds.setUrl(dbUrl);
ds.setUsername(dbUser);
ds.setPassword(dbPwd);
return ds;
}
}
Related
I have this simple component class:
package jason;
import org.springframework.stereotype.Component;
#Component
public class Messenger {
private String message;
public Messenger(String message) {
this.message = message;
}
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
In the argument for the constructor, IntelliJ reports: Could not autowire. No beans of 'String' type found.
There are two more classes in this small toy project: Config:
package jason;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackageClasses = Messenger.class)
public class Config {
#Bean
public Messenger helloWorld(){
return new Messenger("Hello World!");
}
}
and MainApp:
package jason;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Messenger obj = context.getBean("helloWorld", Messenger.class);
obj.getMessage();
}
}
Curiously, besides the seemingly compile-time error, the project builds, but fails at runtime with:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'messenger' defined in file [C:\Users\jasonfil\code\green-taxi\target\classes\jason\Messenger.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
What am I doing wrong here? New to SpringBoot. Might have IOC misconception(s).
You are mixing two ways of bean injection with Messenger class, annotation-based injection with #Component, and you are declaring it as a bean, with #Bean, in the configuration class.
When you try to inject Messenger using the AnnotationConfigApplicationContext with an activated component scan, Spring will use the annotation-based injection first, so #Component.
So Spring will call the default constructor to inject your bean, of course, if there is no constructor based autowiring (that's your case), so you need to add the default constructor to Messenger. if there is no default constructor Spring will use the available constructor, so will get the error above. Of course, you need to delete the #Bean configuration because you are not using it:
package jason;
import org.springframework.stereotype.Component;
#Component
public class Messenger {
private String message;
public Messenger() {
}
public Messenger(String message) {
this.message = message;
}
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
Or, if you want to use the bean configuration, you can remove #Component from Messenger, and also remove #ComponentScan:
package jason;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
public class Config {
#Bean
public Messenger helloWorld(){
return new Messenger("Hello World!");
}
}
You are using Java Config type Bean registration as well as Component Scan type Bean registration.
the quickest solution is to remove #Component from the Messenger class.
This question already has an answer here:
Is it possible to set a bean name using annotations in Spring Framework?
(1 answer)
Closed 4 years ago.
I am testing out simple AOP use case in Spring but am getting the below error,
Exception in thread "main"
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'bean1' is defined
Below are my source files,
DemoConfig.java
package com.luv2code.aopdemo;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.luv2code.aopdemo.aspect.MyDemoLoggingAspect;
import com.luv2code.aopdemo.dao.AccountDAO;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan("com.luv2code.aopdemo")
public class DemoConfig {
#Bean
#Qualifier("bean1")
public AccountDAO accDao() {
return new AccountDAO();
}
#Bean
#Qualifier("bean2")
public MyDemoLoggingAspect myAscpect() {
return new MyDemoLoggingAspect();
}
}
MyDemoLoggingAspect.java
package com.luv2code.aopdemo.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MyDemoLoggingAspect {
// this is where we add all of our related advices for logging
// let's start with an #Before advice
#Before("execution(** com.luv2code.aopdemo.dao.AccountDAO.addAccount(..))")
public void beforeAddAccountAdvice() {
System.out.println("\n=====>>> Executing #Before advice on addAccount()");
}
}
MainDemoApp.java
package com.luv2code.aopdemo;
import com.luv2code.aopdemo.dao.AccountDAO;
public class MainDemoApp {
public static void main(String[] args) {
// read spring config java class
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// get the bean from spring container
AccountDAO theAccountDAO = context.getBean("bean1", AccountDAO.class);
// call the business method
theAccountDAO.addAccount();
// do it again!
System.out.println("\nlet's call it again!\n");
// call the business method again
theAccountDAO.addAccount();
// close the context
context.close();
}
}
I have given my bean ID "bean1", even after that Spring is not able to find my bean in the context. Why am I getting this error and how to resolve this?
The #Qualifier tag is used with the #Autowired annotation.
What you need is
#Bean(name="bean1")
public AccountDAO accDao() {
return new AccountDAO();
}
I would like for Spring Boot to throw an exception if any of my beans are not fully configured during initialization. I thought that the correct way to do that would be to annotate the relevance bean methods with #Required, but it does not behave as I expect.
application.yml:
my_field: 100
Simple bean class:
package com.example.demo;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Component;
#Component
public class MyProperties {
private int myField;
public MyProperties(){}
#Required
public void setMyField(int myField) {
this.myField = myField;
}
#Override
public String toString() {
return "{myField=" + myField + '}';
}
}
My application class:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
#SpringBootApplication
public class DemoApplication {
#Bean
#ConfigurationProperties
public MyProperties getMyProperties() {
return new MyProperties();
}
#PostConstruct
public void init() {
MyProperties myProperties = getMyProperties();
System.out.println(myProperties);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
In the init method of DemoApplication I am printing the resulting bean object. Without the #Required annotation it is loaded correctly and prints {myField=100}. However, when I add the annotation it throws this exception:
org.springframework.beans.factory.BeanInitializationException: Property 'myField' is required for bean 'myProperties'
This is despite the fact that the config file contains the required value.
What is the correct to tell Spring that a field is required?
From the docs
Spring Boot will attempt to validate #ConfigurationProperties classes whenever they are annotated with Spring’s #Validated annotation. You can use JSR-303 javax.validation constraint annotations directly on your configuration class. Simply ensure that a compliant JSR-303 implementation is on your classpath, then add constraint annotations to your fields
You should declare myField as follows:
#NonNull
private int myField;
I am trying to simple spring program that has a class named PersistenceConfig annotated with #Configuration
#Configuration
#PropertySource("classpath:application.properties")
public class PersistanceConfig {
#Value("${dbPassword}")
private String dbPassword;
// Set of Beans and Code
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("jdbc:sqlserver://localhost;databaseName=GovernmentPayment;integratedSecurity=false;");
dataSource.setUsername("sa");
dataSource.setPassword(dbPassword);
return dataSource;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
When i run my program, the value dbPassword is always null but if i try to read the same value inside one my Controllers it reads the value without any issues.
I have tried autowiring Environment variable and using it instead of #Value but it didn't work either. (Spring didn't inject value to the Environment Variable)
I am using Spring 4
What is basically want is to externalize the database username and password in a separate property file.
i don't see any problem with given code.i wrote a simple unit test to your class to prove it works.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=PersistanceConfig.class)
public class PersistanceConfigTest {
#Autowired
private DriverManagerDataSource dataSource;
private final String password = "mydbPassword";
#Test
public void testDriverManagerDataSourcePassword() {
System.out.println("dataSource Password :: " + dataSource.getPassword());
assertNotNull(dataSource);
assertTrue(password.equals(dataSource.getPassword()));
}
}
assuming you have application.properties in src/main/resources and dbPassword=mydbPassword is presented in that file.
Credit goes to Chad Darby
This is an issue with Spring versions.
If you are using Spring 4.2 and lower, you will need to add the code in marked with(**).
package com.luv2code.springdemo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
// #ComponentScan("com.luv2code.springdemo")
#PropertySource("classpath:sport.properties")
public class SportConfig {
// add support to resolve ${...} properties
**#Bean
public static PropertySourcesPlaceholderConfigurer
propertySourcesPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}**
// define bean for our sad fortune service
#Bean
public FortuneService sadFortuneService() {
return new SadFortuneService();
}
// define bean for our swim coach AND inject dependency
#Bean
public Coach swimCoach() {
SwimCoach mySwimCoach = new SwimCoach(sadFortuneService());
return mySwimCoach;
}
}
````
In Spring 4.3 and higher, they removed this requirement. As a result, you don't need this code.
I have solved this by moving that two annotations to the main file.
package com.luv2code.springdemo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.luv2code.springdemo.SportConfig")
public class SwimJavaConfigDemoApp {
public static void main(String[] args) {
// read spring config java class
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SportConfig.class);
// get the bean from spring container
Coach theCoach = context.getBean("swimCoach", Coach.class);
// call a method on the bean
System.out.println(theCoach.getDailyWorkout());
// call method to get the daily fortune
System.out.println(theCoach.getDailyFortune());
// close the context
context.close();
}
}
Configure the main file.
Use component scan and specified the path to the cofig class.
Next, write some Bean's to the config file.
package com.luv2code.springdemo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
public class SportConfig {
// define a bean for sad fortune service
#Bean
public FortuneService sadFortuneService() {
return new SadFortuneService();
}
// define bean for our swim coach and inject dependency
#Bean
public Coach swimCoach() {
return new SwimCoach(sadFortuneService());
}
}
For futher learning : Component Scan.
I have a SqsQueueSender to send messages to AWS. I want to test this class. My thought is that it should be a #Component that is injected in to the classes that need it. Importantly, I want to configure the endpoint of the SqsQueueSender to be different in testing vs. production environments.
I've been moving #Autowired and #Component around the classes various different ways but must have some basic misunderstanding. Here's my latest configuration:
package com.foo.fulfillmentApi.dao;
import com.amazonaws.services.sqs.AmazonSQSAsyncClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.messaging.support.MessageBuilder;
#Component
public class SqsQueueSender {
private static final Logger LOGGER = LoggerFactory.getLogger(SqsQueueSender.class);
private final QueueMessagingTemplate queueMessagingTemplate;
#Autowired
AmazonSQSAsyncClient amazonSQSAsyncClient;
//This value is from /resources/application.properties
private #Value("${sqs.endpoint}") String endpoint;
public SqsQueueSender(AmazonSQSAsyncClient amazonSqsAsyncClient) {
amazonSqsAsyncClient.setEndpoint(endpoint);
this.queueMessagingTemplate = new QueueMessagingTemplate(amazonSqsAsyncClient);
}
public void send(String queueName, String message) {
this.queueMessagingTemplate.convertAndSend(queueName, MessageBuilder.withPayload(message).build());
}
}
The error message on startup states
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type
'com.amazonaws.services.sqs.AmazonSQSAsyncClient' available: expected
at least 1 bean which qualifies as autowire candidate. Dependency
annotations: {}
To implement SqsQueueSender you must pass an AmazonSQSAsyncClient. How do I make sure this component can access an existing bean of that type?
You need to create a configuration class. In your case it would be something like this:
#Configuration
public class AWSConfig {
#Bean(name ="awsClient")
public AmazonSQSAsync amazonSQSClient() {
AmazonSQSAsyncClient awsSQSAsyncClient
= new AmazonSQSAsyncClient();
// set up the client
return awsSQSAsyncClient;
}
If it has problems with injecting then add qualifier in qsQueueSender:
#Autowired
#Qualifier("awsClient")
AmazonSQSAsyncClient amazonSQSAsyncClient;
You can also do this using the xml configuration but as you are using annotations then this is more advisable approach.
Add #Component/#Service in com.amazonaws.services.sqs.AmazonSQSAsyncClient or return an object of that using #Bean annotation from configuration class.
If you use springboot - define into your startup application file as below
#Bean
public AmazonSNSAsync amazonSNSClient() {
ClientConfiguration config = new ClientConfiguration();
return AmazonSNSAsyncClient.asyncBuilder().withClientConfiguration(config)
.withRegion(Regions.fromName(region))
.withCredentials(new DefaultAWSCredentialsProviderChain())
.build();
}