로직과의 사투/Java

POJO(Plain Old Java Object) 필수 값 Check Annotation 만들기

  1. 개요
      // 현재 운영 서비스에 적용되어 있는 극단적인 예시
      // if문 지옥
      if (!lSomeStringField.equalsIgnoreCase(SomeCase) &&     !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      && !lSomeStringField.equalsIgnoreCase(SomeCase) && !lSomeStringField.equalsIgnoreCase(SomeCase)
                      ) {
                  throw new IfException(ERR_CD_PARAM_INVALID, "SomeStringField");
      }
    
      if ("".equalsIgnoreCase(someStringField2.trim())) {
                  throw new IfException(ERR_CD_PARAM_INVALID, "someStringField2");
      }
    
      if ("".equalsIgnoreCase(someStringField3.trim())) {
                  throw new IfException(ERR_CD_PARAM_INVALID, "someStringField3");
      }
    
      if ("".equalsIgnoreCase(someStringField4.trim())) {
                  throw new IfException(ERR_CD_PARAM_INVALID, "someStringField4");
      }

    위 예시처럼 매우 비효율적인 작업이라 생각해 Annotation을 만들어 한꺼번에 체크할 수 있게 작업 한다면 어떨까 생각하였고, 효율적인 방안이라 생각해 공유한다.
  2. TCP 전문 통신은 일반적으로 미리 정의된 길이에 따라 바이트를 잘라 통신에 사용한다. 서버는 클라이언트로부터 밭은 메시지를 각 필드에 길이 별로 잘라 매치시켜 JAVA Field 로 변환시킨다. 이때 필수값 체크가 필요하다면 파싱 이후에 별도의 메쏘드에서 각 상황에 맞는 필수값 체크 로직을 모두 구현해야 한다. 이때의 문제점은 각 필드 별로 필수값 체크를 따로 모두 진행해야 한다는 점이다. 즉, if 문이 각 필드별로 존재하게 되는 것.

  1. 개발우선 필드에 명시하기 위한 어노테이션을 생성한다.requiredIn 에 각 전문코드 (이 예제에선 documentCd 로 표현됨.)를 전달받아 전문 타입 별(상황 별)로 필수 값을 조절할 수 있게 세팅한다.POJO
      @Required
      protected String code;
      @Required
      protected String version;
      @Required
      protected String documentCd;
      @Required(requiredIn = {"201", "301"})
      protected String serialNo;
    Validation Utility 작성하기아래 소스 validateRequiredFields의 로직은 아래의 주석에 달아 둔 것과 같은 플로우를 가진다.
import java.lang.reflect.Field;
     import java.util.Arrays;
     
     public class ValidationUtils {
     
         /**
          * annotation (required) 를 이용하는 필드 값의 필수 값 체크 
          * required annotation 의 requiredIn 필드에 값이 세팅 되어 있는 경우 documentCd 가 requiredIn에 포함된다면 필수값 체크
          * required annotation 만 존재한다면 전체 documentCd 에 대해 필수값 체크
          * @param obj (Packet)
          * @throws IfException
          */
         public static void validateRequiredFields(Object obj) throws IfException {
             Class<?> clazz = obj.getClass();
             while (clazz != null && clazz != Object.class) {    
                 for (Field field : clazz.getDeclaredFields()) { // 1. 해당 오브젝트의 모든 필드 들에 대해
                     if (field.isAnnotationPresent(Required.class)) { // 2. Required 어노테이션이 존재한다면
                         Required requiredAnnotation = field.getAnnotation(Required.class);
                         String[] requiredIn = requiredAnnotation.requiredIn();
                         try {
                             Field codeField = clazz.getDeclaredField("documentCd"); // 3. documentCd 값과 requiredIn의 값을 비교하기 위해 documentCd 값 가져오기.
                             codeField.setAccessible(true);
                             String code = (String) codeField.get(obj);
                             // 4-1. required annotation 존재하지만 필수 코드가 없는 경우 -> 전체 상황에 대해 필수.
                             // 4-2. required annotation 이 존재하며 requiredIn에 값이 있는 경우 documentCd 값이 requiredIn의 값들 중 하나라면
                             // 5. 필수값 검사
                             if (requiredIn.length == 0 || Arrays.asList(requiredIn).contains(code)) {
                                 checkIfNull(field, obj);
                             }
                         } catch (NoSuchFieldException | IllegalAccessException e) {
                             throw new IfException(NiceErrorCode.ERR_CD_UNKNOWN.getCode());
                         }
                     }
                 }
                 clazz = clazz.getSuperclass();
             }
         }
     
         private static void checkIfNull(Field field, Object obj) throws IfException, IllegalAccessException {
             field.setAccessible(true);
             Object value = field.get(obj);
             if (value == null || (value instanceof String && ((String) value).isEmpty())) {
                 // String 이란 가정하에 String 체크 진행 이하 String이 아닌 Integer 등이라면 아래에 해당 값 검사 추가 가능
                 throw new IfException(ERR_CD_PARAM_INVALID.getCode(), field.getName());
             }
         }
     
     }

이제 특정 시점에서 사용자는 해당 Method를 언제든 호출 해 Required 체크를 진행할 수 있게 되었다. 각 상황 별로 validate 메소드를 만들어 if 문 지옥으로 작업하는 방향보다 사용자는 더욱 간단하게 필수값 체크를 진행할 수 있게 되었다.