[Java] Annotation에 대해 알아보았다

PDM·2024년 8월 13일

Java나 Spring을 다룰 때, 아래의 코드들과 같이 @(;AT) 로 시작하는 무언가를 본 적이 있을 것이다.

// java
static class myObject {
	int value;
	String name;
	
	// ...
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
	
	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		return super.equals(obj);
	}
}
// Spring Boot
@Controller
public class HelloController {

	@GetMapping("hello")
	public String hello(Model model) {
		model.addAttribute("data", "spring!!");
		return "hello";
	}
}

이를 Java의 Annotation 이라고 한다.
최근 Java와 Spring Boot를 공부하면서 이를 많이 보았지만 프로그램에서 어떤 식으로 동작하는지 궁금증이 생겼다. 그래서 이에 대해 좀 더 자세히 알아보고자 한다.

Annotation 이란?

Annotation은 JAVA 5부터 추가된 기능이다. annotation은 '주석'이라는 뜻을 가진 단어이다.
그처럼 프로그래밍 언어 자체에는 영향을 미치지 않지만
class나 interface, method, field 등 소스 코드에 추가적인 정보(메타데이터)를 제공하기 위해 사용된다. (이는, Javadoc과 유사한 개념이라 할 수 있다.)
이러한 annotation을 통해 소스 코드의 가독성을 높이고, 유지보수를 용이하게 하며, 개발 과정을 효율화할 수 있다.

Annotation은 왜 만들어졌을까?

이전에 사용된 파일 관리 방법에 대해 알아보자.
annotation이 없을 때, 개발자는 코드에 특정 메타데이터나 설정 정보를 전달하기 위해 xml 파일과 같은 외부 설정 파일을 사용하여 관리하였다.

예시 이미지
하지만 위의 방식이라면 코드와 설정 파일의 일관성을 유지하기 어렵다.
설정 파일이 아닌 주석을 통해 정보를 제공하여도, 컴파일러나 런타임 시스템이 인식을 할 수 없는 문제가 발생하여 이 또한 좋은 방법은 아니었다.

설정 정보와 코드의 일관성을 유지하는 방법 등을 쉽게 할 수 있는 방법으로 나온 것이 Annotation(@)이라 볼 수 있다.

Annotation의 용도

Oracle에서 설명하는 용도는 다음과 같다.

  • 컴파일러에 대한 정보(Information for the compiler)
    어노테이션을 통해 컴파일러가 에러를 찾아내고 경고를 억제할 수 있다.
  • 컴파일 시간 및 배포 시간 처리(Compile-time and deployment-time processing)
    소프트웨어 도구는 annotation을 실행하여 코드를 나타낼 수 있다.
  • 런타임 처리(Runtime processing)
    일부 어노테이션들은 런타임에 검증되는 데에 사용될 수 있다.


Annotation의 종류

Annotation의 유형

Built-in Annotation

Java에서 기본적으로 제공하는 Annotation이다.
주로 컴파일러에게 특별한 지시를 내리거나 경고를 표시하는 데 사용된다.
아래는 Built-in Annotation의 예이다.

@Override

  • 메소드가 슈퍼클래스(super)의 메소드를 오버라이드하고 있음을 컴파일러에게 알린다.
  • 오버라이딩이 제대로 이루어지지 않은 경우, 컴파일러가 오류를 발생시킨다.

@Deprecated

  • 해당 요소(클래스, 메소드, 필드 등)가 더 이상 사용되지 않음을 알린다.
  • 해당 요소가 사용될 때 컴파일러가 경고를 표시한다.

@SafeVarArgs

  • Java에서 가변인자 메소드나 생성자에서 발생할 수 있는 경고를 억제하는 데 사용된다.
    주로 가변인자가 제네릭 타입일 때 발생하는 'unchecked' 경고를 무시하는 데 사용된다.
public class Example {
    @SafeVarargs
    public final <T> void printItems(T... items) {
        for (T item : items) {
            System.out.println(item);
        }
    }

    public static void main(String[] args) {
        SafeVarargsExample example = new SafeVarargsExample();
        example.printItems("Hello", "World");
    }
}
  • final 메서드, static 메서드, 생성자에서만 사용할 수 있으며 이는 상속을 통한 Overriding 에서의 타입 안전성을 보장하기 위해서이다.

@SuppressWarnings

  • 특정 컴파일러 경고를 무시하도록 지시한다.
  • ex) @SuppressWarnings("unchecked")

@FunctionalInterface

  • 함수형 인터페이스임을 명시적으로 표시하는 데 사용됨.
  • 함수형 인터페이스는 추상 메소드가 단 하나만 존재하는 인터페이스를 의미하며, 람다 표현식 또는 메소드 참조로 사용될 수 있다.
