(자바) 참고

모든 샘플 코드는 깃허브이런 경우가 있으니 참고하세요


주석이란 무엇입니까

Java 5에 도입된 기능인 주석은 Java 컴파일러 또는 JVM에 추가 정보를 제공하는 일종의 메타데이터입니다.

@Override
@Getter @Setter @Data
@Controller
...

메타데이터란 무엇입니까?
메타데이터는 데이터를 설명하는 데이터를 의미하며, 예를 들어 이미지의 데이터에는 이미지 자체와 직접적인 관련이 없는 이미지의 크기, 해상도, 날짜 및 시간과 같은 추가 정보가 포함되어 있습니다.
이러한 사진에 대한 추가 정보를 메타데이터라고 합니다.

1. 코드 구문이나 경고를 표시하지 않도록 컴파일러에 지시할 수 있습니다.

@덮어쓰기

메서드의 시작 부분에만 추가할 수 있는 주석으로, 부모 클래스의 메서드를 재정의하고 있음을 컴파일러에 알립니다. 재정의가 부모 클래스 메서드를 잘못 사용하는 경우 컴파일러는 메서드가 잘못되었다고 알려줍니다.


대문자 I 대신 소문자 l로 표시

method does not override or implement a method from a supertype

2. 소프트웨어 도구(Lombok, Mapstruct, …)는 컴파일 시간 또는 배포 시간에 코드를 생성하기 위한 주석 정보를 제공합니다.

@데이터

Lombok의 주석은 모든 필드에 대한 접근자, 수정자 및 생성자와 같은 메서드를 자동으로 생성합니다.


따라서 적절한 주석이 첨부되면 클래스 내에 접근자와 수정자(getter/setter)가 없어도 사용할 수 있습니다.

이러한 주석을 사용하는 경우 상용구 코드반복되는 코드를 줄일 수 있기 때문에 코드가 간결해지고 읽어야 할 코드 수도 줄어듭니다.

그러나 주석의 용도는 무한합니다. 의미가 함축되어 있기 때문에 동작이 무엇인지 명확하지 않습니다. 또한 특정 주석을 재설계해야 할 경우 그 동작과 의미를 정확히 알기 어렵다.

이렇듯 현재로서는 많은 장점이 있는 것 같지만 멀리서 봤을 때 사용하는 것이 적절한지 따져본 후 적용하는 것이 바람직하다.

주석 정의

주석은 인터페이스를 생성할 때와 같이 정의할 수 있지만 인터페이스 앞에 ‘@’ 기호를 추가하여 정의할 수 있습니다.

주석에 선언된 메소드. 주석의 요소말하다, 매개변수가 없고 반환 유형이 있는 양식그것은 구성

public @interface 애너테이션이름 {
    타입 요소이름() // 애너테이션의 요소를 선언
}
---
public @interface CustomAnnotation {
    String name() default "기본값";
}

기본적으로 주석을 붙일 때 주석의 요소 값을 설정해야 하는데 위와 같이 기본값이 설정되어 있으면 생략하고 기본값으로 설정할 수 있다.

(디폴트 값이 설정되지 않은 요소가 있는 경우, 값을 지정하지 않으면 컴파일 타임 에러가 발생합니다.)

주석 규칙

  1. 요소 유형은 기본 유형, 문자열, 열거형, 주석 및 클래스에만 허용됩니다.
  2. 주석 요소에는 매개변수를 할당할 수 없습니다.
  3. 예외는 설명할 수 없습니다. (즉, 예외를 던지는 던지기를 사용할 수 없습니다)
  4. 주석 요소의 유형에 대해 유형 매개변수를 정의할 수 없습니다.

메타 주석

사용자 지정 주석을 정의할 때 사용되는 메타 주석을 살펴보겠습니다.


이미지 출처: Java Classics 3rd Edition Vol. 2 P703

@목표

괄호({ })는 여러 대상 유형을 지정할 때 주석을 적용할 수 있는 영역을 지정하는 주석으로 배열처럼 사용해야 합니다.

https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/ElementType.html

ElementType(자바 플랫폼 SE 8)

이 열거 유형의 상수는 주석이 Java 프로그램에 나타날 수 있는 구문 위치의 간단한 분류를 제공합니다. 이러한 상수는 java.lang.annotation.Target 메타 주석에서 쓰기가 허용되는 위치를 나타내는 데 사용됩니다.

docs.oracle.com

@Target({ElementType.FIELD, ElementType.TYPE, ElementType.TYPE_USE})
public @interface MyAnnotation {}

