[12] Java - Annotation

harold·2021년 3월 6일
0

java

목록 보기
2/13

학습할 것 (필수)

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서

애노테이션 정의하는 방법


정의

Java 5부터 등장한 기능입니다.

흔히 알고 있는 @Override, @Deprecated이 대표적인 예입니다.

AOP(Aspect Oriented Programing; 관심지향프로그래밍)을 편리하게 구성할 수 있습니다.

특징

컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공

소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공

어노테이션을 만들 때 용도를 분명하게 해야 한다.

  • 소스상에서만 유지해야 할지
  • 컴파일된 클래스에도 유지해야 할지
  • 런타임 시에도 유지해야 할지를 지정해야 한다.

구조

@Inherited // 상속
@Documented // 문서에 정보가 표현
@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능합니다
@Retention(RetentionPolicy.CLASS)   // Compiler가 클래스를 참조할 때까지 유효합니다
@Retention(RetentionPolicy.SOURCE)  // 컴파일 이후 사라집니다
@Target({
		ElementType.PACKAGE, // 패키지 선언시
		ElementType.TYPE, // 타입 선언시
		ElementType.CONSTRUCTOR, // 생성자 선언시
		ElementType.FIELD, // 멤버 변수 선언시
		ElementType.METHOD, // 메소드 선언시
		ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
		ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
		ElementType.PARAMETER, // 매개 변수 선언시
		ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
		ElementType.TYPE_USE // 타입 사용시
})
public @interface NesoyAnnotation{
	/* enum 타입을 선언할 수 있습니다. */
	public enum Quality {
		BAD, GOOD, VERYGOOD
	}

	/* String은 기본 자료형은 아니지만 사용 가능합니다. */
	String value() default "NesoyAnnotation : Default String Value";

	/* 배열 형태로도 사용할 수 있습니다. */
	int[] values();

	/* enum 형태를 사용하는 방법입니다. */
	Quality quality() default Quality.GOOD;
}

@retention


  • 어노테이션의 Life Time입니다.

  • Class

    • 바이트 코드 파일까지 어노테이션 정보를 유지한다.
    • 하지만 리플렉션을 이용해서 어노테이션 정보를 얻을 수는 없다.
  • Runtime

    • 바이트 코드 파일까지 어노테이션 정보를 유지하면서 리플렉션을 이용해서 런타임시에 어노테이션 정보를 얻을 수 있다.
  • Source

    • Compile 이후로 삭제되는 형태
  • https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/RetentionPolicy.html

@target


Annotation이 적용할 위치를 선택

종류

  • ElementType.PACKAGE : 패키지 선언
  • ElementType.TYPE : 타입 선언
  • ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
  • ElementType.CONSTRUCTOR : 생성자 선언
  • ElementType.FIELD : 멤버 변수 선언
  • ElementType.LOCAL_VARIABLE : 지역 변수 선언
  • ElementType.METHOD : 메서드 선언
  • ElementType.PARAMETER : 전달인자 선언
  • ElementType.TYPE_PARAMETER : 전달인자 타입 선언
  • ElementType.TYPE_USE : 타입 선언

@documented


문서에도 어노테이션의 정보가 표현됩니다.

애노테이션 프로세서


source-level annotation processing은 개발자가 컴파일 단계에서 원하는 작업을 할 수 있도록 해주는 고마운 기능이다.

일단, Annotation Processor를 이용하여 컴파일 단계에서 소스를 조작할 수 있게 되므로 이와 관련된 라이브러리에는 Annotation Processor을 사용한다고 생각하면 된다.

Lombok, JPA 등등 생각해보면 꽤 많은 라이브러리에서 사용하고 있음을 알 수 있다. baeldung에서는 Annotation Processor가 컴파일 단계에서 모든 소스를 조작할 수 있는 것은 아니며, 새로 작성되는 파일에만 소스를 조작할 수 있고 기존에 있던 파일들은 건드릴 수 없다고 한다.

예외적으로 Lombok의 경우에는 다른 방식을 사용한다고 하는데 이 부분에 대한 자세한 설명은 나와있지 않다. 아마 Lombok 사용을 위해서 플러그인을 설치해야 하는 것과 관련이 있지 싶다.

Annotation Processor 선언

