Spring Boot create #Bean based on #Values - java

I want to get values from my application.properties from spring.datasource.* fields but those fields are null.
Here is what I'm doing:
#Configuration
#ComponentScan
public class DatabaseConfig {
#Value("${spring.datasource.url}")
private String url;
#Value("${spring.datasource.username}")
private String username;
#Value("${spring.datasource.password}")
private String password;
#Bean(name = "database_url")
public String getDatabaseUrl() {
return url+"?user="+username+"&password="+password;
}
}
application.properties
spring.datasource.url=jdbc:postgresql://db:5432/endlessblow_db
spring.datasource.username=kuba
spring.datasource.password=pass
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.platform=postgres
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
this db in url is my docker postgre container host and it works fine.
EDIT
DatabaseConnection.java
public class DatabaseConnection {
private String databaseUrl;
#Autowired
void setDatabaseUrl(#Qualifier("database_url") String databaseUrl) {
this.databaseUrl = databaseUrl;
}
private static DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() {
try {
Class.forName("org.postgresql.Driver");
this.connection = DriverManager.getConnection(databaseUrl);
} catch (Exception ex) {
System.out.println("Database Connection Creation Failed : " + ex.getMessage());
}
}
public Connection getConnection() {
return connection;
}
public static DatabaseConnection getInstance() throws SQLException, URISyntaxException, IOException {
if (instance == null) {
instance = new DatabaseConnection();
} else if (instance.getConnection().isClosed()) {
instance = new DatabaseConnection();
}
return instance;
}
}
What's wrong with this code? Thank you!

For some reasons #Value annotated fields are not initialized I guess. try sending values as #Bean method params.
#Configuration
#ComponentScan
public class DatabaseConfig {
#Bean(name = "database_url")
public String getDatabaseUrl(#Value("${spring.datasource.url}") String url,
#Value("${spring.datasource.username}")String username,
#Value("${spring.datasource.password}") String password
) {
return url+"?user="+username+"&password="+password;
}
}

String is immutable class, meaning each time a new String is created normally a new String might be created in the string pool and a new reference might be returned.
For this reason you can't have String object as a bean in your application context.
#Bean(name = "database_url")
public String getDatabaseUrl() { <----------------The return type of String is wrong
return url+"?user="+username+"&password="+password;
}
So just create a custom object in this #Bean method which could be transferred and used in the application context
Why not return a DatabaseConnection from that #Bean method this would seem the optimal solution in your problem.

You have to create a new file where you 2st read data from property file
#Configuration
#Getter
#Setter
#ConfigurationProperties(prefix = "spring.datasource")
public class DatabaseLoginProperties{
private String url;
private String username;
private String password;
}
----Now above value you can uses in your java file -------------------
#Configuration
public class DatabaseConfig {
#Autowired
private DatabaseLoginProperties databaseLoginProperties;
#Bean
public String getDatabaseUrl() {
return databaseLoginProperties.getUrl()+"?user="+databaseLoginProperties.getUsername()+"&password="+databaseLoginProperties.getPassword();
}
}

Related

Constructor Injection using Parameter

I have the following bean with parameterized constructor:
#Component
public class AuthHelper{
#Autowired
private AuthClient gmailClient;
#Autowired
private AuthClient yahooClient;
private AuthClient client;
public AuthHelper client(String option) {
if(option.equals("gmail")) this.client=gmailClient;
if(option.equals("yahoo")) this.client=yahooClient;
return this;
}
public boolean authLogic(String uid, String pass) {
return client.authorize(uid,pass);
}
}
Could you please help to autowire the above bean:
I am stuck while I call the above bean in the below service,
#Service
public class AuthService{
#Autowired
public AuthHelper authHelper;
public boolean authenticate(String uid, String pass){
return authHelper.client("gmail").authLogic(uid, pass);
}
}
Please suggest... I want the helper class should use the bean based on the parameter that I pass from the service.
After Modification:
The above example is working fine. Please suggest if there is any issue in this implementation...
IMO, the better approach would be to have a AuthHelperFactory which should provide the AuthHelper bean with appropriate client as per input.
public class AuthHelper{
private AuthClient client;
public AuthHelper (AuthClient client) {
this.client = client;
}
public boolean authLogic(String uid, String pass) {
return this.client.authorize(uid,pass);
}
}
#Component
public class AuthHelperFactory {
#Autowired
private AuthClient gmailClient;
#Autowired
private AuthClient yahooClient;
public AuthHelper getAuthHelper(String option) {
if(option.equals("gmail")){
return new AuthHelper(gmailClient);
} else if (option.equals("yahoo")){
return new AuthHelper(yahooClient);
}
}
}
In the AuthService, you need to call the factory method in authenticate method.
return authHelperFactory.getAuthHelper("gmail").authLogic(uid, pass);

Spring Boot - Junit unable to load configuration properties in junit test

