how to read runtime annotations on a field within a class - java

Imagine an annotation called "MyAnn" with runtime retention, a class MyClass, and an abstract class called MyData. MyClass has a field of type MyData, annotated with MyAnn. Within the instance of MyData, how do see if the annotation MyAnn is present and retrieve its information?
Note - in Java8 I know we can directly annotate the inner class at construction - and that works - but I need this working based on the field annotation.
Thanks for any help!
public MyClass extends MySuperClass() {
#MyAnn(value = "something")
protected MyData mydata;
public void doSomething() {
mydata = new MyData() {};
mydata.initialize();
}
}
public abstract MyData() {
String myValue = null;
public void initialize() {
if (***I am annotated with MyAnn) {
myValue = (***value from annotation MyAnn);
}
}
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnn {
String myvalue;
}

MyData cannot directly know whether it has been annotated with MyAnn, because it has no knowledge of whether the current instance is a field or a standalone instance, and if it is a field, then whether the field has or has not been annotated with MyAnn.
So, you will need to somehow obtain and pass the value to initialize(), and you will need to somehow obtain the value at the place where initialize() is called. And from your code, it appears that "something" can be passed as a parameter to initialize(), making the whole thing a lot easier than annotating the field and then checking whether the field is annotated.
But in any case, if you want to check whether a field is annotated, you have to:
obtain the fields of your class with getClass().getDeclaredFields()
loop over the fields; for each field, either
invoke isAnnotationPresent( MyAnn.class ) or
invoke field.getAnnotations(), loop for each annotation, and check whether this annotation instanceof MyAnn.class
Once you have found the annotation, you can get its value with annotation.value();

Related

Missing field annotation in cglib proxy class

#Service
public class TestService{
#DynamicReference
private ITestProvider testProvider;
public void run() {
}
}
DynamicReference dynamicRefrence = filed.getAnnotation(DynamicReference.class);
-->NOT NULL
This code is fine in this case. But when I add #Transactional in method run, then #DynamicReference will lose
#Service
public class TestService{
#DynamicReference
private ITestProvider testProvider;
#Transactional
public void run() {
}
}
DynamicReference dynamicRefrence = filed.getAnnotation(DynamicReference.class);
-->NULL
How can I get field annotation #DynamicReference in cglib proxy class?
this is get Field code:
Object o = this.applicationContext.getBean(beanName);
Class<?> clazz = o.getClass();
for (Field filed : clazz.getDeclaredFields()) {
DynamicReference dynamicRefrence = filed.getAnnotation(DynamicReference.class);
}
From Class.getDeclaredFields():
Returns an array of Field objects reflecting all the fieldsdeclared by the class or interface represented by this Class object. This includes public, protected, default(package) access, and private fields, but excludes inherited fields.
In your case, once you have a subclass-based proxy from cglib, the field will only exist in the superclass. Depending on your use case, you might want to collect all fields up in the inheritance chain that have you custom annotation.
Example code:
Collection<Field> fieldsWithAnnotation = new ArrayList<>();
Class<?> clazz = // your class
while(clazz != null) {
for (Field field : clazz.getDeclaredFields()) {
DynamicReference dynamicRefrence = field.getAnnotation(DynamicReference.class);
if(dynamicRefrence != null)
fieldsWithAnnotation.add(field);
}
clazz = clazz.getSuperclass();
}
EDIT: This approach works to find the annotated field. However, doing field.set(proxyInstance, value) will actually set the field in the proxy. This does not help you, as even though the proxy subclasses, it still uses delegation to forward method calls to a wrapped instance of your actual class. Since your goal is apparently to set the field in this wrapped instance, I would advise you to not use a custom field injection but rather setter injection. Your code would look roughly like this (untested):
// in TestService
private ITestProvider testProvider;
#DynamicReference
public void setTestProvider(ITestProvider testProvider) { ... }
// Getting the method
while(clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
DynamicReference dynamicRefrence = method.getAnnotation(DynamicReference.class);
if(dynamicRefrence != null)
methodsWithAnnotation.add(method);
}
clazz = clazz.getSuperclass();
}
// invoking it
method.invoke(proxyInstance, dependencyInstanceYouWantToSet);
The proxy should delegate the method call to your wrapped instance. Maybe you even want to make the method protected.
The alternative would be getting the callback-field of the proxy and setting the field on that instance, but the approach above seems much cleaner (some might say that magic field injection is evil and you should always use setter/constructor injection for a clean oop approach).
Edit 2: maybe you could also rethink if you want to actually reinvent the DI framework and leverage the underlying existing DI framework functionality. Using #Qualifier or some custom injection resolver comes to mind. See eg this tutorial

MappingException (Ambiguous field mapping detected) with Spring Data on inherited field

The following results in a MappingException. Do I need to change my design?
public class Foo extends Bar {
// if class == Foo do not send this over the wire
#JsonProperty(access = Access.WRITE_ONLY)
public List<X> myList;
}
public class Bar {
// if class == Bar send this over the wire
public List<X> myList;
public void methodsThatAccessMyList() {
// multiple methods exists in here accessing myList
// also, other classes exist extending bar,
//so moving these to the children will result in duplicate code
}
}
However, I need the json property on the child class, to prevent the child class to transport that field over the wire.
What do I need to change to prevent the ambiguous mapping?
org.springframework.data.mapping.MappingException: Ambiguous field
mapping detected! Both protected java.util.List ... and
#com.fasterxml.jackson.annotation.JsonProperty(index=-1,
access=WRITE_ONLY, value="", defaultValue="", required=false)protected
java.util.List ... map to the same field name ...! Disambiguate using
#Field annotation!
It turns out you can put the JsonProperty on the getter of the field, it will work as expected.
Like this, you don't need to override the field itself in the extending class.

