자바의 어노테이션에 대해 학습하세요.

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

어노테이션 정의하는 방법

어노테이션 (Annotation)

  • @Override와 같이 @ 기호를 사용하는 문법 요소
  • java 5부터 등장

어노테이션의 장점

  • 기존 자바는 개발을 하며 각 계층별 설정 데이터들을 XML로 명시
  • 서비스의 규모가 커질수록 설정량이 많아지고 도메인 처리의 데이터들이 분산되어 수정이 힘들었다.
  • 어노테이션이 등장하며 데이터 유효성 검사 등 직접 클래스에 명시해 줄 수 있게 되어
    수정이 필요할 때 쉽게 파악할 수 있으며 어노테이션의 재사용도 가능하게 되었다.
  • AOP 를 쉽게 구성할 수 있게 해준다.

어노테이션의 용도

  • 문서화
  • 컴파일러 체크
  • 코드 분석과 자동 생성
  • 런타임 프로세싱
  • 유효성 검사와 같은 메타데이터 로써 사용되고 reflection을 이용하여 특정 클래스를 주입할 수도 있다.

어노테이션의 분류

  • Maker 어노테이션
    멤버 변수가 없고 컴파일러에게 의미를 전달하기 위한 표식으로 사용 ex. @Override
  • Single-Value 어노테이션
    멤버로 단일변수를 갖고 데이터를 전달할 수 있는 어노테이션
  • Full 어노테이션
    둘 이상의 변수를 갖는 어노테이션으로 데이터를 키 = 값 형태로 전달

빌트인 어노테이션

  • 자바에 내장되어 있는 어노테이션으로 컴파일러를 위한 어노테이션

@Override

  • 현재 메서드가 슈퍼 클래스의 메서드를 오버라이드한 것임을 명시

@Deprecated

  • 마커 어노테이션으로 다음 버전에 지원되지 않을 수 있기 때문에 사용하지 말라고 경고 알림

@SuppressWarning

  • 경고를 제거하는 어노테이션으로 개발자가 의도를 가지고 설계했는데 
    컴파일은 이를 알지 못하고 컴파일 경고를 띄울 수 있으므로 이를 제거하는 목적

@SafeVarargs

  • 자바 7 이상에서 사용 가능하고 제네릭 같은 가변인자 매개변수 사용 시 경고 무시

@Functionalinterface

  • 자바 8 이상에서 사용 가능하고 컴파일러에게 함수형 인터페이스라는 것을 알리는 어노테이션 이다.
  • 함수형 인터페이스
    1개의 추상 메서드만들 가지고 있는 인터페이스 ex.Runnable

메타 어노테이션

  • 어노테이션에 사용되는 어노테이션으로 어노테이션을 정의하기 위해 사용된다.

@Retention

  • 어노테이션이 유지되는 기간(Life time)을 설정
public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}
  • SOURCE
    .java 까지 남아있는다.
    컴파일러에 의해 버려진다.
  • CLASS
    .class 까지 남아있는다.
     Default
  • RUNTIME
    런타임까지 남아있는다 (= 사라지지 않는다)
    클래스파일에도 존재하고 런타임에 VM에 의해 유지되어 리플렉션을 통해
    클래스 파일의 정보를 읽어 처리 가능하다.

@Target

  • 어노테이션이 적용가능한 대상 ( 동작 대상 )을 지정한다.
  • 다른 타입이 온다면 컴파일 에러를 호출한다.
  • @Target(ElementType.~)와 같이 사용한다.
  • 단순하게 적용될 위치가 한개라면 ()를 사용해서 나타내지만 만약 여러개라면 {}를 넣어 이를 나타내게 된다.
public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE,
    MODULE,

    @jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,essentialAPI=true)
    RECORD_COMPONENT;
}

기본

  • TYPE
    Class, Interface(어노테이션 타입 포함), Enum, jdk 14에 생긴 record
  • FIELD
    필드 값(프로퍼티), Enum 상수 값
  • METHOD (메소드)
  • PARAMETER (매개변수)
  • CONSTRUCTOR (생성자)
  • LOCAL_VARIABLE (지역변수)
  • ANNOTATION_TYPE (어노테이션)
  • PACKAGE (자바 패키지)

