We're using Custom Qualifier Annotations to create and inject beans. How can we select a bean dynamically at runtime by just specifying the Custom Qualifiers.
Custom Qualifier :
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE,
ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PlatformQualifiers {
public static enum OperatingSystems {
IOS, ANDROID
}
OperatingSystems operatingSystem() default OperatingSystems.IOS;
public enum DeviceTypes {
Mobile, Tablet, ANY, Other
}
DeviceTypes[] deviceType() default { DeviceTypes.ANY };
}
Bean Interface :
#FunctionalInterface
public interface Platform {
String getDeviceDetails();
}
Bean Configs :
#Configuration
public class PlatformConfig {
#Bean
#PlatformQualifiers(operatingSystem = OperatingSystems.IOS, deviceType = DeviceTypes.Mobile)
public Platform getIphone6() {
return () -> "iphone6";
}
#Bean
#PlatformQualifiers(operatingSystem = OperatingSystems.IOS, deviceType = DeviceTypes.Tablet)
public Platform getIpad() {
return () -> "ipad3";
}
#Bean
#PlatformQualifiers(operatingSystem = OperatingSystems.ANDROID, deviceType = DeviceTypes.Mobile)
public Platform getAndroidPhone() {
return () -> "AndroidPhoneSamsung";
}
}
Current Application Code :
#Configuration
#ComponentScan
public class MainApplication {
#Autowired
#PlatformQualifiers(operatingSystem = OperatingSystems.IOS, deviceType = DeviceTypes.Mobile)
Platform iphone;
#Autowired
#PlatformQualifiers(operatingSystem = OperatingSystems.ANDROID, deviceType = DeviceTypes.Mobile)
Platform androidPhone;
public void getDevice(String osType, String deviceType ) {
if(osType == "ios" && deviceType == "mobile") {
System.out.println(iphone.getDeviceDetails());
}
if(osType == "android" && deviceType == "mobile") {
System.out.println(androidPhone.getDeviceDetails());
}
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication.class);
MainApplication mainApplication = context.getBean(MainApplication.class);
mainApplication.getDevice("ios" ,"mobile");
mainApplication.getDevice("android" , "mobile");
}
}
I am looking for a solution like where on runtime I can access bean using the qualifiers, something like this :
#Configuration
#ComponentScan
public class MainApplication2 {
#Autowired
ApplicationContext context;
public void getDevice(DeviceTypes deviceType, OperatingSystems osType ) {
>>>>>>>>>>> Looking of something of type following :
Platform p = context.getBean(some input consisting to identify bean by deviceType and osType)
System.out.println(p.getDeviceDetails());
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainApplication2.class);
MainApplication2 application = context.getBean(MainApplication2.class);
application.getDevice(DeviceTypes.Mobile, OperatingSystems.ANDROID);
}
}
In this case, How can I get a bean from applicationContext on runtime based on the DeviceTypes and OperatingSystems ?
One of the approaches could be to create a Map of Platform beans and inject it into the bean which calls getDevice. The key for the map could be device type.
Another approach along the same lines could be to make your bean implement InitializingBean and ApplicationContextAware and use 'getBeansWithAnnotation' in 'afterPropertiesSetin conjuction withfindAnnotationOnBean` to populate the Map or lookup the bean dynamically.
Related
I'm trying to create system like #Repository.
I have lots of interfaces like:
#Client(uri = "http://example.com", username = "httpBasicUsername", password = "httpBasicPassword")
public interface RequestRepository {
#Request(method = Method.POST, uri = "/mono")
Mono<Response> ex1(Object body);
#Request(method = Method.POST, uri = "/flux")
Flux<Response> ex2(Object body);
}
Right now, I'm creating bean with using this function:
#Bean
public RequestRepository requestRepository(WebClient.Builder builder) {
return (RequestRepository) Proxy.newProxyInstance(
RequestRepository.class.getClassLoader(),
new Class[]{RequestRepository.class},
new MyDynamicInvocationHandler(builder)
);
}
But I have lots of these interfaces. For every new interface I need to create another bean function. But I don't want to do that.
Is there a way to say spring (spring boot) if there is #Client annotation then create bean like this etc?
I've solved with creating custom interface scanner.
For more details: https://stackoverflow.com/a/43651431/6841566
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import({InterfaceScanner.class})
public #interface InterfaceScan {
String[] value() default {};
}
public class InterfaceScanner implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(InterfaceScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0)
basePackages = new String[]{((StandardAnnotationMetadata) metadata)
.getIntrospectedClass().getPackage().getName()};
ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(false, environment) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata meta = beanDefinition.getMetadata();
return meta.isIndependent() && meta.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(Client.class));
for (String basePackage : basePackages)
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage))
registry.registerBeanDefinition(
generateName(beanDefinition.getBeanClassName()),
getProxyBeanDefinition(beanDefinition.getBeanClassName()));
}
}
}
#InterfaceScan
#SpringBootApplication
public class ExampleApplication {
...
}
I have three classes:
open class RedirectProcessor(
private val adProcessor: AdProcessor
) {
fun run(depth: Int): String =
if (depth < 3) adProcessor.run(depth + 1) else "redirect"
}
open class FallbackProcessor(
private val adProcessor: AdProcessor
) {
fun run(depth: Int): String =
if (depth < 3) adProcessor.run(depth + 1) else "fallback"
}
open class AdProcessor(
private val redirectProcessor: RedirectProcessor,
private val fallbackProcessor: FallbackProcessor
) {
fun run(depth: Int): String =
depth.toString() +
redirectProcessor.run(depth) +
fallbackProcessor.run(depth)
}
So, they depends on each other. I try to configure spring context as below:
#Configuration
class Config {
#Bean
#Lazy
fun redirectProcessor(): RedirectProcessor = RedirectProcessor(adProcessor())
#Bean
#Lazy
fun fallbackProcessor(): FallbackProcessor = FallbackProcessor(adProcessor())
#Bean
fun adProcessor() = AdProcessor(
redirectProcessor = redirectProcessor(),
fallbackProcessor = fallbackProcessor()
)
}
I known that I have to use #Lazy annotation. If I mark my services with #Component annotation and use #Lazy in constructor it works fine. But I need to define beans using #Bean annotation and it causes problems. Is there any way to solve it?
I can't say for Kotlin (my knowledge of kotlin is rather limited at this point), but in Java with the last available spring version (5.2.6.RELEASE)
I've got it working with the following "kotlin to java" translation of your example:
public class RedirectProcessor {
private final AdProcessor adProcessor;
public RedirectProcessor(AdProcessor adProcessor) {
this.adProcessor = adProcessor;
}
public String run(int depth) {
if(depth < 3) {
return adProcessor.run(depth + 1);
}
else {
return "redirect";
}
}
}
public class FallbackProcessor {
private final AdProcessor adProcessor;
public FallbackProcessor(AdProcessor adProcessor) {
this.adProcessor = adProcessor;
}
public String run(int depth) {
if(depth < 3) {
return adProcessor.run(depth + 1);
}
else {
return "fallback";
}
}
}
public class AdProcessor {
private RedirectProcessor redirectProcessor;
private FallbackProcessor fallbackProcessor;
public AdProcessor(RedirectProcessor redirectProcessor, FallbackProcessor fallbackProcessor) {
this.redirectProcessor = redirectProcessor;
this.fallbackProcessor = fallbackProcessor;
}
public String run (int depth) {
return depth + redirectProcessor.run(depth) + fallbackProcessor.run(depth);
}
}
Then The trick was to use the configuration in a different (yet totally "legitimate" way from Java Configuration rules's standpoint):
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public RedirectProcessor redirectProcessor (#Lazy AdProcessor adProcessor) {
return new RedirectProcessor(adProcessor);
}
#Bean
public FallbackProcessor fallbackProcessor (#Lazy AdProcessor adProcessor) {
return new FallbackProcessor(adProcessor);
}
#Bean
public AdProcessor adProcessor (RedirectProcessor redirectProcessor, FallbackProcessor fallbackProcessor) {
return new AdProcessor(redirectProcessor, fallbackProcessor);
}
#EventListener
public void onApplicationStarted(ApplicationStartedEvent evt) {
AdProcessor adProcessor = evt.getApplicationContext().getBean(AdProcessor.class);
String result = adProcessor.run(2);
System.out.println(result);
}
}
Note the usage of #Lazy annotation on a parameter and not on the bean itself.
The listener is done for testing purposes only. Running the application prints 23redirectfallback3redirectfallback
Now why does it work?
When spring sees such a #Lazy annotated parameter - it creates a runtime generated proxy (with CGLIB) from the parameter class.
This proxy acts in a way that it wraps the bean and this bean will be fully created only when it's "required" for the first time (read, we'll call methods of this bean in this case).
If you work with #Component its the same as the following declaration:
#Component
public class FallbackProcessor {
private final AdProcessor adProcessor;
public FallbackProcessor(#Lazy AdProcessor adProcessor) {
this.adProcessor = adProcessor;
}
public String run(int depth) {
...
}
}
One Side note, I haven't put #Autowired on constructor of FallbackProcessor class in the last example, only because if there is a single constructor spring will "recognize that" and use it to inject all the dependencies.
The following tutorial and this somewhat old thread of SO can be relevant as well (worth reading).
I ran into same issue, and #Autowire annotation doesn't work for some reason I don't know.
So I used another workaround:
inject ApplicationContext instead of bean itself
to retrieve bean instance from ApplicationContext
code like:
class ServiceA(
private val serviceB: ServiceB
) {
......
}
class ServiceB(
private val applicationContext: ApplicationContext
) {
private val serviceA: ServiceA by lazy {
// we need this logic for only once
// so "property delegated by lazy ..." is perfect for this purpose
applicationContext.getBean(ServiceA::class.java)
}
......
}
I have a config and it's #Import-ed by an annotation. I want the values on the annotation to be accessible by the config. Is this possible?
Config:
#Configuration
public class MyConfig
{
#Bean
public CacheManager cacheManager(net.sf.ehcache.CacheManager cacheManager)
{
//Get the values in here
return new EhCacheCacheManager(cacheManager);
}
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setShared(true);
return ehCacheManagerFactoryBean;
}
}
The annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(MyConfig.class)
public #interface EnableMyCaches
{
String value() default "";
String cacheName() default "my-cache";
}
How would I get the value passed below in my config?
#SpringBootApplication
#EnableMyCaches(cacheName = "the-cache")
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}
Use simple Java reflection:
Class c = MyServiceApplication.getClass();
EnableMyCaches enableMyCaches = c.getAnnotation(EnableMyCaches.class);
String value = enableMyCaches.value();
Consider how things like #EnableConfigurationProperties are implemented.
The annotation has #Import(EnableConfigurationPropertiesImportSelector.class) which then imports ImportBeanDefinitionRegistrars. These registrars are passed annotation metadata:
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
You can then get annotation attributes from annotation metadata:
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableMyCaches.class.getName(), false);
attributes.get("cacheName");
I am testing Spring's #Conditional in which I load the bean depending on the value present in the .properties file. So I created a .properties file in src/main/resources/application-config.properties and I have the configuration class as:
#Configuration
#PropertySource(value = {"classpath:application-config.properties"}, ignoreResourceNotFound = false)
#ComponentScan(basePackages = {"com.app.test"})
public class ApplicationContextConfig {...}
I have 2 Condition implementations as below:
public class ConditionalBeanOne implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String name= conditionContext.getEnvironment().getProperty("condition.name");
return name.equalsIgnoreCase("condition_one");
}
}
public class ConditionalBeanTwo implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String name= conditionContext.getEnvironment().getProperty("condition.name");
return name.equalsIgnoreCase("condition_two");
}
}
I have respective POJO classes as:
#Component
#Conditional(value = ConditionalBeanOne.class)
public class BeanOne implements ServiceBean {}
#Component
#Conditional(value = ConditionalBeanTwo.class)
public class BeanTwo implements ServiceBean {}
When I run the application, I get following exception:
Caused by: java.io.FileNotFoundException: class path resource [application-config.properties] cannot be opened because it does not exist
I am running this through main method as following:
public class ConditionalMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
.....
}
}
I couldn't reproduce your problem so I created a complete working example based on your use case, which is also available on my GitHub. I noticed that your conditions are really the same, only the values are different, so you don't really need to duplicate the code there. Other than that, it's pretty much what you did.
I'd say that you're reinventing the wheel here. Spring Boot already has a ConditionalOnProperty which does this.
Application.java:
public class Application {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ApplicationConfig.class)) {
ApplicationConfig.GreeterService greeterService =
applicationContext.getBean(ApplicationConfig.GreeterService.class);
String actual = greeterService.greeting();
System.out.printf("Greeting: %s.\n", actual);
}
}
}
ApplicationConfig.java:
#Configuration
// The / doesn't matter, but I prefer being explicit
#PropertySource("classpath:/application.properties")
#ComponentScan
public class ApplicationConfig {
#FunctionalInterface
public static interface GreeterService {
String greeting();
}
#Bean
#ConditionalOnProperty("hello")
public GreeterService helloService() {
return () -> "hello";
}
#Bean
#ConditionalOnProperty("hi")
public GreeterService hiService() {
return () -> "hi";
}
}
ConditionalOnProperty.java:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnPropertyCondition.class)
public #interface ConditionalOnProperty {
String value() default "";
}
OnPropertyCondition.java:
public class OnPropertyCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> attributes = annotatedTypeMetadata
.getAnnotationAttributes(ConditionalOnProperty.class.getName());
String value = (String) attributes.get("value");
String name = conditionContext.getEnvironment().getProperty("greeting");
return !isEmpty(name) && name.equalsIgnoreCase(value);
}
}
application.properties:
greeting=hello
Run normally:
Output:
Greeting: hello.
Run with -Dgreeting=hi:
Output:
Greeting: hi.
I have been trying to get a very basic example of a custom PropertySource running in a Spring Application.
This is my PropertySource:
public class RemotePropertySource extends PropertySource{
public RemotePropertySource(String name, Object source) {
super(name, source);
}
public RemotePropertySource(String name) {
super(name);
}
public Object getProperty(String s) {
return "foo"+s;
}
}
It gets added to the ApplicationContext via an ApplicationContextInitializer:
public class RemotePropertyApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext ctx) {
RemotePropertySource remotePropertySource = new RemotePropertySource("remote");
ctx.getEnvironment().getPropertySources().addFirst(remotePropertySource);
System.out.println("Initializer registered PropertySource");
}
}
Now I created a simple Unit-Test to see if the PropertySource is used correctly:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RemotePropertySourceTest.ContextConfig.class, initializers = RemotePropertyApplicationContextInitializer.class)
public class RemotePropertySourceTest {
#Autowired
private UnderTest underTest;
#Autowired
Environment env;
#Test
public void testContext() {
assertEquals(env.getProperty("bar"),"foobar");
assertEquals(underTest.getFoo(),"footest");
}
#Component
protected static class UnderTest {
private String foo;
#Autowired
public void setFoo(#Value("test")String value){
foo=value;
}
public String getFoo(){
return foo;
}
}
#Configuration
#ComponentScan(basePackages = {"test.property"})
protected static class ContextConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
return configurer;
}
}
}
Accessing the value via the Environment gives me the correct result ("foobar"), but using the #Value-Annotation fails. As far as I have read in the documentation the PropertySourcesPlaceholderConfigurer in my Configuration should automatically pick up my PropertySource from the environment but apparently it does not. Is there something I am missing?
I know that accessing properties explicitly via the environment is preferrable but the existing application uses #Value-Annotations a lot.
Any help is greatly appreciated. Thanks!
To get value from property source with #Value you have to use ${} syntax:
#Autowired
public void setFoo(#Value("${test}")String value){
foo=value;
}
Take a look at official documentation.