12주차 : 어노테이션

Joo·2023년 4월 20일

백기선 자바 스터디

목록 보기
12/13
post-thumbnail

1. JavaDoc

Java 소스 코드에서 HTML 형식의 API 문서를 생성하기 위해 사용되는 문서 생성기

  • 다른 API를 하이퍼링크를 통해 접근할 수 있음

  • 사용 목적

    • 개발자들이 소스 코드에 대한 별도의 문서를 작성하지 않고 소스코드와 문서를 하나의 파일로 관리하기 위해 사용함
  • 생성 방법

    • Java 코드 → 어노테이션
    • IDE → /** ⇒ javadoc.exe 실행하면 html 파일 생성됨
  • ex

    package org.example.javadoc;
    
    /**
     * javadoc 테스트
     */
    public class Test {
    
        /**
         * 테스트 메소드
         * @param a 전달받은 매개변수
         * @return 'a test'
         */
        public String testMethod(int a) {
            return a + " test";
        }
    }
    javadoc -d docs Test.java
    • -d docs

      • 생성된 문서를 docs 폴더에 저장
    • docs/index.html

      • Test 링크

JavaDoc Tag

  • JavaDoc 문서를 작성하기 위해 필요한 tag
  • 종류
    • @param
    • @return
    • @see
      • 다른 필드나 메소드에 대한 참조를 나타내고 싶을 때
      • 인터페이스에서 구현된 메소드의 javadoc을 상속받고 싶을 때
    • @author
    • @deprecated
    • @exception
    • @serial
    • @serialDate
    • @serialField
    • @since
    • @throws
    • @version

2. 어노테이션

자바 소스코드에 추가하여 코드에 대한 정보를 제공하는 메타 데이터

⭐ 백기선님 라이브 스터디
어노테이션은 주석이다.
런타임 시 필요한 중요한 정보는 안들어간다.
컴파일러 수준에서 사용하는 정적인 정보들이다. 그 정보들로 문서를 만들 수 있다.
따라서 어노테이션에는 변수 자체가 들어갈 수 없다. 정적인 데이터이기 때문에 (static final은 들어갈 수 있음)

  • 컴파일 & 런타임 시 코드를 어떻게 컴파일하고 처리할 것인지 알려줌
  • 모든 어노테이션은 java.lang.annotation.Annotation을 상속받음
    • 어노테이션은 다른 어노테이션 상속 불가
    • Annotation
      package java.lang.annotation;
      
      /**
       * The common interface extended by all annotation types.  Note that an
       * interface that manually extends this one does <i>not</i> define
       * an annotation type.  Also note that this interface does not itself
       * define an annotation type.
       *
       * More information about annotation types can be found in section 9.6 of
       * <cite>The Java&trade; Language Specification</cite>.
       *
       * The {@link java.lang.reflect.AnnotatedElement} interface discusses
       * compatibility concerns when evolving an annotation type from being
       * non-repeatable to being repeatable.
       *
       * @author  Josh Bloch
       * @since   1.5
       */
      public interface Annotation {
      
          boolean equals(Object obj);
      
          int hashCode();
      
          String toString();
      
          Class<? extends Annotation> annotationType();
      }
  • 용도
    1. 코드 문법 에러 체크를하도록 정보를 제공 (제약 사항 선언)

      ex) @Override, @Deprecated, @NotNull

    2. 빌드 툴이 코드를 자동 생성할 수 있도록 정보를 제공

      ex) Lombok - @Getter @Setter

    3. 런타임 시 특정 기능을 수행하도록 정보를 제공

      ex) Spring - @RequestMapping, @Autowired

  • 사용하는 이유
    • 어노테이션이 없을 때는 프로그램 관련 설정XML이나 properies라는 파일에 지정했음
      • 설정이 복잡해지면 파일이 커지고 복잡해져 이해하기 힘든 단점이 있었음
    • 어노테이션을 사용하면서 설정이 필요한 위치에 어노테이션이 위치하기 때문에 가독성이 매우 좋아짐
    • 추가로 롬복(lombok)이라는 툴을 이용하면 개발자가 필요한 작업을 어노테이션만으로 처리할 수 있기 때문에 코드를 줄이고 개발을 편하게 할 수 있게 해줌