---

@MyAnnotation     // 적용 대상이 TYPE
public class MyClass {
    @MyAnnotation // 적용 대상이 FIELD
    int i;
    
    @MyAnnotation
    MyClass mc;   // 적용 대상이 TYPE_USE
}

@withholding

주석이 유지되는 기간을 결정하는 주석입니다. 보존 정책이 지정되지 않은 경우 이를 CLASS라고 합니다.

https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/RetentionPolicy.html

보존 정책(Java Platform SE 8)

docs.oracle.com

보존 정책 의미
원천 – 주석은 소스 파일에만 존재합니다. 이것은 말 그대로 주석으로 사용하는 것을 의미합니다.
컴파일러가 컴파일되면 이 주석에 대한 메모리가 삭제됩니다.
수업 – 주석은 클래스 파일에 있지만 런타임에는 사용할 수 없습니다. (기본)
– 컴파일러는 컴파일 타임에 주석을 저장하지만 런타임에 사라집니다. “런타임에 사라짐”이라는 문구는 리플렉션을 통해 선언된 주석에서 데이터를 검색할 수 없음을 의미합니다.
지속 – 클래스 파일(*.class)에 존재하며 실행 시 사용할 수 있습니다.
– JVM은 Java 바이트코드 클래스 파일을 런타임 환경으로 구성하고 런타임이 종료될 때까지 메모리에 남아 있습니다.

각 유지보수 정책에 대한 정의를 적어두었는데 CLASS와 같이 모호한 부분이 있어 정책별로 컴파일된 클래스를 살펴보도록 하겠습니다.

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

@Retention(RetentionPolicy.{SOURCE/CLASS/RUNTIME})
@Target(ElementType.METHOD)
public @interface RetentionAnnotation {}

사용자 지정 주석인 RetentionAnnotation은 다음과 같이 정의되며 다음 예제에서는 보존 정책만 수정됩니다.

그리고 이 주석은 RetentionTest 클래스의 printClass 메서드에 적용됩니다.

public class RetentionTest {
    @RetentionAnnotation
    void printClass() {}
}

다음 코드 블록 예제는 소스 파일(xx.java)의 코드가 아니라 컴파일된 클래스 파일(xx.class)의 디컴파일된 코드입니다.

원천

// RetentionTest.class
package annotation;

public class RetentionTest {
    public RetentionTest() {
    }

    void printClass() {
    }
}

유지 관리 정책이 SOURCE인 경우 주석처럼 사용되므로 소스 파일에만 존재하고 클래스 파일에는 존재하지 않습니다.

수업

package annotation;

public class RetentionTest {
    public RetentionTest() {
    }

    @RetentionAnnotation
    void printClass() {
    }
}

유지 관리 정책이 CLASS인 경우 클래스 파일에도 주석이 존재하도록 정의됩니다. 따라서 해당 클래스 파일에도 애노테이션이 적용되어 있음을 알 수 있다.

그 다음에

런타임에 주석이 사라진다는 정보는 어디에서 얻을 수 있습니까?

디컴파일 되지 않은 클래스 파일을 직접 열면 밑에 바이트코드로 쓰여진 부분이 있습니다. RunTimeInVisibleAnnotation존재하는데 이 부분으로 나뉩니다.


RetentionTest.class

CLASS 유지 정책은 바이트코드에서 확인할 수 있는 수준에서만 정의되며 주석 정보는 런타임 시 사라진다.

클래스 파일과 런타임에 주석을 보존하는 RUNTIME 정책은 어떻습니까?

지속

package annotation;

public class RetentionTest {
    public RetentionTest() {
    }

    @RetentionAnnotation
    void printClass() {
    }
}

물론 CLASS가 아닌 클래스 파일을 직접 열었을 때 디컴파일된 클래스 파일에 주석이 보존되는 것을 볼 수 있습니다. 런타임 표시 주석로 정의되어 있음을 알 수 있다.


@상속

부모 클래스에서 정의된 주석은 자식 클래스에서 상속됩니다.

import java.lang.annotation.*;

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
    String value() default "inheritance";
}
public class InheritedTest {
    public static void main(String() args) {
        Parent parent = new Parent();
        Child child = new Child();

        InheritedAnnotation parentAnnotation = parent.getClass().getAnnotation(InheritedAnnotation.class);
        InheritedAnnotation childAnnotation = child.getClass().getAnnotation(InheritedAnnotation.class);

        String parentVal = parentAnnotation.value();
        String childVal = childAnnotation.value();

        System.out.println("parentVal = " + parentVal); // parentVal = parent
        System.out.println("childVal = " + childVal);   // childVal = parent

    }
}

