Reputation: 12896
Given a POJO in Spring Boot with several dozen fields of type String
which is deserialized by Jackson. For demonstration purposes the following example only contains three fields:
@NoArgsConstructor
public class SomeRequest {
@JsonProperty("field_1")
private String field1;
@JsonProperty("field_2")
private String field2;
@JsonProperty("field_3")
private String field3;
}
I'm looking for a way to override the setter method but only for certain fields, i.e. I'd like to avoid repeating the below code for every affected field. This is doable for a handful number of fields but gets tedious for more than a handful.
public setField2(String field2) {
this.field2 = field2 + "?";
}
My idea was to place an annotation on the field like this:
@NoArgsConstructor
public class SomeRequest {
// ...
@JsonProperty("field_2")
@AppendQuestionMark
private String field2;
// ...
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AppendQuestionMark {
}
But I'm lacking information on how to "implement" the AppendQuestionMark
annotation which would override the field's setter method.
Or am I thinking way too complicated?
Upvotes: 4
Views: 3380
Reputation: 4547
Instead of creating a new annotation that appends a question mark to one generic string field in your pojo you can use the already present JsonDeserialize
annotation over the string fields you are interested:
@Data
@NoArgsConstructor
public class SomeRequest {
@JsonProperty("field_1")
private String field1;
@JsonProperty("field_2")
//here the custom deserializer appends the question mark character
@JsonDeserialize(using = StringAppendQuestionMarkDeserializer.class)
private String field2;
}
In your spring boot project you can register the custom deserializer with the JsonComponent
annotation like below:
@JsonComponent
public class StringAppendQuestionMarkDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return node.asText() + "?";
}
}
A spring boot test example using the custom deserializer:
@JsonTest
class CorespringApplicationTests {
@Test
void testDeserialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SomeRequest request = mapper.readValue("{\"field_1\":\"value1\",\"field_2\":\"value2\"}", SomeRequest.class);
System.out.println(request); //<-- SomeRequest(field1=value1, field2=value2?)
}
}
Upvotes: 1
Reputation: 868
You can't change the setter
method's body if that's what you are asking. But you can create a method that will take an object (i.e. SomeRequest
) as input and check which fields have your Annotation
and change the values for those fields as you want.
For example, I created an annotation AppendStr
.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AppendStr {
public String str();;
}
Then I created another class 'AppendStrImpl` that will handle the implementation. I used the following code -
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AppendStrImpl {
public void changeFields(Object object) throws Exception {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(AppendStr.class)) {
// get the getter method name from the field name
String fieldName = field.getName();
String getterMethodName =
"get" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method getterMethod = clazz.getMethod(getterMethodName);
String returnValue = (String) getterMethod.invoke(object);
String setterMethodName = getterMethodName.substring(0, 1).replace("g", "s")
+ getterMethodName.substring(1);
Method setterMethod = clazz.getMethod(setterMethodName, String.class);
setterMethod.invoke(object, returnValue + getAppendingString(field));
System.out.println((String) getterMethod.invoke(object));
}
}
}
private String getAppendingString(Field field) {
return field.getAnnotation(AppendStr.class)
.str();
}
}
And this is my POJO class -
public class POJO {
@AppendStr(str = "?")
private String filed1;
@AppendStr(str = "!")
private String filed2;
private String filed3;
@AppendStr(str = "+")
private String filed4;
// ... getters and setters
}
Then I called this method from the main method -
POJO pojo = new POJO("a", "b", "c", "d");
AppendStrImpl appendStrImpl = new AppendStrImpl();
try {
appendStrImpl.changeFields(pojo);
} catch (Exception e) {
e.printStackTrace();
}
Now you can make this call with hard coding or you can use @Aspect
too if you want.
The github link is here.
Upvotes: 3
Reputation: 17470
Something like the following should do the trick:
@Aspect
@Component
public class AppendQuestionMarkAspect {
@Around("@annotation(AppendQuestionMark)")
public Object appendQuestionMark(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] arguments = joinPoint.getArgs();
return joinPoint.proceed(new Object[] {((String) arguments[0]) + "?"});
}
}
Of course, it would be advisable to check that only one argument exists and that it is, in fact, a String
. Or you can also define the pointcut as to be applied only to methods starting with set
. But the essence of the code is there.
Upvotes: 0