일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- kotlin
- traceasynccustomautoconfiguration
- Spring Boot
- list
- spring3 spring2 traceid
- @FunctionalInterface
- DeferredImportSelector
- elasticsearch
- java.util.list
- b3-propagation
- traceId
- ResponseBody
- java list
- EnableWebMvc
- CompletableFuture
- asynccustomautoconfiguration
- jpa
- java
- asyncconfigurer
- micrometer tracing
- SpringMVC
- aws secretmanager
- Spring JPA
- HashMap
- Sleuth
- map
- awssecretsmanagerpropertysources
- spring
- java lambda
- spring MVC
- Today
- Total
du.study기록공간
lombok + jackson 사용하여 직렬화시, 변수명 첫 단어가 한글자일때 본문
다른회사로 요청,응답을 기록하는 과정에, 필드의 로그값이 이중으로 찍혀있는 모습을 보고 내용을 정리해봅니다.
우선 발생된 상황을 정리해보면 다음과 같습니다.
1. request dto는 롬복의 @Getter와 @JsonProperty를 사용하고 있습니다.
2. 변수명중 두번째 글자가 대문자인 변수들이 있고, JsonProperty는 이 값을 다르게 사용하고 있습니다.
3. objectMapper.writeValusAsString() 를 활용하여 로그를 작성중에 있습니다.
이 경우, 의도했던 값과 다르게 유사한 변수명이 추가로 String에 노출되는 현상이 발생됩니다.
lombok의 getter를 사용하면서 objectMapper.writeValusAsString()을 사용할때 발생될 수 있는 문제점을 확인하고자합니다.
우선 간단한 예시를위해 클레스를 만듭니다.
@Getter
@Builder
public class Test1 {
private String pCouponCode;
}
빌드된 파일을 보면 두번째 글짜가 대문자인 변수를 만들면 lombok은 아래와같은 getter를 만들어줍니다.
요걸 실제로 objectMapper.writeValusAsString 를 통해 찍어보면 아래와같이 변환이됩니다.
{"pcouponCode":"pCouponCode"}
하지만 보통 원하는 값은 아래와 같을겁니다.
{"pCouponCode":"pCouponCode"}
우선.. 이게 왜 이렇게 변했나를 확인해보기 위해 jackson 변환과정의 해당 클래스를 확인해봅니다. (궁금하니까.. 한줄한줄 보면서 들어가봅니다)
com.fasterxml.jackson.databind.util.BeanUtil
해당 클레스에서 get, is, set의 prefix를 사용하는 경우 아래 method를 통해 변수명이 변환됩니다.
요 코드가 한글자를 쓰면 안된다는 이유입니다.
protected static String legacyManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
return null;
}
// next check: is the first character upper case? If not, return as is
char c = basename.charAt(offset);
char d = Character.toLowerCase(c);
if (c == d) {
return basename.substring(offset);
}
// otherwise, lower case initial chars. Common case first, just one char
StringBuilder sb = new StringBuilder(end - offset);
sb.append(d);
int i = offset+1;
for (; i < end; ++i) {
c = basename.charAt(i);
d = Character.toLowerCase(c);
if (c == d) {
sb.append(basename, i, end);
break;
}
sb.append(d);
}
return sb.toString();
}
해당 로직을 보면 첫글자를 소문자로 만들고, 연속된 글자중 대문자는 전부 소문자로 변경후, name을 가져옵니다!
이 로직으로 인해 원했던 pCouponCode 이 아니라 pcouponCode 를 가져옵니다.
그렇다면 원하는 값을 어떻게 가져올것인가를 고민해보면..
1. getter를 직접 생성한다.
해당 방법을 이용한다면, {"pCouponCode":"pCouponCode"} 와 같은 응답을 얻을 수 있습니다.
위에 BeanUtil의 로직을 보면 첫글자가 소문자인경우 그대로 리턴을 해주고 있습니다.
@Getter
@Builder
public class Test1 {
private String pCouponCode;
public String getpCouponCode(){
return pCouponCode;
}
}
하지만 수작업을 피하고 롬복을 사용하고 싶으니 다른방법을 찾아봅니다.
2. @JsonProperty를 사용하는경우
아래와 같이 클래스를 만들고 objectMapper.writeValusAsString() 를 하면 전혀 의도되지않은 값을 만날 수 있습니다.
{"pcouponCode":"pCouponCode","pCouponCode":"pCouponCode"}
@Getter
@Builder
public class Test1 {
@JsonProperty(value = "pCouponCode")
private String pCouponCode;
}
다시 코드를 파해쳐보면.. 아래 부분의 로직을 보다보면 getter와 jsonProperty로 설정한 값이 각각 다른 key값을 가지게 되기때문에 두개의 값이 추가되어 변환되는 모습을 보게 됩니다. (하나는 변수명(pCouponCode), 하나는 getter의 이름 (pcouponCode))
com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector
POJOPropertiesCollector {
...
protected void collectAll()
{
LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();
// First: gather basic data
_addFields(props);
_addMethods(props);
// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
// inner classes, see [databind#1502]
if (!_classDef.isNonStaticInnerClass()) {
_addCreators(props);
}
_addInjectables(props);
// Remove ignored properties, first; this MUST precede annotation merging
// since logic relies on knowing exactly which accessor has which annotation
_removeUnwantedProperties(props);
// and then remove unneeded accessors (wrt read-only, read-write)
_removeUnwantedAccessor(props);
}
이부분을 해결하기 위해서는 아래와같이 @JsonAutoDetect를 사용하여 직렬화 대상을 설정해줍니다.
@Getter
@Builder
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
public class Test1 {
@JsonProperty(value = "pCouponCode")
private String pCouponCode;
}
해당 방식으로 설정 후, objectMapper를 사용하여 변환하면 사용하는 DTO의 수정만으로 원하는 값을 얻을 수 있게됩니다.
'자바' 카테고리의 다른 글
Ehcache에서 만난 warning 'The JVM is preventing Ehcache from accessing the subgraph beneath ~ cache sizes may be underestimated as a result' (0) | 2023.03.28 |
---|---|
Sentry environment 설정방법 (0) | 2023.03.23 |
Arrays.asList List에 add를 했더니 UnsupportedOperationException 를 만났을 때 (0) | 2022.10.20 |
stream과 함께 CompletableFuture multi task join 하기 (3) | 2021.12.27 |
Java @FunctionalInterface - Function (0) | 2021.12.14 |