使用自定义setter或自定义注释修改POJO类字段(在Spring Boot中)



给定一个POJO在Spring Boot中有几十个字段类型为String,由Jackson反序列化。为了便于演示,下面的示例只包含三个字段:

@NoArgsConstructor
public class SomeRequest {
@JsonProperty("field_1")
private String field1;
@JsonProperty("field_2")
private String field2;
@JsonProperty("field_3")
private String field3;
}

我正在寻找一种方法来覆盖setter方法,但只针对某些字段,即我想避免重复下面的代码为每个受影响的字段。这对于少数字段是可行的,但对于超过少数字段就变得冗长了。

public setField2(String field2) {
this.field2 = field2 + "?";
}

我的想法是像这样在字段上放置一个注释:

@NoArgsConstructor
public class SomeRequest {
// ...
@JsonProperty("field_2")
@AppendQuestionMark
private String field2;
// ...
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AppendQuestionMark {
}

但是我缺乏关于如何"实现"的信息。AppendQuestionMark注释将覆盖字段的setter方法。

还是我想得太复杂了?

你不能改变setter方法的主体,如果这是你想要的。但是你可以创建一个方法,将一个对象(即SomeRequest)作为输入,并检查哪些字段有你的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();;
}

然后我创建了另一个类'AppendStrImpl '来处理实现。我使用了以下代码-

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();
}
}

这是POJO类-

public class POJO {
@AppendStr(str = "?")
private String filed1;
@AppendStr(str = "!")
private String filed2;
private String filed3;
@AppendStr(str = "+")
private String filed4;
// ... getters and setters
}

然后我从主方法中调用这个方法-

POJO pojo = new POJO("a", "b", "c", "d");
AppendStrImpl appendStrImpl = new AppendStrImpl();
try {
appendStrImpl.changeFields(pojo);
} catch (Exception e) {
e.printStackTrace();
}

现在你可以用硬编码或者你也可以使用@Aspect,如果你愿意的话。

github链接在这里。

您可以在您感兴趣的字符串字段上使用已经存在的JsonDeserialize注释,而不是在pojo中为一个通用字符串字段添加问号的新注释:

@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; 

}

在您的spring启动项目中,您可以使用JsonComponent注释注册自定义反序列化器,如下所示:

@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() + "?";
}
}

使用自定义反序列化器的spring引导测试示例:

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

应该像下面这样做:

@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]) + "?"});
}
}

当然,最好检查是否只存在一个参数,并且它实际上是一个String。或者您也可以将切入点定义为只应用于以set开头的方法。但是代码的本质就在那里。

最新更新