프로그래밍 언어/Java

[Java] 어노테이션 정리

제이온 (Jayon) 2022. 4. 11.

어노테이션이란?

어노테이션은 메타 데이터로서, 프로그램 그 자체의 일부분은 아니지만 프로그램에 대한 데이터를 제공한다. 그래서 어노테이션 자체는 어노테이션을 붙은 코드 동작에 영향을 미치지는 않는다. 참고로 어노테이션은 다음과 같은 상황에 사용된다.

 

  • 컴파일러에게 필요한 정보를 제공
    • 컴파일러가 에러를 감지하거나, 경고를 띄우지 않게 하기 위함.
  • 컴파일/배포 시에 필요한 처리 기능
    • SW 개발 툴에서 어노테이션의 정보를 통해 특정 코드를 추가할 수 있음.
  • 런타임 처리 제공
    • 런타임에도 어노테이션의 정보를 통해 필요한 처리를 할 수 있음. (Java Reflection)

 

요약하자면, 어노테이션은 다음과 같이 정의가 가능하다.

어노테이션은 작성한 코드에 대해 추가적인 정보를 제공하면서 컴파일 타임 혹은 런타임 시점에서 해당 코드에 필요한 추가적인 처리를 해 주는 역할을 한다.

 

메타 어노테이션

메타 어노테이션은 어노테이션을 위한 어노테이션 으로, 어노테이션을 정의할 때 사용된다.

 

@Target

어노테이션이 적용 가능한 대상을 지정한다.

  • ElementType.TYPE
    • 클래스, 인터페이스, 열거 타입
  • ElementType.ANNOTATION_TYPE
    • 어노테이션
  • ElementType.FIELD
    • 필드
  • ElementType.CONSTRUCTOR
    • 생성자
  • ElementType.METHOD
    • 메소드

 

@Retention

어노테이션이 유지되는 기간을 지정한다.

 

  • RetentionPolicy.SOURCE: 소스 코드(.java)까지 남아있는다.
    • 컴파일 전까지만 유효하다.
  • RetentionPolicy.CLASS: 클래스 파일(.class)까지 남아있는다. (=바이트 코드)
    • 클래스 로더가 클래스를 로딩하기 전까지만 유효하다.
  • RetentionPolicy.RUNTIME: 런타임까지 남아 있는다. (사실상 사라지지 않는다.)
    • 클래스를 로딩한 이후에도 유효하다.
    • Java 리플렉션 API를 사용하여 어노테이션 정보를 알 수 있다.

 

Untitled

 

@Documented

해당 어노테이션을 javadoc에 포함시킨다.

 

@Inherited

어노테이션의 상속을 가능하게 한다.

 

@Repeatable

어노테이션을 반복해서 적용할 수 있게 한다.

 

표준 어노테이션

표준 어노테이션은 자바에서 기본적으로 제공하는 어노테이션을 의미한다.

 

@Override

컴파일러에게 오버라딩하는 메소드라는 것을 알린다.

 

@Deprecated

앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.

 

@SuppressWarnings

컴파일러의 특정 경고 메시지가 나타나지 않게 해 준다.

 

@SafeVarargs

제네릭 타입의 가변 인자에 사용한다.

 

@FunctionalInterface

함수형 인터페이스라는 것을 알린다.

 

@Native

native 메소드에서 참조되는 상수 앞에 붙인다.

 

그 외, 모든 메타 어노테이션은 표준 어노테이션에 속한다.

 

어노테이션이 사용되는 상황

@Override

클래스를 상속하거나, 인터페이스를 구현하는 과정에서 @Override 어노테이션을 사용하는데, 해당 어노테이션은 다음과 같이 정의되어 있다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

 

해당 어노테이션은 Method가 target이고, 소스 코드 범위까지 유효하다.

 

@Service

스프링 프레임워크에서 자주 쓰이는 @Service 어노테이션은 핵심 비즈니스 로직을 담은 서비스 클래스를 빈으로 등록하기 위해 사용된다. @Service 어노테이션은 다음과 같이 정의되어 있다.

 

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

    @AliasFor(annotation = Component.class)
    String value() default "";

}

 

해당 어노테이션은 Type이 target이고, 런타임 시점까지 유효하다. 참고로, @Component는 스프링에서 관리되는 빈을 뜻한다.

 

@GetMapping

스프링 프레임워크에서 자주 쓰이는 @GetMapping 어노테이션은 @GetMapping 안에 적어둔 URL로 GET 요청이 들어오면, 해당 어노테이션이 붙은 메소드가 실행된다. @GetMapping 어노테이션은 다음과 같이 정의되어 있다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {

    @AliasFor(annotation = RequestMapping.class)
    String name() default "";

    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] path() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] params() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] headers() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] consumes() default {};

    @AliasFor(annotation = RequestMapping.class)
    String[] produces() default {};

}

 

