들어가면서
자바에는 두 가지 종류의 주석이 있다. 주석(Comments)과 어노테이션(Annotations)
주석은 코드의 실행에 전혀 영향을 미치지 않으면서 개발자들이 의도와 로직을 명확하게 전달할 수 있게 도와준다. 그러나 자바의 어노테이션은 단순한 주석을 넘어서는 일을 한다. 어노테이션은 코드에 메타데이터를 추가하여, 컴파일러, 개발 도구, 실행 시간 프레임워크가 이 정보를 사용하여 다양한 작업을 수행할 수 있도록 한다
어노테이션은 코드에 대한 지침이나 설정을 ‘마킹’하는 데 사용되며, 이는 오버라이딩 검사, 경고 억제, 더 이상 사용되지 않는 코드 표시 등 다양한 형태로 나타날 수 있다. 더 나아가, 어노테이션은 자바 프로그래밍 언어의 기능을 확장하여, 프레임워크 및 라이브러리들이 런타임에 특정 동작을 수행하거나 설정을 적용하는 데 필수적인 역할을 한다
어떻게 이들이 단순한 주석을 넘어서 프로그램의 동작과 구조에 깊이 관여할 수 있는지, 단순히 코드에 대한 메모가 아닌, 강력한 프로그래밍 기법으로서 어떻게 활용될 수 있는지 알아보자
자바 어노테이션이란?
- 어노테이션은 코드 사이에 메타데이터를 제공하는 방법을 제공한다
- 이 메타데이터는 컴파일 타임, 런타임, 또는 배포 시에 다양한 방식으로 활용될 수 있다
- 예를 들어,
@Override
어노테이션은 메서드가 오버라이드 되었음을 나타낸다 - 어노테이션의 사용은 코드의 가독성, 유지보수성을 높이고, 에러를 줄이는 데 도움을 준다
어노테이션의 용도
- 컴파일러에 대한 지시 : 예를 들어,
@Override
어노테이션은 메소드가 오버라이드 되었음을 컴파일러에게 알린다 - 컴파일 타임 및 배포 타임 처리 : 소스 코드를 컴파일하거나 배포할 때 특정 처리를 자동화한다
- 런타임 시 처리 : 어노테이션 정보를 런타임에 사용하여 특정 기능을 실행한다
내장 어노테이션
자바에는 여러 내장 어노테이션이 있는데, 대표적인 것들은 다음과 같다
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
이 내장 어노테이션들은 자바의 표준 라이브러리에 포함되어 있으며, 각각 특정한 목적을 가지고 있다
@Override
@Override
어노테이션은 메소드가 상위 클래스의 메소드를 오버라이딩한다는 것을 나타낸다- 만약 상위 클래스에 해당 메소드가 없다면, 컴파일러는 오류를 발생시킨다. 이를 통해 의도하지 않은 메소드 오버라이딩을 방지할 수 있다
@Deprecated
@Deprecated
어노테이션은 특정 요소(클래스, 메소드, 필드 등)가 더 이상 사용되지 않고 향후 버전에서 제거될 가능성이 있음을 나타낸다- 이 어노테이션을 사용하면 개발자가 해당 요소를 사용할 때 경고를 받게 된다
@SuppressWarnings
@SuppressWarnings
어노테이션은 특정 경고 메시지가 컴파일러에 의해 출력되지 않도록 한다- 예를 들어,
@SuppressWarnings("deprecation")
은 사용 중인 요소가 더 이상 사용되지 않는다는 경고를 무시하도록 한다
@SafeVarargs
@SafeVarargs
어노테이션은 제네릭 타입의 가변 인자를 사용하는 메소드가 안전하다는 것을 나타낸다- 이는 제네릭과 가변 인자가 함께 사용될 때 발생할 수 있는 잠재적인 문제를 방지하는 데 도움이 된다
@FunctionalInterface
@FunctionalInterface
어노테이션은 인터페이스가 함수형 인터페이스임을 나타낸다- 함수형 인터페이스는 정확히 하나의 추상 메소드만 가져야 하며, 이 어노테이션은 그 규칙을 강제하는 데 사용된다
내장 어노테이션 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JavaBuiltInAnnotations {
@Override
public String toString() {
return "JavaBuiltInAnnotations 클래스";
}
@Deprecated
public void deprecatedMethod() {
// 이 메소드는 더 이상 사용되지 않음
}
@SuppressWarnings("unchecked")
public void suppressWarningMethod() {
// 경고를 억제하는 메소드
}
@SafeVarargs
public final void safeVarargsMethod(T... args) {
// 안전한 가변 인자 메소드
}
@FunctionalInterface
interface SimpleFunctionalInterface {
void execute();
}
}
메타 어노테이션
- 메타 어노테이션은 다른 어노테이션을 위한 어노테이션이다
- 어노테이션의 동작 방식을 지정하거나 어노테이션과 관련된 정보를 제공한다
@Target
@Target
어노테이션은 특정 어노테이션이 적용될 수 있는 Java 요소를 지정한다- 종류
ElementType.PACKAGE
: 패키지 선언부를 대상으로 한다ElementType.TYPE
: 타입 선언부를 대상으로 한다ElementType.CONSTRUCTOR
: 생성자 선언부를 대상으로 한다ElementType.FIELD
: 필드 선언부를 대상으로 한다ElementType.METHOD
: 메서드 선언부를 대상으로 한다ElementType.ANNOTATION_TYPE
: 어노테이션 타입 선언부를 대상으로 한다ElementType.LOCAL_VARIABLE
: 지역변수 선언부를 대상으로 한다ElementType.PARAMETER
: 매개변수 선언부를 대상으로 한다ElementType.TYPE_PARAMETER
: 매개변수 타입 선언부를 대상으로 한다ElementType.TYPE_USE
: 타입 사용부를 대상으로 한다
@Retention
@Retention
어노테이션은 어노테이션의 유지 정책을 지정한다- 종류
RetentionPolicy.SOURCE
: 컴파일 전까지만 유효 (컴파일 이후에는 사라짐)RetentionPolicy.CLASS
: 컴파일러가 클래스를 참조할 때까지 유효RetentionPolicy.RUNTIME
: 컴파일 이후에도 JVM에 의해 계속 참조가 가능
@Documented
@Documented
어노테이션은 해당 어노테이션이 Javadoc에 포함되어야 함을 나타낸다
@Inherited
@Inherited
어노테이션은 어노테이션이 서브클래스에 상속될 수 있음을 나타낸다
@Repeatable
@Repeatable
어노테이션은 Java 8부터 도입되어, 하나의 요소에 동일한 어노테이션을 여러 번 적용할 수 있게 한다
커스텀 어노테이션
- 커스텀 어노테이션은 메타 어노테이션을 이용하여 사용자가 직접 정의하는 어노테이션이다
- 자바는 기본적으로 여러 어노테이션을 제공하지만, 특정한 경우에는 사용자 정의 어노테이션을 만들어 사용하는 것이 유용하다
커스텀 어노테이션 예시
1
2
3
4
5
6
7
8
9
10
11
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();
}
1
2
3
4
5
6
7
public class AnnotationExample {
@CustomAnnotation(value = "Example Method")
public void exampleMethod() {
// 메소드 구현
}
}
커스텀 어노테이션 활용
자바에서 커스텀 어노테이션을 활용하는 방법은 다양하다. 커스텀 어노테이션을 활용하는 방법 몇가지를 살펴보자
어노테이션과 Reflection
- 자바 Reflection을 사용하면 런타임에 클래스의 메타데이터를 읽고, 수정할 수 있다
- 커스텀 어노테이션의 경우, Reflection을 통해 어노테이션이 적용된 메소드를 찾고, 추가적인 로직을 실행할 수 있다.
예시 1 : 리플렉션을 통한 어노테이션 조회
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
public class AnnotationReflectionExample {
public static void main(String[] args) {
try {
// ExampleClass의 Class 객체를 가져온다
Class<?> clazz = ExampleClass.class;
// 'exampleMethod' 메서드의 Method 객체를 가져온다
Method method = clazz.getMethod("exampleMethod");
// 해당 메서드에 적용된 MyAnnotation 어노테이션을 가져온다
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Value: " + myAnnotation.value());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
// 커스텀 어노테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value() default "Default Value";
}
// ExampleClass 정의
static class ExampleClass {
// 어노테이션 적용
@MyAnnotation(value = "Hello, Annotation!")
public void exampleMethod() {
}
}
}
실행 결과
1 Value: Hello, Annotation!
예시 2 : 리플렉션을 통한 커스텀 어노테이션이 적용된 메소드의 실행 시간 로깅
- 앞선 예시를 활용하면 메서드의 수행 시간을 계산하는 작업도 가능하다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
public class AnnotationReflectionExample2 {
public static void main(String[] args) throws Exception {
process(new ExampleClass());
}
static public void process(Object obj) throws Exception {
for (Method method : obj.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(LogExecutionTime.class)) {
long startTime = System.currentTimeMillis();
method.invoke(obj);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + " 실행 시간: " + (endTime - startTime) + "ms");
}
}
}
// 커스텀 어노테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@interface LogExecutionTime {
}
static class ExampleClass {
@LogExecutionTime
public void exampleMethod() throws InterruptedException {
Thread.sleep((long) (Math.random() * 100));
}
}
}
실행 결과
1 exampleMethod 실행 시간: 79ms
어노테이션의 한계와 주의사항
어노테이션은 유용하게 사용되지만 몇가지 주의해야할 사항들이 있다
- 런타임 오버헤드 : 어노테이션은 런타임에 분석되어야 하므로 성능에 영향을 미칠 수 있다
- 남용의 위험 : 너무 많은 어노테이션은 코드를 복잡하게 만들 수 있다
- 호환성 문제 : 일부 어노테이션의 경우 특정 라이브러라나 프레임워크에 의존적이므로 호환성에 문제가 발생할 수 있다
더 나아가서
앞서 제시한 예시의 사용법 외에도 어노테이션은 더 강력하고 다양하게 활용될 수 있다. 어노테이션에 대해 더 공부하고 활용해보고 싶다면 다음 내용도 공부해보자
- AOP(Aspect Oriented Programming)
- 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하는 패러다임
- AOP를 위한 라이브러리로는 Spring AOP, AspectJ 등의 라이브러리들이 있으며, 어노테이션을 이용한 AOP를 지원한다
- 어노테이션 프로세싱
- 컴파일 타임에 어노테이션을 분석하고 처리하는 과정을 통해 소스 코드를 생성하거나 수정하는 가능
- 예를 들어, Lombok 라이브러리는 이 기능을 사용하여 바이트 코드를 조작하고, 게터, 세터, 생성자 등을 자동으로 생성할 수 있다
Comments powered by Disqus.