How to use #Configuration to define configurations runtime? - java

I am pretty new to spring so please bear with me and feel free to suggest more reading with right links if you see necessary.I am using a class to define a connection settings for different Rest endpoints I will be calling out to (username port etc.). So I am thinking of storing them to application.properties as follows:
twitter.username="a"
twitter.password="b"
twitter.host="www.twitter.com"
facebook.username="c"
facebok.password="d"
facebook.host="www.facebook.com"
Now I want to define a class that will take all the prefix (such as "twitter" or "facebook") and return me the configuration class based off of the corresponding properties in applicaton.properties.
I was thinking of doing something similar to following:
#Configuration
#PropertySource("classpath:application.properties")
public class RESTConfiguration
{
class RESTServer{
public String username;
public String password;
public String host;
private RESTServer(String username, String password, String host)
{
this.username = username;
this.password = password;
this.host = host;
}
}
#Autowied
private String ednpointName;
#Bean
public RESTServer restServer(Environment env)
{
return new RESTServer(env.getProperty(ednpointName + ".user"),
env.getProperty(ednpointName + ".password"),
env.getProperty(ednpointName+".host"));
}
}
But it clearly won't work since there will be only one Bean and I won't have a way to pass multiple endpointName. Help appreciated!

A better approach would be to use a factory design pattern. Something along the lines of:
#Configuration
#PropertySource("classpath:application.properties")
public class RESTConfiguration
{
#Bean
public RESTServerFactory restServer()
{
return new RESTServerFactory()
}
}
public class RESTServer {
public String username;
public String password;
public String host;
private RESTServer(String username, String password, String host)
{
this.username = username;
this.password = password;
this.host = host;
}
}
public class RESTServerFactory {
#Autowired Environment env;
public RESTServer getRESTServer(String endpointName)
{
return new RESTServer(env.getProperty(ednpointName + ".username"),
env.getProperty(ednpointName + ".password"),
env.getProperty(ednpointName+".host"));
}
}
An example of using this factory:
#Autowired RESTServerFactory factory;
public RESTServer createServer(String endpoint) {
return factory.getRESTServer(endpoint);
}

Two things to do:
Move RestServer outside to its own class, it should not be an inner class of RestConfiguration.
Use scope=DefaultScopes.PROTOTYPE in order to allow creation of several instances of RestServer, one per injection point:
#Bean(scope=DefaultScopes.PROTOTYPE)
public RESTServer restServer(Environment env) { ...

Related

Values not getting picked up from application.properties

I'm trying to access values from my application.properties file in the Neo4jConnectionFactory class. I am able to access the properties within my Setup beans class. But, I want to be able to access them from Neo4jConnectionFactory. When I try to do so I get null values. Is this possible?
Applications.properties
neo4j.url = bolt://localhost:7687
neo4j.user = neo4j
neo4j.password = neo4j
SetupBeans.java
#Component
public class SetupBeans {
#Bean
public Neo4jClient neo4jClient() {
return new Neo4jConnectionFactory().build();
}
}
Neo4jConnectionFactory.java
#Configuration("neo4j")
public class Neo4jConnectionFactory {
#Value("${neo4j.url}")
private String url;
#Value("${neo4j.user}")
private String user;
#Value("${neo4j.password}")
private String password;
public Neo4jClient build() {
return new Neo4jClient(getUrl(), getUser(), getPassword());
}
}
MyApplication.java
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I think this automatically prepends the prefix neo4j
#Configuration("neo4j")
So your #Value statments should omit the prefix, like this:
#Value("${url}")
private String url;
#Value("${user}")
private String user;
#Value("${password}")
private String password;

How to inject a repository into a controller or a service? [duplicate]

This question already has an answer here:
Internal working of field injection in spring and why is it not recommended to use
(1 answer)
Closed 6 months ago.
I am very new to Spring Boot. I have created a repository, which looks like this:
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByEmail(String email);
User findById(long id);
}
user.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
private boolean verified;
protected User() {}
public User(String email, String password, boolean verified) {
this.email = email;
this.password = password;
this.verified = verified;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
And now I want to inject the repository into my controller. This is how I tried it:
#RestController
public class Register {
#Autowired
private UserRepository userRepository;
#PostMapping("/register")
public User registerUser() {
return userRepository.save(new User("test#example.com", "password", true));
}
}
Is my approach correct? If so, then why do I get a warning on #Autowired that says:
Field injection is not recommended
?
You are applying field injection into your controller layer.
There is a more efficient way to do that but first, take a look at why field injection is not recommended.
Reasons
you can not define your dependent components as final(immutable).
you can not instantiate your components for test purposes.
your application may lead to circular dependencies.
your application is tightly coupled to your container.
There may be more reasons that you can search for them if you want. So now take a look at better ways to perform dependency injection.
Constructor Injection
This purpose is just to define your required dependencies as parameters to the class's constructor.
Let's say we have a class called A
#Component
Class A {}
Notice that we have to define our class as a Component so the container can use it later.
Now we want to inject class A to class B with constructor injection purpose.
Class B {
private final A a;
Public B(A a) {
this.a = a;
}
}
We successfully performed constructor injection instead of field injection.
There is another way to do dependency injection called setter injection which is useful for injecting optional dependent components.
That's it.

how spring-mvc finds the right class to instantiate and fill its data with POST-REQUEST fields

first of all,i am fairly new with spring mvc so ..
how springmvc find the right class to instantiate and fill its object properties when sending post-request to some controller.
for example lets assume i have this class
package springmvc_test.user
public class User {
private String username;
private String password;
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;
}
}
and controller class as the flowing
#Controller
#RequestMapping(value = {"/user"} )
public class UserController {
private List<User> users;
#Autowired
public UserController(List<User> users) {
this.users = users;
}
#RequestMapping(value = "/add",method = POST)
public String addUser(User user,Model m){
users.add(user);
//do some other stuf
//.....
}
}
when i do post-request for http://localhost/myapp/user/add
along with form fields that has the same names as User class properties,
it works fine.
but my question is that
how could springmvc find the User class and instantiated it ?although the User class is not annotated with noting at all
note:
i know that spring binds the User object properties by matching their names with form fields names
As the Spring Framework reference states in the respective section you should use either:
#RequestBody, if you want to let a HttpMessageConverter deserialize your HTTP request to any Java Object
#ModelAttribute if you want to get access to model variables, e.g. also out of your HTTP request.
on the parameter which you want to associate with your request data.

