티스토리 뷰

JVM/JAVA

[Java] Annotation 의 동작의 이해

글을 쓰는 개발자 2022. 10. 6. 15:51
반응형

참고: https://docs.oracle.com/javase/tutorial/java/annotations/

Java Annotation

Annotations, a form of metadata, provide data about a program that is not part of the program

메타데이터의 한 형태인 어노테이션은 프로그램의 일부가 아닌 프로그램에 대한 데이터를 제공합니다.

 

  • 컴파일러를 위한 정보 - 어노테이션은 컴파일러가 에러나 supress warnings를 찾기 위해 사용된다.
  • Compile-time 그리고 deployment-time 가공과정 - 소프트웨어 도구들은 어노테이션 정보들을 활용하여 코드, XML, JSON 등을 생성한다.
  • 런타임 처리 - 몇몇 어노테이션은 런타임 시간에 검사되는 데 사용된다.

Declaring an Annotation Type

실제 프로젝트를 진행하면서 어떤 클래스에 중요한 정보를 저장해야 할 때 다음과 같이 표현할 수 있다.

public class MyClass {
// Author: Park Suhan
// Date: 2022/10/01
// Current Version: 1.5.6
// Last Modified: 2022/10/30
// By: Park Suhan
// Reviewers: Mike, Sandy, Alice
}

하지만 어노테이션을 가지고 메타데이터를 더한다면, 다음과 같이 정의할 수 있다.

@interface ClassRecord {
	String author();
    String date();
    double currentVersion() default 1.0;
    String lastModified() default "N/A";
    String lastModifiedBy() default "N/A";
    String[] reviewers();
}

어노테이션 타입 정의는 인터페이스 정의하는 방식 ( 앞에 '@'붙였다는 사실만 다를 뿐)과 비슷하다.

 

타입을 다음과 같이 설정했으면 우리는 어노테이션 타입에 대한 값 설정을 다음과 같이 할 수 있다.

 

@ClassRecord(
        author = "Park Suhan",
        date = "2022/10/01",
        currentVersion = 1.56,
        lastModified = "2022/10/30",
        lastModifiedBy = "Park Suhan",
        reviewers = {"Alice", "Sandy", "Mike"}
)
public class MyClass {
}

 

이 때 약간의 팁을 주자면 해당 어노데이션은 문서의 성향이 있으며 Javadoc을 원한다면 다음과 같이 추가해주면 더 좋다.

@Documented
public @interface ClassRecord {
    // 생략
}

Predefined Annotation Types

@Deprecated, @Override, @SuppressWarnings

 

@Deprecated

Deprecated 어노테이션은 해당 클래스 또는 메소드를 더 이상 사용하지 않을 것이라고 표현하는 메타데이터이다. 컴파일러가  @Deprecated 어노테이션 표시된 클래스, 메소드 등을 프로그램에서 사용할 때 사용하지 말라고 경고문을 날린다. 

 

@Override

@Override 어노테이션은 컴파일러에게 부모 클래스로부터 상속받은 메소드라는 정보를 준다. @Override 어노테이션은 의무적으로 사용해야 하는 것은 아니지만, 사용할 경우 에러를 예방할 수 있다.

 

@SuppressWarnings

SuppressWarnings 어노테이션은 특정 경고 메시지를 없애는 데 사용된다.  해당 사용되는 곳은 컬렉션 프레임워크를 보면 심심치 않게 볼 수 있다. 

 

@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

모든 컴파일러 경고는 하나의 카테고리에 속하게 되는데 "unchecked", "deprecation"이 이에 속한다.

 

@SafeVarags

SafeVarags 어노테이션은 메소드나 생성자에 지원되는 어노테이션으로 코드가 varargs 매개변수에 대해 잠재적으로 안전하지 않은 작업을 수행하지 않음을 나타냅니다

https://www.baeldung.com/java-safevarargs

 

@FunctionalInterface

FunctionalInterface 어노테이션은 람다를 사용할 때 컴파일 에러를 체크하기 위해 사용되는 어노테이션으로서 메소드를 하나만 정의하도록 설정하는 어노테이션입니다. 두개 이상이 정의되었을 때 에러 표시가 나면서 실수를 줄일 수 있게 해줍니다.

 

Annotation을 지원하는 Annotation

@Rentention

어느 시점까지 유지할 것인지 표시하는 어노테이션

  • RetentionPolicy.SOURCE – 소스 코드 상까지는 유지되며 그 이후 (컴파일러 이후) 부터는 효력이 없음
  • RetentionPolicy.CLASS – 컴파일 시점까지 유지되며,  그 이후 부터는 무시(Java Virtual Machine (JVM)에 의해)
  • RetentionPolicy.RUNTIME – JVM시점 부터 유지되며 런타임때 사용가능하다.

@Documented

JavaDoc 관련 문서화할 때 사용되는 어노테이션

 

@Target

