Java

[JAVA] 어노테이션

김개발자새발자 2023. 4. 24. 22:15

나는 스프링 프레임워크로 웹 개발에 입문했다. 

스프링은 개발 시에 많은 어노테이션을 사용하여 편하게 개발할 수 있도록 도와준다.

그래서 이번엔 평소에 궁금했던 어노테이션에 관하여 알아보고 커스텀 어노테이션을 만들어 보았다. 


우선 어노테이션은 기본적으로 세가지 타입이 존재한다. 

 

Marker Annotation : 표시만 해 놓는 어노테이션

Sigle Value Annotation : 하나의 입력 값만 존재하는 어노테이션

Multi Value Annotation : 여러 입력 값이 존재하는 어노테이션


다음으로 커스텀 어노테이션은 어노테이션을 위한 어노테이션인 메타 어노테이션(Meta Annotation)을 사용한다.

 

@Retention : 컴파일러가 어노테이션을 다루는 방법을 기술, 어느 시점까지 영향을 미치는지를 결정
RetentionPolicy.SOURCE : 컴파일 전까지만 유효
RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효
RetentionPolicy.RUNTIME : 컴파일 이후 런타임 시기에도 JVM에 의해 참조가 가능(리플렉션)

 

@Target : 어노테이션 적용할 위치 선택
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 : 해당 어노테이션을 Javadoc에 포함시킴


@Inherited : 어노테이션의 상속을 가능하게 함


@Repeatable : Java8 부터 지원하며, 연속적으로 어노테이션을 선언할 수 있게 함


어노테이션은 추가적인 정보만 제공해준다. 이 정보를 이용하기 위해 자바 리플렉션을 사용하여 개발자가 표시한 정보에 접근하고 해당 정보를 활용하도록 프로그램을 작성하는 것이다.

 

아래는 자바 리플렉션에 관해 참조할 만한 글이다

https://steady-coding.tistory.com/609

 

[Java] Reflection 개념 및 사용 방법

java-study에서 스터디를 진행하고 있습니다. Reflection이란? 리플렉션은 힙 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메

steady-coding.tistory.com

추후 리플렉션에 대한 글도 포스팅 할 예정이다.


커스텀 어노테이션을 생성하여 이를 활용해 보았다. 

과일에 관한 정보를 입력하도록 커스텀 어노테이션을 구성하고 이를 실행한다.

 

1. @FruitColor 어노테이션

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    enum Color{BLUE, RED, GREEN}

    Color fruitColor() default Color.GREEN;
}

클래스의 필드 값에 적용하도록 @Target 을 ElementType.FIELD

런타임 시기에도 값을 참조할 수 있도록 @Retention 을 RetentionPolicy.RUNTIME (리플렉션을 사용하려면 런타임 시기까지 어노테이션이 살아있도록 해야한다.)

로 구성하였다.

 

2. @FruitName 어노테이션

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "모르는 과일";
}

1번과 같이 동일하게 적용되도록 하고 값을 value 에 대한 값을 갖도록 설정해 주었다. 

 

3. @FruitProvider 어노테이션

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    int id() default -1;
    String name() default "";
    String address() default "";
}

1, 2번과 동일하게 적용되도록 하고 multi value 를 갖도록 만들었다.

 

4. FruitInfoUtil

어노테이션에 관한 정보를 사용할 수 있도록 Util을 만들어 주었다.

import java.lang.reflect.Field;

public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz){
        String strFruitName = " 과일 이름 : ";
        String strFruitColor = " 과일 색 : ";
        String strFruitProvider = " 과일 파는 곳 : ";

        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields){
            if (field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value();
                System.out.println(strFruitName);
            }else if(field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor + fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            }else if(field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                strFruitProvider = " 과일 파는 곳의 ID: " + fruitProvider.id() 
                		+ " 지점 이름 : " + fruitProvider.name() 
                        + " 지점 주소 : " + fruitProvider.address();
                System.out.println(strFruitProvider);
            }
        }
    }
}

리플렉션 API 인 class.getDeclaredFields()를 사용하여, 어노테이션이 사용된 클래스들을 전부 가져와 

반복하며 어노테이션이 존재하는 필드를 처리하는 것이다.

 

5. Apple

정보를 입력할 클래스를 생성하고 어노테이션으로 각 필드에 표시해준다.

public class Apple {
    @FruitName("Apple")
    private String appleName;
    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;
    @FruitProvider(id = 1, name = "HomePlus", address = "Seoul")
    private String appleProvider;

    public String getAppleName() {
        return appleName;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }
}

6. FruitRun

public class FruitRun {
    public static void main(String[] args){
        FruitInfoUtil.getFruitInfo(Apple.class);
    }
}

main 메소드에서 Util의 메소드를 사용해주면 

 과일 이름 : Apple
 과일 색 : RED
 과일 파는 곳의 ID: 1 지점 이름 : HomePlus 지점 주소 : Seoul

이와같이 사전에 클래스 파일에 설정한 값대로 결과가 나타나게 된다.

 


스프링에서 주로 Bean을 등록할 때 어노테이션을 많이 사용한다. 

이는 @Component를 적용한 클래스들을 스프링이 시작된 후 ClassPathBeanDefinitionScanner.java 파일의 doScan() 메서드에서 스캔하며 IoC컨테이너에 등록하는 과정을 거쳐 적절하게 사용될 수 있게 한다.