3. 표준 어노테이션

자바 코드에서 사용하기 위해 정해져 있는 어노테이션

3.1 @Override

오버라이딩 한 메소드 앞에 붙이는 어노테이션

  • 어떤 메소드가 오버라이딩 되었는지 알아보기 쉬움
  • 오버라이딩 할 메소드와 메소드 시그니처 또는 리턴 타입이 다른 경우 컴파일 에러남 → 제대로 오버라이딩 되었는지 확인할 수 있음

3.2 @Deprecated

자바 버전이 올라가면서 더 이상 사용되지 않는 클래스, 메소드에 붙이는 어노테이션

  • Deprecated가 선언된 것을 사용하면 -Xlint:deprecation 옵션을 사용하라는 메세지가 나옴
    • 해당 옵션으로 컴파일하면 경고와 함께 정상적으로 컴파일 됨 (에러x)
  • 불필요한 클래스나 메소드를 당장 삭제하면 참조하고 있는 부분에서 문제가 생길 수 있기 때문에 하위 호환성을 위해 사용함

3.3 @SuppressWarnings

컴파일러에게 경고가 발생하지 않도록 명시하기 위해 사용하는 어노테이션

3.4 @FunctionalInterface

Functional Interface임을 명시하는 어노테이션

  • Functional Interface
    • 구현해야 할 추상 메소드가 1개인 인터페이스
  • 해당 어노테이션을 인터페이스에 붙이면 추상 메소드가 1개임을 보장해줌
    • 아닐 시 컴파일 에러 발생

+ 메타 어노테이션

4. 메타 어노테이션

어노테이션을 위한 어노테이션

❗ 개발자가 직접 어노테이션을 정의하고 사용할 일은 많지 않으므로 보고 이해할 수 있을 정도로만 파악하자

4.1 @Target

어노테이션 대상을 지정

  • @Target을 생략하는 경우 모든 선언에서 사용할 수 있음
  • ElementType
    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
    
        /** Field declaration (includes enum constants) */
        FIELD,
    
        /** Method declaration */
        METHOD,
    
        /** Formal parameter declaration */
        PARAMETER,
    
        /** Constructor declaration */
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
        ANNOTATION_TYPE,
    
        /** Package declaration */
        PACKAGE,
    
        /**
         * Type parameter declaration
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8
         */
        TYPE_USE,
    
        /**
         * Module declaration.
         *
         * @since 9
         */
        MODULE
    }
    • TYPE_PARAMETER
      • 제네릭의 타입 (타입 매개변수) 선언 시 사용 가능
        public class MyClass<@MyAnnotation T> {
            // Class code...
        }
        
        public <@MyAnnotation T> void myMethod(T param) {
            // Method code...
        }
    • TYPE_USE
      • 타입 선언시 사용되는 모든 곳에서 사용 가능
        package org.example.annotation;
        
        @MyAnnotation
        public class TestClass<@MyAnnotation T> {
        
            @MyAnnotation("hi")
            int a;
        
            public<@MyAnnotation TT> @MyAnnotation String test(@MyAnnotation int a, T t, TT tt) {
                return "hi";
            }
        }

4.2 @Retension