@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();

    // default 또는 static 메서드는 여러 개 정의할 수 있음
    default void anotherMethod() {
        System.out.println("Another method");
    }
}

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        MyFunctionalInterface func = () -> System.out.println("Lambda implementation");
        func.myMethod();
    }
}
  • 컴파일 타임에 함수형 인터페이스의 정의를 검증하고, 가독성과 코드의 명확성을 높이는 데 도움이 된다.

Meta Annotation

Annotation을 정의할 때 사용하는 annotation이다.
동작 방식을 설정하거나 제어하는 데 사용되며 Java에는 여러 meta annotation이 존재한다.

  1. @Retention
    annotation의 유지 정책(Retention Policy)을 지정하는 데 사용된다.
    annotation이 언제까지 유지될 것인지를 정의하며, RetentionPolicy는 세 가지 종류가 있다.
  • SOURCE
    annotation이 소스 코드에만 존재하며, 컴파일 과정에서 제거된다.
    @Override 이 SOURCE 레벨로 유지되는 annotation의 예시이다.
  • CLASS
    annotation이 컴파일된 클래스 파일에 포함되지만, 런타임에서는 사용할 수 없다.
    기본값으로 많이 사용된다.
  • RUNTIME
    annotation이 런타임까지 유지되며, 리플렉션을 통해 annotation 정보를 읽을 수 있다.
    @Deprecated@SuppressWarnings가 그 예시이다.

Java의 리플렉션(Reflection)
프로그램이 실행 중에 클래스, 인터페이스, 메소드, 필드 등을 동적으로 분석하고 조작할 수 있는 기능을 의미한다. 런타임에 객체의 정보를 알 수 있고 메소드를 호출하거나 필드에 접근할 수 있다.
이는 클래스와 구성 요소에 대한 메타데이터가 런타임 동안 유지되며 이는 자바의 클래스 파일에 포함되어 있다. 그리고 JVM이 이를 읽고 관리한다.
리플렉션 API는 이 정보를 활용하여 런타임에 클래스의 구조를 탐색하고 조작할 수 있게 해준다.
Annotation의 작동 원리가 이 리플렉션에 있다고 할 수 있다.

  1. @Target
    annotation이 적용될 수 있는 요소(대상)을 지정하는 데 사용된다.
    이를 통해 annotation이 적용될 위치를 제한할 수 있다.

    ElementType 유형설명
    ANNOTATION_TYPE다른 annotation에 적용 가능
    CONSTRUCTOR생성자에 적용 가능
    FIELD필드(멤버 변수)에 적용 가능
    LOCAL_VARIABLE로컬 변수에 적용 가능
    METHOD메소드에 적용 가능
    PACKAGE패키지에 적용 가능
    PARAMETER메소드 매개변수에 적용 가능
    TYPE클래스, 인터페이스(annotation 포함), 열거형에 적용 가능
  1. @Inherited
    annotation이 자식 클래스에 상속될 수 있음을 나타낸다.
    기본적으로 annotation은 상속되지 않지만, @Inherited를 사용하면 부모 클래스에 정의된 annotation이 자식 클래스에서 상속된다.

  2. @Documented
    해당 annotation이 Javadoc과 같은 도구로 생성된 문서에 포함되어야 함을 나타낸다.
    즉, annotation이 API 문서에 노출되어야 할 때 사용한다.

  3. @Repeatable
    같은 annotation을 한 요소에 여러 번 적용할 수 있게 해준다.
    이를 사용하려면, annotation 타입을 중복 적용할 수 있도록 annotation 컨테이너를 정의해야 한다.

import java.lang.annotation.Repeatable;

@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value();
}

public @interface MyAnnotations {
    MyAnnotation[] value();
}

// 사용 예시
@MyAnnotation("First")
@MyAnnotation("Second")
public class MyClass {
}

Custom Annotation

Java에서 개발자가 특정한 목적을 위해 직접 정의한 Annotation
만드는 방법은 아래에 후술

Annotation의 타입(Category)

먼저 어떤 annotation이든 가질 수 있는 타입은 3(+2)가지이다.

  1. Marker Annotations
    아무런 element를 가지지 않고 단순히 표시하기 위한 annotation이다.
    대표적인 예시로 @Override가 있다.

  2. Single value Annotations
    하나의 element만 가지고 있으며, 값을 명시하여 데이터를 전달하기 위해 사용된다.

    @TestAnnotation("Testing")

  3. Full Annotations
    둘 이상의 element를 갖는 annotation. 데이터를 key-value 형태로 전달하게 된다.

    @TestAnnotation(owner="Peter", value="Class myObject")




