Daily Notebook

[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)'라는 개념을 책과 강의를 통해서만 접해봤지 실제로 사용하긴 처음이다.

물론 엄청 간단하게 사용한 케이스이지만, 이 경험을 토대로 리플렉션에 대해 공부해보자 라는 마음이 생겼기 때문에 만족한다.

 

스택오버플로우에서 발견한 보물같은 답변이며, 내 상황에 맞게 적용해봤다.

이 글 또한 누군가에게 도움이 되었기를 바란다 : )

 

 

출처

 

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

블로그의 정보

배부른코딩로그

배부른코딩로그

활동하기