Custom #Qualifier with Spring autoconfig - java

I'm not sure how to use custom qualifier interface with component scanning and autowire in Spring. I have an interface:
#Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface BigBean {
}
a bean I want to injected to:
#Component
public class Bean {
#Autowired
#BigBean("A")
private SomeBean sb;
public SomeBean getSb() {
return sb;
}
public void setSb(SomeBean sb) {
this.sb = sb;
}
}
and beans of the same type to be distinguished by custom qualifier:
#Component
#BigBean("A") //<-????
public class SmallBeanA implements SomeBean{
}
#Component
public class SmallBeanB implements SomeBean{
}
What I found in spring documentation doesn't compile in my case. How to use this custom qualifier I have?

You will need to add attribute value to BigBean annotation as
#Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface BigBean {
String value() default "";
}

This a bit old thread now, not sure if my answer will be of any help or not but let me try. Below is code to inject a selected instance of SomeBean.
#Qualifier
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface BigBean {
String value() default "";
}
public interface SomeBean {
}
#Component
#BigBean("A")
public class SmallBeanA implements SomeBean {
private SmallBeanA() {
System.out.println("SmallBeanA constructed");
}
#Override
public String toString() {
return "SmallBeanA";
}
}
#Component
#BigBean("B")
public class SmallBeanB implements SomeBean{
private SmallBeanB() {
System.out.println("SmallBeanB constructed");
}
#Override
public String toString() {
return "SmallBeanB";
}
}
#Component
public class Bean {
#Autowired
#BigBean("A")
private SomeBean sb;
public SomeBean getSb() {
return sb;
}
#Override
public String toString() {
return "Bean [sb=" + sb + "]";
}
}
public class QualifierTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Module.xml");
Bean myBean = context.getBean(Bean.class);
System.out.println(myBean.getSb().toString());
//OR
System.out.println(myBean.toString());
}
}

Related

Sonar-Java how to get annotations of a variable class