Load external properties file with same keys

I have two properties file in a folder in file system.
The path of this folder is passed using system properties -D=path/to/properties/folder
ex: java -jar -DpropDir=abc/def app.jar
These are properties files.
Please note that both files have common key username,password.
mysql.properties
url=jdbc:mysql://localhost:3306
username=root
password=pass
vertica.properties
dburl=jdbc:vertica://123.123.123:4321/abcd
username=abcd
password=pass123
Now I want to access all these properties in respective classes.
MySqlProperties.java and VerticaProperties.java like this.
#Component
public class VerticaProperties {
#Value("${dburl}")
private String dburl;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return dburl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
and similarly MySqlProperties.java
#Component
public class MySqlProperties {
#Value("${url}")
private String url;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
But as key is same value is overriding for username and password.
How to access mysql.properties in MySqlProperties.java and vertica.properties in VerticaProperties.java classes.
You import external properties using #PropertySource
Given the location is shared as
-Dmysql.properties=file:/path-to-mysql.properties -Dvertica.properties=file:/path-to-vertica.properties
#Component
#PropertySource("${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
public class MySqlProperties {
....
}
or Given
-Dmysql.properties=/path-to-mysql.properties -Dvertica.properties=/path-to-vertica.properties
#Component
#PropertySource("file:${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("file:${mysql.properties}")
public class MySqlProperties {
....
}
Additionally , you may also use prefix with #ConfigurationProperties along with #PropertySource.
The annotation works best when we have hierarchical properties that all have the same prefix, so we mention the prefix too as a part of the annotation.
Add prefix to keys like mysql.url , vertica.url in respective files
#Component
#PropertySource("${vertica.properties}")
#ConfigurationProperties(prefix="vertica")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
#ConfigurationProperties(prefix="mysql")
public class MySqlProperties {
....
}

Prefix for nested configuration properties in spring

Spring boot 2.0.0.RELEASE
I have properties class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
public static class AddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
}
And application.yml:
person:
address:
line1: line1OfAddress
It is not binding properly as my AddressProperties object is null.
When a class has the same name as yml properties AddressProperties -> Address it is working well. I tried to add Qualifier or ConfigurationProperties with a prefix address but it is not working. Unfortunately, I cannot find useful information about this case in spring docs.
How to specify a prefix for nested properties?
Property defined in yaml / property file should match with the variables defined in class.
Either change yaml file as
person:
# addressProperties will also work here
address-properties:
line1: line1OfAddress
Or define your bean as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
// here variable name doesn't matter, it can be addressProperties as well
// setter / getter should match with properties in yaml
// i.e. getAddress() and setAddress()
private AddressProperties address;
public AddressProperties getAddress() {
return address;
}
public void setAddress(AddressProperties address) {
this.address = address;
}
}
If you want to get all properties under address without defining them in separate bean you can define your PersonProperties class as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
private Map<String, Object> address;
public Map<String, Object> getAddress() {
return address;
}
public void setAddress(Map<String, Object> address) {
this.address = address;
}
}
Here PersonProperties#address will contain {line1=line1OfAddress}
Now All properties under address will be in the Map.
You could simply un-nest the two classes, allowing each to have it's own prefix.
First class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
}
Second class:
#Configuration
#ConfigurationProperties(prefix="person.address")
public class PersonAddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
Edit: As was pointed out in the comments, you'd have to inject both of these classes if one block of code needed to refer to both sets of properties.

Categories