모든 샘플 코드는 깃허브이런 경우가 있으니 참고하세요
주석이란 무엇입니까
Java 5에 도입된 기능인 주석은 Java 컴파일러 또는 JVM에 추가 정보를 제공하는 일종의 메타데이터입니다.
@Override
@Getter @Setter @Data
@Controller
...
메타데이터란 무엇입니까?
메타데이터는 데이터를 설명하는 데이터를 의미하며, 예를 들어 이미지의 데이터에는 이미지 자체와 직접적인 관련이 없는 이미지의 크기, 해상도, 날짜 및 시간과 같은 추가 정보가 포함되어 있습니다.
이러한 사진에 대한 추가 정보를 메타데이터라고 합니다.
1. 코드 구문이나 경고를 표시하지 않도록 컴파일러에 지시할 수 있습니다.
@덮어쓰기
메서드의 시작 부분에만 추가할 수 있는 주석으로, 부모 클래스의 메서드를 재정의하고 있음을 컴파일러에 알립니다. 재정의가 부모 클래스 메서드를 잘못 사용하는 경우 컴파일러는 메서드가 잘못되었다고 알려줍니다.

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 "기본값";
}
기본적으로 주석을 붙일 때 주석의 요소 값을 설정해야 하는데 위와 같이 기본값이 설정되어 있으면 생략하고 기본값으로 설정할 수 있다.
(디폴트 값이 설정되지 않은 요소가 있는 경우, 값을 지정하지 않으면 컴파일 타임 에러가 발생합니다.)
주석 규칙
- 요소 유형은 기본 유형, 문자열, 열거형, 주석 및 클래스에만 허용됩니다.
- 주석 요소에는 매개변수를 할당할 수 없습니다.
- 예외는 설명할 수 없습니다. (즉, 예외를 던지는 던지기를 사용할 수 없습니다)
- 주석 요소의 유형에 대해 유형 매개변수를 정의할 수 없습니다.
메타 주석
사용자 지정 주석을 정의할 때 사용되는 메타 주석을 살펴보겠습니다.

@목표
괄호({ })는 여러 대상 유형을 지정할 때 주석을 적용할 수 있는 영역을 지정하는 주석으로 배열처럼 사용해야 합니다.
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존재하는데 이 부분으로 나뉩니다.

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로 인증 처리를 해보는 것도 할일 목록 중 하나였는데, 조금 적용해서 요청 값으로 한글 이름만 허용하도록 구현했습니다.
GitHub – ahn-sj/spring-box: 스프링을 더 잘 다루고 싶다. 이 저장소는 내가 배운 것을 적용하기 위한 것입니다.
나는 봄을 더 잘 다루고 싶다. 이 저장소는 내가 배운 것을 적용하기 위한 것입니다. – GitHub – ahn-sj/spring-box: 스프링을 더 잘 다루고 싶습니다. 이 저장소는 내가 무엇을 적용하기 위한 것입니다…
github.com
참조
잘못된 정보나 내용이 있다면 댓글로 남겨주세요.