Blog
스터디
CS Study with SON
15주차
자바 어노테이션

출처 - https://github.com/jmxx219/CS-Study (opens in a new tab)

Java Annotation

어노테이션이란

  • 어노테이션은 주석이다. 주석처럼 프로그래밍 언어에 영향을 미치지 않고 정보를 제공할 수 있다. 즉, 작동하는(구현된) 코드가 아니라 표시를 해놓은 것이다.
  • 어노테이션을 사용하여 프로그램에 필요한 설정 정보를 소스 코드안에 심을 수 있다.
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
void method(){
 
        }

등장 배경

  • 예전에는 소스 코드에 대한 문서를 따로 관리했고, 소스 코드 변경이 발생할 때마다 관련 문서를 수정해줬어야 했다. 이러한 불편한 점 때문에 소스 코드에 대한 문서를 따로 만들기보다 소스 코드와 문서를 하나의 파일로 관리할 수 있도록 소스 코드에 주석을 함께 작성하여 문서를 관리했다.

  • 위 기능을 응용하여 소스 코드 안에 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 어노테이션이다. 예전에는 프로그램을 위한 정보를 소스 코드와 분리된 설정 파일(ex: XML)을 따로 관리했었는데, 어노테이션을 사용함으로써 관리를 용이하게 했다.


엘리먼트 규칙

  • 어노테이션 내에 선언된 메서드를 엘리먼트라고 한다.

  • 각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.

    • 타입으로는 기본형, String, enum, annotation, Class 그리고 이들의 배열을 허용한다.
    • 엘리먼트 이름 뒤에는 ()를 붙여야 한다. ()안에 매개변수는 선언할 수 없다.
    • 엘리먼트를 타입 매개변수로 정의할 수 없다. (ex: ArrayList<T> list();)
    • 엘리먼트가 하나 뿐이고, 이름이 value인 경우, 어노테이션을 적용할 때 엘리먼트 이름을 생략하고 값만 적어도 된다.
      • public @interface MyAnnotation {
          String value();
        } 
        @MyAnnotation("Hello World")
          public class MyClass {
        }
  • 어노테이션에 값을 전달할 때 런타임 중에 알아내어야 하는 값은 들어갈 수 없다. 컴파일러 수준에서 해석되는 정적인 값만 들어갈 수 있다.

  • 엘리먼트에는 예외를 선언할 수 없다. (ex: int count() throws RuntimeException;)


@interface AnnotationElement {
    int count() default 1;
 
    String testBy();
 
    String[] testTools() default {"aaa", "bbb", "ccc"};
 
    EnumType enumType();
}

표준 어노테이션

자바에서 기본적으로 제공해주는 어노테이션이다.

어노테이션 이름설명장점
@Override컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.조상 메서드의 이름을 잘못 써서 오버라이딩을 하지 못하는 것을 방지할 수 있음.
@Deprecated앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.더이상 사용되지 않는 것을 삭제하지 않고, 사용하지 말라고 권장할 수 있음
@SuppressWarnings컴파일러의 특정 경고메시지가 나타나지 않게 해준다.컴파일러에 의해 생성되는 경고 메시지를 줄일 수 있음
@SafeVarargs메서드의 가변인자는 타입 안정성이 있다고 컴파일러에게 알린다.타입 안정성 경고 메시지를 줄일 수 있음
@FunctionalInterface함수형 인터페이스라는 것을 알린다.함수형 인터페이스를 올바르게 선언했는지 확인함
@Nativenative 메서드에 의해 참조되는 상수 앞에 붙인다.

@FunctionalInterface

  • 함수형 인터페이스라는 것을 알림
  • 함수형 인터페이스는 하나의 추상 메서드만을 가지는 인터페이스 (default 메서드나 static 메서드는 제외)
@FunctionalInterface
public interface SimpleFunctionalInterface {
    void execute();
}
 
public class Test {
    public static void main(String[] args) {
        // 람다 표현식을 사용하여 인터페이스 구현
        SimpleFunctionalInterface sfi = () -> System.out.println("Executing...");
        sfi.execute();
    }
}


메타 어노테이션

어노테이션을 정의하기 위한 어노테이션의 어노테이션이다.

Annotation설명
@Target어노테이션 적용 가능 대상을 지정 (CLASS, METHOD, FIELD, CONSTRUCTOR 등)
@Retention어노테이션이 유지되는 범위를 지정 (SOURCE, CLASS, RUNTIME)
@Documented어노테이션 정보가 javadoc으로 작성된 문서에 포함된다.
@Inherited상속받은 클래스에서도 어노테이션이 유지된다.
@Repetable하나의 대상에 같은 어노테이션을 여러번 붙일 수 있다

