[Java] @Required 직접 만들어보기
by 배부른코딩로그💡 스프링의 @Required 애노테이션을 사용할 수 없는 환경이라면, 직접 만들어보자!
목표
- @애노테이션이 어떻게 구현되는지 설명할 수 있다.
- 순수 자바 환경에서 @Required 애노테이션을 입맛에 맞게 구현할 수 있다.
- POJO가 무슨 의미인지 설명할 수 있다.
필수 속성(프로퍼티)를 지정하기 위해서 @Required 를 사용한다.
스프링 기반의 API 서버는 @Required 가 요청 데이터를 쉽게 검증할 수 있도록 도와지만,
순수 자바 기반의 시스템에서 @Required 는 빨간줄이 생겨버린다.
그렇다.......
@Required 는 org.springframework.beans.factory.annotation 스프링프레임워크 패키지 내에 존재한다!!!
필자는 특이하게 Packet을 송수신하는 순수 자바 서버인 경우인데, JSON 포맷을 도입하기로 했다.
가벼운 자바 서비스라는 점에서 스프링과 같은 거대한 라이브러리는 비효율적이었다.
자주 쓰던 @Required 없이 빠르게 구현하다 보니, JSON 검증 코드가 복잡해졌고,
이를 깔끔하게 리팩토링 하고자 했다.
다음 예제 코드는 처음 접했을 때 상당히 흥미로웠고,
JSON 검증을 위해 Google/Gson을 사용했다는 것 외에 어디든 재활용 가능한 코드라는 점을 참고바란다.
@Requied 구현하기
애노테이션 이름은 원하는 것으로 변경 가능하다.
@Required로 하고 싶다면 고민하지 말고 바로 진행하자.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface CustomRequired {
}
애노테이션을 직접 생성해봤으니, 바로 WeatherRequest 클래스에 다음과 같이 적용하자.
class WeatherRequest {
@CustomRequired
public String region;
// Default: Today
public String date;
}
WeatherRequest는 요청 데이터를 담는 DTO 객체이다.
요청 항목 중 지역(region)은 필수값(Mandantory, Required)이고, 날짜(date)는 선택값(Conditional)이다.
날짜 항목이 없을 경우, 기본값인 오늘일자로 값이 들어가게 된다.
@Requied 필드 검증하기
애노테이션을 만들고, 객체에 적용까지 완료했다.
이제 JSON to Object 코드를 통해 요청 데이터를 객체에 담고, 이를 검증하면 된다.
그 과정의 코드는 다음과 같다.
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import com.google.gson.*;
class AnnotatedDeserializer<T> implements JsonDeserializer<T> {
public T deserialize(JsonElement je,
Type type,
JsonDeserializationContext jdc) throws JsonParseException {
T pojo = new Gson().fromJson(je, type);
Field[] fields = pojo.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.getAnnotation(CustomRequired.class) == null) {
continue;
}
try {
f.setAccessible(true);
if (f.get(pojo) == null) {
throw new JsonParseException(
"Missing required field in JSON: " + f.getName());
}
} catch (IllegalArgumentException ex) {
Logger.getLogger(AnnotatedDeserializer.class.getName())
.log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(AnnotatedDeserializer.class.getName())
.log(Level.SEVERE, null, ex);
}
}
return pojo;
}
}
자바의 리플렉션(Reflection) API를 유용하게 활용하는 예제이다.
객체의 필드(변수)는 타입과 이름 가지고 있다는 점을 이용하여 모든 필드를 순회하는 방식이다.
CustomRequired.class 기반의 @CustomRequired 애노테이션이 있는지 확인해보고,
없으면, continue;
있다면, WeatherRequest 에 해당 필드의 값이 NULL 인지 아닌지 검증한다.
각 상황에 따른 반환값은 코드를 통해 추측해보자.
@Requied 검증결과 살펴보기
// If, json is { "region": "seoul" }
WeatherRequest weatherRequest = gson.fromJson(json, WeatherRequest.class);
System.out.println(weatherRequest.region); // seoul
System.out.println(weatherRequest.date); // ex) 2022-04-23(YYYY-MM-DD)
// If, json2 is { "location": "seoul" }
WeatherRequest weatherRequest2 = gson.fromJson(json2, WeatherRequest.class);
/**
* Exception in thread "main" com.google.gson.JsonParseException:
* Missing required field in JSON: region
* ...
*/
요청 데이터인 json 값을 요리조리 변경해보면 잘 동작하는지 파악할 수 있다.
필수 항목이 없다면 위에서 지정한 바와 같이 Exception Message를 뿜어내는 것을 볼 수 있다.
결론
객체 직렬화나 검증 등의 애노테이션이 요런 느낌으로 만들어졌겠구나 싶다.
또한, '리플렉션(reflection)'라는 개념을 책과 강의를 통해서만 접해봤지 실제로 사용하긴 처음이다.
물론 엄청 간단하게 사용한 케이스이지만, 이 경험을 토대로 리플렉션에 대해 공부해보자 라는 마음이 생겼기 때문에 만족한다.
스택오버플로우에서 발견한 보물같은 답변이며, 내 상황에 맞게 적용해봤다.
이 글 또한 누군가에게 도움이 되었기를 바란다 : )
출처
- Gson optional and required fields, Brian Roach, 2014-02-07
- POJO - (Plain Old Java Object)란 뭘까?, siyoon210, 2019-02-15
Last Updated. 2022. 04. 23.
'Java' 카테고리의 다른 글
[Java] Null Safe한 자바 컬렉션 정렬 (0) | 2022.05.24 |
---|---|
[Java] Enum 소화하기 (0) | 2022.05.09 |
[Java] YAML 활용하기 (0) | 2022.05.04 |
[Java] Array, List, Set, Map 선언과 초기화 (1) | 2021.11.05 |
[Java] Java Optional (0) | 2021.06.14 |
블로그의 정보
배부른코딩로그
배부른코딩로그