java, annotaion

2020-01-20
  • java
  • 백기선님의 인프런 강의, 스프링 웹 MVC 의 요청 맵핑하기 6부 커스텀 애노테이션 을 보다가 정리한다.

    spring 에서 custom mapping annotaion 을 만들다

    package com.harm.annotaion;
    
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @RequestMapping(method = RequestMethod.GET, value = "/usage02/hello3", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public @interface Usage02Hello3GetMediaTypeJsonMapping {
    }
    

    요딴 걸 만들었는데, 테스트케이스에서 자꾸 404가 떨어져서 한참 찾았는데, @Retention 라는 meta annotaion 이 없어서 그렇다는것을 알았다. 겸사겸사 meta annotaion 에 대해서 알아본다.

    meta annotaion 에는 몇 가지가 있는데

    @Retention

    enum RetentionPolicy 을 매개변수로 받는다.

    • SOURCE : 컴파일러가 버림. 의존성이 있는 어노테이션의 경우, 컴파일 에러가 나기도 한다.
    • CLASS : 클래스파일에는 존재하지만 vm 에선 버린다. 런타임에서 읽기 불가.
    • RUNTIME : 런타임에도 남아있는다.

    @Repeatable

    annotaion 을 반복해서 사용할 수 있도록 해준다. 다수의 어노테이션 그룹핑해주는 기능인 것 같다.

    annotaion 에 파라미터로 사용될 열거형

    public enum REPEATABLE_VALUE {
        REPEATABLE_ONE,
        REPEATABLE_TWO,
        REPEATABLE_THREE
    }
    

    반복사용될 annotaion

    @Repeatable(value = RepeatableGroup.class)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RepeatableItem {
        REPEATABLE_VALUE value();
    }
    

    그룹핑할 annotaion

    @Retention(RetentionPolicy.RUNTIME)
    public @interface RepeatableGroup {
        RepeatableItem[] value();
    }
    

    테스트 케이스로 알아보자

    package com.harm.unit.reflect.annotaion.repeatable;
    
    import org.junit.Assert;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.lang.annotation.Annotation;
    
    public class RepeatableTest {
        private Logger logger = LoggerFactory.getLogger(RepeatableTest.class);
    
        @Test
        public void repeatableTest01() {
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_ONE)
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_TWO)
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_THREE)
            class RepeatableTestTarget01 { }
    
            Annotation[] annotations = RepeatableTestTarget01.class.getAnnotationsByType(RepeatableItem.class);
    
            Assert.assertTrue(annotations.length == 3); //그룹핑 없이 읽는 법
    
        }
    
        @Test
        public void repeatableTest02() {
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_ONE)
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_THREE)
            class RepeatableTestTarget02 { }
    
            Annotation[] annotations = RepeatableTestTarget02.class.getAnnotations();
            Assert.assertTrue(annotations.length == 1); //RepeatableGroup 1 그룹핑된 annotaion 한개가 나온다.
    
            RepeatableGroup repeatableGroup = RepeatableTestTarget02.class.getAnnotation(RepeatableGroup.class);
            Assert.assertTrue(repeatableGroup.value().length == 2); //repeatableGroup values 2 이렇게 읽을 수 있다.
    
        }
    
        @Test
        public void repeatableTest03() {
            @RepeatableItem(REPEATABLE_VALUE.REPEATABLE_THREE)
            class RepeatableTestTarget03 { }
    
            RepeatableGroup repeatableGroup = RepeatableTestTarget03.class.getAnnotation(RepeatableGroup.class);
            Assert.assertTrue(repeatableGroup == null); //annotaion 이 한개면 그룹핑이 안된다.
    
            RepeatableItem[] repeatableItems = RepeatableTestTarget03.class.getAnnotationsByType(RepeatableItem.class);
            Assert.assertTrue(repeatableItems.length == 1); //그룹핑 되지 않아도 읽을 수 있도록 RepeatableItem 에 @Retention(RetentionPolicy.RUNTIME) 을 붙이도록 하자.
        }
    }
    

    @Target

    annotaion 을 어디에 붙일지 enum ElementType 로 설명한다. 잘못 붙이면 컴파일 에러가 난다.

    @Documented

    annotaion 정보를 java doc 에 표시해준다.

    @Inherited

    annotaion 이 붙은 클래스의 자식 클래스에도 annotaion 이 적용되도록 한다. 뭔 뜻이냐면

    요딴 annotaion 을 하나 정의해놓고,

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InheritedItem {
        String name() default "";
        String[] value() default {};
    }
    

    아래와 같이 테스트를 해보자

    @InheritedItem({"hello", "world"})
    class InheritedTarget { }
    class InheritedTargetChild extends InheritedTarget { }
    
    Annotation[] annotations = InheritedTargetChild.class.getAnnotations();
    logger.debug("annotations.length -> {}", annotations.length);
    for(Annotation annotation : annotations) {
        logger.debug("{}", annotation);
    }
    
    InheritedItem inheritedItem = InheritedTargetChild.class.getAnnotation(InheritedItem.class);
    for(String val : inheritedItem.value()) {
        logger.debug("inheritedItem.value() -> {}", val);
    }
    

    그럼 자식클래스에선 annotaion 을 붙이지 않았는데도 붙은 것처럼 확인할 수가 있다. Runtime 에도 확인할 수 있도록 @Retention(RetentionPolicy.RUNTIME) 을 붙이는 것을 잊지 말도록 하자.

    2020-01-28 16:18:04.765 DEBUG --- [ main ] c.h.u.r.a.i.InheritedTest : annotations.length -> 1
    2020-01-28 16:18:04.770 DEBUG --- [ main ] c.h.u.r.a.i.InheritedTest : @com.harm.unit.reflect.annotaion.inherited.InheritedItem(name=, value=[hello, world])
    2020-01-28 16:18:04.770 DEBUG --- [ main ] c.h.u.r.a.i.InheritedTest : inheritedItem.value() -> hello
    2020-01-28 16:18:04.770 DEBUG --- [ main ] c.h.u.r.a.i.InheritedTest : inheritedItem.value() -> world