예시: @Target

 
@Documented
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션 유지됨
@Target(ElementType.ANNOTATION_TYPE) // 적용 대상은 어노테이션
public @interface Target {
    ElementType[] value();
}
 

Spring Annotation


Annotation설명
@Component스프링 컨테이너가 클래스를 스프링 빈 자동 등록할 때 사용
@Bean클래스 생성을 직접 제어하여 수동으로 스프링 빈으로 등록할 때 사용
@Autowired자동으로 의존성을 클래스에 주입받을때 사용됨
@Configuration설정 클래스임을 명시
@Qualifier("name")동일한 타입의 빈이 여러개일 경우 이름으로 식별할 때 사용
@ControllerPresentation Layer 클래스임을 명시
@ServiceService Layer 클래스임을 명시
@RepositoryPersistence Layer 클래스임을 명시
@ComponentScan스프링 컨텍스트에 등록될 클래스(@Component, @Bean, @Configuration 등이 붙은 )를 지정된 범위에서 스캔할 때 사용

상기 어노테이션 말고도 스프링에서 사용되는 어노테이션은 엄청나게 많은데, 스프링에서 많은 어노테이션을 사용하는 이유가 뭘까?

어노테이션이 개발된 이유와 동일하다. 스프링 설정을 위한 정보를 소스 코드에 포함시키기 위해서 어노테이션을 사용한다. 또한 어노테이션을 통해 클래스나 메서드의 역할을 바로 알 수 있기 때문에 가독성을 향상시킨다.


Lombok

반복적인 코드 작성을 줄이기 위해서 사용하는 라이브러리이다.

Annotation설명
@NoArgsConstructor파라미터 없는 기본 생성자를 자동으로 생성합니다.
@AllArgsConstructor모든 필드를 파라미터로 받는 생성자를 자동으로 생성합니다.
@RequiredArgsConstructorfinal 또는 @NonNull로 선언된 필드만을 파라미터로 받는 생성자를 자동으로 생성합니다.
@Getter클래스의 모든 필드에 대한 getter 메서드를 자동으로 생성합니다. 필드별로 어노테이션을 적용할 수도 있습니다.
@Setter클래스의 모든 필드에 대한 setter 메서드를 자동으로 생성합니다. 필드별로 어노테이션을 적용할 수도 있습니다.
@ToString클래스의 toString() 메서드를 오버라이드하여 필드 정보를 문자열로 반환하는 메서드를 자동으로 생성합니다.
@EqualsAndHashCodeequals()hashCode() 메서드를 자동으로 생성하여, 객체의 동등성 비교와 해시 코드 생성 로직을 제공합니다.
@Builder빌더 패턴을 구현하는 코드를 자동으로 생성하여, 객체의 점진적인 생성과정을 단순화시킵니다.
@Data@Getter, @Setter, @ToString, @EqualsAndHashCode와 필요에 따라 @RequiredArgsConstructor를 포함하여 적용합니다.
  • @Getter, @Setter, @ToString, @EqualsAndHashCode는 필드별로 적용할 수 있다.
  • @Getter, @Setter, @ToString, @EqualsAndHashCode등의 Annotation은 클래스 파일을 디컴파일 해보면 실제로 구현된 메서드를 볼 수 있다.
    • User.java 파일에 실제로 구현된 @Getter 클래스의 메서드를 보고 싶으면 User.class 파일을 디컴파일 해보면 된다.

주의점

@AllArgsConstructor@RequiredArgsConstructor를 사용할 경우, 같은 타입의 필드의 순서가 바뀌어도 알아차기 어렵다.

@AllArgsConstructor
public class Product {
    private int discountPrice; // 할인 가
    private int price;         // 정가
}

서비스 레이어에서 이렇게 사용하고 있다고 가정해보자

@Service
public class ProductService {
    public void test() {
        new Product(5_000, 10_000);
    }
}

어떠한 이유로 Product 필드 순서를 바꾼다고 가정해보자

@AllArgsConstructor
public class Product {
    private int price;
    private int discountPrice;
}

이렇게 변경되었을 때, 해당 클래스를 생성하는 다른 클래스는 변경을 알아차리기 어렵다. 동일한 문제가 @RequiredArgsConstructor에서도 발생한다.

@Data, @Value, @Builder 어노테이션은 모두 내부적으로 @AllArgsConstructor, @RequiredArgsConstructor 어노테이션을 사용한다.