Custom Spring (Boot) annotation: #ConditionalOnProperty with default key name - java

How to create a custom Spring (Boot) annotation #Country that "inherits" #ConditionalOnProperty, but predefines the property key?
Given a some services that share a common interface
interface WizardService {
void doMagic()
}
and a set of country specific implementations that are selected via #ConditionalOnProperty(name = "country", havingValue = "[iso-code]"), i.e. the implementation is selected based on the value of the country property
#ConditionalOnProperty(name = "country", havingValue = "de")
#Service
class WizardServiceGermany {
#Override void doMagic() { System.out.println("Simsalabim!"); }
}
#ConditionalOnProperty(name = "country", havingValue = "en")
#Service
class WizardServiceGreatBritain {
#Override void doMagic() { System.out.println("Wingardium Leviosa!"); }
}
is it possible to define a custom spring annotation which always sets the name property to "country" by default so that I can have an #Country annotation? For instance:
#Country("en")
#Service
class WizardServiceGreatBritain {
#Override void doMagic() { System.out.println("Wingardium Leviosa!"); }
}
I tried creating a meta-annotation, but it gets ignored (while replacing it with its #ConditionalOnProperty equivalent works fine):
#ConditionalOnProperty(name = "country")
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Documented
public #interface Country {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
String havingValue() default "";
}

Related

Cant load property in java match-

Trying to implement conditional bean loading in spring. This is the code, problem is, I am not able to load the property inside match method,
#Configuration
public class Class implements Condition {
#Value("${test.property}")
private boolean testProperty;
#Override
public boolean matches(ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
sout(testProperty);
return true;
}
}
I can however print the property if I inject it into a constructor,
but that does not solve my issue, any thoughts?
For conditional instantiation based on environment variables you could use the #ConditionalOnProperty annotation on top of your bean definitions.
#Configuration
public class Config {
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public YourBean instantiateIfTrue() {
// instantiate and return YourBean in case the property is true
}
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "false")
public YourBean instantiateIfFalse() {
// instantiate and return YourBean in case the property is false
}
}
In your case, you could add the #ConditionalOnProperty on top of your #Configuration (and no longer extend Condition).
#Configuration
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public class Config {
// ...
}
Rely on extending Condition only if #ConditionalOnProperty is not flexible enough for your use case.

Making a defined spring bean primary

I have 3 beans of the same type defined in spring.xml. This is inside a jar file which I cannot edit. I want to make one of these primary in my spring-boot application using annotation. Is there a way to do it?
A straightforward approach is to use a bridge configuration, which will register the desired bean as a new primary bean. A simple example:
The interface:
public interface Greeter { String greet(); }
The configuration which you don't control:
#Configuration
public class Config1 {
#Bean public Greeter british(){ return () -> "Hi"; }
#Bean public Greeter obiWan(){ return () -> "Hello there"; }
#Bean public Greeter american(){ return () -> "Howdy"; }
}
The bridge configuration:
#Configuration
public class Config2 {
#Primary #Bean public Greeter primary(#Qualifier("obiWan") Greeter g) {
return g;
}
}
The client code:
#RestController
public class ControllerImpl {
#Autowired
Greeter greeter;
#RequestMapping(path = "/test", method = RequestMethod.GET)
public String test() {
return greeter.greet();
}
}
Result of curl http://localhost:8080/test will be
Hello there
You can use #Qualifier("___beanName__") annotation to choose the correct one
I tried #jihor solutions but it doesn't work. I have a NullPointerException in defined configuration.
Then I find the next solution on Spring Boot
#Configuration
public class Config1 {
#Bean
#ConfigurationProperties(prefix = "spring.datasource.ddbb")
public JndiPropertyHolder ddbbProperties() {
return new JndiPropertyHolder();
}
#ConditionalOnProperty(name = "spring.datasource.ddbb.primary", matchIfMissing = false, havingValue = "true")
#Bean("ddbbDataSource")
#Primary
public DataSource ddbbDataSourcePrimary() {
return new JndiDataSourceLookup().getDataSource(ddbbProperties().getJndiName());
}
#ConditionalOnProperty(name = "spring.datasource.ddbb.primary", matchIfMissing = true, havingValue = "false")
#Bean("ddbbDataSource")
public DataSource ddbbDataSource() {
return new JndiDataSourceLookup().getDataSource(ddbbProperties().getJndiName());
}
}
And my application.properties if I need this datasource as primary, otherwise don't set the property or set false.
spring.datasource.ddbb.primary=true

