Change the value of a parameter annotation [duplicate] - java
Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?
Related
Test custom constraint annotations with JUnit 5
Implementing a custom constraint annotation, like #MySize requires me testing it with unit tests to see if it functions correctly: public class MySizeTest { #Test public void noMinMax() { Dummy dummy = new Dummy(); // some asserts or so dummy.setMyField(""); dummy.setMyField(null); dummy.setMyField("My text"); } #Test public void onlyMin() { // change #MySize to have min: #MySize(min = 1) ... how? ... then test with some setMyField: Dummy dummy = new Dummy(); // some asserts or so dummy.setMyField(""); dummy.setMyField(null); dummy.setMyField("My text"); } #Test public void onlyMax() { // change #MySize to have max: #MySize(max = 50) ... } #Test public void bothMinMax() { // change #MySize to have min and max: #MySize(min = 1, max = 50) ... } private class Dummy { #MySize() String myField; public String getMyField() { return myField; } public void setMyField(String myField) { this.myField = myField; } } } I assume this has to be done with reflection, but I have no idea how.
Basicly don't have to use reflection just create a Validator instance and use that for validating. For examaple: When the annotation is: #Target(ElementType.FIELD) #Retention(RetentionPolicy.RUNTIME) #Constraint(validatedBy = MyValidator.class) public #interface MyAnnotation { String message() default "Invalid value (it must be foo)"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } and the related validator is: public class MyValidator implements ConstraintValidator<MyAnnotation, String> { #Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { if (null == s) return true; return "foo".equalsIgnoreCase(s); } } Then the tests sould be like these: #TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyValidatorTest { private Validator validator; #BeforeAll void init() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } private static class TestObject { #MyAnnotation private String testField; TestObject() { this(null); } TestObject(String value) { testField = value; } public String getTestField() { return testField; } public void setTestField(String testField) { this.testField = testField; } } #Test void shouldValidForNullValue() { var obj = new TestObject(); var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>> Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size())); } #Test void shouldValidForFooValue() { var obj = new TestObject("foo"); var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>> Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size())); } #Test void shouldInvalidForBarValue() { var obj = new TestObject("bar"); var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>> Assertions.assertEquals(1, violations.size()); } } Update (2020.05.21.) - Using attributes and AnnotationFactory Based on comments I've updated my answer. If you want to test only the validation logic then just create an Annotation instance and call the isValid method which is returns true or false Hibernate Validator provides AnnotationFactory.create(...) method to make annotaion instance. After that you can create an instance of your custom validator and call initialize and isValid methods in your test case. #Target(ElementType.FIELD) #Retention(RetentionPolicy.RUNTIME) #Constraint(validatedBy = MyHasAttributesValidator.class) public #interface MyAnnotationHasAttributes { String message() default "Invalid value (it must be foo)"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int attributeOne() default 10; int attributeTwo() default 20; } related validator: public class MyHasAttributesValidator implements ConstraintValidator<MyAnnotationHasAttributes, String> { private MyAnnotationHasAttributes ann; #Override public void initialize(MyAnnotationHasAttributes constraintAnnotation) { ann = constraintAnnotation; } #Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { if (null == s) return true; return s.length() >= ann.attributeOne() && s.length() < ann.attributeTwo(); } } and the modified test (which has failing assertion): public class HasAttributeValidatorTest { private MyAnnotationHasAttributes createAnnotation(Integer one, Integer two) { final Map<String, Object> attrs = new HashMap<>(); if (null != one) { attrs.put("attributeOne", one); } if (null != two) { attrs.put("attributeOne", two); } var desc = new AnnotationDescriptor.Builder<>(MyAnnotationHasAttributes.class, attrs).build(); return AnnotationFactory.create(desc); } #ParameterizedTest #MethodSource("provideValues") void testValidator(Integer one, Integer two, String input, boolean expected) { MyAnnotationHasAttributes ann = createAnnotation(one, two); MyHasAttributesValidator validator = new MyHasAttributesValidator(); validator.initialize(ann); var result = validator.isValid(input, null); Assertions.assertEquals(expected, result, String.format("Validation must be %s but found: %s with params: %d, %d, %s", expected, result, one, two, input)); } private static Stream<Arguments> provideValues() { return Stream.of( Arguments.of(null, null, null, true), Arguments.of(null, 20, "foo", true), Arguments.of(null, null, RandomStringUtils.randomAlphabetic(30), false) ); } } Limitations of this solution Vendor lock In this case your test using Hibernate Validator which is a specific implementation if the Bean Validation standards. Honestly I don't think it's a huge problem, because Hibernate Validator is the refecerence implementation and the most popular bean validation library. But technically it's a vendor lock. Cross field validation is unavailable This soulution works only in one-field situations. If you have e.g a cross-field validator (e.g. password and confirmPassword matching) this example won't fit. Type independent validation needs more work Like previously mentioned #Size annotation belongs to several different validator implementations based on type (primitives, collections, string, etc.). Using this solution you always have to chose the certain validator manually and test it. Only the isValid method can be tested In this case you won't be able to test another things just the isValid method. I mean e.g. error message has expected format and parameters or something like this. In sort, I know creating many different fields with different annotation attributes is boring but I strongly prefer that way because you can test everything you need about your validator.
String emptiness checker annotation
I am trying to write an annotation that will help me find out if the String assigned to memberVariable of a model class is empty or not. If empty than don't proceed with registration. I have written the following code but I am confused as how will I tell the Annotation, what value to check and how will the annotation notify me if the String is empty. IsStringEmpty.java #Target(ElementType.PARAMETER) #Retention(RetentionPolicy.RUNTIME) public #interface IsStringEmpty { String value(); } StringEmptinessChecker.java public class StringEmptinessChecker { public boolean process(Object instance) { Class<?> clazz = instance.getClass(); for (Method m : clazz.getDeclaredMethods()) { if (m.isAnnotationPresent(IsStringEmpty.class)) { IsStringEmpty annotation = m.getAnnotation(IsStringEmpty.class); String val = annotation.value(); return val.isEmpty(); } return false; } return false; } } Main.java public void foo() { MyModel model = new MyModel(); #IsStringEmpty()?????????? model.setName(nameET.getText().toString()); // if nameEt.getText().toString() is empty. Dont make network call for registratoin }
Mapping json string to interace with anonymous class
I have an interface, which I want to use for serialize/deserialize. I want to omit some of the fields. Code below is not working so far. #JsonAutoDetect(fieldVisibility = Visibility.NONE) public interface MyWrapper { //no annotation to not serialize String getMyField(); //annotation to deserialize #JsonProperty("my_field") void setMyField(); }
You can either specify #JsonIgnore annotation on the method, or #JsonIgnoreProperties(value = {"myfield"}) annotation on the class. see examples here EDIT: which version of Jackson are you using? becuase in the one I am using (2.5) the use of #JsonIgnore together with #JsonProperty works perfectly. also, notice that the setter needs to receive an argument to actually be used by Jackson interface with fixed setter: #JsonAutoDetect(fieldVisibility = Visibility.NONE) public interface MyWrapper { #JsonIgnore String getMyField(); // annotation to deserialize #JsonProperty("my_field") void setMyField(String f); } implementation (nothing exciting here) public class Foo implements MyWrapper { private String myField; public Foo() {} public Foo(String f) { setMyField(f); } #Override public String getMyField() { return myField; } #Override public void setMyField(String f) { myField = f; } } testing : public static void main(String[] args) { ObjectMapper mapper = new ObjectMapper(); // serialization - ignore field try { MyWrapper w = new Foo("value"); String json = mapper.writeValueAsString(w); System.out.println("serialized MyWrapper: " + json); } catch (Exception e) { e.printStackTrace(); } // de-serialization - read field String json = "{\"my_field\":\"value\"}"; try (InputStream is = new ByteArrayInputStream(json.getBytes("UTF-8"))) { MyWrapper w = (MyWrapper)mapper.readValue(is, Foo.class); System.out.println("deserialized MyWrapper: input: " + json + " ; w.getMyField(): " + w.getMyField()); } catch (Exception e) { e.printStackTrace(); } } output: serialized MyWrapper: {} deserialized MyWrapper: input: {"my_field":"value"} ; w.getMyField(): value
jdbi BindBean userdefined property of the bean(nested object)
I have a bean class public class Group{string name;Type type; } and another bean public class Type{String name;} Now, i want to bind group by using jdbi #BindBean #SqlBatch("INSERT INTO (type_id,name) VALUES((SELECT id FROM type WHERE name=:m.type.name),:m.name)") #BatchChunkSize(100) int[] insertRewardGroup(#BindBean ("m") Set<Group> groups); How can i bind the user defined object's property as member of the bean??
You could implement your own Bind-annotation here. I implemented one that I am adopting for this answer. It will unwrap all Type ones. I think it could be made fully generic with a little more work. Your code would look like this (please note that m.type.name changed to m.type): #SqlBatch("INSERT ... WHERE name=:m.type),:m.name)") #BatchChunkSize(100) int[] insertRewardGroup(#BindTypeBean ("m") Set<Group> groups); This would be the annotation: #BindingAnnotation(BindTypeBean.SomethingBinderFactory.class) #Retention(RetentionPolicy.RUNTIME) #Target({ElementType.PARAMETER}) public #interface BindTypeBean { String value() default "___jdbi_bare___"; public static class SomethingBinderFactory implements BinderFactory { public Binder build(Annotation annotation) { return new Binder<BindTypeBean, Object>() { public void bind(SQLStatement q, BindTypeBean bind, Object arg) { final String prefix; if ("___jdbi_bare___".equals(bind.value())) { prefix = ""; } else { prefix = bind.value() + "."; } try { BeanInfo infos = Introspector.getBeanInfo(arg.getClass()); PropertyDescriptor[] props = infos.getPropertyDescriptors(); for (PropertyDescriptor prop : props) { Method readMethod = prop.getReadMethod(); if (readMethod != null) { Object r = readMethod.invoke(arg); Class<?> c = readMethod.getReturnType(); if (prop.getName().equals("type") && r instanceof Type) { r = ((Type) r).getType(); c = r.getClass(); } q.dynamicBind(c, prefix + prop.getName(), r); } } } catch (Exception e) { throw new IllegalStateException("unable to bind bean properties", e); } } }; } } }
Doing this in JDBI is not possible , you have to bring out the property and give is a argument.
Get list of fields with annotation, by using reflection
I create my annotation public #interface MyAnnotation { } I put it on fields in my test object public class TestObject { #MyAnnotation final private Outlook outlook; #MyAnnotation final private Temperature temperature; ... } Now I want to get list of all fields with MyAnnotation. for(Field field : TestObject.class.getDeclaredFields()) { if (field.isAnnotationPresent(MyAnnotation.class)) { //do action } } But seems like my block do action is never executed, and fields has no annotation as the following code returns 0. TestObject.class.getDeclaredField("outlook").getAnnotations().length; Is anyone can help me and tell me what i'm doing wrong?
You need to mark the annotation as being available at runtime. Add the following to your annotation code. #Retention(RetentionPolicy.RUNTIME) public #interface MyAnnotation { }
/** * #return null safe set */ public static Set<Field> findFields(Class<?> classs, Class<? extends Annotation> ann) { Set<Field> set = new HashSet<>(); Class<?> c = classs; while (c != null) { for (Field field : c.getDeclaredFields()) { if (field.isAnnotationPresent(ann)) { set.add(field); } } c = c.getSuperclass(); } return set; }