Java Annotation Processing how to check if VariableElement is a primitive type(int, float) or an object some class

I have a class
public class SomeClass {
#CustomAnnotation1
String stringProperty = "LALALA";
#CustomAnnotation1
int integerProperty;
#CustomAnnotation1
ClassObject classObject;
}
CustomAnnotation1 is a custom annotation defined by me which can be put over any Field. Suppose class ClassObject is something like
public class ClassObject {
#CustomAnnotation1
public String someOtherString;
public String log;
}
What I want to achieve - If my annotation is put on any field which is not a primitive type, I want to access all the fields of that class.
My Approach - Get all the fields annotated with CustomAnnotation1, iterate over all of them and if it is non-primitive, get all the fields inside that class and process.
What I've tried - I am able to get all the elements annotated with my annotation using the below code in my AbstractProcessor class.
Collection<? extends Element> annotatedElements = roundEnvironment.getElementsAnnotatedWith(CustomAnnotation1.class);
List<VariableElement> variableElements = ElementFilter.fieldsIn(annotatedElements);
Questions -
I've researched a lot about the VariableElement class but unable to find a way to check if the field is primitive or not. Can this be done?
Is there any better approach to achieve this?
VariableElement.asType().getKind().isPrimitive()

Is there any way to mix or inherit annotation value in Java?

I have an annotation like this
public #interface anno{
String a1() default "defaultValueA1";
String a2() default "defaultValueA2";
String a3() default "defaultValueA3"
}
Class SuperClass{
#anno(a1="myA1", a3="myA3")
private String field1;
}
Class SubClass extends SuperClass(){
#anno(a2="myA2", a3="defaultValueA3")
private String field1;
}
currently when I try to get annotaion from subclass, the anno only contains customized a2, but a1 is only able to get default value, is there any way get an annotation mix all superclass specified fields like {a1:myA1, a2:myA2, a3:defaultValueA3} but not {a1:defaultValueA1,a2:myA2, a3:defaultValueA3}?
update:
I know annotation is not inheritable, so I tried mix subClass annotation {a1:defaultValueA1, a2:myA2, a3:defaultValueA3} and superClass annotaion {a1:myA1, a2: defalutValueA2, a3:myA3}, my way is get all subClass customized values and copy them to superClass annotation, but the problem is when I get all annotation value from subClass, I can't distiguish which value is user defined and which value is come from default value, anyone have suggestion?
private void mixAnnotaiont(Annotation target, Annotation source){
Method[] methods = source.annotationType().getMethods();
Map<String, Object> sourceCfg = AnnotationUtils.getAnnotationAttributes(source);
for (int i = 0; i < methods.length; i++) {
// skip default value, but it's incorrect, user may specify default value in subClass to overwrite superClass value
Object defaultValue = methods[i].getDefaultValue();
String name = methods[i].getName();
if(sourceCfg.get(name).equals(defaultValue)){
ignoreProperties.add(name);
}
}
BeanUtils.copyProperties(source, target, ignoreProperties.toArray(new String[] {}));
}
Thanks for your attention.
Annotations aren't inherited according to JavaDoc:
Open Declaration Annotation[] java.lang.Class.getDeclaredAnnotations()
Returns all annotations that are directly present on this element.
Unlike the other methods in this interface, this method ignores
inherited annotations. (Returns an array of length zero if no
annotations are directly present on this element.) The caller of this
method is free to modify the returned array; it will have no effect on
the arrays returned to other callers.
But you can try have a while loop and get all the declared annotations using this method
java.lang.Class.getSuperclass()
while(loop thru the super class)
{
// get declared annotation
}

Instantiate object by it's path got from annotation property

1. Have following annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Trackable {
String builder();
}
2. usage of this annotation:
#Trackable(builder = "pkg1.SomeVO")
public class MyService
3. pkg1.SomeVO - is path to the Java object,that should be instantiated further in my aspect class.
4. I've got String value of build,that is equals to 'pkg1.SomeVO' from reflection.
The question is,how actually to instantiate SomeVO object?
I need to it like:
MyBuilder mb=new SomeVO();
where MyBuilder is abstract class,already defined.
It may be any object,e. g. SomeVO2 etc.,so I definitely doesn't know in my aspect(see step 3.),what class should be instantiated.
Do something like this to get the annotation value and create the class. Also, you may want to use the default value field and a Class instead of a String in your annotations to make things easier.
for (Method m : MyService.class.getDelcaredMethods())
if (m.getAnnotation(Trackable.class) != null) {
String className = m.getAnnotation(Trackable.class).builder()
Class.forName(className).newInstance();
}
If your class has no default constructor you need to figure out what args it takes:
for (Constructor c : Class.forName(className).getConstructors())
if (c.getParameterTypes() == /* your expected arg types */)
return c.newInstance(/* your args */);
To ensure your class is of a certain type (e.g. MyBuilder) you can make your annotation be:
public #interface Trackable {
Class<? extends MyBuilder> value()
}
Assuming by path, you mean the package your class is in (which is hopefully on the classpath).
String myClass = getClassNameFromAnnotationReflectively(); // YOU define this method...
Class<SomeVO> someVoClass = Class.forName(myClass);
SomeVO someVo = someVoClass.newInstance();
If your class doesn't have a default constructor, you can find the proper one using getConstructor(Class<?> ...) (where you pass in the types of the arguments of the constructor you are looking for), and then calling newInstance(Object...) on that (passing in the actual values).
There are a lot of fun methods for use on the Class object. I recommend taking a look.

Categories