Is it possible to extend Spring #RequestMapping with an own annotation?

I have a custome annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Controller
#RequestMapping("auth/firebase")
public #interface FirebaseAuth {
#AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] path() default {};
}
I want to achive, that a controller which is annotated with:
#FirebaseAuth(path = "user")
public class UserController {
Will end up with a path like this: auth/firebase/user, to secure the resource
How is it possible to extend the path with auth/firebase before "user" with an annotation (not with a superclass)?
Thanks! :)

Spring Annotation and Qualifier Based Scan

Is there a way in spring to apply qualifiers while performing an annotation based component scan?
I have a couple of classes annotated with my custom annotation, MyAnnotation.
#MyAnnotation
public class ClassOne {
}
#MyAnnotation
public class ClassTwo {
}
#Configuration
#ComponentScan(basePackages = { "common" }, useDefaultFilters = false, includeFilters = { #ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class) })
public class ClassProvider {
}
What I want to do is, scan a subset of the classes with this annotation, selectively based on some condition say some input from the user.
Is it possible to say specify a qualifier along with the annotation and also specify it with the component scan filter, something like this -
#MyAnnotation (qualifier = "one")
public class ClassOne {
}
#MyAnnotation (qualifier = "two")
public class ClassTwo {
}
#Configuration
#ComponentScan(basePackages = { "common" }, useDefaultFilters = false, includeFilters = { #ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class, qualifier = "one") })
public class ClassProvider {
}
so that only ClassOne gets scanned?
You can implement a custom TypeFilter so that your #ComponentScan can look like:
#ComponentScan(includeFilters = { #ComponentScan.Filter(type = FilterType.CUSTOM, value = MyAnnotation.class) })
And the TypeFilter implementation:
public class TypeOneFilter implements TypeFilter {
#Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
final AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
if (annotationMetadata.hasAnnotation(MyAnnotation.class.getName())) {
final Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(MyAnnotation.class.getName());
return "one".equals(attributes.get("qualifier"));
}
return false;
}
}

Instantiate a spring bean conditionally based on a property placeholder

Is it possible to configure spring to instantiate a bean or not, depending on a boolean placeholder property? Or at least to exclude a package from annotation scanning based on such a property?
I think you should be using Spring profiles to configure different behaviours. However if you are using annotations you could create an #Configuration object and and a factory method to create a bean based on the property value
e.g.
#Configuration
class ExampleConfig {
private final String prop;
public ExampleConfig(#Value("${your.prop.name}" prop} {
this.prop = prop;
}
#Bean
public YourBeanClass create() {
if (prop.equals("someValue") {
return new YourBeanClass();
}
return new OtherClass(); // must be subclass/implementation of YBC
}
}
You can use ConditionalOnProperty:
#Bean
#ConditionalOnProperty(value = "property", havingValue = "value", matchIfMissing = true)
public MyBean myBean() ...
Also, check this answer.
This may not fit your needs, and I'm assuming that you have control over the class in question (i.e. not vendor code), but have you considered marking the the bean to be lazy loaded? At least, that way it won't get instantiated until it actually gets used, which may happen conditionally depending on the rest of your configuration.
You can also use #Conditional
Step 1 : Create a class that implements Condition
public class ProdDataSourceCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbname = context.getEnvironment().getProperty("database.name");
return dbname.equalsIgnoreCase("prod");
}}
Step 2 : Use the above class with #Conditional
#Configuration
public class EmployeeDataSourceConfig {
....
#Bean(name="dataSource")
#Conditional(ProdDataSourceCondition.class)
public DataSource getProdDataSource() {
return new ProductionDatabaseUtil();
}
}
http://javapapers.com/spring/spring-conditional-annotation/
We can use ConditionalOnProperty . Just define a property deployment.environemnt in application.properties file . And based on this property you can control the creation of objects.
#Bean(name = "prodDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "production")
public DataSource getProdDataSource() {
return new ProductionDatasource();
}
#Bean(name = "devDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "dev")
public DataSource getDevDataSource() {
return new devDatasource();
}

Categories

Resources