Untitled 개발 이후, 회사의 다른 서비스를 만들면서 하나의 계정으로 회사의 모든 서비스들을 이용할 수 있으면 유저들이 편할 것 같다는 생각을 했다. 그래서 대표님게 SSO(Single Sign On)을 건의했고, 그렇게 통합 인증 서버를 만들게 되었다.
통합 인증 서버는 앞으로 회사의 모든 서비스들에서 트래픽이 몰릴 것이므로 좀 더 단단하게 만들고 싶었고, 이전에는 사용하지 않았던 Validation을 적용해보기로 했다.
Validation
먼저 Spring Validation은 @RequestBody, @RequestParam, @PathVariable에 대해 유효성 검증을 수행하고, 유효하지 않는 데이터인 경우 Exception을 발생시키는 라이브러리이다
내가 제일 먼저 적용한 API는 '이메일 중복 확인' 기능이다. 이메일 중복 확인의 경우 Email 형식에 맞는지 검사하는 @Email 어노테이션, 값이 비어있거나 null인지를 검사하는 @NotEmpty, 설정한 길이 미만/초과를 검사하는 @Size를 사용한다
사진을 보면 알 수 있겠지만, 단순히 하나의 RequestParam을 받는데 @Email, @NotEmpty, @Size를 모두 사용하다보니 코드가 길어지고 가독성이 현저히 떨어진다.
따라서 Validation을 수행하는 세 개의 어노테이션을 하나의 어노테이션으로 쓸 수 있다면 이런 부분을 해결할 수 있을 것이다
해결한 코드를 먼저 보고 지나가자
Custom Annotation 만들기
그렇다면 이제 어떻게 Custom Annotation을 만드는지 확인해보고, 내가 만든 @EmailValidation을 확인한 후 글을 마무리 하겠다
Custom Annotation 구성
: Annotation의 구성은 다음과 같다
@Target({ElementType.[적용대상]})
@Retention(RetentionPolicy.[정보가 유지되는 시점])
public @interface '어노테이션 이름'{
public '타입' '멤버이름()' default '기본값';
...
}
Custom Annotation 규칙
- 지정 가능한 메타 어노테이션은 다음과 같다
- @Target: 해당 어노테이션이 적용되는 대상
- PACKAGE: 패키지
- TYPE: 타입
- ANNOTATION_TYPE: 어노테이션 타입
- CONSTRUCTOR: 생성자
- FIELD: 멤버 변수
- LOCAL_VARIABLE: 지역 변수
- METHOD: 메서드
- PARAMETER: 전달 인자
- TYPE_PARAMETER: 전달 인자 타입
- TYPE_USE: 타입
- @Retention: 해당 어노테이션의 생명주기를 설정
- SOURCE: 컴파일 전까지 유효
- CLASS: 컴파일러가 클래스를 참조할 때까지 유효
- RUNTIME: 런타임 시기에도 JVM에 의해 참조가 가능(리플렉션)
- @Documented: 해당 어노테이션을 JavaDoc에 포함시킨다
- @Inherited: 어노테이션의 상속을 가능하도록 설정
- @Repeatable: 해당 어노테이션을 연속적으로 선언할 수 있음, Java 8부터 지원
- @Target: 해당 어노테이션이 적용되는 대상
- 타입은 '@interface' 여야 한다
- 필드의 접근자는 public이거나 default여야 한다
- 필드에서 가능한 타입은 다음과 같다
- (원시 타입) byte, short, char, int, float, double, boolean
- (참조 타입) String
- Enum
- Class
- Annotation
Sample
내가 Validation을 적용하기 위한 'EmailValidation' 어노테이션의 구조는 다음과 같다
- @Email, @NotEmpty, @Size
- Validation을 진행할 어노테이션
- @Documented, @Target, @Retention
- 문서화 여부, 적용 대상, 생명주기를 설정하는 메타 어노테이션
- @Constraint
- 커스텀 검증 어노테이션에서 검증 로직을 정의하는 역할. '검증 어노테이션'임을 선언하는 것이므로, 필수로 들어가야 한다
- 'ConstraintValidator' 인터페이스를 구현한 클래스를 지정해야 한다
- 만약 빈값 '{}' 을 지정했다면, @Constraint가 적용된 어노테이션들의 내부 검증 로직을 사용한다
ex) @Email, @NotEmpty, @Size ...
- message, groups, payload
- 세 가지 필드는 커스텀 검증 어노테이션에서 표준 규약으로 사용되는 필드이다
- message: 기본 검증 메시지를 정의하는 필드로, 없으면 검증 실패시 오류 메시지를 설정하지 못한다. 필수
- groups: 검사를 수행할 그룹을 정의한다. 없다면 모든 검증이 동일한 그룹으로 처리되어 상황에 맞게 검증을 나눌 수 없다. 필수
- payload: 검증 실패 시 추가적인 메타데이터를 전달할 때 사용하는 필드. 없다면 메타데이터를 활용할 수 없다. 선택사항이다
Reference
- Spring Annotation의 원리와 Custom Annotation 만들어보기: https://donghyeon.dev/spring/2020/08/18/Spring-Annotation%EC%9D%98-%EC%9B%90%EB%A6%AC%EC%99%80-Custom-Annotation-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0/
- [java] 커스텀 어노테이션 만들기: https://velog.io/@potato_song/Java-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0