I cannot get bean what I want, when using CDI and Annotation #Qualifier
#Qualifier #interface for Type
#Repeatable(Type.List.class)
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Type {
String value();
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
Type[] value();
}
}
and AnnotationLiteral implementation
public class TypeAL extends AnnotationLiteral<Type> implements Type {
private final String type;
public TypeAL(String type) {
this.type = type;
}
#Override
public String value() {
return type;
}
}
#Qualifier #interface for Related
#Target({TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Related {
Class value();
}
and AnnotationLiteral implementation
public class RelatedAL extends AnnotationLiteral<Related> implements Related {
private final Class clazz;
public RelatedAL(Class clazz) {
this.clazz = clazz;
}
#Override
public Class value() {
return clazz;
}
}
When I annotated sth like this:
#Type(TYPE_ONE)
#Type(TYPE_TWO)
#Related(RelatedClassWhichWillDoLogic.class)
public class LogicToRelatedClass implements BaseLogic {}
and when I'd like to get CDI.current().select(BaseLogic.class, new TypeAL(TYPE_ONE), new RelatedAL(RelatedClassWhichWillDoLogic.class)) i go nothing...
Why is that?
what is the version of your CDI? I think repeating qualifiers has been supported from version 2.
https://issues.jboss.org/browse/CDI-471
https://docs.google.com/document/d/1KUaxXIXJ_r-h5UJGIij6I4vrLS7uXkeeeZr2SaRipWQ/edit#
To choose one implementation between different implementations. we could use qualifier members to narrow the list of possible beans. the injection point must be completely matched with the qualifiers on the bean(if you want the exact one). you have two Type annotations on your bean class but use one of them in your CDI.current().select method call.
Instance<BaseLogic> findedBeans = CDI.current().select(BaseLogic.class, new TypeAL("TYPE_ONE"), new TypeAL("TYPE_TWO"), new RelatedAL(RelatedClassWhichWillDoLogic.class));
I tested it In one Weld Java SE program. you can download it from WELD (CDI) + JPA
just in the main method, in App class, add the following line of the code.
UserApplication userApplication = container.instance()
.select(UserApplication.class)
.get();
Instance<BaseLogic> type_one = CDI.current().select(BaseLogic.class, new TypeAL("TYPE_ONE"), new TypeAL("TYPE_TWO"), new RelatedAL(RelatedClassWhichWillDoLogic.class));
Related
In Payara 5, Jakarta EE 8, I try to inject all the qualified beans and then select a specific one using a qualifier as shown right below:
#Stateless
public class ScheduledTaskExecutor {
#Inject
#Any
private Instance<QCScheduledTask> scheduledTasks;
#Asynchronous
public void executeTask(final String taskName, final String jobID) {
final ScheduledTaskQualifier qualifier = new ScheduledTaskQualifier(taskName);
final QCScheduledTask scheduler = scheduledTasks.select(qualifier).get();
scheduler.execute(jobID);
}
}
public interface QCScheduledTask {
public void execute(final String jobID);
}
The abstract class which extends the interface along with an implementer:
public abstract class AbstractQCScheduledTask implements QCScheduledTask {
private String jobID;
protected abstract void executeTask();
public void execute(final String jobID) {
//
}
protected void updateStatus(final TaskStatus status) {
//
}
}
#Stateless
#QCScheduled(taskName = "TASK_BACKGROUND_JOB_EVALUATION")
public class BackgroundJobEvaluationExecuter extends AbstractQCScheduledTask {
#Inject
private BackgroundJobEvaluator backgroundJobEvaluator;
#Override
protected void executeTask() {
}
}
And the qualifier
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, FIELD, PARAMETER, TYPE})
public #interface QCScheduled {
/**
* Task name discriminator
*
* #return
*/
String taskName();
}
The result is unsatisfied dependencies error.
The same code works on JavaEE 7 application server.
I could not find any difference in the JakartaEE 8 specification, besides, I think that there should not be a restriction on using interfaces and abstract classes together for a runtime resolution of the desired bean implementer.
Annotate the interface QCScheduledTask with #jakarta.ejb.Local
Here is an example
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Annotation {
}
#Configuration
public class Configuration {
#Bean
#Annotation
public Test getTest() {
return new Test();
}
}
public class Test() {
public void test() {
// how can get the annotation `#Annotation` here?
}
}
Here is what I have tried getClass().getAnnotations() but this returns empty array. I can see why since getClass() return Test.class which does not have the annotation. How can I get the method that creates this instance and then get the annotation?
You could, in theory, inspect the current Thread stack to determine the name of your caller, then look up the class definition, locate the method, and read its annotations:
var t = Thread.currentThread().getStackTrace()[2];
var className = t.getClassName();
Class<?> clazz;
try {
clazz = Test.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Caller was loaded by a different ClassLoader :-(");
}
for (var method : clazz.getDeclaredMethods()) {
if (method.getName().equals(t.getMethodName())) {
return method.getAnnotation(YourAnnotation.class).value();
}
}
throw new RuntimeException("Method not found - I might have found the wrong class definition");
However:
inspecting the stack is rather slow, in particular if the stack is deep
inspecting the stack is brittle with respect to refactorings (people don't expect that factoring out code into a utility method will change behaviour)
the compiler can not check that the caller provides the required annotation
this only works reliably if all code is loaded by the same ClassLoader
this can not distinguish overloaded methods
This is therefore a rather brittle hack. Are you sure that there is no better option? For instance, requiring the caller to pass the value as a method parameter would have none of these shortcomings ...
You can use ConfigurableListableBeanFactory to get metadata about any Bean by name. Use BeanNameAware interface to retrieve Bean name.
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface CustomAnnotation {
}
#org.springframework.context.annotation.Configuration
public static class ContextConfiguration {
#Bean(name = "TEST")
#CustomAnnotation
public TestObject getTest() {
return new TestObject();
}
}
public class TestObject implements BeanNameAware {
private String beanName;
#Autowired
ConfigurableListableBeanFactory beanFactory;
#Override
public void setBeanName(String name) {
this.beanName = name;
}
public void test() {
CustomAnnotation customAnnotation = (CustomAnnotation) getBeanAnnotation(beanName, CustomAnnotation.class);
}
private Annotation getBeanAnnotation(String beanName, java.lang.Class<? extends Annotation> clazz) {
Annotation annotation = null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if( beanDefinition != null && beanDefinition.getSource() instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) beanDefinition.getSource();
annotation = Arrays.stream(metadata.getIntrospectedMethod().getDeclaredAnnotations()).filter(annot -> annot.annotationType().equals(clazz)).findFirst().orElse(null);
}
return annotation;
}
}
When dealing with CDI in java, I want to inject two instances of two different classes, implementing the same interface.
As I understand, I can inject an instance of a class which does not implement an interface, e.g.:
class MyClass {
// ...
}
class XY {
#Inject MyClass myClass;
}
When my class implements an interface I have to declare the member by the interface name (and specify the concrete implementation):
class MyClass implements MyInterface {
// ...
}
class XY {
#Inject MyInterface myClass;
}
But as soon as I want to inject different implementations, I get the "Api type [...] is not found with the qualifiers" exception:
class MyClassOne implements MyInterface {
// ...
}
class MyClassTwo implements MyInterface {
// ...
}
class XY {
#Inject MyClassOne myClassOne;
#Inject MyClassTwo myClassTwo;
}
I appreciate any ideas what to try or where to continue reading (the obvious keywords for a search on this topic give very unspecific results).
Thanks in advance!
In order to inject different instances, there are different ways to construct and inject beans.
Approach 1:
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, TYPE, METHOD})
public #interface ClassifierOne {
}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, TYPE, METHOD})
public #interface ClassifierTwo {
}
These qualifiers can be used in your class part of construction parameter injection or setter injection level.
#ClassifierOne
public class MyClassOne implements MyInterface {
// ...
}
#ClassifierTwo
public class MyClassTwo implements MyInterface {
// ...
}
public class XY {
private final MyInterface myClassOne;
private final MyInterface myClassTwo;
#Inject
public XY ( #ClassifierOne MyInterface myClassOne, #ClassifierTwo MyInterface myClassTwo ) {
this.myClassOne = myClassOne;
this.myClassTwo = myClassTwo;
}
}
Approach 2: Use of #Produces
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public #interface MyClassType {
ClassImplName value();
}
public enum ClassImplName {
CLASS_ONE(MyClassOne.class),
CLASS_TWO(MyClassTwo.class);
private Class<? extends MyInterface> classType;
private ClassImplName(Class<? extends MyInterface> clazz) {
this.classType = clazz;
}
public Class<? extends MyInterface> getClassType(){
return classType;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public #interface ClassType {
ClassImplName value();
}
Above custom qualifiers will allow you to chose type of implementation by removing abibuaty in producer method.
And, you can use below mentioned MyClassFactory to produce interfaces. This mechanism would be efficient as it uses InjectionPoint where the bean is injected.
public class MyInterfaceFactory {
#Produces
#MyClassType
public MyInterface createMyClasses(#Any Instance<MyInterface> instance, InjectionPoint injectionPoint) {
Annotated annotated = injectionPoint.getAnnotated();
ClassType classTypeAnnotation = annotated.getAnnotation(ClassType.class);
Class<? extends MyInterface> classType = classTypeAnnotation.value().getClassType();
return instance.select(classType).get();
}
}
Finally, you can use these generated instances in your class.
public class XY {
#Inject
#ClassType(ClassImplName.CLASS_ONE)
#MyClassType
private MyInterface myClassOne;
#Inject
#ClassType(ClassImplName.CLASS_TWO)
#MyClassType
private MyInterface myClassTwo;
// Other methods using injected beans ...
}
I know how to validate #PathVariable from https://stackoverflow.com/a/35404423/4800811
and it worked as expected with standard annotations but not with the customized one using a Repository bean. Maybe the bean is not initialized and I end up with NullPointerException when accessing the end point has #PathVariable validated. So how to get that work?
My Controller:
#RestController
#Validated
public class CustomerGroupController {
#PutMapping(value = "/deactive/{id}")
public HttpEntity<UpdateResult> deactive(#PathVariable #CustomerGroupEmpty String id) {
}
}
My custom validator:
public class CustomerGroupEmptyValidator implements ConstraintValidator<CustomerGroupEmpty, String>{
#Autowired
private CustomerRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// NullPointerException here (repository == null)
if (value!=null && !repository.existsByCustomerGroup(value)) {
return false;
}
return true;
}
}
My Custom Annotation:
#Documented
#Constraint(validatedBy = CustomerGroupEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomerGroupEmpty {
String message() default "The customer group is not empty.";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}
code in this post is correct, only mistake is that validator need to override initialize method as well. Probably user123 incorrect configure repository bean, the simply way to check this is define it manually in configuration class
I am trying to an annotation processor to implicitly make particular method(annotated) arguments final?
My Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MakeFinal_Args {
}
and my annotation processor looks like this:
#SupportedAnnotationTypes("com.walmart.annotations.MakeFinal_Args")
public class Processor extends AbstractProcessor {
private ProcessingEnvironment env;
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Class<?> clazz = Runtime.getRuntime().getClass();
for (Parameter parameter : clazz.getEnclosingMethod().getParameters()) {
parameter.
}
return true;
}
}
Stuck here.Need help with the same. Thanks in advance.