모든 프로세서들은 AbstractProcessor를 상속받아야 한다. AbstractProcessor도 Processor 인터페이스를 확장하는 것을 보면 Processor 인터페이스를 이용해서도 만들 수 있을듯하지만, 일단은 AbstractProcessor부터 공부해보자

일단 추상 메서드는 process()가 있다.

public abstract boolean process(
 Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
public synchronized void init(ProcessingEnvironment processingEnv)
public SourceVersion getSupportedSourceVersion()
public Set<String> getSupportedAnnotationTypes()
public Set<String> getSupportedOptions()
public Iterable<? extends Completion> getCompletions(...)
protected synchronized boolean isInitialized()
private Set<String> arrayToSet(...)

process Method

각 프로세서의 main 메서드와 같은 역할을 한다. 이 메서드에 원하는 코드를 작성하면 된다.

getSupportedAnnotationTypes

이 프로세서가 처리할 어노테이션들을 명시한다. 이 메서드는 어노테이션을 대체할 수 있다.(Java 1.7이상)

getSupportedSourceVersion

특정 자바버전을 명시하는데 사용할 수 있으며, 위와 마찬가지로 어노테이션으로 대체 가능하다.(Java 1.7이상)

다만, 어노테이션과 메서드의 차이점은 어노테이션에서는 SourceVersion.latestSupported()를 사용하지 못하고 특정 버전을 명시해줘야 한다는 점이 다르다.

init(ProcessingEnvironment processingEnvironment)

모든 어노테이션 프로세서는 기본 생성자를 가져야한다. 대신 init 메서드를 사용할 수 있는데, ProcessingEnvironment에서 상당히 유용한 유틸 클래스들을 제공하므로 상당히 유용한 메서드이다.


Annotation Processor의 동작

Annotation Processor는 여러 번 반복해서 수행된다.
각 라운드는 컴파일러가 어노테이션을 검색하고, 해당 어노테이션에 맞는 Processor를 선택하는것에서부터 시작된다.

If any files are generated during this process, another round is started with the generated files as its input. This process continues until no new files are generated during the processing stage.
( 해석: 만약 이 프로세스 중 파일이 생성될 경우, 이 파일의 작성과 함께 다른 라운드가 시작된다. 이 프로세스는 새 파일이 생성되지 않을 때까지 계속된다. )

Annotation Processor가 컴파일 단계에서 소스를 조작할 수 있다는 것이 굉장히 deep하게 느껴질 수 있다. 왠지 다른 라이브러리는 사용하지 못하고, 모든 것을 직접 만들어야 할 것만 같은 느낌이다.

하지만, Annotation Processor또한 JVM 위에서 돌아가므로 다른 라이브러리를 끌어다 사용할 수 있다. 이렇게 만든 프로세서를 .jar 파일로 만들어야 하는데, 이렇게 압축할 때 META-INF/services 안에 있는 Processor를 같이 압축해줘야 한다.

javax.annotation.processing.Processor

사실, 위 작업은 google에서 만든 auto-service를 이용하면 알아서 해주기 때문에 별로 신경 쓸 필요가 없으므로, 자세하게 들어가지는 말자. 참고 링크를 보면 조금 더 자세히 나와있으니 궁금하면 참고하도록 하자.

Java Poet 라이브러리

Annotation Processor를 구글에 검색해보면 가장 쉽게 볼 수 있는 것이 다음 포스팅에서 같이 만들어볼 Factory Annotation만들기이다. 이 때, Java Poet 라이브러리를 사용하여 소스 코드를 만드는데, Java Poet에 대해서도 알아보도록 하자.

Java Poet 라이브러리는 매우 유용해 보이는 라이브러리이므로, 더 공부해서 깊게 다루고 싶은 내용이지만, 일단 튜토리얼을 간단하게 따라해보도록 하자.


MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

간단한 HelloWorld 소스를 만드는 코드

public static void main(String[] args) throws IOException {
   System.out.println( "Hello World!" );
   HelloWorldPoet poet = new HelloWorldPoet();
   poet.generatedHelloWorld();
}
실행 결과
>> Hello World!
>> package com.example.helloworld;
>> import java.lang.String;
>> import java.lang.System;
>> public final class HelloWorld {
>>  public static void main(String[] args) {
>>    System.out.println("Hello, JavaPoet!");
>>  }
>>}

<출처>


0개의 댓글