어노테이션의 유지 시간(유지 정책) 지정

  • 유지 정책 종류
    1. SOURCE

      • 소스 파일에만 존재
        • 컴파일 시 사용할 수 없는 정보
        • 바이트 코드에 존재하지 않음
          • 주석처럼 보이는 용으로 쓰려고 하는 것 ex) Override → 오버라이딩 한 메소드임을 명시하고 타입 체크를 해줌
      • 거의 사용되지 않고 디버깅 목적으로 사용됨 ex) @Override, @SuppressWarnings
    2. CLASS - default

      • 클래스 파일에 정보가 저장됨
        • 컴파일 시 사용할 수 있는 정보
        • 바이트코드에 어노테이션 정보가 포함되어 있음
        • but, JVM 로딩 시에는 어노테이션 정보가 누락됨 → 리플렉션을 사용할 수 없음
      • 컴파일러에게 정보를 제공하기 위해 사용됨
        • ex) 롬복(@Setter, @Getter), AOP
    3. RUNTIME
      - 클래스 파일에 정보가 저장됨
      - 실행 시 리플렉션을 통해 클래스 파일에 저장된 어노테이션 정보를 읽어서 처리함
      - 이 때부터 리플렉션이 가능함

      public enum RetentionPolicy {
          /**
           * Annotations are to be discarded by the compiler.
           */
          SOURCE,
      
          /**
           * Annotations are to be recorded in the class file by the compiler
           * but need not be retained by the VM at run time.  This is the default
           * behavior.
           */
          CLASS,
      
          /**
           * Annotations are to be recorded in the class file by the compiler and
           * retained by the VM at run time, so they may be read reflectively.
           *
           * @see java.lang.reflect.AnnotatedElement
           */
          RUNTIME
      }

⭐ SOURCE → CLASS → RUNTIME

4.3 @Documented

어노테이션에 대한 정보가 JavaDoc 문서로 포함되도록 함

4.4 @Inherited

어노테이션이 자식 클래스에 상속되도록 함

  • 부모 클래스에 있는 어노테이션을 자식 클래스에서도 동일하게 붙인 효과

4.5 @Native

네이티브 메소드가 참조하는 상수 필드에 붙임

5. 어노테이션 정의하는 방법

  • @interface를 사용하여 선언
    • public or default
  • 필드 (요소)
    • 리턴 타입이 있고 매개변수가 없는 추상 메소드의 형태
    • 무조건 public
      • number, text → public이 붙었음을 확인
    • 여러 개의 요소가 존재할 수 있지만 어노테이션 사용 시 반드시 값을 입력해야 함
      • 매개변수로 값을 넣어주지 않을거면 반드시 default로 기본값을 설정해놔야 함
  • value()
    • 어노테이션 매개변수의 기본값을 정의
    • 매개변수로 특정 값을 지정하지 않고 값을 입력하면 value로 지정됨
    • ex
      @Target(ElementType.FIELD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface MyAnnotation {
          String value() default "value";
      
          String exp() default "exp";
      }
      package org.example.annotation;
      
      public class TestClass {
      
          @MyAnnotation("hi")
          int a;
      }
      package org.example.annotation;
      
      import java.lang.reflect.Field;
      
      public class AnnotationMain {
      
          public static void main(String[] args) throws NoSuchFieldException {
              TestClass testClass = new TestClass();
      
              Field a = testClass.getClass().getDeclaredField("a");
              MyAnnotation annotation = a.getAnnotation(MyAnnotation.class);
      
              System.out.println("annotation.value() = " + annotation.value());
              System.out.println("annotation.exp() = " + annotation.exp());
          }
      }
      annotation.value() = hi
      annotation.exp() = exp

5.1 어노테이션 요소의 규칙

  1. 요소의 타입은 기본형, String, enum, class, 어노테이션만 허용
  2. () 내부에 매개변수 선언 불가
  3. 예외 선언 불가
  4. 요소를 타입 매개변수(제네릭)로 정의 불가

5.2 선언한 어노테이션을 확인하는 방법

  • 리플렉션 사용
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        public int number();
    
        public String text();
    }
    public class UseMyAnnotation {
    
        @MyAnnotation(number = 3, text = "hi")
        void sampleMethod() {
        }
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method method = UseMyAnnotation.class.getDeclaredMethod("sampleMethod");
    
            MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
            int number = annotation.number();
            String text = annotation.text();
    
            System.out.println("입력받은 text를 number만큼 반복시키는 어노테이션");
            for (int i = 0; i < number; i++) {
                System.out.print(text);
            }
        }
    }
  • 어노테이션 바이트 코드
    
    public abstract @interface week12_annotation/basic/MyAnnotation 
    implements java/lang/annotation/Annotation {
    
      // compiled from: MyAnnotation.java
    
      @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.TYPE, Ljava/lang/annotation/ElementType;.METHOD})
    
      @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)
    
      // access flags 0x401
      public abstract number()I
    
      // access flags 0x401
      public abstract text()Ljava/lang/String;
    }
    • java.lang.annotation.Annotation 인터페이스를 구현함
  • 어노테이션을 사용한 클래스의 바이트코드 (디컴파일 버전)
    public class UseMyAnnotation {
        public UseMyAnnotation() {
        }
    
        @MyAnnotation(
            number = 3,
            text = "hi"
        )
        void sampleMethod() {
        }
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method method = UseMyAnnotation.class.getDeclaredMethod("sampleMethod");
            MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
            int number = annotation.number();
            String text = annotation.text();
            System.out.println("입력받은 text를 number만큼 반복시키는 어노테이션");
    
            for(int i = 0; i < number; ++i) {
                System.out.print(text);
            }
    
        }
    }
    • 메소드에 붙은 어노테이션의 정보를 저장하고 있음
  • @Override 어노테이션
    • @Retention(RetentionPolicy.*SOURCE*)
      - 어노테이션 정보가 바이트코드에 남아있지 않음

