Formatting (string-) array as part of bean-validation message - java

I created my first custom validation annotation with validator class as inner class (which i find quite well-arranged).
It looks like this:
#Target( { ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {OneOfGroupNotNull.Validator.class})
#Documented
public #interface OneOfGroupNotNull {
// custom annotation properties
String[] fields();
// required by JSR-303
String message() default "One of group must be not null. {fields}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public class Validator implements ConstraintValidator<OneOfGroupNotNull, Object> {
private String[] fields;
#Override
public boolean isValid(Object bean, ConstraintValidatorContext cvc) {
int countNotNull = 0;
for (String field : fields) {
try {
String property = BeanUtils.getProperty(bean, field);
if (property != null) {
countNotNull++;
}
} catch (Exception ex) {
throw new RuntimeException("Validation for field " + field + " of type " + bean.getClass()+ " raised exception.", ex);
}
}
return countNotNull == 1;
}
#Override
public void initialize(OneOfGroupNotNull a) {
fields = a.fields();
}
}
}
A bean class that is annotated with this validator may look like this:
#OneOfGroupNotNull(fields = {"a", "b", "c"})
public interface MyBean {
String getA();
Rarity getB();
Boolean getC();
}
The problem is that I cannot find a way to format the string array "fields". It just takes the to string method which results in something like:
One of group must be not null. [Ljava.lang.String;#157d954

If you changed type of fields from String[] to String then message with field names will be shown correctly. To get field names in constraint just split() it by comma.
Another option is to generating custom message inside constraint, like this:
cvc.disableDefaultConstraintViolation();
cvc.buildConstraintViolationWithTemplate("error message")
.addNode("field name with error")
.addConstraintViolation();

Which implementation of Bean Validation are you using? If you are using Hibernate Validator 4.3 this should actually work. See also https://hibernate.onjira.com/browse/HV-506.
As a workaround, why not use a List? There the default toString is more sensible.

Related

Validation Exception - the right way to handle

I am trying to validate the postal code in my but the approach i am thinking of is not working out and I can't understand why.
I created a Validator, that hast to throw a ValidationException if it's not valid.
#Service
public class ZipCodeValidator{
public void validate(String zipCode){
validateNotEmpty(zipCode);
validateHasNoSpaces(zipCode);
}
private void validateNotEmpty(String zipCode){
if (zipCode.length() != 0){
throw new ValidationException("Postal Code can't be empty");
}
}
private void validateHasNoSpaces(String zipCode) {
if (zipCode.contains(" ")){
throw new ValidationException("Postal Code can't contain spaces");
}
}
}
In my service, where i receive the postal code I want to throw my custom exception (to which i pass the error message) like this:
try{
validator.validate(zipCode);
}catch (ValidationException ex){
throw new ZipCodeValidationException(ex.getMessage());
}
However it doesn't seem to work, that exception is not caught so the program runs further.
What am I doing wrong?
Is the whole approach wrong? What would you recommend?
Here's my custom Exception
public class ZipCodeValidationException extends Exception{
public ZipCodeValidationException(String message) {
super(message);
}
}
I recommend the following:
to process all exceptions in universal place as ExceptionHandler class, for more details see: https://www.baeldung.com/exception-handling-for-rest-with-spring
you can extend ValidationException from RuntimeException, that approach will allow unloading the code from try-catch constructions
#Service annotation is not quite right for converters or validators, as rule #Service class contains business logic, for helpers classes will be better use #Component, in total no differences between these two annotations only understanding which layer of application that component has
Please share the code for more suggestions and help.
Hi Please find my answer in 2 steps, first the correct and then the second the suggested way to implement.
Correction:
Please use ObjectUtils.isEmpty(arg) for checking if string is 0 length or null. Here is the modified version of your code
public interface ZipcodeService {
void validate(#Zipcode String zipCode) throws ZipCodeValidationException;
}
#Service
public static class ZipcodeServiceImpl implements ZipcodeService {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeServiceImpl() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public void validate(String zipCode) throws ZipCodeValidationException {
// uncomment for Regex Validation
// boolean valid = zipCodeRegexMatcher.isValid(zipCode);
// uncomment for Simple text validation
final boolean valid = !ObjectUtils.isEmpty(zipCode);
if (!valid) {
throw new ZipCodeValidationException("Invalid zipcode");
}
}
}
This is how the caller looks like from Controller
#GetMapping(path = "dummy")
public String getDummy(#RequestParam("zipcode") String zipCode) {
try {
zipcodeService.validate(zipCode);
return zipCode;
} catch (ZipCodeValidationException e) {
return e.getMessage();
}
}
Suggested way:
add following entry to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Create Annotation and Validator as given below
#Constraint(validatedBy = {ZipcodeValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Zipcode {
String message() default "Invalid Zipcode value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class ZipcodeValidator implements ConstraintValidator<Zipcode, String> {
private final ZipCodeRegexMatcher zipCodeRegexMatcher;
public ZipcodeValidator() {
zipCodeRegexMatcher = new ZipCodeRegexMatcher();
}
#Override
public boolean isValid(String zipCode, ConstraintValidatorContext constraintValidatorContext) {
return zipCodeRegexMatcher.isValid(zipCode);
}
}
Once this setup is done, head over to Controller class and annotated class with
#Validated and field you want to have validation on with the Custom Annotation i.e Zipcode we have just created. We are creating a Custom Validator in this case ZipcodeValidator by extending ConstraintValidator.
This is how the caller looks like:
#GetMapping
public String get(#Zipcode #RequestParam("zipcode") String zipCode) {
return zipCode;
}
On Failed validation, it throws javax.validation.ConstraintViolationException: get.zipCode: Invalid Zipcode value which you can customize according to your need by using ControllerAdvice.
You can also use #Zipcode annotation at the service level and it works the same way. Regarding ZipCodeRegexMatcher instead of creating it inside the constructor you can create a bean and inject that dependency. It is a simple class that has regex for zipcode and performs validation.
public static class ZipCodeRegexMatcher {
public static final String ZIP_REGEX = "^[0-9]{5}(?:-[0-9]{4})?$";
private final Pattern pattern;
public ZipCodeRegexMatcher() {
pattern = Pattern.compile(ZIP_REGEX);
}
public boolean isValid(String zipCode) {
return pattern.matcher(zipCode).matches();
}
}
The entire code is located here

How to apply size annotation for a field which can be either of two sizes

In my spring boot application I have a size validation on one of field in my dto. Now as per new requirement field size can be either 18 or 36. Earlier it was 36 so I had done like this:
#Size(min=36,max = 36,message = "id length should be 36")
Now as I have to validate against two sizes, Is there any way to do it with the annotation itself ?
Thanks,
Make a custom validator Annotation.
In CustomSize.java
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = CustomSizeValidator.class)
#Documented
public #interface CustomSize{
String message() default "{CustomSize.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
In CustomSizeValidator.java
class CustomSizeValidator implements ConstraintValidator<CustomSize, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
try {
if(value.length()==18 || value.length()==36){
return true;
}else{
return false;
}
} catch (Exception e) {
return false;
}
}
}
Use this in your POJO code.
#CustomSize
private String xyz;
OR
use #Pattern
#Pattern(regexp = "^(?:[A-Za-z0-9]{18}|[A-Za-z0-9]{36})$")
For Pattern see more here -> https://stackoverflow.com/a/34311990/1459174