jdk 1.8이후 추가

  • TYPE_PARAMETER (타입 매개 변수)
  • TYPE_USE (타입 사용)
  • MODULE (모듈)

jdk 14이후 추가

  • RECORD_COMPONENT (recode 컴포넌트)

@documented

  • 어노테이션의 정보가 javadoc 문서에 포함되도록 하는 어노테이션
package annotationex;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomAnnotation {
    String testDocument();
}
package annotationex;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomAnnotationNoDocument {
    String testDocumentNoDocument();
}
package annotationex;

public class CustomAnnotationTest {
    public static void main(String[] args) {

    }

    @CustomAnnotation(testDocument = "document test")
    public void testDocument() {}

    @CustomAnnotationNoDocument(testDocumentNoDocument = "document test")
    public void testNoDocument() {}


}
  • JavaDoc Export 시, @Documented를 사용한 어노테이션만 javadoc에 어노테이션 표시

 

@Inherited

  • 자식 클래스에게도 어노테이션이 상속되도록 하는 어노테이션

@Repeatable

  • 어노테이션을 반복적으로 선언할 수 있게 하는 어노테이션

커스텀 어노테이션

  • @interface 형태로 만들어진다.
  • 메타 어노테이션을 붙여 메타 데이터를 표시할 수 있다.
  • @interface 안에 매개변수가 없다면 Maker
    코드와 같이 한 개만 존재한다면 Single-value
    두 개 이상을 갖는다면 Full 어노테이션 이다. 
package annotationex;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
    String name() default "다슬";
}
package annotationex;

public class AnnotationExClass {
    @AnnotationTest
    private String defaultName;

    @AnnotationTest(name="다슬")
    private String customName;

    public AnnotationExClass() {
        this.defaultName = "다슬";
        this.customName = "다슬";
    }

    public String getDefaultName() {
        return defaultName;
    }
}

reflect를 활용한 어노테이션 값 접근

  • 매개 변수의 값은 "다슬" 이지만 각 필드에 할당된 어노테이션의 필드 값은 다르다.
package annotationex;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

public class AnnotationExClass {
    @AnnotationTest
    private String defaultName;

    @AnnotationTest(name="바보")
    private String customName;

    public AnnotationExClass() {
        this.defaultName = "다슬";
        this.customName = "다슬";
    }

    public String getDefaultName() {
        return defaultName;
    }
}

class AnnotationExClassTest {
    public static void main(String[] args) {
        AnnotationExClass annotationExClass = new AnnotationExClass();
        Field[] declaredFields = annotationExClass.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.print(declaredField.getName());
            Annotation[] declaredAnnotations = declaredField.getDeclaredAnnotations();
            for (Annotation declaredAnnotation : declaredAnnotations) {
                AnnotationTest declaredAnnotation1 = (AnnotationTest) declaredAnnotation;
                System.out.println(declaredAnnotation1.name());
            }
        }
    }
}

// defaultName리플렉션
// customName바보
  • Retention을 RUNTIME으로 지정하지 않을 경우 리플렉션을 이용한 값을 불러오지 못한다.


어노테이션 프로세서

  • 자바 컴파일러 플러그인의 일종
  • 이미 작성되어 있는 코드의 특정 지점을 가로채어 동작 방식에 변화를 주는 기술
  • 컴파일 도중 새로운 소스코드를 생성하거나 기존 소스코드를 변경할 수 있다.

어노테이션 프로세서 사용 예

  • 롬복 ( 기존 코드를 변경 )
  • AutoService ( 리소스 파일을 생성 )
  • @Override 

어노테이션 프로세서의 장점

  • 바이트코드에 대한 조작은 런타임에 발생되는 조작이므로 런타임에 대한 비용이 발생한다.
  • 애노테이션 프로세서는 어플리케이션을 구동하는 런타임 시점이 아닌,
    컴파일 시점에 조작하여 사용하므로써 런타임에 대한 비용이 제로가 된다
  • 하지만, 기존의 코드를 고치는 방법으로는 현재로써는 public한 API가 없다.