6. 어노테이션 프로세서

컴파일 시 어노테이션을 읽고 처리하는 프로그램

  • 컴파일 시 특정 어노테이션이 있으면 사용자 지정 코드를 실행할 수 있음
    • 기존 코드를 검사, 수정 ex) @Override
    • 새로운 코드를 생성 ex) Lombok - @Getter, @Setter, @Builder…
  • 컴파일 중에만 작동하고 여러 개의 어노테이션 프로세서가 등록되어 사용됨
    • META-INF/services에 등록
  • 어노테이션 프로세서를 직접 만들려면 javax.annotation.processing.AbstractProcessor 인터페이스를 구현해야 함
    • AbstractProcessor
      package javax.annotation.processing;
      
      public abstract class AbstractProcessor implements Processor {
          ...
      
          public Set<String> getSupportedAnnotationTypes() {
                  ...
              }
      
          ...
      
          /**
           * {@inheritDoc}
           */
          public abstract boolean process(Set<? extends TypeElement> annotations,
                                          RoundEnvironment roundEnv);
      
          ...
      }
      • getSupportedAnnotationTypes()

        • 어떤 어노테이션에 대한 처리인지 판단
        • 어노테이션_타입.getClass().getName()을 Set에 넣어서 리턴
      • process()
        - 해당 어노테이션이 있는 경우 처리할 로직을 작성
        - 클래스 (바이트코드) 생성 가능
        - 소스 코드와 별개의 리소스 생성 가능

        ⇒ 필요한 경우 자세히 찾아보기

  • 장점
    • 컴파일 시점에 조작하기 때문에 런타임 비용이 들지 않음

ServiceLoader

런타임 시 인터페이스 구현 클래스를 로드하고 인스턴스화 하여 주입해주는 유틸리티 클래스
java.lang.ServiceLoader

  • 인터페이스만 제공하고 구현체만 바꿔 끼울 때 사용
  • 각 제조사에서 다양한 구현체(jar) 생성했다고 가정했을 때 ServiceLoader가 jar 파일 내에 해당 인터페이스에 대한 구현체를 가져와서 개발자에게 제공함
  • 그럼 특정 인터페이스를 구현한 구현체를 jar에서 어떻게 찾는가??
    • ServiceLoader가 인스턴스화 될 때 META-INF/services/인터페이스 풀네임에서 해당 구현체를 찾고 클래스 로드를 함

Reference

12주차 : 애노테이션

javadoc

애노테이션 프로세서 실습

어노테이션 프로세서 만들기

0개의 댓글