I have a configuration class with this constructor:
public CxfConfigurerImpl(#Value("${cxfclient.timeout.connection}") long connectionTimeout,
#Value("${cxfclient.timeout.connection-request}") long connectionRequestTimeout,
#Value("${cxfclient.timeout.receive}") long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}
But I have different values to set according with my endpoints requests.
Ex:
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Autowired
private CxfConfigurerImpl cxfConfigurer3Seg = new CxfConfigurerImpl(connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
My environment variables are:
CXFCLIENT_TIMEOUT_CONNECTION=1000
CXFCLIENT_TIMEOUT_CONNECTIONREQUEST=1000
CXFCLIENT_TIMEOUT_RECEIVE=1000
CXFCLIENT_TIMEOUTTHREE_CONNECTION=3000
CXFCLIENT_TIMEOUTTHREE_CONNECTIONREQUEST=3000
CXFCLIENT_TIMEOUTTHREE_RECEIVE=3000
The problem is that for the object "cxfConfigurer3Seg" I'm getting the default constructor values (1000). Is there any way to override the values?
OBS: I can't change the "CxfConfigurerImpl" constructor implementation.
Please notes that
#Value : by definition is an Annotation used at the field or method/constructor parameter levelthat indicates a default value expression for the annotated element.
And Note that actual processing of the #Value annotation is performedby a BeanPostProcessor
So values are setted after bean built, otherwise you will already find the value 1000 in your variables
So you should change your implementation here :
You should init configuration in the application level and use #Qualifier for two different bean instead the #Value constructor injection:
#Component
#Getter
#Setter
#ToString
public class B {
private long receiveTimeout;
private long connectionTimeout;
private long connectionRequestTimeout;
public B () {
}
public B ( long connectionTimeout,
long connectionRequestTimeout,
long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout =
connectionRequestTimeout;
}
}
In the spring boot application startup class : build two beans with two configurations : (b1, b2)
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Value("${cxfclient.timeout.connection}")
private long receiveTimeout;
#Value("${cxfclient.timeout.connection-request}")
private long connectionTimeout;
#Value("${cxfclient.timeout.receive}")
private long connectionRequestTimeout;
#Bean ("b1")
public B createAsB1() {
return new B (connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
}
#Bean ("b2")
public B createAsB2() {
return new B(connectionTimeout, connectionRequestTimeout, receiveTimeout);
}
use them by :
#Component
#Getter
#Setter
#ToString
public class UseB {
#Autowired
#Qualifier ("b1")
private B b1;
#Autowired
#Qualifier ("b2")
private B b2;
}
String d = ab.getB1().toString();
System.out.println(d);
d = ab.getB2().toString();
System.out.println(d);
result of run :
B(receiveTimeout=3000, connectionTimeout=3000,
connectionRequestTimeout=3000)
B(receiveTimeout=1000, connectionTimeout=1000,
connectionRequestTimeout=1000)
There are two options -
1- if you want both the beans available in your application context then you can create two separate beans with two different values.
2- If you want to have both values in your application but only one active bean at a time then you can use #ConditionalOnBean, attaching [here][1] a document for your help. Below is sample rough conditionalonbean code snippet and can enable only one of bean from your configurations file.
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "true",
"name" = "abc")
public A methodA() {}
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "false",
"name" = "abc")
public A methodB() {}
Related
I realized a strange behavior in SpringBoot.
In a yml file I have the following configuration:
main:
record-id:
start-position: 1
value: 1
enabled: true
record-name:
start-position: 2
value: main
enabled: true
invented:
start-position: 3
value: 01012020
enabled: false
And these are the classes for it:
public class FieldType {
private Integer startPosition;
private String value;
private Boolean enabled;
getters/setters
}
#Component
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId;
private FieldType recordName;
private FieldType invented;
getters/setters <-- sometimes without getters
}
As you can see, the main class has #ConfigurationProperties annotation to load the properties from yml into that bean.
And here is what I have found:
if I don't provide getters for the fields in the main class, then sometimes the fields in the main call stay null, so not initiated
if I restart the SpringBoot, then randomly other (1 or more) fields stay null, so not initiated
if I restart the SpringBoot n times, then, again and again, random fields stay null
if I provide getters for the fields in the main class, then all the fields will be always instantiated from tye yml file, no matter how many times I restart SpringBoot
Why is this? Why SpringBoot requires getters for fields which represent properties in yml?
You don't need getter's to bind the properties, you need setters to bind properties if you are using default constructor, docs
If nested POJO properties are initialized (like the Security field in the preceding example), a setter is not required. If you want the binder to create the instance on the fly by using its default constructor, you need a setter.
In case if you are initializing the FieldType in Main class, then you don't need setters as well
#Component
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId = new FieldType();
private FieldType recordName = new FieldType();
private FieldType invented = new FieldType();
}
You can also use Constructor binding by completely avoiding setters
public class FieldType {
private Integer startPosition;
private String value;
private Boolean enabled;
public FieldType(Integer startPosition, String value, Boolean enabled) {
this.startPosition = startPosition;
this.value = value;
this.enabled = enabled
}
#ConstructorBinding
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId;
private FieldType recordName;
private FieldType invented;
public Main(FieldType recordId,FieldType recordName,FieldType invented) {
this.recordId = recordId;
this.recordName = recordName;
this.invented = invented;
}
Just a note on Constructor Binding
To use constructor binding the class must be enabled using #EnableConfigurationProperties or configuration property scanning. You cannot use constructor binding with beans that are created by the regular Spring mechanisms (e.g. #Component beans, beans created via #Bean methods or beans loaded using #Import)
Is it ok to initialize a property as part of a data class like in the code below, when the property is defined as a #Component?
#SuperBuilder
#Data
public class DataClass{
private final RandomUUIDGenerator generator = new RandomUUIDGenerator();
#Builder.Default
String uuid = generator.generate();
}
The RandomUUIDGenerator is defined like this:
#Component
public class RandomUUIDGenerator implements UUIDGenerator {
public UUID generate() {
return UUID.randomUUID().toString();
}
}
You can do it, however it is not recommended as you are not utilising the benefits of dependency injection. You should autowire RandomUUIDGenerator using #Autowired annotation.
I have the following enum:
public enum MyEnum {
NAME("Name", "Good", 100),
FAME("Fame", "Bad", 200);
private String lowerCase;
private String atitude;
private long someNumber;
MyEnum(String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
}
I want to setup the someNumber variable different for both instances of the enum using application.properties file.
Is this possible and if not, should i split it into two classes using an abstract class/interface for the abstraction?
You can't/shouldn't change values of a enum in Java. Try using a class instead:
public class MyCustomProperty {
// can't change this in application.properties
private final String lowerCase;
// can change this in application.properties
private String atitude;
private long someNumber;
public MyCustomProperty (String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
// getter and Setters
}
Than create a custom ConfigurationProperties:
#ConfigurationProperties(prefix="my.config")
public class MyConfigConfigurationProperties {
MyCustomProperty name = new MyCustomProperty("name", "good", 100);
MyCustomProperty fame = new MyCustomProperty("fame", "good", 100);
// getter and Setters
// You can also embed the class MyCustomProperty here as a static class.
// For details/example look at the linked SpringBoot Documentation
}
Now you can change the values of my.config.name.someNumber and my.config.fame.someNumber in the application.properties file. If you want to disallow the change of lowercase/atitude make them final.
Before you can use it you have to annotate a #Configuration class with #EnableConfigurationProperties(MyConfigConfigurationProperties.class). Also add the org.springframework.boot:spring-boot-configuration-processor as an optional dependency for a better IDE Support.
If you want to access the values:
#Autowired
MyConfigConfigurationProperties config;
...
config.getName().getSumeNumber();
Well what you can do is the following:
Create a new class: MyEnumProperties
#ConfigurationProperties(prefix = "enumProperties")
#Getter
public class MyEnumProperties {
private Map<String, Long> enumMapping;
}
Enable ConfigurationProperties to your SpringBootApplication/ any Spring Config via
#EnableConfigurationProperties(value = MyEnumProperties.class)
Now add your numbers in application.properties file like this:
enumProperties.enumMapping.NAME=123
enumProperties.enumMapping.FAME=456
In your application code autowire your properties like this:
#Autowired
private MyEnumProperties properties;
Now here is one way to fetch the ids:
properties.getEnumMapping().get(MyEnum.NAME.name()); //should return 123
You can fetch this way for each Enum value the values defined in your application.properties
I am experimenting with spring data elasticsearch by implementing a cluster which will host multi-tenant indexes, one index per tenant.
I am able to create and set settings dynamically for each needed index, like
public class SpringDataES {
#Autowired
private ElasticsearchTemplate es;
#Autowired
private TenantIndexNamingService tenantIndexNamingService;
private void createIndex(String indexName) {
Settings indexSettings = Settings.builder()
.put("number_of_shards", 1)
.build();
CreateIndexRequest indexRequest = new CreateIndexRequest(indexName, indexSettings);
es.getClient().admin().indices().create(indexRequest).actionGet();
es.refresh(indexName);
}
private void preapareIndex(String indexName){
if (!es.indexExists(indexName)) {
createIndex(indexName);
}
updateMappings(indexName);
}
The model is created like this
#Document(indexName = "#{tenantIndexNamingService.getIndexName()}", type = "movies")
public class Movie {
#Id
#JsonIgnore
private String id;
private String movieTitle;
#CompletionField(maxInputLength = 100)
private Completion movieTitleSuggest;
private String director;
private Date releaseDate;
where the index name is passed dynamically via the SpEl
#{tenantIndexNamingService.getIndexName()}
that is served by
#Service
public class TenantIndexNamingService {
private static final String INDEX_PREFIX = "test_index_";
private String indexName = INDEX_PREFIX;
public TenantIndexNamingService() {
}
public String getIndexName() {
return indexName;
}
public void setIndexName(int tenantId) {
this.indexName = INDEX_PREFIX + tenantId;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
}
So, whenever I have to execute a CRUD action, first I am pointing to the right index and then to execute the desired action
tenantIndexNamingService.setIndexName(tenantId);
movieService.save(new Movie("Dead Poets Society", getCompletion("Dead Poets Society"), "Peter Weir", new Date()));
My assumption is that the following dynamically index assignment, will not work correctly in a multi-threaded web application:
#Document(indexName = "#{tenantIndexNamingService.getIndexName()}"
This is because TenantIndexNamingService is singleton.
So my question is how achieve the right behavior in a thread save manner?
I would probably go with an approach similar to the following one proposed for Cassandra:
https://dzone.com/articles/multi-tenant-cassandra-cluster-with-spring-data-ca
You can have a look at the related GitHub repository here:
https://github.com/gitaroktato/spring-boot-cassandra-multitenant-example
Now, since Elastic has differences in how you define a Document, you should mainly focus in defining a request-scoped bean that will encapsulate your tenant-id and bind it to your incoming requests.
Here is my solution. I create a RequestScope bean to hold the indexes per HttpRequest
how does singleton bean handle dynamic index
I want to store a property into the database as a Long, but use the object with helper methods in the code.
However the object type is a custom type I have that has an internal value (a long) that I want to store to the database.
public final class MyBean extends Number implements Serializable, Comparable<MyBean>
{
private long myValue;
public MyBean(Long value) { this.myValue = value; }
// Other helper methods and overrides
public MyBean valueOf(Long data)
{
return new MyBean(data);
}
#Override
public String toString()
{
return String.valueOf(myValue);
}
}
This is how I am using it:
#Entity
#Table(name = "mybeans")
public class MyBean implements Serializable
{
private static final long serialVersionUID = 1L;
MyBean myBean;
#Id
#Column(name = "mybean", nullable = false)
public MyBean getMyBean() { return myBean; }
public void setMyBean(MyBean value) { this.myBean = value; }
}
Deserializing this object calls toString and works fine (jax-rs/jersey). But when I try to pull it out of the database using my EJB, the error I get is:
The object [1,427,148,028,955], of class [class java.lang.Long], could
not be converted to [class com.MyBean]
Saving it produced the error
Can't infer the SQL type to use for an instance of com.MyBean. Use
setObject() with an explicit Types value to specify the type to use.
Which makes sense.
But what methods can I add in to male the EJB get the long as the value and use the long to set up a new object?
ANSWER:
Making the class #Embeddable and adding the following attributes worked.
#Embedded
#AttributeOverrides({
#AttributeOverride(name="value", column=#Column(name="mybean"))
})
(I didn't add EmbeddedId because I added a serial primary key id and just made this a column)
The one caveat is that it won't work with dynamic weaving. I had to add
<property name="eclipselink.weaving" value="static"/>
to my persistence.xml
You can try making MyBean an Embeddable to use that as an EmbeddedId, something like this:
#Embeddable
public final class MyBean extends Number implements Serializable, Comparable<MyBean> {
private Long myValue;
public MyBean(Long myValue) {
this.myValue = myValue;
}
// Other helper methods and overrides
public MyBean valueOf(Long data) {
return new MyBean(data);
}
#Override
public String toString() {
return String.valueOf(myValue);
}
}
In your entity, MyBean will be an EmbeddedId and will look like this:
#Entity
#Table(name = "mybeans")
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
private MyBean myBean;
#EmbeddedId
#AttributeOverride(name="myValue", #Column(name="mybean_id"))
public MyBean getMyBean() {
return myBean;
}
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}
Adjust MyBean as you need, such as making Transient some attributes.