티스토리 뷰
1. Overview
Assert는 단순히 if문을 줄이는 역할만 하는 것은 아닙니다. 프로젝트 규칙을 적용하고 공통을 재사용한다는 것에 큰 의미가 있습니다.
복잡한 기술이 필요한 것도 아니고 누구나 쉽게 사용할 수 있는 것이기에 초기 공통 개발 시 반드시 고려해야할 항목 중 하나입니다.
2. Spring Assert를 사용하는 목적
Spring Assert는 인수를 검증하고 조건에 맞지 않는 경우 IllegalArgumentException 또는 IllegalStateException를 발생시킵니다.
이 부분은 조건문을 단순화하고 반복적인 코드를 줄이는 역할을 합니다.
다음 코드를 보겠습니다.
1 2 3 | if(user == null) { throw new IllegalArgumentException("사용자 정보가 존재하지 않습니다."); } | cs |
위의 코드는 아래처럼 바꿀 수 있습니다.
1 | Assert.notEmpty(user, "사용자 정보가 존재하지 않습니다."); | cs |
Spring Assert는 소스를 보면 그다지 복잡하거나 사용에 어려움은 없습니다. 개발자라면 누구나 쉽게 이해할 수준입니다.
Spring Assert와 관련해서는 굳이 설명을 하지는 않겠습니다. 직접 소스를 보거나 테스트 코드를 짜보면 될거 같습니다.
3. Assert의 확장
개발자들은 값을 검증하는 방법을 여러 형태로 만들어낼 수 있습니다. 또 exception도 다양하게 사용할 수 있습니다.
그런데 프로젝트를 하다 보면 값을 검증하는 방법을 통합하거나 예외의 사용을 제한할 필요가 있습니다. 에러 페이지나 API에서 사용하기 위해 Exception을 정의해야 합니다. 더불어 공통 로직을 만들어 관리해야 합니다.
프로젝트 검증 예제
- 숫자 검증 : 단순히 숫자여야만 한다.
- 실수 검증 : 숫자, '-', '+', 소수점 허용
- 전화번호 검증
- 사업자 번호 검증
- 날짜 검증 : 프로젝트에서 정의한 포맷의 날짜 확인
- 비밀번호 검증 : 비밀번호는 영문, 숫자, 특수문자 혼용으로 8자 이상이어야 한다.
- 사용자 아이디 검증 : 영문, 숫자 혼용 4자 이상 10자 이내 등등
이런 여러 규칙을 일관성 있게 적용하고 재사용하기 위해 Assert를 확장해서 사용할 필요가 있습니다.
4. 사용자 정의 Assert 예제
아래는 Spring Assert를 상속 받아서 사용자가 정의한 예외를 사용하게 오버로딩한 부분과 숫자 체크, 패턴 체크 등을 추가해서 만들어봤습니다. 프로젝트 초기에 규칙을 정의하고 필요한 기능을 추가한 후 개발자와 공유하면 개발도 훨씬 쉽고 코드 품질도 향상하게 됩니다.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | public class EbloAssert extends Assert { private static final String NUMBER_ALPHABET_PATTERN = "^[a-zA-Z\\d]+$"; private static final String NUMERIC_PATTERN = "^[\\+\\-]{0,1}[\\d]+$"; private static final String FLOAT_PATTERN = "^[\\+\\-]{0,1}[\\d]+[\\.][0-9]+$"; /** * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다. * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.state(id == null, "The id property must not already be initialized", EbloInvalidRequestException.class);</pre> * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @param exceptionClass * @throws IllegalStateException if {@code expression} is {@code false} */ public static void state(boolean expression, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (!expression) { throwException(message, exceptionClass); } } /** * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다. * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.isTrue(user != null, "사용자 정보가 존재하지 않습니다.", EbloNotFoundException.class);</pre> * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @param exceptionClass * @throws IllegalStateException if {@code expression} is {@code false} */ public static void isTrue(boolean expression, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (!expression) { throwException(message, exceptionClass); } } /** * Assert that an object is {@code null}. 객체가 null이 아닌 경우 사용자가 정의한 예외를 던진다. * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.isNull(user, "기존 사용자 정보가 존재합니다.", EbloInvalidRequestException.class);</pre> * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @param exceptionClass * @throws IllegalStateException if {@code expression} is {@code false} */ public static void isNull(@Nullable Object object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (object != null) { throwException(message, exceptionClass); } } /** * null인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.notNull(user, "사용자 정보가 존재하지 않습니다.", EbloNotFoundException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void notNull(@Nullable Object object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (object == null) { throwException(message, exceptionClass); } } /** * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.hasLength(value, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void hasLength(@Nullable String text, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (!StringUtils.hasLength(text)) { throwException(message, exceptionClass); } } /** * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.hasText(value, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void hasText(@Nullable String text, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (!StringUtils.hasText(text)) { throwException(message, exceptionClass); } } /** * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.notEmpty(array, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void notEmpty(@Nullable Object[] array, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (ObjectUtils.isEmpty(array)) { throwException(message, exceptionClass); } } /** * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.notEmpty(collection, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void notEmpty(@Nullable Collection<?> collection, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (CollectionUtils.isEmpty(collection)) { throwException(message, exceptionClass); } } /** * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. * <pre class="code">Assert.notEmpty(map, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre> * @param object * @param message * @param exceptionClass */ public static void notEmpty(@Nullable Map<?, ?> map, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if (CollectionUtils.isEmpty(map)) { throwException(message, exceptionClass); } } private static void throwException(String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { try { throw exceptionClass.getDeclaredConstructor( String.class ).newInstance( message ); } catch (Exception e) { e.printStackTrace(); throw new EbloSystemException("예외 처리 중 오류가 발생했습니다. "+e.getMessage()); } } /** * 값이 영문 알파벳, 숫자가 아닌 경우 예외 발생 * @param object * @param message */ public static void isAlphaNumber(String object, String message) { isMatched(object, NUMBER_ALPHABET_PATTERN, message, EbloInvalidRequestException.class); } /** * 값이 영문 알파벳, 숫자가 아닌 경우 사용자 정의 예외 발생 * @param object * @param message * @param exceptionClass */ public static void isAlphaNumber(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { isMatched(object, NUMBER_ALPHABET_PATTERN, message, exceptionClass); } /** * 값이 숫자가 아닌 경우 예외 발생 * @param object * @param message */ public static void isNumeric(String object, String message) { isMatched(object, NUMERIC_PATTERN, message, EbloInvalidRequestException.class); } /** * 값이 숫자가 아닌 경우 사용자 정의 예외 발생 * @param object * @param message * @param exceptionClass */ public static void isNumeric(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { isMatched(object, NUMERIC_PATTERN, message, exceptionClass); } /** * 값이 float, double이 아닌 경우 예외 발생 * @param object * @param message */ public static void isFloat(String object, String message) { isMatched(object, FLOAT_PATTERN, message, EbloInvalidRequestException.class); } /** * 값이 float, double이 아닌 경우 사용자 정의 예외 발생 * @param object * @param message * @param exceptionClass */ public static void isFloat(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { isMatched(object, FLOAT_PATTERN, message, exceptionClass); } /** * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 사용자 정의 예외 발생 * @param object * @param pattern * @param message * @param exceptionClass */ public static void isMatched(String object, String pattern, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) { if(object == null || "".equalsIgnoreCase(object)) return; if(!object.matches(pattern)) { throwException(message, exceptionClass); } } /** * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 예외 발생 * @param object * @param pattern * @param message */ public static void isMatched(String object, String pattern, String message) { if(object == null || "".equalsIgnoreCase(object)) return; if(!object.matches(pattern)) { throwException(message, EbloInvalidRequestException.class); } } } | cs |
5. 테스트
1 2 3 4 5 6 7 8 | @Test public void test() { EbloAssert.isTrue(1>0, "test", EbloInvalidRequestException.class); EbloAssert.isNumeric("1234567", "숫자만"); EbloAssert.isFloat("-12345.67", "FLOAT DOUBLE 형태"); EbloAssert.isFloat("-12345.0", "FLOAT DOUBLE 형태"); EbloAssert.isMatched("-12345.0", "^[\\+\\-]{0,1}[\\d]+[\\.][0-9]+$", "FLOAT DOUBLE 형태"); } | cs |
6. 마무리
프로젝트를 하다 보면 다양한 검증 로직을 봅니다. 어디에서는 null 체크를 빼먹기도 하고 어디서는 null 체크만 하기도하고 또
비슷한 코드가 여기 저기 중복으로 코딩되기도 합니다. 또 프로젝트 규칙이 변경될 때 수정해야 할 곳이 여기 저기 산발적으로 흩어져 있을 수도 있습니다. 이런 것들을 Assert를 통해 쉽게 관리할 수 있습니다.
참고로 도메인 객체에 애노테이션을 이용해서 request의 값이 바인딩 될 때 validation 처리를 많이 이용 합니다.
1 2 3 4 5 6 7 | @Size(max=5, message="상품코드는 5자여야 합니다.") @Pattern(regexp="^[0-9]{5}$", message="상품코드는 5자리 숫자여야 합니다.") protected String goodCd; @NotNull(message="상품명은 필수 입력값입니다.") @Size(max=100, message="상품명은 50자 이내여야 합니다.") protected String goodNm; | cs |
Assert는 이외의 경우, 프로세스에서 검증할 때 많이 사용할 수 있습니다.
Github 링크 : https://github.com/kkaok/examples
'Spring Frameworks' 카테고리의 다른 글
Spring boot - API Gateway, Zuul 예제 (0) | 2019.03.17 |
---|---|
Spring boot - RestTemplate 설정(Timeout, socketTimeOut) (2) | 2019.03.07 |
Spring boot - Object Mapper (0) | 2019.03.04 |
Spring boot - Ehcache, cache 예제 (0) | 2019.02.26 |
Spring boot - 빌드 profile 설정 (0) | 2019.02.20 |
- Total
- Today
- Yesterday
- Javascript
- UI
- restful서비스
- ag grid
- 엑셀
- example
- 메시지
- 스프링
- mapToList
- oracle
- SHEETJS
- 스프링부트
- 샘플
- AG-GRID
- springboot
- listToMap
- sample
- cache
- spring
- 그리드
- REST
- RESTful
- 예제
- 타임리프
- lombok
- Spring Boot
- java
- mybatis
- 설정
- thymeleaf
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |