Unicorns

All the things

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Study] - 애노테이션(Annotation)
    Java 2021. 10. 15. 12:11

    애노테이션

    애노테이션이란?

    자바 소스 코드에 추가하여 사용할 수 있는 메터데이터의 일종으로 보통 @ 기호를 앞에 붙여서 사용한다.

    주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다

    JDK 1.5 버전 이상에서 사용 가능하다. 

    자바 애노테이션은 클래스 파일에 임베디드되어 컴파일러에 의해 생성된 후 자바 가상머신에 포함되어 작동한다.

     

    메타데이터란?

    애플리케이션이 처리해야 할 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보이다.

     

    애노테이션은 다음 세 가지 용도로 사용된다.

    • 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공
    • 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 제공
    • 실행 시(런타임 시) 특정 기능을 실행하도록 정보를 제공

     

    애노테이션 정의하기

    애노테이션 타입을 정의하는 방법은 인터페이스를 정의하는 것과 유사하다.

    다음과 같이 @interface를 사용해서 애노테이션을 정의한다.

    public @interface 애노테이션이름 {
        타입 요소이름(); //애노테이션의 요소를 선언한다.
    }

    애노테이션의 메서드는 추상 메서드이다.

     

    예) 

    public @interface MyAnnotation {
        String name();
    }

    애노테이션 사용

    public class Test {
        @MyAnnotation(name = "cony")
        private String test;
    }

    age() 추가

    public @interface MyAnnotation {
        String name();
        int age();
    }

    age() 를 사용하지 않으면 컴파일 에러가 발생한다.

    ('age' missing though required)

     

    age() 를 사용하자 , 이때 순서는 상관없다.  

    public class Test {
        @MyAnnotation(age = 28 ,name = "cony")
       // @MyAnnotation(name = "cony" ,age = 28 )  둘 다 가능
        private String id;
    }

    기본값 지정하기

    public @interface MyAnnotation {
        String name() default "cony"; // 기본값을 cony로 지정
    }
    public class Test {
        @MyAnnotation //  @MyAnnotation(name = "cony")와 동일
        private String id;
    }

    value()

    요소가 하나이고 이름이 value 일 때는 요소의 이름을 생략해도 된다.

    public @interface MyAnnotation {
        String value();
    }
    public class Test {
        @MyAnnotation("pass") //@MyAnnotation(value = "pass") 와 동일
        private String id;
    }

    배열

    요소의 타입이 배열인 경우, 괄호{} 를 사용해야 한다.

    public @interface MyAnnotation {
        String[] value();
    }
    public class Test {
        @MyAnnotation({"pass","none" ,"all"})
        private String id;
    }

     

     

     

    표준 애노테이션

    Java에서 제공하는 애노테이션

     

    @Override

    오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.

    class Student extends Person {
        @Override // 컴파일 에러 발생
        public void walk2() { // 메서드 이름 불일치
            
        }
    }
    
    class Person {
        public void walk() {
        }
    }

    오버라이딩을 제대로 하지 않았을 때 컴파일 에러가 발생한다.

     

     

    @Deprecated

    앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.

     

    예시) java.Util.Date 의 getDate() 메서드

    JDK version 1.1 부터 Date의 getDate() 대신 Calendar.get() 메서드로 대체 되었다.

    그래서 사용하지 않을 것을 권장한다. 

     

    똑똑한 인텔리제이는 date.getDate()를 사용하자 메서드에 줄을 그어 사용하지 말라는 압박을 준다.

     

     

    @Deprecated가 붙은 대상이 있는 코드를 컴파일하면 컴파일러가 warning 메시지를 출력한다.

    -Xlint:deprecation 키워드 사용

    deprecation에 대한 상세 정보를 출력한다. 

     

    @FunctionalInterface

    함수형 인터페이스인지 아닌지 컴파일러가 체크한다.

    함수형 인터페이스는 하나의 추상 메서드만 가져야 한다는 제약이 있음

     

    추상 메서드가 2개이기 때문에 컴파일 에러가 발생한다. 

     

    컴파일 에러가 발생하지 않음.

     

    @SuppressWarnings

    컴파일러의 경고 메시지가 나타나지 않게 억제한다.

    괄호 ( ) 안에 억제하고자 하는 경고의 종류를 문자열로 지정

     

    Deprecated 된 getDate() 메서드를 사용하였는데,

    @SuppressWarnings("deprecation") 을 사용하여 경고 메시지가 나오지 않게 하였다.

     

    아까와 달리 getDate() 메서드 줄이 없다. 

     

    또,

    컴파일 해도 아무런 경고메시지가 나타나지 않는다.

     

     

    메타 애노테이션

    메타 애노테이션은 애노테이션을 만들 때 사용한다.

    '애노테이션을 위한 애노테이션' 이다.

    java.lang.annotation 패키지에 포함되어 있다.

     

    @Target

    애노테이션을 정의할 때, 적용대상 지정에 사용

     

    애노테이션을 적용할 수 있는 대상은 java.lang.annotation.ElementType 열거 상수로 다음과 같이 정의되어 있다.

    대상 타입 의미
    ANNOTATION_TYPE 애노테이션
    CONSTRICTOR  생성자
    FIELD 필드(멤버변수, enum 상수)
    LOCAL_VARIABLE 지역변수
    METHOD 메서드
    PACKAGE 패키지
    PARAMETER 매개변수
    TYPE 타입(클래스, 인터페이스, enum)
    TYPE_PARAMETER 타입 매개변수(JDK 1.8)
    TYPE_USE 타입이 사용되는 모든 곳(JDK1.8)

    예시)

    @Target({ElementType.METHOD , ElementType.FIELD})
    public @interface MyAnnotation {
    
    }

    적용할 수 있는 대상 METHOD , FIELD

    public class Test {
        @MyAnnotation //FIELD
        private String id;
    
        @MyAnnotation // METHOD 
        public static void main(String[] args) {
    
        }
    }

    그 외의 대상에 적용하면 컴파일 에러가 발생한다.

    @MyAnnotation // 컴파일 에러 발생
    public class Test {
        @MyAnnotation
        private String id;
    
        @MyAnnotation
        public static void main(String[] args) {
    
        }
    }

    똑똑한 인텔리제이가 type은 적용되지 않았다고 알려준다.

    또 다른 예로 @Override 애노테이션이 있다.

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

    @Override 애노테이션을 살펴보면  @Target 에 METHOD 만 설정되어 있다. 

    그래서 @Override 애노테이션은 메서드 외에 다른 곳에 적용할 수 없다.

     

     

    @Retention

    애노테이션이 유지(retention)되는 기간을 지정하는 데 사용

    • 소스상에만 유지
    • 컴파일된 클래스까지 유지
    • 런타임  시에도 유지
    RententionPolicy 열거 상수 설명
    SOURCE 소스상에서만 애노테이션 정보를 유지한다. 소스 코드를 분석할 때만 의미가 있으며, 바이트 코드 파일에는 정보가 남지 않는다. (소스 파일에만 존재 , 클래스파일에는 존재하지 않음)
    CLASS 바이트 코드 파일까지 애노테이션 정보를 유지한다. 하지만 리플렉션을 이용해서 애노테이션 정보를 얻을 수는 없다. (클래스 파일에 존재 , 실행시 에 사용불가)
    RUNTIME 바이트 코드 파일까지 애노테이션 정보를 유지하면서 리플렉션을 이용해서 런타임 시에 애노테이션 정보를 얻을 수 있다. (클래스 파일에 존재, 실행 시에 사용가능

    ※ 리플랙션(Reflection)이란?

    런타임 시에 클래스의 메타 정보를 얻는 기능을 말한다. 

    클래스가 가지고 있는 필드가 무엇인지, 어떤 생성자를 갖고 있는지, 어떤 메소드를 가지고 있는지, 적용된 애노테이션이 무엇인지 알아내는 것이 리플랙션이다. 

    리플랙션을 이용해서 런타임 시에 애노테이션 정보를 얻으려면 애노테이션 유지 정책을 RUNTIME으로 설정해야 한다.

     

     

    기본 value 는 RetentionPolicy 타입의 세 가지 상수 중 하나를 지정하면 된다.

    @Retention(RetentionPolicy.CLASS)
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.RUNTIME)

    지정하지 않으면 default 값은 RetentionPolicy.CLASS 이다.

     

     

    예)

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

    @Overeride 애노테이션은 소스상에서만 애노테이션 정보를 유지한다.

    바이트 코드에는 해당 애노테이션의 정보가 없다.

     

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

    @FunctionalInterface 애노테이션은 RUNTIME까지 존재한다. 


     

    이번에는 Hello 라는 애노테이션을 정의해보고 Retention 을 CLASS로 적용해보자

    @Retention(RetentionPolicy.CLASS)
    public @interface Hello {
    
    }

    그리고 HelloController 클래스에 커스텀한 @Hello 와 @RestController 을 사용하였다.

    @Hello
    @RestController
    public class HelloController {
    
    }

     

    이제 리플렉션으로 애노테이션 정보를 알아보자

    public class HelloMain {
        public static void main(String[] args) {
            Annotation[] annotations = HelloController.class.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
        }
    }

    출력 결과

    @RestController 에 대한 정보가 출력되었다.

    그 이유는

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
    
    	/**
    	 * The value may indicate a suggestion for a logical component name,
    	 * to be turned into a Spring bean in case of an autodetected component.
    	 * @return the suggested component name, if any (or empty String otherwise)
    	 * @since 4.0.1
    	 */
    	@AliasFor(annotation = Controller.class)
    	String value() default "";
    
    }

    @RestController 는 RetentionPolicy 타입이 RUNTIME 이기 때문이다.

     

    이제  Hello 애노테이션  RetentionPolicy 타입을 RUNTIME 으로 수정 한 뒤

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hello {
    
    }

     

    HelloMain.java 을 다시 살행 해보면

    @Hello 애노테이션도 출력돼었다.

     

    즉, 리플렉션을 사용하려면 해당 정보가 RUNTIME 까지 가야 된다.

     

    CLASS 와 RUNTIME의 차이

    클래스로더가 바이트코드를 읽은 뒤 메모리에 로드할 때,

    CLASS로 설정되어 있으면 애노테이션 정보를 메모리에 누락시키고,

    RUNTIME이면 애노테이션 정보까지 함께 로드한다.

     

    따라서, 직접 애노테이션을 만들 때는

    사용 범위를 고려하여 만드는 것이 좋다.

     

    @Documented

    javadoc으로 작성한 문서에 포함시키려면 @Documented를 붙인다.

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }

     

    @Inherited

    애노테이션을 자손 클래스에 상속하고자 할 때, @Inherited를 붙인다.

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }

    예)

    @Hello 애노테이션에 @Inherited사용 

    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface Hello {
    
    }
    @Hello
    public class Animal {
        void breath() {
        }
    }

    Cat 클래스에는 @Hello 애노테이션을 사용하지 않았지만

    부모 클래스인 Animal에 @Hello 애노테이션을 사용했기 때문에

    public class Cat extends Animal  {
        @Override
        void breath() {
            super.breath();
        }
    }

    리플렉션으로  Cat 클래스의 애노테이션을 출력해보면

    public class App {
        public static void main(String[] args) {
            Class<Cat> catClass = Cat.class;
            Arrays.stream(catClass.getAnnotations())
                    .forEach(System.out::println);
        }
    }

    애노테이션도 상속 되는 것을 알 수있다.

     

     

     

     

    @Repeatable

    반복해서 붙일 수 있는 애노테이션을 정의할 때 사용

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Repeatable {
        /**
         * Indicates the <em>containing annotation type</em> for the
         * repeatable annotation type.
         * @return the containing annotation type
         */
        Class<? extends Annotation> value();
    }

     

    @MyAnnotation 정의

    @Repeatable(MyAnnotations.class) // @MyAnnotation을 하나로 묶는 컨테이너 애노테이션
    public @interface MyAnnotation {
    
    }

    @Repeatable 을 사용하기 위해 컨테이너 애노테이션도 정의해야 한다.

    public @interface MyAnnotations {
        MyAnnotation[] value();
    }

     

     

     

    이것이 자바다 
    자바의 정석
    더 자바, 코드를 조작하는 다양한 방법
    https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98
    https://www.javatpoint.com/java-annotation

     

    댓글

Designed by Tistory.