이후 아래의 2가지는 Java 8 부터 제공하는 annotation 타입이다.

이전 버전의 Java에서는 선언에만 Annotation을 쓸 수 있었다.

  1. Type Annotations
    어노테이션을 이용하여 타입 체크를 더 강력하게 하기 위해 만들어졌다.
    타입을 사용하는 곳이면 어디든 어노테이션을 사용할 수 있다.
	@NotNull String str1 = "hello";
    
    MyObject object = new @Interned MyObject();
  1. Repeating Annotations
    annotation을 선언하거나 타입을 사용할 때, 동일한 annotation을 반복해서 사용할 수 있다.
	@Schedule(dayOfMonth="last")
    @Schedule(dayOfWeek="Fri", hour="23")
    public void doPeriodicCleanUp() {
    	// ...
    }

Custom Annotation는 어떻게 만드는가?

  1. Annotation 인터페이스 정의
    annotation은 @interface 키워드를 사용하여 인터페이스처럼 정의된다.
    정의 내부에는 메서드처럼 보이는 요소들을 정의할 수 있다.
    이는 애너테이션을 사용할 때 값을 지정할 수 있는 항목이 된다.
public @interface CustomAnnotation {
	String value();
    int number() default 0; // 기본값 지정 가능
}

위와 같이 value, number라는 두 개의 요소를 가진 'CustomAnnotation'이라는 Annotation을 정의했다.

  1. Meta Annotation 추가
    필요에 따라, meta annotation을 사용하여 동작 방식을 정의할 수 있다.
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 CustomAnnotation {
    String value();
    int number() default 0;
}
  1. Annotation 사용
    정의한 annotation을 실제 코드에서 사용.
public class MyClass {

    @CustomAnnotation(value = "Example usage", number = 5)
    public void myMethod() {
        // Method implementation
    }
}
  1. 리플렉션을 통한 annotation 정보 읽기
    런타임에 리플렉션을 사용하여 annotation 정보를 읽어올 수 있다.
    이를 통해 annotation에 지정된 값을 동적으로 처리할 수 있다.
import java.lang.reflect.Method;

public class AnnotationProcessor {
    public static void main(String[] args) {
        try {
            // Method method = MyClass.class.getMethod("myMethod");
            Method method = MyClass.getClass().getDeclaredMethod("myMethod");
            if (method.isAnnotationPresent(CustomAnnotation.class)) {
                CustomAnnotation annotation = method.getAnnotation(CustomAnnotation.class);
                System.out.println("Value: " + annotation.value());
                System.out.println("Number: " + annotation.number());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

Spring Boot의 Annotation을 살펴보았다

위의 정보들을 활용하여 Spring Boot에서 많이 사용되는 Annotation 중 하나인 @RestController를 살펴보았다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 * @since 4.0.1
	 */
	@AliasFor(annotation = Controller.class)
	String value() default "";

}

Spring Boot의 @RestController는 Restful 서비스를 개발할 때 사용하는 Annotation이다.
이 Annotation을 보면, @Retention(RetentionPolicy.RUNTIME) 으로 설정되어 있어
런타임까지 이 Annotation이 유지되어, 리플렉션을 통해 annotation의 정보를 읽을 수 있게 설정되어 있다.
@Target(ElementType.TYPE) 으로 클래스, 인터페이스, 열거형에만 사용할 수 있게 설정되었다.
메소드에는 사용할 수 없다!

@Documented Annotation이 있어 Javadoc과 같은 도구를 이용하여 해당 API에 대한 설명이 있어야 함을 명시하였고,
실제 구현되어 있는 곳에서도 Javadoc을 통해 API 문서에 노출되어 있다.

그리고 @RestController 에는 @Controller@ResponseBody가 같이 명시되어 있어,
@RestController는 이 둘의 기능을 모두 제공함을 알 수 있다.

@Controller
요청을 처리하고 모델 데이터를 View에 전달하는 역할.

@ResponseBody
메소드의 반환 값을 View 이름으로 사용하지 않고, HTTP 응답 본문에 직접 쓰도록 지정한다.
즉, 메소드의 반환된 객체는 자동으로 JSON이나 XML 형식으로 변환되어 HTTP response으로 클라이언트에게 전달된다.

위와 같이 Spring Boot에서 기본적으로 제공하는 Annotation 등이 어떤 역할을 하는지 파악할 수 있다.

참고 사이트

profile
조금씩 조금씩 성장하자

0개의 댓글