@InheritedAnnotation(value = "parent")
class Parent {}
class Child extends Parent {}

InheritedAnnotation에 상속된 주석이 없으면 런타임에 NPE(NullPointerException)가 발생합니다.


@반복 가능

기본적으로 하나의 객체에 하나의 주석만 연결할 수 있으며 해당 주석이 지원되기 전에는 여러 속성을 정의해야 할 때 다음과 같이 정의했습니다.

@Chrome
@Firefox
@Edge
public class WebBrowser { ... }

JDK 1.8부터는 동일한 주석을 중복 정의할 수 있는 @Repeatable을 사용하여 클래스 또는 메소드에서 주석을 여러 번 정의할 수 있습니다.

@Browser(webBrowser = "Chrome")
@Browser(webBrowser = "Firefox")
@Browser(webBrowser = "Edge")
class WebBrowser {}

다만, 일반 노트와 달리 이 노트는 하나의 대상에 동일한 이름의 여러 주석이 적용되므로 주석을 그룹으로 관리하는 컨테이너 주석도 정의해야 합니다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Browsers { // 컨테이너 애너테이션
    Browser() value();       // 이름이 반드시 value여야 함
}
@Repeatable(value = Browsers.class)
public @interface Browser {
    String webBrowser();
}
public class RepeatableTest {
    public static void main(String() args) {

        WebBrowser webBrowser = new WebBrowser();
        Browsers browsers = webBrowser.getClass().getAnnotation(Browsers.class);

        for (Browser browser : browsers.value()) {
            System.out.println("browser = " + browser);
        }
        // (실행결과)
        // browser = @annotation.Browser(webBrowser="Chrome")
        // browser = @annotation.Browser(webBrowser="Firefox")
        // browser = @annotation.Browser(webBrowser="Edge")
    }
}

@Browser(webBrowser = "Chrome")
@Browser(webBrowser = "Firefox")
@Browser(webBrowser = "Edge")
class WebBrowser {}

사용자 지정 주석의 예

지금까지의 내용을 바탕으로 간단한 커스텀 애노테이션을 생성하고 적용하는 예를 들어보겠습니다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomAnnotation {
    String name() default "기본값";
}

Annotation은 위와 같이 정의되어 있으며, 리플렉션을 통해 import하여 annotation 정보를 확인하는 예이다.

public class AnnotationTest {

    @CustomAnnotation(name = "스터디")
    static class MyClass {}

    @CustomAnnotation
    static class DefaultClass {}

    @Test
    @DisplayName("애너테이션 예제")
    void annotationTest() throws Exception {
        AnnotationExample.DefaultClass defaultClass = new AnnotationExample.DefaultClass();
        CustomAnnotation customAnnotation = defaultClass.getClass().getAnnotation(CustomAnnotation.class);
        String 기본값 = customAnnotation.name();
        System.out.println("(Default) customAnnotation.name() = " + 기본값);

        AnnotationExample.MyClass myClass = new AnnotationExample.MyClass();
        customAnnotation = myClass.getClass().getAnnotation(CustomAnnotation.class);
        String 스터디 = customAnnotation.name();
        System.out.println("(Assign)  customAnnotation.name() = " + 스터디);

        assertThat("기본값").isEqualTo(기본값);
        assertThat("스터디").isEqualTo(스터디);
    }
}
// (실행 결과)
// (Default) customAnnotation.name() = 기본값
// (Assign)  customAnnotation.name() = 스터디


아까부터 애노테이션과 AOP로 인증 처리를 해보는 것도 할일 목록 중 하나였는데, 조금 적용해서 요청 값으로 한글 이름만 허용하도록 구현했습니다.

https://github.com/ahn-sj/spring-box/blob/master/annotation-sample/src/main/java/springbox/annotationsample/annotation/aspect/KoreanNameCheckerAspect.java

GitHub – ahn-sj/spring-box: 스프링을 더 잘 다루고 싶다. 이 저장소는 내가 배운 것을 적용하기 위한 것입니다.

나는 봄을 더 잘 다루고 싶다. 이 저장소는 내가 배운 것을 적용하기 위한 것입니다. – GitHub – ahn-sj/spring-box: 스프링을 더 잘 다루고 싶습니다. 이 저장소는 내가 무엇을 적용하기 위한 것입니다…

github.com

참조


잘못된 정보나 내용이 있다면 댓글로 남겨주세요.