ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Annotation이란 무엇인가?
    Dahum Library/Java 2020. 3. 23. 15:17

    어노테이션은 주석이다?

    어노테이션은 코드의 메타데이터를 표현하는 기능이라는 말이 있다. 그 말대로라면 어노테이션은 주석(comment) 이다. 코드는 기계어로 컴파일되어 컴퓨팅 리소스를 동원하게 만든다. 반면 주석은 그 주석을 읽는이에게 어떠한 정보를 전달하는데에 목적이 있다. 흔히 우리가 "//", "/* */"과 같은 형태로 작성하는 주석은 사람이 읽는 주석이다. 이것은 소스코드가 컴파일 되는데에 아무런 영향을 미치지 않는다.

     

    그런데 어노테이션은 사람만을 위한게 아니다. 위의 문단에서 굳이 "읽는이"라고 표현한 이유가 있는데, 어노테이션은 사람에게도 코드의 메타데이터에 대한 정보를 제공하지만 컴파일러, 혹은 다른 코드에게도 그러한 역할을 한다.

     

    그러므로 코드가 동작하는데 아무런 영향을 미치지 않는 주석(comment)과는 달리 어노테이션은 실제 동작에 영향을 미치기도 한다.

     

    class MySuperClass {
            
        void hi() {
            System.out.println("Hi, I'm super class");
        }
    }
    
    class MySubClass extends MySuperClass {
        
        @Override // 수퍼클래스 재정의시 붙여줌. 안붙여줘도 Syntax Error 안남
        void hi() {
            System.out.println("Hi, I'm sub class");
        }
        
        @Override // Syntax 에러 발생
        void bye() {
            System.out.println("good bye!");
        }
    }

     

    위의 코드에서 서브클래스는 수퍼클래스의 hi()를 재정의하고있고, @Override 어노테이션을 붙였다. 이걸 붙여줌으로써 개발자는 서브클래스만 봐도 "아, 이 hi()는 수퍼클래스에서 재정의한거구나" 하고 알 수 있다. IDE를 쓰면 눈에 띄는 컬러링도 해준다. 붙여주지 않아도 상관없다. 별다른 에러가 발생하지는 않는다.

     

    하지만 아래의 bye()메소드를 보면 수퍼클래스에 존재하지 않는 메소드를 정의하면서 @Override어노테이션을 붙여줬다. 이 경우 컴파일러가 Syntax에러를 감지한다. (빨간줄 그인다)

     

     

     

     

    어노테이션의 구성

    주석은 아무데서나 쓸 수 있지만 어노테이션은 아무데서나 쓸 수 있는건 아니다. 모든 어노테이션은 아래와 같은 속성을 갖고있다.

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

    이건 실제 @Override의 정의인데, 일단 .java파일이다. 어노테이션은 @interface라는 키워드로 정의된다. 이걸통해서 개발자가 직접 어노테이션을 만들수도 있다. 그리고 @Override 어노테이션 위에 또 다른 어노테이션이 붙어있다.

    • Target 

      이 Override라는 어노테이션이 타겟으로 하는 코드를 말한다. 인자로 들어간 ElementType.METHOD는 메소드에 쓸 수 있단 말임을 알 수 있다. 이 밖에도 FIELD, PARAMETER, TYPE(class, interface, enum)등등 enum의 형태로 어노테이션이 쓰일 수 있는 타겟이 정의되어있다.
    • Retention

      지속기간을 뜻한다. 인자로 들어간 RetentionPolicy를 보면 SOURCE, CLASS, RUNTIME 이렇게 세가지 옵션이 있다. RetentionPolicy를 SOURCE로 주면 주석 처럼 컴파일되어 바이트코드에는 남아있지 않는다.
      CLASS는 Retention옵션을 따로 주지 않으면 적용되는 기본값인데, 바이트코드로 컴파일이 되어도 남아있지만 JVM이 이를 인식하지는 않는다.
      • 이 부분이 의아해서 찾아봤더니, 정말 쓰는 경우가 드물다고한다. 하지만 아이러니하게도 CLASS가 RetentionPolicy의 기본값이다... 혹시 모르니 더 알아봐야겠다.

    그러면 위의 @Override는 메소드에 써야하고, 컴파일때 까지만 영향을 주는 어노테이션이라 할 수 있겠다.

     

     

     

    어노테이션의 사용

    RetentionPolicy가 SOURCECLASS 인 어노테이션들은 어쩌면 정말 주석과 비슷한 역할을 한다고 할 수 있겠다. 그러나 주석과는 다르게 그걸 읽는이가 내가 아니라 컴파일러 인것이다.

     

    RUNTIME은 좀 다르다. 말 그대로 이 어노테이션은 런타임에까지 살아있다. 실제 동작하는 코드인 것이다.

     

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    @interface MySourceAnnotation {
        String name() default "source";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    @interface MyClassAnnotation {
        String name() default "class";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyRuntimeAnnotation {
        String name() default "runtime";
    }
    
    public class AnnotationTest {
    
        @MySourceAnnotation(name = "john")
        String john;
        @MyClassAnnotation(name = "mike")
        String mike;
        @MyRuntimeAnnotation(name = "alice")
        String alice;
    
        public void printNames (String[] args) {
            System.out.println("hi my name is " + john);  // john과 mike는 이름이 출력이 안된다.
            System.out.println("hi my name is " + mike);  // "hi my name is null"
            System.out.println("hi my name is " + alice); // alice만 정상 출력
        }
    }

    위의 코드를 통해 여러가지를 알 수 있는데, 우선 어노테이션에는 메서드를 선언할 수 있다.

    어노테이션에 메서드를 선언하기 위해서는 아래와 같은 규칙을 지켜야한다.

    1. throws를 사용할 수 없다.
    2. return type은 primitive, String, Class, enum 타입과 해당 타입의 배열만 가능하다.
    3. 매개변수를 받지 못한다.

     

    어노테이션의 메서드는 일반적인 메서드와는 달리 보통은 setter같은 역할로 많이 쓰인다. 그리고 위의 코드에서 알 수 있듯이 default값을 가질 수 있다.

     

    또 한가지 중요한점은 RUNTIME어노테이션은 reflection을 통해 그 속성들을 가져올 수 있다. 이 때문에 Spring이나 JUnit등의 프레임워크들이 이 어노테이션들을 통해서 클래스들을 식별하고 공통적인 속성을 지정하는 등의 기능을 구현할 수 있는 것이다.

     

    Spring을 예로들면 좀 더 명확히 와닿는데, Spring을 처음 접하게되면 가장 큰 장벽으로 느껴지는 것이 바로 컨텍스트와 의존성을 설정하는 것이다. 이는 원래 XML파일을 통해서 관리하던 것이었는데, 내용이 많아짐에 따라 XML로 또다른 코딩을 해야하는 지경까지 와버렸다.

     

    이후 어노테이션을 통해서 모든 설정이 가능해짐에 따라 별도의 설정파일에 대한 필요가 사라졌고, 코드와 어노테이션이 함께있기 때문에 명시적이고 가독성이 좋아졌다.

     

    중요한건 Spring 설정이 쉬워졌다는게 아니라, XML이 하던 프레임워크의 런타임 설정이 어노테이션으로 대체되었다는 것이다.

     

     

     

     

     

     

     

     

     

     

    댓글

Designed by Tistory.