I have a java class called UserData, and it has a field called users that I'm reading from application.yml in my spring-boot application, by using the #ConfigurationProperties annotation.
In this class I want to declare another bean to find the oldest user. When I try to create the getOldestUser bean, I'm getting a NullPointerException in the initialization of getOldestUser bean, because the users variable is null.
Here's how my code looks like:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Component
#Configuration
#ConfigurationProperties
#EnableConfigurationProperties
public class UserData {
Map<String, Integer > users;
#Bean
public String getOldestUser() {
Integer maxAge = 0;
String oldestUser = null;
for (Map.Entry<String, Integer> entry : users.entrySet()) { // This line throws NPE, because 'users' is null
if(entry.getValue() > maxAge) {
oldestUser = entry.getKey();
maxAge = entry.getValue();
}
}
return oldestUser;
}
}
Here's the users declaration in application.yml:
users:
joey: 32
jim: 29
How can I access the fields of the class bean userData inside it's method bean getOldestUser? I've also tried passing the userData bean as an argument to this method, but that doesn't work either.
Firstly, try to split the configuration part from properties to separate classes.
Also, the #ConfigurationProperties annotation needs to have a prefix which binds all child properties to the fields.
Lastly, the method getOldestUser which you made as a spring bean is just a calculating function which can be under the UserProperties class and called whenever you need this data, spring is not required :).
data:
users:
joey: 32
jim: 29
#Getter
#Setter
#ConfigurationProperties(prefix = "data")
public class UserProperties{
private Map<String, Integer > users;
public String getOldestUser() {
Integer maxAge = 0;
String oldestUser = null;
for (Map.Entry<String, Integer> entry : users.entrySet()) { // This line throws NPE, because 'users' is null
if(entry.getValue() > maxAge) {
oldestUser = entry.getKey();
maxAge = entry.getValue();
}
}
return oldestUser;
}
}
#Configuration
#EnableConfigurationProperties(UserProperties.class)
public class UserConfiguration {}
Related
For my Quarkus application I'm looking for a way to define a configuration map from within a custom ConfigProperties class. I tried the following:
import io.quarkus.arc.config.ConfigProperties;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigProperties(prefix = "my-properties")
public class MyPropertiesConfiguration {
#ConfigItem
public Map<String, FooConfiguration> foo;
// ...
}
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigGroup
public class FooConfiguration {
#ConfigItem
public String myProperty;
}
Given those two classes and the following application.properties file...
my-properties.foo.anystring.my-property=bar
on startup the application fails with error message:
javax.enterprise.inject.spi.DeploymentException: No config value of type [java.util.Map] exists for: my-properties.foo
As far as I understand https://quarkus.io/guides/writing-extensions#configuration-maps the sample should work. What am I doing wrong? Could it happen that this functionality is just limited to Quarkus extensions only?
As written in this Quarkus github issue, this is currently not supported.
My dirty workaround was to use the ConfigProvider directly. Use with care.
public static Map<String, String> getMapFromConfig(String prefix) {
final Config config = ConfigProvider.getConfig();
final Iterable<String> propertyNames = config.getPropertyNames();
return StreamSupport.stream(propertyNames.spliterator(), false)
.filter(name -> name.startsWith(prefix) && !name.equalsIgnoreCase(prefix))
.collect(
Collectors.toMap(
propertyName -> cleanupPropertyName(propertyName.substring(prefix.length() + 1)),
propertyName -> config.getOptionalValue(propertyName, String.class).orElse("")));
}
/** Remove start and end double quotes */
public static String cleanupPropertyName(String name) {
if (name.startsWith("\"") && name.endsWith("\"")) {
return name.substring(1, name.length() - 1);
}
return name;
}
My config looks like this:
property-templates:
"my.key": value 1
"my.second.key": value 2
Declare the configuration like this
import io.quarkus.arc.config.ConfigProperties;
#ConfigProperties(prefix = "myapp")
public class AppSpecificConfig {
public String property;
}
The application.properties file will contain
myapp.property=foo
And then you can #Inject an instance of this class anywhere within your application.
For more details, see https://quarkus.io/guides/config#using-configproperties
I am using below annotations in my config class to get the values from properties file(yml).
Configuration
EnableConfigurationProperties
ConfigurationProperties (prefix = "notification")
I am able to get the values inside public methods without problem using the class . But I am getting 'Error Creating bean' Error when I try to assign value instance variable of the class using config class.
Below is my code. Can someone please throw some light.
This is my config class
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties (prefix = "notification")
public class NotifyYaml {
private String subscriptionId;
public String getSubscriptionId() {
return subscriptionId;
}
public void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
Below is the class where I am getting error during startup.
#Component
public class PubSubController {
#Autowired
private NotifyYaml notify;
public PubSubController() {
// TODO Auto-generated constructor stub
}
String projectId = "ccc-g-pre-proj-cacdate";
//Error in this line
String subscriptionId = notify.getSubscriptionId();
The #Autowired object only gets filled in after the object is created.
This means that while the object is being created, it tries to call a method from a null object.
I would suggest using something like a #PostConstruct method. (Note: you will need to include javax.annotations into your project somehow.)
String subscriptions; // remove the value for now...
#PostConstruct
private void init() {
subscriptions = notify.getSubscriptionId(); // ...and add it back in here.
}
I'm using Spring Boot 1.4.3 #AutoConfiguration where I create beans automatically based on properties user specifies. User can specify an array of services, where name and version are required fields:
service[0].name=myServiceA
service[0].version=1.0
service[1].name=myServiceB
service[1].version=1.2
...
If the user forgets to specify a required field on even just one service, I want to back-off and not create any beans. Can I accomplish this with #ConditionalOnProperty? I want something like:
#Configuration
#ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
This is the custom Condition I created. It needs some polishing to be more generic (ie not hardcoding strings), but worked great for me.
To use, I annotated my Configuration class with #Conditional(RequiredRepeatablePropertiesCondition.class)
public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());
public static final String[] REQUIRED_KEYS = {
"my.services[i].version",
"my.services[i].name"
};
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<String> missingProperties = new ArrayList<>();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
Map<String, Object> services = resolver.getSubProperties("my.services");
if (services.size() == 0) {
missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
return getConditionOutcome(missingProperties);
}
//gather indexes to check: [0], [1], [3], etc
Pattern p = Pattern.compile("\\[(\\d+)\\]");
Set<String> uniqueIndexes = new HashSet<String>();
for (String key : services.keySet()) {
Matcher m = p.matcher(key);
if (m.find()) {
uniqueIndexes.add(m.group(1));
}
}
//loop each index and check required props
uniqueIndexes.forEach(index -> {
for (String genericKey : REQUIRED_KEYS) {
String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
if (!resolver.containsProperty(multiServiceKey)) {
missingProperties.add(multiServiceKey);
}
}
});
return getConditionOutcome(missingProperties);
}
private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
if (missingProperties.isEmpty()) {
return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.found("property", "properties")
.items(Arrays.asList(REQUIRED_KEYS)));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.didNotFind("property", "properties")
.items(missingProperties)
);
}
}
Old question, but I hope my answer will help for Spring2.x:
Thanks to #Brian, I checked migration guide, where I was inspired by example code. This code works for me:
final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);
I did try to get List of POJO (as AutoConfigureService) but my class differs from AutoConfigureServices. For that purpose, I used:
final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);
Well, keep playing :-D
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration. Somewhat similar to what #Strumbels proposed but more reusable.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty/null and if all items within are valid.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("your.property.prefix")
#ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {
// Service is your POJO representing the name and version subproperties
private final List<Service> services;
#Override
public boolean isPresent() {
return services != null && services.stream().all(Service::isValid);
}
}
And then in Spring #Configuration class.
#Configuration
#ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
You can leverage the org.springframework.boot.autoconfigure.condition.OnPropertyListCondition class. For example, given you want to check for the service property having at least one value:
class MyListCondition extends OnPropertyListCondition {
MyListCondition() {
super("service", () -> ConditionMessage.forCondition("service"));
}
}
#Configuration
#Condition(MyListCondition.class)
class AutoConfigureServices {
}
See the org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition used on org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor for an example within Spring itself.
I have add some config inside my application.yml file and I want to read it from my Java code.
The added node inside the YAML file looks like this:
myConfig:
projectOne:
mantisID: 501
user: username
password: passwd
projectTwo:
mantisID: 502
user: username
password: passwd
What I want is to get a List of Project objects where
Project.mantisID = 501,
Project.user = "username",
Project.password = "passwd",
etc...
I know spring can read this file with some #Value annotation but how can I use this in order to get what I need?
You can use #ConfigurationProperties annotation to map your configuration to a Bean, then you'll be able to inject your Bean anywhere and fetch those properties.
To do so, first create a class which represents the data structure in your configuration. Then annotate it with #ConfigurationProperties and #Configuration annotations.
#Configuration
#ConfigurationProperties
public class MyConfig {
private final Map<String, Project> myConfig = new HashMap<>();
public Map<String, Project> getMyConfig() {
return myConfig;
}
public static class Project {
private String mantisID;
private String password;
private String user;
// Getters and setters...
}
}
Note that getters and setters are required in the Project class. Also keep in mind that naming of getters and setters is important here.
After you have setup this class, you can inject it anywhere in your project and access its properties.
#Service
public class SomeService {
private final Map<String, MyConfig.Project> projects;
#Autowired
public SomeService(MyConfig config) {
this.projects = config.getMyConfig();
projects.get("projectOne").getMantisID();
projects.get("projectTwo").getPassword();
}
}
You can read more about this here.
Just to finish, I answered myself to my second question.
This is what my service looks like now :
#Service
public class MantisProjectService {
private final Map<String, MantisProjectConfiguration.Project> projects;
private List<MantisProjectConfiguration.Project> mantisProjects = new ArrayList<>();
#Autowired
public MantisProjectService(MantisProjectConfiguration mantisProjectConfiguration)
{
this.projects = mantisProjectConfiguration.getMantisProjectConfiguration();
for (Map.Entry<String, MantisProjectConfiguration.Project> project : projects.entrySet())
{
MantisProjectConfiguration.Project mantisProject = project.getValue();
mantisProject.setName(project.getKey());
mantisProjects.add(mantisProject);
}
}
public List<MantisProjectConfiguration.Project> getMantisProjects()
{
return mantisProjects;
}
}
It returns a List of all the projects. And it is awesome! =)
I am using Spring MVC for project. I have some constant values which is stored in properties file and I want to fetch from properties file. Question I am unable to fetch values in Model Classes from properties file. It is getting null.
I have set property file location in servlet-context.xml
<context:property-placeholder location="classpath:myproperties.properties" />
Now by using #Value annotation I inject value from properties file.
#Component
class ModelTest {
#Value("${fname}")
private String fname;
// Default Constructor
public ModelTest(){
Sysout(fname); // getting null here
}
#PostConstruct
public void initMembers(){
Sysout(fname) // Prints fname value properly
}
public void setFname(String fname){
this.fname=fname;
}
public String getFname(){
return fname;
}
#Override
public String toString() {
Sysout(fname);
return "ModelTest [variableFirst=" + variableFirst + "]";
}
}
Here is ServiceTest class.
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
public String printTest(){
sysout(fname); // Prints fname value
return new ModelTest().toString() // Prints null
}
}
Here is ControllerHome Class :
#Controller
public class ControllerHome {
#Value("${fname}")
private String fname;
#Autowired
private ServiceTest service;
#RequestMapping("/")
public #ResponseBody String printData(){
sysout(fname); // Prints fname value
return service.printTest(); // Print null
}
}
In model class fname is getting null while In controller and service class value is coming properly.
is anyone face such issue?
When you say model class, do you mean the value passed to a controller method indicated by #ModelAttribute?
If so, that class is created by ordinary constructor invocation through reflection. It is not a spring bean, and thus #Value does nothing.
Addressing your edit, I think there is some fundamental misunderstanding about how Spring works.
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
public String printTest(){
sysout(fname); // Prints fname value
// Calling new here means Spring does nothing
// ModelTest is not a Spring bean
// `#Component`, `#PostConstruct` and `#Value` in ModelTest mean nothing.
return new ModelTest().toString() // Prints null
}
}
Instead, you have to do something like this:
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
#Autowired
private ModelTest modelTest;
public String printTest(){
sysout(fname); // Prints fname value
// modelTest is now a Spring bean
return modelTest.toString() // Should not print null
}
}
Now, Spring will create ModelTest, and #Component, #PostConstruct and #Value will be honored by Spring.
However, #Component by itself has a default singleton scope. So, you will have the same modelTest always.
So, you have to do something like this:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
class ModelTest {
// ...
}
Now, while the modelTest reference in ServiceTest will remain constant, the use of a proxy will divert the method calls to a new instance of ModelTest, created by Spring, per request.
This is how I do it :
#Component
#PropertySource("classpath:myproperties.properties") // <-Add this.
class ModelTest {
#Autowired
private Environment env;
public void test(){
String name = env.getProperty("name"); //Assuming you have a 'name' key in your myproperties.property
}
}