I am working on spring but not spring boot. I have a class marked as #Component (but not as #Configuration [it's not config class]). I would like to create it based on the serviceA.create property in app.properties. It the file it looks like this: serviceA.create=true. I would think, that in the code it narrows down to:
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("serviceA.create"));
}
But based on different questions/answers (f.e. this) on the same topic, it sounds like adding #PropertySource(value="classpath:config.properties") is mandatory for this to work (to actually read the property value).
Question is, if I am able to get property from ConditionContext without defining #PropertySource ? because currently I am getting null without that annotation.
It should be done in the following way:
#Component
#Conditional(ServiceACondition.class)
#PropertySource(value="classpath:app.properties")
public class ServiceA {
(...)
}
and the cond. class
public class ServiceACondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("serviceA.start"));
}
}
Related
I have application.yml that looks like this:
feature:
toggles:
checksLoginAndRegistration: true
I am trying to get it in my class with #Value annotation, but it's not working.
public class UMLUserRepository implements UserRepository {
#Value("${feature.toggles.checksLoginAndRegistration}")
private boolean checksLoginAndRegistration;
private void validateLoginNow(LoginInfo info, User user) {
checkKnownBlock(info, user.username);
if(checksLoginAndRegistration){
try {
service.validateLogin(user.username);
} catch (ValidationException alidationException) {
throw new Exception(user.username);
}
}
}
When I debug the code my checksLoginAndRegistration variable is set to false.
According to the comments you have used #Value annotation within a simple POJO. Not inside a Spring Bean like #Component, #Service or #Configuration.
You cannot inject a value to a POJO class using #Value.
This annotation can be used for injecting values into fields in Spring-managed beans, and it can be applied at the field or constructor/method parameter level.
But still you get value false for checksLoginAndRegistration parameter because it is an primitive type which has a default value false. If you chaged it to boxed type Boolean you can see the value of checksLoginAndRegistration is null
Update
#ConfigurationProperties(prefix = "feature.toggles")
public class AppConfig {
private Boolean checksLoginAndRegistration;
}
Then update your UMLUserRepository class, (We make checksLoginAndRegistration is a dependency to UMLUserRepository class)
public class UMLUserRepository implements UserRepository {
private final Boolean checksLoginAndRegistration;
public UMLUserRepository(Boolean checksLoginAndRegistration) {
this.checksLoginAndRegistration = checksLoginAndRegistration;
}
}
This is the class where you crate instance of UMLUserRepository class. An it should be a Spring Bean.
#Component (or #Service)
public class ClassYouInitatingUMLUserRepository {
#Autowire
private AppConfig appConfig;
public void yourMethod() {
UMLUserRepository repo = new UMLUserRepository(appConfig.getChecksLoginAndRegistration());
}
I would encourage you to check the possibility to convert UMLUserRepository class to a Spring bean. Then this won't be needed.
Hmm, it seems like you do everything correctly. I can suggest what can go wrong
Is it all what file contains?
If not, check is there only one feature key or not. If there's another one, remove it.
Have you added #Configuration annotation to your configuration class?
If not, add it.
Let's say we have a simple Spring Condition which has to match against a file property from the properties file:
public class TestCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
context.getEnvironment().getProperty("my.property");
context.getBeanFactory().resolveEmbeddedValue("${my.property}");
context.getEnvironment().resolvePlaceholders("${my.property}");
// ... more code
}
}
Unfortunately, none of the mentioned above method calls return a real property which is defined in the property file. Instead, I get null when I call the getProperty method and "${my.property}" string for the other two (apparently, the property hasn't been resolved).
How about PropertiesLoaderUtils? Just put it into your method, instead of what you have there.
// path to your .properties file
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
....
String someValue = props.getProperty("someKey", "DEFAULT_VALUE");
Maybe try this if your stuff does not work.
you have various ways to access the properties in spring, the main and most simple are as follow
using injected values
#PropertySource("classpath:foo.properties")
public class foo {
#Value("${db.driver}")
private String dbDriver;
}
or you can use the Environment
#PropertySource("classpath:config.properties")
public class foo {
#Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
}
more info can be found here
I had the similar problem.
Property was loaded by XML
<context:property-placeholder location="classpath:conf/coreConf.properties"/>
So I had to add empty java #Configuration to import properties.
#Configuration
#PropertySource("classpath:conf/coreConf.properties")
public class Configuration {
}
After that I was able to get property
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty("main.settings.db.active");
return property != null && Boolean.parseBoolean(property.trim());
}
I have the following classes:
#Component
#ConifgurationProperties("redis")
public class RedisProperties {
private List<String> hosts;
// getters, setters
}
#Component
public class StaticRedisHostsProvider implements RedisHostsProvider {
private final RedisProperties redisProperties;
public StaticRedisHostsProvider(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
#Override
public List<String> getAll() {
return redisProperties.getHosts();
}
}
#Component
public DiscoveryBasedRedisHostsProvider { ... }
I want StaticRedisHostsProvider to be used if redis.hosts property is specified, DiscoveryBasedRedisHostsProvider otherwise.
I could annotate StaticRedisHostsProvider with #ConditionalOnProperty(prefix = "redis", name = "hosts"), but there is no similar #ConditionalOnMissingProperty annotation for using with DiscoveryBasedRedisHostsProvider.
I tried to use #ConditionalOnExpression("#redisProperties.hosts.empty"), but it doesn't work for some reason:
Description:
A component required a bean named 'redisProperties' that could not be found.
Action:
Consider defining a bean named 'redisProperties' in your configuration.
Is there some way to fix that (maybe with #Order or similar annotations)?
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration.
#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 or null.
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("redis")
#ConstructorBinding
public class RedisProperties implements OptionalProperties {
private final List<String> hosts;
#Override
public boolean isPresent() {
return hosts != null && !hosts.isEmpty();
}
}
And then in Spring #Configuration class.
#Configuration
class YourConfiguration {
#ConditionalOnConfigurationProperty(prefix = "redis", targetClass = RedisProperties.class)
StaticRedisHostsProvider staticRedisHostsProvider() {
...
}
#ConditionalOnMissingBean(StaticRedisHostsProvider.class)
DiscoveryBasedRedisHostsProvider discoveryRedisHostsProvider() {
...
}
}
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.
I want to implement a conditional Bean depending on a flag in my application.properties. Example:
// application.properties
service=foobar
The idea is to make different service implementations configurable, let assume I got a central configuration class for this service in Spring:
#Configuration
#Import({ServiceA.class, ServiceB.class, ...})
public class ServiceConfiguration {
...
}
And possible service implementations would look like
#Configuration
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// getProperty will alsways return null for some reason
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
// This will be null anyways
#Value("${service}")
private String confService;
}
Since the class implementing Condition (here just the same class ServiceA) will be initialized via default constructor #Value-injections won't work. How ever, by what I understand getProperty()should return the correct value. What am I doing wrong? How can I access application properties at this point?
I found at "dirty workarround", I really don't like that solution, how ever, it solves the problem. As mentioned here a #PropertySource fixes the problem (I haven't tried this before posting here since it wasn't an accpeted answer).
#Configuration
#PropertySource(value="file:config/application.properties")
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// Will work now
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
}
Although this works I don't like it for several reason:
With every implementation I have code redundancy (giving a path to a config file)
It's highly unmaintainable when having multiple configuration files
Example: Behavior like load default.properties <-then load and overwrite with -> customer.properties won't work anymore (altough this should be solvable using #PropertySources which would, on the other hand, increase code redundancy)
My problem is that I have application, which uses Spring profiles. Building application on server means that the profile is set to "wo-data-init". For other build there is "test" profile. When any of them is activated they are not supposed to run the Bean method, so I though this annotation should work:
#Profile({"!test","!wo-data-init"})
It seems more like it's running if(!test OR !wo-data-init) and in my case I need it to run if(!test AND !wo-data-init) - is it even possible?
In Spring 5.1.4 (Spring Boot 2.1.2) and above it is as easy as:
#Component
#Profile("!a & !b")
public class MyComponent {}
Ref: How to conditionally declare Bean when multiple profiles are not active?
Spring 4 has brought some cool features for conditional bean creation. In your case, indeed plain #Profile annotation is not enough because it uses OR operator.
One of the solutions you can do is to create your custom annotation and custom condition for it. For example
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#Conditional(NoProfilesEnabledCondition.class)
public #interface NoProfilesEnabled {
String[] value();
}
public class NoProfilesEnabledCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean matches = true;
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(NoProfileEnabled.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
String[] requiredProfiles = (String[]) value;
for (String profile : requiredProfiles) {
if (context.getEnvironment().acceptsProfiles(profile)) {
matches = false;
}
}
}
}
}
return matches;
}
}
Above is quick and dirty modification of ProfileCondition.
Now you can annotate your beans in the way:
#Component
#NoProfilesEnabled({"foo", "bar"})
class ProjectRepositoryImpl implements ProjectRepository { ... }
I found a better solution
#Profile("default")
Profile default means no foo and no bar profiles.