I have a consumer.properties file with the following contents in src/main/resources, and an accompanying Configuration class that loads and stores the file's contents into class member variables:
//consumer.properties file in src/main/resources:
com.training.consumer.hostname=myhost
com.training.consumer.username=myusername
com.training.consumer.password=mypassword
//ConsumerConfig.java
#Configuration
#PropertySource(
value= {"classpath:consumer.properties"}
)
#ConfigurationProperties(prefix="com.training.consumer")
public class ConsumerConfig {
private String hostname;
private String username;
private String password;
public ConsumerConfig() { }
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "ConsumerConfig [hostname=" + hostname + ", username=" + username + ", password=" + password + "]";
}
}
I also have a ConfigsService class that autowires the ConsumerConfig class to retrieve the individual properties:
#Component
public class ConfigsService {
#Autowired
ConsumerConfig consumerConfig;
public ConsumerConfig getConsumerConfig() {
return consumerConfig;
}
public void showConfig() {
consumerConfig.toString();
}
public ConsumerConfig getConfig() {
return consumerConfig;
}
}
The properties are loaded up just fine when running the ConfigsService's methods. The problem is in the unit tests, where invoking configService.getConfig().getHostname() returns a null value -- even after having created a src/test/resources directory, and adding my consumer.properties file in it:
#TestPropertySource("classpath:consumer.properties")
public class ConfigsServiceTest {
#Mock
ConsumerConfig consumerConfig;
#InjectMocks
ConfigsService configService;
#Before
public void beforeEach() {
MockitoAnnotations.initMocks(this);
}
#Test
public void someTest() {
System.out.println(configService.getConfig().getHostname()); //outputs null here -- wth!
Assert.assertTrue(true);
}
}
you are getting null values because of the mock object. I will suggest using spring runner with context configuration which will load properties in the config class and create the bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ConsumerConfig.class, ConfigsService.class})
#TestPropertySource(locations = "classpath:consumer.properties")
public class ConfigsServiceTest {
#Autowired
private ConfigsService configsService;
#Test
public void someTest() {
Assert.assertNotNull(configService.getConfig().getHostname());
}
}
The error may be because in the ConfigsServiceTest class you are establishing that your ConsumerConfig is a Mock and that is why it is not loading your configuration if you remove that code it should work

Attributes in property file returns null

I have a DBconfig property file with the attributes as follows;
DB_url = jdbc:mysql://localhost:8080/studentdb
DB_username = root
DB_password = abc123
i want to print the values inside my a Database service class (DBconnect.class),
#PropertySource("classpath:DBconfig.properties")
public class DBconnection {
private Connection con= null;
#Value("${DB_url}")
private String url;
#Value("${DB_username}")
private String username;
#Value("${DB_password}")
private String password;
public DBconnection() {
System.out.println(url); // Output = null
System.out.println(username); // Output = null
System.out.println(password); // Output = null
}
}
the exact same code works and the values are printed when i try to print the values from the controller;
#Controller
#PropertySource("classpath:DBconfig.properties")
public class HomeController {
#Value("${DB_url}")
private String url;
#Value("${DB_username}")
private String username;
#Value("${DB_password}")
private String password;
#RequestMapping(value="/", method=RequestMethod.GET)
public String Message() {
System.out.println(url); //jdbc:mysql://localhost:8080/studentdb
System.out.println(username); //root
System.out.println(password); //abc123
DBconnection conn = new DBconnection();
return "home";
}
}
why does it work in the controller and not my Service package ? how do i get this to work in my service package ?
I only have 2 packages in src/main/java;
controller package( HomeController.class included)
service package (DBconnect.class included)
src/main/resources contain the DBconfig.properties file
When you use #Value, the Spring container when inject the value via BeanPostProcessor
Hence in your constructor, your value are all null.
In your controller, you can access the injected value because the bean now fully instantiated
If you want to access your injected value in service, add #Configuration to your class and add #PostConstruct annotation to your method:
#Configuration
#PropertySource("classpath:DBconfig.properties")
public class DBconnection {
#PostConstruct
public void init() {
System.out.println(url); //jdbc:mysql://localhost:8080/studentdb
System.out.println(username); //root
System.out.println(password); //abc123
}
}

#Value with SpEL not able to get values from properties

I have the following code in a sample Spring Boot Application
#Configuration
#PropertySource("classpath:second.properties")
public class PropertyConfig {
#Value("#{guru.username}")
String user;
#Value("#{guru.password}")
String password;
#Value("#{guru.url}")
String url;
#Bean
FakeDataSource getFakeDataSource() {
FakeDataSource fk = new FakeDataSource();
fk.setName(user);
fk.setPassword(password);
fk.setUrl(url);
return fk;
}
#Bean
PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer= new PropertySourcesPlaceholderConfigurer();
//placeholderConfigurer.setLocation(new ClassPathResource("second.properties"));
return placeholderConfigurer;
}
}
And FakeDataSource is a simple pojo with the name, passowrd, url properties.
Then my main application
#SpringBootApplication
public class SpringGuru101DependencyInjectionApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SpringGuru101DependencyInjectionApplication.class, args);
// Step 2: Make Class
FakeDataSource fakeDataSource = ctx.getBean(FakeDataSource.class);
System.out.println(fakeDataSource.getName());
}
}
but the sout statement is printing null,
my second.properties file is present in my resources directory with following content
guru.username=Saurabh
guru.password=ido
guru.url=http://example.com
There are two places should be corrected:
(1) As I said in the comment of your question, you should replace the wellhead sign (#) to dollar sign ($) for reading values from your configuration file. For example: #Value("${guru.username}").
(2) You missed public static in front of the method getPropertySourcesPlaceholderConfigurer.
And this modified method should be looked like as follows:
#Bean
public static PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer placeholderConfigurer= new PropertySourcesPlaceholderConfigurer();
//placeholderConfigurer.setLocation(new ClassPathResource("second.properties"));
return placeholderConfigurer;
}
If you are using Spring-boot, you can use another method for reading configuration which is Spring boot recommended. This will help you get rid of all the #Value notations, allowing spring inject properties without additional hints.
You can potentially do something like:
#ConfigurationProperties("foo")
public class FooProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
The POJO above defines the following properties:
foo.enabled, false by default
foo.remote-address, with a type that can be coerced from String
foo.security.username, with a nested "security" whose name is determined by the name of the property. In particular the return type is not used at all there and could have been SecurityProperties
foo.security.password
foo.security.roles, with a collection of String
More details: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

autowired component is null

I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}

Categories

Resources