롬복(Lombok)의 동작원리

Lombok

  • @Getter @Setter @Builder 등의 어노테이션과
    어노테이션 프로세서를 제공하여 표준적으로 작성해야 할 코드를 개발자 대신 생성해주는 라이브러리이다.
  • 컴파일 시점에 애노테이션 프로세서를 사용하여
    소스코드의 AST (Abstract Syntax Tree)를 조작한다.
  • 소스코드의 AST는 원래 참조만 할 수 있다. / 그러나 수정이 됨을 확인함
  • 참조만 해야하는 것을 내부 클래스를 사용하여 기존의 코드를 조작하는 것으로 해킹이라고 하기도 한다.

논란

  • 공개된 API가 아닌 컴파일러 내부 클래스를 사용하여 기존 소스코드를 조작
  • 특히 이클립스의 경우 Java Agent를 사용하여 컴파일러 클래스까지 조작하여 사용한다.
  • 해당 클래스들 역시 공개된 API가 아니다보니 버전 호환성에서 문제가 생길 수 있고 언제라도 그런 문제가 발생해도 이상하지 않다.
  • 그럼에도 불구하고 편리함 때문에 널리 쓰이고 있으며,
    대안이 몇 가지 있지만 롬복의 모든 기능과 편의성을 대체하지는 못하는 상황이다.

ServiceLoader

  • 애플리케이션 내부에서 플러그인을 제공할 때 사용
  • 특정 기능을 제공하기 위한 인터페이스가 있고,
    다양한 벤더 회사들이 이 인터페이스를 기반으로 자신만의 구체 서비스를 구현하게 된다.
  • 사용자 입장에서는,
    어떤 벤더 회사가 어떻게 구현을 하던 공통 인터페이스만 가지고 있으면
    각 벤더 회사 서비스의 장단점을 고려해 원하는 구현체만 골라서 사용하면 된다.
  • 이러한 인터페이스를 SPI (Service Provider Interface) 라고 한다.

ServiceLoader 사용법

  • 사용자가 불특정 플러그인을 클래스패스가 읽을 수 있는 장소에 다운로드 했다고 가정하고,
    애플리케이션은 해당 클래스패스를 이용해 SPI를 구현하고 있는 모든 구현체를 애플리케이션 내부로 가져와야 함
  • 그리고 원하는 조건에 맞는 인스턴스를 골라 사용하면 된다.
  • 이 과정을 구현하기 위해 개발자가 리플렉션을 사용해 직접 구현할 수 있지만 자바에서 라이브러리 형태로 서비스 로더를 제공한다.
java.util.ServiceLoader

1. 인터페이스 정의 및 구현

public interface RandomInt{
	public int ranmdom();
}
package com.adduci;

public class MyRandomInt implements RandomInt{
	@Override
        public int random(){
        	return new Random().nextInt(10);
        }
}

2. META-INF/services 디렉터리를 만들고, 텍스트 파일을 넣어 위에 구현한 MyRandomInt 클래스 명시

com.adduci.MyRandomInt

3. 어플리케이션 내에서 로드하여 사용

ServiceLoader<RandomInt> loader = ServiceLoader.load(RandomInt.class);

Iterator<RandomInt> iterator = loader.iteratr();

for(RandomInt service : iterator){
	System.out.println(service.random());
}

// Stream 사용
loader.stream().map(Service.Provider::get).findFirst();

'Back-end > java' 카테고리의 다른 글

스터디 14주차 - 제네릭  (0) 2021.07.08
스터디 13주차 - I/O  (0) 2021.07.07
스터디 11주차 - Enum  (0) 2021.07.05
스터디 10주차 - 멀티쓰레드 프로그래밍  (0) 2021.07.02
스터디 9주차 - 예외 처리  (0) 2021.07.01

+ Recent posts