필요 이유
예를 들어, 메서드 이름으로 URL경로를 매칭한다고 할 경우 add-user 같은 경로는 메서드 명 만으로는 매핑할 수가 없습니다.
이럴 때, 부가적인 정보를 사용할 수 있게 해주는 것이 어노테이션 입니다.
정의
@interface 키워드로 정의- 인터페이스와 비슷하게 정의
1
2
3
4
5
6
7
8
9
10
11
12
|
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoElement {
String value();
int count() default 0;
String[] tags() default {};
//MyLogger data(); // 다른 타입은 적용X
Class<? extends MyLogger> annoData() default MyLogger.class;
}
|
정의 규칙
데이터 타입
- 기본 타입
- String
- Class(메타 데이터) 또는 인터페이스
- Enum
- 다른 어노테이션 타입
- 위으 타입들의 배열
- 일반적인 클래스는 사용할 수 없음
default 값
요소 이름
- 메서드 형태로 정의
- 괄호()를 포함하되 매개변수는 없어야 함
반환 값
예외
특별한 요소 이름
- value라는 이름의 요소를 하나만 가질 경우, 애너테이션 사용 시 요소 이름을 생략할 수 있음
메타 어노테이션
@Retention
옵션
- RetentionPolicy.SOURCE
- RetentionPolicy.CLASS
- 컴파일 후 class 파일까지는 남아있지만 자바 실행 시점에 제거
- 기본 값
- RetentionPolicy.RUNTIME
- 자바 실행 중에도 남아 있음
- 대부분 이 설정을 사용
@Target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE,
MODULE,
RECORD_COMPONENT;
}
|
@Documented
- 자바 API 문서를 만들 때 해당 어노테이션이 함께 포함되는지 지정
@Inherited
- 자식 클래스가 어노테이션을 상속 받을 수 있음
- 인터페이스는 적용 안됨
자바 기본 어노테이션
@Override
- 오버라이드 할 메서드와 시그니처가 일치한지 체크
@Deprecated
- 더 이상 사용하지 않는 곳에 사용
- 이 어노테이션이 적용된 기능은 사용을 권장하지 않음
속성
- since: 더 이상 사용하지 않게 된 버전 정보
- forRemoval: 미래에 버전에 코드가 제거 될 예정
@SuppressWarnings
속성
- all: 모든 경고를 억제
- deprecation: 사용이 권장되지 않는 코드를 사용할 때 발생하는 경고를 억제
- unchecked: 제네릭 타입과 관련된 unchecked 경고를 억제
- serial:Serializable 인터페이스를 구현할 때 serialVersionUID 필드를 선언하지 않은 경우 발생하는 경고를 억제
- rawtypes: 제네릭 타입이 명시되지 않은 타입을 사용할 때 발생하는 경고를 억제
- unused: 사용되지 않는 변수, 메서드, 필드 등을 선언했을 때 발생하는 경고를 억제
사용 예시
Validator 만들기
1
2
3
4
5
6
|
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmpty {
String message() default "값이 비어있습니다.";
}
|
1
2
3
4
5
6
7
8
9
10
|
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
int min();
int max();
String message() default "범위를 넘었습니다.";
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class Team {
@NotEmpty(message = "이름이 비었습니다.")
private String name;
@Range(min = 1, max = 999, message = "회원 수는 1과 999 사이여야 합니다.")
private int memberCount;
public Team(String name, int memberCount) {
this.name = name;
this.memberCount = memberCount;
}
public String getName() {
return name;
}
public int getMemberCount() {
return memberCount;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class User {
@NotEmpty(message = "이름이 비었습니다.")
private String name;
@Range(min = 1, max = 100, message = "나이는 1과 100 사이어야 합니다.")
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
|
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
| public class Validator {
public static void validate(Object obj) throws Exception {
Field[] fields = obj.getClass().getDeclaredFields(); // 객체의 모든 필드
for (Field field : fields) {
field.setAccessible(true); // private 접근 허용
if (field.isAnnotationPresent(NotEmpty.class)) { // @NotEmpty가 필드에 있는지 확인
String value = (String) field.get(obj);
NotEmpty annotation = field.getAnnotation(NotEmpty.class);
if (value == null || value.isEmpty()) { // 검증
throw new RuntimeException(annotation.message());
}
}
if (field.isAnnotationPresent(Range.class)) { // @Range가 필드에 있는지 확인
long value = field.getLong(obj);
Range annotation = field.getAnnotation(Range.class);
if (value < annotation.min() || value > annotation.max()) { // 검증
throw new RuntimeException(annotation.message());
}
}
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class ValidatorV2Main {
public static void main(String[] args) {
User user = new User("user1", 0);
Team team = new Team("", 0);
try {
log("== user 검증 ==");
Validator.validate(user);
} catch (Exception e) {
log(e);
}
try {
log("== team 검증 ==");
Validator.validate(team);
} catch (Exception e) {
log(e);
}
}
}
|
참고