포스트

어노테이션

어노테이션

필요 이유

예를 들어, 메서드 이름으로 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 값

  • 요소에 Default 값을 지정할 수 있음

요소 이름

  • 메서드 형태로 정의
  • 괄호()를 포함하되 매개변수는 없어야 함

반환 값

  • void를 반환타입으로 사영할 수 없음

예외

  • 예외를 선언할 수 없음

특별한 요소 이름

  • 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);
    }
  }
}

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.