#Component
public MyClass{
private MyOtherClass myOtherClass;
#Autowired
public MyClass(MyOtherClass myOtherClass){
this.myOtherClass = myOtherClass;
}
}
#Component
#Scope("prototype")// OR
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyOtherClass{
}
I am writing a custom plugin to detect classes which declare variable of type MyOtherClass and give a warning because MyOtherClass is of type prototype.
Basically I need to get field from MyClass and need to get Annotation on the field(MyOtherClass) class and need to find if the annotation value contains prototype
#Override
public void visitNode(Tree tree) {
org.sonar.plugins.java.api.tree.VariableTree variableTree = (VariableTree) tree;
if (variableTree.type().symbolType().symbol().metadata().isAnnotatedWith(SCOPE_ANNOTATION_FQN)) {
System.out.println("prototype annotation found " + variableTree.symbol().metadata().toString());
reportIssue(variableTree.simpleName(), "This spring bean is of type PROTOTYPE");
}
}
Found a way to read annotations on a variable class.
Is this your answer?
public class MyClass {
private MyOtherClass myOtherClass;
public MyClass(MyOtherClass myOtherClass){
this.myOtherClass = myOtherClass;
}
public static void main(String[] args) {
Field[] declaredFields = MyClass.class.getDeclaredFields();
for(Field field:declaredFields){
Scope scope = field.getType().getAnnotation(Scope.class);
if(scope!=null){
String value = scope.value();
System.out.println(value);
}
}
}
}
#Scope("prototype")
public class MyOtherClass {
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Scope {
String value();
}

Custom annotation is not working on spring Beans

I have created my new custom annotation #MyCustomAnnotation
#Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
#Retention(RUNTIME)
public #interface MyCustomAnnotation{
}
I applied that annotation on component and bean. Here is the code,
#MyCustomAnnotation
#Component
public class CoreBussinessLogicHandler implements GenericHandler<BussinessFile> {
//some bussiness logic
}
And
#Configuration
public class BussinessConfig {
#Autowired
private CoreIntegrationComponent coreIntegrationComponent;
#MyCustomAnnotation
#Bean(name = INCOMING_PROCESS_CHANNEL)
public MessageChannel incomingProcessChannel() {
return coreIntegrationComponent.amqpChannel(INCOMING_PROCESS_CHANNEL);
}
//some other configurations
}
Now i want all the beans annotated with #MyCustomAnnotation. So here is the code,
import org.springframework.context.ApplicationContext;
#Configuration
public class ChannelConfig {
#Autowired
private ApplicationContext applicationContext;
public List<MessageChannel> getRecipientChannel(CoreIntegrationComponent coreIntegrationComponent) {
String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(MyCustomAnnotation.class);
//Here in output I am getting bean name for CoreBussinessLogicHandler Only.
}
}
My question is why I am not getting Bean with name 'INCOMING_PROCESS_CHANNEL' as it has #MyCustomAnnotation ? If I want to get bean with name 'INCOMING_PROCESS_CHANNEL' what code changes should I do ?
This happens because your bean does not receive this annotation as you have placed it on a "bean definition configuration". So it is available but only through BeanFactory and beanDefinitions. You can either put the annotation on your bean class or write a custom method that would do the searching using bean factory.
See the accepted answer here.
You have to use AnnotationConfigApplicationContext instead of ApplicationContext
Here is a working example:
#SpringBootApplication
#Configuration
public class DemoApplication {
#Autowired
public AnnotationConfigApplicationContext ctx;
#Bean
public CommandLineRunner CommandLineRunner() {
return (args) -> {
Stream.of(ctx.getBeanNamesForAnnotation(MyCustomAnnotation.class))
.map(data -> "Found Bean with name : " + data)
.forEach(System.out::println);
};
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#interface MyCustomAnnotation{
}
#MyCustomAnnotation
#Component
class Mycomponent {
public String str = "MyComponentString";
}

How inject #Any Singletons

I have problem with inject singletons to Instance. I want to inject all singletons which implements interface Inter, I want to take value from class annotation. How I can do that. My solution not working :(
Interface:
public interface Inter {
String getString();
}
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface IConf {
String fileName();
}
Class which implements interface
#Singleton
#IConf(fileName = "Config.xml")
public class ConfObjt implements Inter{
public String getString() {
// TODO Auto-generated method stub
return "";
}
}
Main class
#Singleton
#Startup
public class Sing {
#Inject #Any
Instance<Inter> beans;
#PostConstruct
public void init(){
System.out.println("metoda init");
for(Inter inter : beans) {
Class<?> clazz = inter.getClass();
IConf ano = clazz.getAnnotation(IConf.class);
System.out.println(ano.fileName());
}
}
}

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

Unable to resolve any beans for Type: xxx; Qualifiers: [#javax.enterprise.inject.Any()]

I have a LoginProvider interface:
public interface LoginProvider {
boolean login(String username, String password);
}
And 2 different implementations:
public class LoginProvider1Impl implements LoginProvider {
#Override
public boolean login(String username, String password) {
...
}
}
public class LoginProvider2Impl implements LoginProvider {
#Override
public boolean login(String username, String password) {
...
}
}
Then a producer annotation:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({FIELD, METHOD, PARAMETER, TYPE, CONSTRUCTOR})
public #interface LoginProviderProducer {
}
An annotation to specify the Login Provider implementation:
#Retention(RetentionPolicy.RUNTIME)
#Target({FIELD, METHOD, TYPE})
public #interface LoginProviderType {
LoginProviderName value();
public enum LoginProviderName {
PROVIDER1(LoginProvider1Impl.class),
PROVIDER2(LoginProvider2Impl.class);
private Class<? extends LoginProvider> loginProviderType;
private LoginProviderName(Class<? extends LoginProvider> loginProviderType) {
this.loginProviderType = loginProviderType;
}
public Class<? extends LoginProvider> getLoginProviderType() {
return loginProviderType;
}
}
}
And a factory:
#ApplicationScoped
public class LoginProviderFactory {
#Produces
#LoginProviderProducer
public LoginProvider createLoginProvider(#Any Instance<LoginProvider> instance, InjectionPoint injectionPoint) {
Annotated annotated = injectionPoint.getAnnotated();
LoginProviderType loginProviderTypeAnnotation = annotated.getAnnotation(LoginProviderType.class);
Class<? extends LoginProvider> loginProviderType = loginProviderTypeAnnotation.value().getLoginProviderType();
return instance.select(loginProviderType).get();
}
}
Finally I have a helper in which the login providers are injected:
#ApplicationScoped
public class LoginProviderHelperImpl implements LoginProviderHelper {
#Inject
#LoginProviderProducer
#LoginProviderType(LoginProviderName.PROVIDER1)
private LoginProvider provider1;
#Inject
#LoginProviderProducer
#LoginProviderType(LoginProviderName.PROVIDER2)
private LoginProvider provider2;
...
}
I get this error when the helper is used:
org.jboss.weld.exceptions.UnsatisfiedResolutionException:
WELD-001308: Unable to resolve any beans for Type:
class com.xxx.LoginProvider1Impl; Qualifiers: [#javax.enterprise.inject.Any()]
Thanks!
I added the annotation #ApplicationScoped to LoginProvider1Impl and LoginProvider2Impl.
Another way to make it work is to modify the factory method:
#ApplicationScoped
public class LoginProviderFactory {
#Produces
#LoginProviderProducer
public LoginProvider createLoginProvider(#Any Instance<LoginProvider> instance, InjectionPoint injectionPoint) {
Annotated annotated = injectionPoint.getAnnotated();
LoginProviderType loginProviderTypeAnnotation = annotated.getAnnotation(LoginProviderType.class);
Class<? extends LoginProvider> loginProviderType = loginProviderTypeAnnotation.value().getLoginProviderType();
//return instance.select(loginProviderType).get();
if (loginProviderType == LoginProvider1Impl.class) {
return new LoginProvider1Impl();
} else if (loginProviderType == LoginProvider2Impl.class) {
return new LoginProvider2Impl();
} else
return null;
}
}

Categories

Resources