티스토리 뷰

Spring Frameworks

Spring boot - Assert 사용 예제

까오기 까오기 2019. 3. 5. 14:19

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

댓글
댓글쓰기 폼