적용되는 어노테이션의 범위를 제한하는 어노테이션

  • ElementType.ANNOTATION_TYPE  -> annotation type.
  • ElementType.CONSTRUCTOR -> constructor.
  • ElementType.FIELD -> field or property.
  • ElementType.LOCAL_VARIABLE -> local variable.
  • ElementType.METHOD -> method-level annotation.
  • ElementType.PACKAGE -> package declaration.
  • ElementType.PARAMETER -> parameters of a method.
  • ElementType.TYPE -> any element of a class.

 

@Inherited

어노테이션 상속을 허용해주는 어노테이션. 기본적으로 어노테이션은 상속 불가능하다. (자세한 것은 밑의 예시를 확인할 것)

 

@Repeatable

하나의 예시를 보자

 

Name.java

해당 어노테이션은 값을 저장하는 어노테이션으로 중복해서 사용할 수 있는 어노테이션이다.

중복된 값을 가져올 때는 Names.class를 사용하라는 표시로 @Repeatable를 표시한다.

 

@Target(ElementType.TYPE)
@Repeatable(Names.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
    String value();
}

Names.java

Name 어노테이션들을 관리하는 어노테이션으로 사용하는 코드에서 해당 어노테이션을 통해 정보를 가져온다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Names {
    Name[] value();
}
@Name("A")
@Name("B")
@Name("C")
public class TargetName {
}
public class Runner {
    public static void main(String[] args) {
        TargetName targetName = new TargetName();
        Names names = targetName.getClass().getAnnotation(Names.class);
        Name[] nameArray = targetName.getClass().getAnnotationsByType(Name.class);
        Name[] values = names.value();
        for (Name value : values) {
            System.out.println(value);
        }
        System.out.println(Arrays.toString(nameArray));
    }
}
@com.playground.springplayground.annotation.Name("A") @com.playground.springplayground.annotation.Name("B") @com.playground.springplayground.annotation.Name("C") [@com.playground.springplayground.annotation.Name("A"), @com.playground.springplayground.annotation.Name("B"), @com.playground.springplayground.annotation.Name("C")]

사용하는 방법은 다음 두가지 정도 있다.

  • Name을 관리하는 Names를 통해 가져오는 방법
  • Name을 지정하여 Array로 받는 방법

취향이니 좀 더 끌리는 것을 선택하면 좋을 것 같다.

 

Spring  Annotation

@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   
   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default {};
   
   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};
   
   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};

   
   @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

}

 

@Target(ElementType.TYPE)

해당 어노테이션은 클래스에만 사용가능하도록 한정시키는 역할

 

@Retention(RetentionPolicy.RUNTIME)

JVM 동작하는 시점에 적용되며, 흔히 런타임 시점에서 동작한다고 생각하면 된다.

 

@Inherited

적용된 어노테이션의 A 클래스가 존재한다고 했을 때 A 클래스를 상속한 B 클래스에서 A 클래스에 적용된 메타 데이터를 사용할 수 있게 해주는 것이 @Inherited 이다.

 

예시

@Inherited 를 적용한 클래스

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherit {
    String value() default "hi";
}

@Inherited 를 적용하지 않은 클래스

public @interface NoInherit {
    String value() default "noInherit";
}

 

적용한 클래스

@NoInherit
@MyInherit
public class AClass {
}

 

테스트할 클래스

public class BClass extends AClass{
}

 

 

실행 결과

public class Runner {
    public static void main(String[] args) {
        final BClass bClass = new BClass();
        Annotation[] annotations = bClass.getClass().getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}
@com.playground.springplayground.annotation.inherited.MyInherit("hi")

 

보시는 바와 같이 @Inherited 어노테이션을 적용한 곳에서만 값을 가져올 수 있는 것을 볼 수가 있다.

이 점을 유의해가면 사용하시길 바란다.

 

@AliasFor

@AliasFor 에 정의된 어노테이션의 메타데이터 값을 가져오는 역할을 한다.

 

@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};

 

"해당 부분은 SpringBootApplication 어노테이션에 있는 exclude method의 값은 EnableAutoConfiguration 어노테이션의 exclude 값의 역할을 부여한다." 라고 이해하면 된다.

 

하나의 예시를 들어보자

MyRequestMapping.java

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping
public @interface MyRequestMapping {
    @AliasFor(annotation = RequestMapping.class, attribute = "value")
    String route() default "";
    @AliasFor(annotation = RequestMapping.class, attribute = "method")
    RequestMethod[] define() default {};

}

MyRequestMapping은 RequestMapping의 일부분을 재정의한 어노테이션으로서 RequestMapping의 value 부분은 route로 재정의하였으며, method 부분은 define을 재정의하였다.

 

@RestController
public class AnnoController {
    @MyRequestMapping(define = RequestMethod.GET, route = "/web/annotation")
    public String view() {
        return "view";
    }
}

 

 

 

해당 부분을 테스트 해보면 view가 정상적으로 찍히는 것을 알 수 있다.

 

어노테이션에 대한 이해는 이 정도 하면 될 것 같다.

나중에 하면서 부족하거나 필요한 지식은 추가해나갈 것이다.

읽어주셔서 감사합니다.

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함