Annotation

zl존석동

·

2022. 2. 20. 19:48

 

 

어노테이션이란?

어노테이션(Annotation)은 코드에 메타데이터를 추가하는 방법이다.

어노테이션은 @ 기호로 시작하며, 컴파일러나 런타임 시점에 정보를 제공하는데 사용된다.

 

 

언제쓸까?

 

컴파일 시점 체킹

어노테이션을 사용하여 코드를 컴파일할 때 에러를 검출하는 기능을 추가할 수 있다

 

런타임 처리

어노테이션을 사용하여 런타임에 동작을 변경하는 기능을 추가할 수 있다

 

문서

어노테이션을 사용하여 문서를 생성하는 기능을 추가할 수 있다.

 

 

자바 표준 어노테이션

 

@Override

선언한 메소드가 상속받은 것임을 나타낸다.

 

@SuppressWarnings

선언한 곳의 컴파일 경고를 무시한다.

 

@Deprecated

선언한 곳이 더이상 사용되지 않는 부분임을 선언하며 사용하면 컴파일 시 경고를 해준다.

 

@FunctionalInterface

java8에서 나온 것으로 함수형 인터페이스임을 지정해주는 어노테이션이다.

따라서 default 메소드를 제외한 하나의 유일한 메소드가 반드시 있어야 한다.

 

 

어노테이션을 위한 어노테이션

 

@Retention

자바 컴파일러가 어노테이션을 다루게 하는 방법을 선언해주는 것이다.

생명주기와 관련있다.

 

- RetentionPolicy.SOURCE : 컴파일 전까지 유효.

- RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효.

- RetentionPolicy.RUNTIME : 런타임 중에도 참조 가능(리플렉션)

 

@Inherited

어노테이션을 상속 가능하게 해준다.

 

@Documented

javadoc에 포함시킨다.

 

@Target

어노테이션을 선언할 위치를 선언해준다.

 

- ElementType.PACKAGE : 패키지 선언

- ElementType.TYPE : 타입 선언

- ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언

- ElementType.CONSTRUCTOR : 생성자 선언

- ElementType.FIELD : 멤버 변수 선언

- ElementType.LOCAL_VARIABLE : 지역 변수 선언

- ElementType.METHOD : 메서드 선언

- ElementType.PARAMETER : 전달인자 선언

 

 

 

사용예시

 

DI를 구현해보자

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}

클래스의 멤버에 사용해야 하니 FIELD 타겟을 선언했다.

런타임 시점에 찾아서 주입해야 되니 RUNTIME으로 생명주기를 선언했다.

 

 

public class AutowiredAnnotationProcessor {
    public static void process(Object object) throws IllegalAccessException {
        Class<?> clazz = object.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                Class<?> fieldClass = field.getType();
                Object fieldValue = findBean(fieldClass);
                field.set(object, fieldValue);
            }
        }
    }

    // 필드 타입에 해당하는 구현체를 찾는 메소드
    private static Object findBean(Class<?> clazz) {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new Exception("Failed to instantiate class " + clazz.getName() + ": " + e.getMessage());
        }
    }
}

DI를 실제로 처리할 수행 클래스를 작성한다.

@Autowired 가 붙은 필드의 타입을 가져와 찾아 초기화 한 다음 해당 필드에 넣어준다.

 

물론 이 경우 인터페이스 필드에 대해 사용된다면 대응할 수 없고

여러 곳에서 주입되는 경우 새로운 인스턴스를 계속해서 가져오는 형태겠지만 싱글턴 패턴을 사용해서 한 번만 초기화 하고 두번째 부터는 같은 인스턴스를 반환하게 해줄 수 있다.

 

public class Person {
    @Autowired
    private SomeService someService;

    public void say() {
        System.out.println(someService.getSomething());
    }
}

public static void main(String[] args) throws IllegalAccessException {
    Person person = new Person();
    AutowiredAnnotationProcessor.process(person);
    person.say();
}