简体   繁体   中英

Modify POJO class fields with custom setter or custom annotation (in Spring Boot)

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, ie 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?

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 (ie 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 .

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?)

    }

}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM