User #Value springboot annotation as enum value - java

Given:
application.yml --> content
kafka:
topicA: topic-a
topicB: topic-b
public enum KafkaTopicBeanMapping {
TOPICA(#Value("${kafka.topicA}", "ratelimiterABean"));
TOPICB(#Value("${kafka.topicB}", "ratelimiterBBean"));
private final String topicName;
private final String ratelimiterBeanName;
}
But in the above case, I am getting error that #Value("${kafka.topicA}") cannot be used here.
I don't want to put ratelimiterBeanName as part of application.yml.
Is there a way to achieve this?

Java Enums are static in nature, they can be initialized only once. #Value in Spring makes values dynamic in nature which enums are not capable of.

Related

Int from application.properties

In application.properties:
comment.length=3000
Now I'd like to use this constant:
#Entity(name="clients_client")
public class Client {
#Column(length="${comment.length}")
private String comment;
}
When compiling, I get this error:
java: incompatible types: java.lang.String cannot be converted to int
This is very close to being a duplicate of How to import value from properties file and use it in annotation?, but I think there is a subtle difference between the questions.
You are trying to refer to a property in the #Column annotation by using ${comment.length}. What is really happening is that you try to assign the String "${comment.length}" to the length attribute of the annotation. This is of course not allowed, it expects an int.
Java, or Spring, can not "magically" replace ${propertyName} with a property. Spring, however, has its own way of injecting property values:
#Value("${value.from.file}")
private String valueFromFile;
Even if your entity was a Spring bean (for example annotated with #Component), and you injected the property with #Value, it cannot be used in the annotation. This is because values in annotations need to be constant, and is explained in more detail in the accepted answer to the near duplicate question.
Now I'd like to use this constant:
It simply is not a constant, it is determined at runtime.

Validate YAML sequence with hibernate validator against enum

I am trying to create my own validator for validating a List<String> read from a YAML configuration file. What I want to do, is validate it against a Enum class that I have created which looks like the following:
public enum BundleName {
DK("dk.bundle"),
UK("uk.bundle"),
US("us.bundle"),
DK_DEBUG("dk.bundle.debug"),
UK_DEBUG("uk.bundle.debug"),
US_DEBUG("us.bundle.debug"),
;
private String bundleName;
BundleName(String bundleName) {
this.bundleName = bundleName;
}
public String getBundleName() {
return this.bundleName
}
}
My YAML file has the following defined:
bundleNames:
- ${DK}
- ${UK}
- ${US}
- ${DK_DEBUG}
- ${UK_DEBUG}
- ${US_DEBUG}
Now either some of these environment variables might be set or all of them might be set or just one is set. Different environment different combo. Anyway what I want is to be able to validate these environment variables against the bundleName in enum BundleName.class. Now Hibernate have a nice example, and also all other examples I find on the net is, where validation is done against a specific simple enum. I do not want to check on the enum name but the enum value, and I simply cannot figure out how. All what I have tried so fare is some combinations of what I found in some other posts like this and this and many more.
In my ApiConfiguration.java I would like to end up with something like the following on my list read from YAML:
#NotNull
#BundleNameValidation(acceptedValues = BundleName.class)
private List<String> bundleNames;
If that is possible somehow.

Read environment variable in SpringBoot

What is the best way to read environment variables in SpringBoot?
In Java I did it using:
String foo = System.getenv("bar");
Is it possible to do it using #Value annotation?
Quoting the documentation:
Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments. You can use properties files, YAML files, environment variables and command-line arguments to externalize configuration. Property values can be injected directly into your beans using the #Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via #ConfigurationProperties.
So, since Spring boot allows you to use environment variables for configuration, and since Spring boot also allows you to use #Value to read a property from the configuration, the answer is yes.
For example, the following will give the same result:
#Component
public class TestRunner implements CommandLineRunner {
#Value("${bar}")
private String bar;
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void run(String... strings) throws Exception {
logger.info("Foo from #Value: {}", bar);
logger.info("Foo from System.getenv(): {}", System.getenv("bar")); // Same output as line above
}
}
You can do it with the #Value annotation:
#Value("${bar}")
private String myVariable;
You can also use colon to give a default value if not found:
#Value("${bar:default_value}")
private String myVariable;
Here are three "placeholder" syntaxes that work for accessing a system environment variable named MY_SECRET:
#Value("${MY_SECRET:aDefaultValue}")
private String s1;
#Value("#{environment.MY_SECRET}")
private String s2;
#Value("${myApp.mySecretIndirect:aDefaultValue}") // via application property
private String s3;
In the third case, the placeholder references an application property that has been initialized from the system environment in a properties file:
myApp.mySecretIndirect=${MY_SECRET:aDefaultValue}
For #Value to work, it must be used inside a live #Component (or similar). There are extra gochas if you want this to work during unit testing -- see my answer to Why is my Spring #Autowired field null?
Alternatively, you can use the org.springframework.core.env.Environment interface to access environment variables:
import org.springframework.core.env.Environment;
#Autowired
private Environment env;
//...
System.out.println(env.getProperty("bar"));
Read more...
Yes, you can. However, most answer didn't mention, the ordering is very important, please check this https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html
Your OS environment variables will overwrite the value come from Application properties packaged inside your jar (application.properties and YAML variants)., so basically, your environment variables has higher priority.
you can use it with The #Value annotation for the #Components and #service class
Some times it won't work if it is a normal class
Example:
#Component
public class Testclass{
#Value("${MY_SECRET:aDefaultValue}")
private String test1;
#Value("#{environment.MY_SECRET}")
private String test1;
#Value("${myApp.mySecretIndirect:aDefaultValue}")
private String test1;
//to get the properties list whih are in "," seperated
#Value("${my.list.of.strings}")
private List<String> myList;
}
You can place your environment variable in an application.yml/application.properties file and then you can fetch the value using the #Value annotation.
But in order to use #Value annotation your class should be a bean and should be annotated with #Component annnotation.
You can also provide a default value for the variable.
#Component
#NoArgsConstructor
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MyClass {
#Value("${something.variable:<default-value>}")
private String myEnvVariable;
}
First, you have to define the relevant field information in the properties configuration file, and then use # value to obtain and use
example:
#Value("${abc}")
private String abc;

Spring Boot #ConfigurationProperties correct usage

We are actually using Spring Boot's #ConfigurationProperties as basically a configuration mapper : it provides us an easy shortcut to map properties on objects.
#ConfigurationProperties("my.service")
public class MyService {
private String filePrefix;
private Boolean coefficient;
private Date beginDate;
// getters/setters mandatory at the time of writing
public void doBusinessStuff() {
// ...
}
}
Although this was a nice productivity boost when we were prototyping the app, we came to question if this was right usage.
I mean, configuration properties have a different status in Spring Boot's context, they're exposed through actuator endpoints, they can be used to trigger conditional beans, and seem more oriented toward technical configuration properties.
Question : Is it "correct" to use this mechanism on any business property/value, or is it plain misuse ?
Any potential drawback we missed ?
Right now our only concern is that we cannot use #ConfigurationProperties on immutable classes, which is closely related to this issue on Spring Boot's tracker : Allow field based #ConfigurationProperties binding
If your property represents something that is configurable based on the environment/profile that is what the mechanism is there for. Though I'm a little unclear what you mean by
"map properities on objects".
I would not favor this style in general, especially if your bean has multiple properties to set. A more standard idiom is to have a class that encapsulates the properties/settings used to create your bean:
#ConfigurationProperties("my.service")
public class MyServiceProperties {
private String filePrefix;
private Boolean coefficient;
private Date beginDate;
// getters/setters mandatory at the time of writing
}
then your Service class would look like this:
#EnableConfigurationProperties(MyServiceProperties.class)
public class MyService {
#Autowired
private MyServiceProperties properties;
//do stuff with properties
public void doBusinessStuff() {
// ...
}
}
This would at least allow you to pass the properties easily into an immutable class through it's constructor (make copies of any mutable properties). Also having the properties bean can be reused if you find other parts of your app need some shared configuration.

What is best way of injecting values from environment variables?

Consider this example
#Stateless
public class UniqueIdGenerator {
private static final String COLON = ":";
private String serverPrivateKey;
#SuppressWarnings("UnusedDeclaration")
public UniqueIdGenerator() {
}
#Inject
public UniqueIdGenerator(#Nonnull final String serverPrivateKey) {
this.serverPrivateKey = serverPrivateKey;
}
...
}
I would like to #Inject value of serverPrivateKey based on an environment variable available in different environments.
What is the best way to inject it here?
To inject values from the environment, rather than writing your own producer methods, you may want to have a look at the Configuration API of Apache DeltaSpike.
Using a #ConfigProperty qualifier, you can inject values from a number of different property sources, like system properties, environment variables or JNDI.
Example:
#Inject
#ConfigProperty(name = "SERVER_PRIVATE_KEY")
private String serverPrivateKey;
We use the following pattern: There is a bean which gives us the value which we need. The bean knows how to get the value (environment, System property, whatever). To make things easier later, the type of the bean should be an interface (in your case that might be IPrivateKeyProvider).
The UniqueIdGenerator is then created and we inject the first bean. The setup then decides which bean this will be (some kind of mock for tests and a real implementation for production code).
You will have to use producer Method :
According to related oracle documentation :
A producer method generates an object that can then be injected. Typically, you use producer
methods in the following situations:
[...]
When the concrete type of the object to be injected may vary at runtime
See an example here

Categories