Modify annotation value on Java 8 using reflection [duplicate]

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?

Is there a 'choice' Java annotation equivalent to <xs:choice>?

I am looking to field validate a web service request by only allowing one of two different fields in the request. I know from past experience from using xsd that you can have something like this to only allow either FieldOne or FieldTwo:
<xs:complexType name="SomeType">
<xs:choice>
<xs:element name="FieldOne" type="target:FieldOneType"/>
<xs:element name="FieldTwo" type="target:FieldTwoType"/>
</xs:choice>
</xs:complexType>
I would like to do the same using Java annotations. I am currently using annotations for limiting field length (#Digits) and null checks (#NotNull).
Is there something I can use for a 'choice'?
Thanks for any help.
UPDATE: Basically I am looking for some way of only allowing one of two different fields to be entered in a web service request without having to do this validation in my code manually. I am currently using bean validation annotations to limit field lengths and to determine whether a field is mandatory or optional e.g.:
#NotNull(message="Field cannot be empty")
#Size(max = 6, message = "Field length is too long")
private String fieldOne;
I would like to be able to say that the user can only enter either fieldOne or fieldTwo, but not both. Is this possible through annotations or am I stuck to writing this validation in my code?
Edited:
To validate either one field has value but not other, I think you can use a custom validator at class level. Following is the idea:
1.
Create interface for your annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ChoiceValidator.class)
#Documented
public #interface Choice {
String[] fields();
String message() default "{Choice.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.
Create a implementation of ConstraintValidator to check the value to be validated is in fields inside Choice annotation:
public class ChoiceValidator
implements ConstraintValidator<Choice, Object> {
private List<String> fields;
#Override
public void initialize(final Choice choice) {
fields = Arrays.asList(choice.fields());
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext ctx) {
int nonNullFieldCount = 0;
for (String field : fields) {
try {
final String fieldValue = BeanUtils.getProperty(value, field);
if (fieldValue != null) {
nonNullFieldCount++;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return nonNullFieldCount == 1;
}
}
After that, you can use it something like:
#Choice(fields= {"fieldOne", "fieldTwo"})
public class Foo {
String fieldOne;
String fieldTwo;
}
Original:
I am not sure I really get you mean, but looks like you want a validation on the Class types of a Object field. If there is the case, you may try to create your custom annotation and ConstraintValidator to do so. Following is the idea:
1.
Create interface for your annotation:
public #interface Choice {
Class<?>[] types();
}
2.
Create a implementation of ConstraintValidator to check the value to be validated is in types inside Choice annotation:
public class ChoiceValidator implements ConstraintValidator<Choice, Object> {
private List<Class<?>> clazzes;
#Override
public void initialize(Choice choice) {
clazzes = Arrays.asList(choice.types());
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
for (Class<?> clazz : clazzes) {
if (value.getClass().equals(clazz)) {
return true;
}
}
return false;
}
}
After that, you can use it something like:
#Choice(types = {FieldOneType.class, FieldTwoType.class})
public class Foo {
Object someType;
}
Hope this can help.

Modify a class definition's annotation string parameter at runtime

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?

Categories