해당 어노테이션은 Method가 Target이고, 런타임 시점까지 어노테이션이 유효하다. 앞선 어노테이션들과는 달리 어노테이션 내부에 다양한 필드들이 정의되어 있다. 이곳에서 필드를 적절히 사용하여 다음과 같이 정의해 줄 수 있다. 참고로 @RequestMapping 어노테이션의 method 속성은 특정 메소드의 요청만 오도록 필터링을 한다.

 

@GetMapping(value = "/stations", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<StationResponse>> showStations() {
    return ResponseEntity.ok().body(stationService.findAllStationResponses());
}

 

value가 위에서 언급한 URL에 해당한다.

 

커스텀 어노테이션을 정의하는 방법

기존에 있는 어노테이션을 혼합하여 재활용하는 방법도 있겠지만, 이번 포스팅에서는 순수하게 어노테이션을 정의해보겠다. 간단한 예제를 위해 @Target, @Retention 정도만 사용하여 위의 @GetMapping을 흉내내려고 한다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JayonAnnotation {

    String value();
    String[] produces();
}

 

어노테이션 타입 선언은 @interface를 사용해야 하고, 그 옆에 어노테이션의 이름을 적으면 된다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JayonAnnotation {

    String value();
    String[] produces();
}

public class TestController {

    @JayonAnnotation(value = "/jayon", produces = "json")
    public void speak() {
        System.out.println("My name is Jayon");
    }
}

public class Main {
    public static void main(String[] args) throws NoSuchMethodException {
        final TestController testController = new TestController();

        final Method testMethod = testController.getClass().getMethod("speak");
        final JayonAnnotation annotation = testMethod.getAnnotation(JayonAnnotation.class);
        final String value = annotation.value();
        final String[] produces = annotation.produces();

        System.out.println("value = " + value);
        System.out.println("produces = " + produces[0]);
    }
}

// 실행 결과
value = /jayon
produces = json

 

@JayonAnnotation은 런타임 시점까지 유효하므로 리플렉션을 사용하여 접근할 수 있다. 그래서 해당 어노테이션의 필드 값(value, produces)을 가져올 수 있다.

어노테이션을 통해 정의해 둔 값을 추출하여 런타임에 필요한 세팅을 해줄 수 있고, 이러한 원리로 스프링에서는 @GetMapping을 통해 매핑될 URL을 지정해 줄 수가 있다.

 

출처

 

예상 면접 질문 및 답변

어노테이션이란?

어노테이션은 작성한 코드에 대해 추가적인 정보를 제공하면서 컴파일 타임 혹은 런타임 시점에서 해당 코드에 필요한 추가적인 처리를 해 주는 역할을 한다.

 

메타 어노테이션의 종류는?

@Target

어노테이션이 적용 가능한 대상을 지정한다.

  • ElementType.TYPE
    • 클래스, 인터페이스, 열거 타입
  • ElementType.ANNOTATION_TYPE
    • 어노테이션
  • ElementType.FIELD
    • 필드
  • ElementType.CONSTRUCTOR
    • 생성자
  • ElementType.METHOD
    • 메소드

 

@Retention

어노테이션이 유지되는 기간을 지정한다.

  • RetentionPolicy.SOURCE: 소스 코드(.java)까지 남아있는다.
    • 컴파일 전까지만 유효하다.
  • RetentionPolicy.CLASS: 클래스 파일(.class)까지 남아있는다. (=바이트 코드)
    • 클래스 로더가 클래스를 로딩하기 전까지만 유효하다.
  • RetentionPolicy.RUNTIME: 런타임까지 남아 있는다. (사실상 사라지지 않는다.)
    • 클래스를 로딩한 이후에도 유효하다.
    • Java 리플렉션 API를 사용하여 어노테이션 정보를 알 수 있다.

 

@Documented

해당 어노테이션을 javadoc에 포함시킨다.

 

@Inherited

어노테이션의 상속을 가능하게 한다.

 

@Repeatable

어노테이션을 반복해서 적용할 수 있게 한다.

 

표준 어노테이션의 종류는?

@Override

컴파일러에게 오버라딩하는 메소드라는 것을 알린다.

 

@Deprecated

앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.

 

@SuppressWarnings

컴파일러의 특정 경고 메시지가 나타나지 않게 해 준다.

 

@SafeVarargs

제네릭 타입의 가변 인자에 사용한다.

 

@FunctionalInterface

함수형 인터페이스라는 것을 알린다.

 

@Native

native 메소드에서 참조되는 상수 앞에 붙인다.

 

그 외, 모든 메타 어노테이션은 표준 어노테이션에 속한다.

댓글

추천 글