Language/JAVA

[JAVA] 애너테이션(annotation)

1. 애너테이션이란?

애너테이션(annotation)이 만들어진 계기와 개념에 대해 다룹니다.

더보기

자바를 개발한 사람들은 소스코드에 대한 문서를 따로 만들기보다 소스코드와 문서를 하나의 파일로 관리하는 것이 낫다고 생가했따. 그래서 소스코드의 주석 '/** ~ */'에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML문서를 생성해내는 프로그램(javadoc.exe)을 만들어서 사용했다.

/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * More information about annotation types can be found in section 9.6 of
 * <cite>The Java Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation type from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {

'/**'로 시작하는 주석 안에 소스코드에 대한 설명들이 있고, 그 안에 '@'이 붙은 태그 들이 눈에 띌 것이다. 미리 정의된 태그들을 이용해서 주석 안에 정보를 저장하고, javadoc.exe라는 프로그램이 이 정보를 읽어서 문서를 작성하는데 사용한다. 이 기능을 응용하여, 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애너테이션이다. 애너테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.

Example Code

@Test	// 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	...
}
/*
테스트 프로그램에게 테스트할 메서드를 일일이 알려주지 않고, 해당 메서드 앞에 애너테이션만 붙임으로 해결됌
*/

 

 

2. 표준 애너테이션

표준 애너테이션의 종류와 용도에 대해 다룹니다.

더보기

자바에서 기본적으로 제공하는 애너테이션들은 몇 개 없다. 그나마 이들의 일부는 '메타 애너테이션(meta annotation)'으로 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다. 작성된 애너테이션을 사용하는 경우가 많을 것이므로 가벼운 마음으로 읽어보자.

애너테이션 설명
@Override 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.
@Deprecated 앞으로 사용하지 않을 것을 권장하는 대상에게 붙인다.
@SuppressWarnings 컴파일러의 특정 경고메시지가 나타나지 않게 해준다.
@SafeVarargs 지네릭스 타입의 가변인자에 사용한다.(JDK1.7)
@FunctionalInterface 함수형 인터페이스라는 것을 알린다.(JDK1.8)
@Native native 메서드에서 참조되는 상수 앞에 붙인다.(JDK1.8)
@Target 애너테이션이 적용가능한 대상을 지정하는데 사용한다.
@Documented 애너테이션 정보가 javadoc으로 작성한 문서에 포함되게 한다.
@Inherited 애너테이션이 자손 클래스에 상속되도록 한다.
@Retention 애너테이션이 유지되는 범위를 지정하는데 사용한다.
@repeatable 애너테이션을 반복해서 적용할 수 있게 한다.(JDK1.8)

 

@Override

메서드 앞에만 붙일 수 있는 애너테이션, 조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려주는 역할

오버라이딩시 함수명을 잘못적는 등의 실수를 해도 @Override 애너테이션을 통해 알아 낼 수 있음

 

@Deprecated

새로운 버전의 JDK로 버전업 시 기존의 부족했던 기능에 대해 사용하지 않을 것이라고 묵시적으로 알려줌

더이상 사용되지 않는 필드나 메서드에 @Deprecated를 붙이는 것이다.

이 애너테이션이 붙은 대상은 다른것으로 대체되었으니 더 이상 사용하지 않음을 권장한다는 의미

ex) Date 클래스의 getDate()

 

@SuppressWarnings

컴파일러가 보여주는 경고메시지가 나타나지 않게 억제해준다.

뒤에 문자열 변수를 넣어 해당하는 경우의 에러를 보이지 않게 해준다.

  • unchecked - 지네릭스 타입을 지정하지 않았을 때 발생하는 경고
  • deprecation - @Deprecated가 붙은 대상을 사용해서 발생하는 경고
  • rawtypes - 지네릭스를 사용하지 않아서 발생하는 경고
  • varargs - 가변인자의 타입이 지네릭 타입일 때 발생하는 경고

둘 이상의 경고를 동시에 억제시 다음과 같이 작성

@SuppressWarnings({"deprecation", "unchecked", "varargs"})

@SafeVarags

메서드에 선언된 가변인자의 타입이 non-reifiable타입(타입 소거자에 의해 컴파일 타임에 타입 정보가 사라지는 것 [런타임에 구체화하지 않는것]) 일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 '@SafeVarargs'를 사용

 

 

3. 메타 애너테이션

'애너테이션을 위한 애너테이션'

즉 애너테이션에 붙이는 애너테이션으로 애너테이션을 정의할 때

애너테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는데 사용된다.

 

메타 애너테이션의 종류와 사용방법등에 대해서 다룹니다.

더보기

@Target

애너테이션이 적용가능한 대상을 지정하는데 사용된다.

 

'@Target'으로 지정할 수 있는 애너테이션 적용대상의 종류는 아래와 같다.

대상 타입 의미
ANNOTATION_TYPE 애너테이션
CONSTRUCTOR 생성자
FIELD 필드(멤버변수, enum상수)
LOCAL_VARIABLE 지역변수
METHOD 메서드
PACKAGE 패키지
PARAMETER 매개변수
TYPE 타입(클래스, 인터페이스, enum)
TYPE_PARAMETER 타입 매개변수 (JDK1.8)
TYPE_USE 타입이 사용되는 모든 곳 (JDK 1.8)

 

Example Code

import static java.lang.annotation.ElementType.*;

@Target({FIELD, TYPE, TYPE_USE})	// 적용대상이 FILED, TYPE, TYPE_USE
public @interface MyAnnotation { }	// MyAnnotation을 정의

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

'FIELD'는 기본형에, 그리고 'TYPE_USE'는 참조형에 사용된다는 점을 주의

 

@Retention

애너테이션이 유지(retention)되는 기간을 지정하는데 사용된다.

애너테이션의 유지 정책(retention policy)의 종류는 다음과 같다.

유지 정책 의미
SOURCE 소스 파일에만 존재, 클래스파일에는 존재하지 않음
CLASS 클래스 파일에 존재. 실행시에 사용불가. 기본값
RUNTIME 클래스 파일에 존재. 실행시에 사용가능.

'@Override'나 '@SuppressWarnings'처럼 컴파일러가 사용하는 애너테이션은 유지 정책이 'SOURCE'이다.

컴파일러를 직접 작성할 것이 아니면, 이 유지정책은 필요없다.

 

유지 정책을 'RUNTIME'으로 하면, 실행 시에 '리플렉션(reflection)[구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API]'을 통해 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있다. '@FunctionalInterface'는 '@Override'처럼 컴파일러가 체크해주는 애너테이션이지만, 실행 시에도 사용되므로 유지 정책이 'RUNTIME'으로 되어 있다.

유지 정책 'CLASS'는 컴파일러가 애너테이션의 정보를 클래스 파일에 저장할 수 있게는 하지만, 클래스 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어 실행 시에 애너테이션에 대한 정보를 얻을 수 없다. 이것이 'CLASS'가 유지정책의 기본값임에도 불구하고 잘 사용되지 않는 이유이다.

 

@Documented

애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.

자바에서 제공하는 기본 애너테이션 중에 '@Override'와 '@SuppressWarnings'를 제외하고는 모두 이 메타 애너테이션이 붙어 있다.

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface FUnctionalInterface {}

 

@Inherited

애너테이션이 자손 클래스에 상속되도록 한다.

'@Inherited'가 붙은 애너테이션을 조상클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식된다.

 

@Inherited	// @SupperAnno가 자손까지 영향 미치게
@interface SupperAnno {}

@SuperAnno
class Parent {}

class child extends Parent {} // Child에 애너테이션이 붙은 것으로 인식

 

@Repeatable

보통은 하나의 대상에 한 종류의 애너테이션을 붙이는데, '@Repeatable'이 붙은 애너테이션은 여러 번 붙일 수 있다.

 

@Repeatable(ToDos.class)	// ToDo애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDo {
	String value();
}

일반적인 애너테이션과 달리 같은 이름의 애너테이션이 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 애너테이션들을 하나로 묶어 다룰 수 있는 애너테이션도 추가로 정의해야 한다.

@interface ToDos {	// 여러 개의 Todo 애너테이션을 담을 컨테이너 애너테이션 ToDos
	ToDo[] value();	// ToDo 애너테이션 배열타입의 요소를 선언. 이름이 반드시 value이어야 함
}

@Repeatable(ToDos.class)	// 괄호 안에 컨테이너 애너테이션을 지정해 줘야한다.
@interface Todo {
	String value();
}

 

@Native

네이티브 메서드(native method)에 의해 참조되는 '상수 필드(constant field)'에 붙이는 애너테이션이다. 아래는 java.lang.Long클래스에 정의된 상수이다.

 

@Native public static final long MIN_VALUE = 0x8000000000000000L

 네이티브 메서드는 JVM이 설치된 OS의 메서드를 말한다. 네이티브 메서드는 보통 C언어로 작성되어 있는데, 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다. 그래서 추상 메서드처럼 선언부만 있고 몸통이 없다.

public class Object {
	private static native void registerNatives();	// 네이티브 메서드
    
    static {
    	registerNatives()	// 네이티브 메서드를 호출
    }
    
    protected native Object clone() throws CloneNotSupportedException;
    public final native Class<?> getClass();
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long timeout) throws INterruptedException;
    public native int hashCode();
    ...
}

이처럼 모든 클래스의 조상인 Object클래스의메서드들은 대부분 네이티브 메서드이다.

네이티브 메서드는 자바로 정의되어 있기 때문에 호출하는 방법은 자바의 일반 메서드와 다르지 않지만 실제로 호출되는 것은 OS의 메서드이다.

그냥 아무런 내용도 없는 네이티브 메소드를 선언해 놓고 호출한다고 되는 것은 아니고, 자바에 정의된 네이티브 메서드와 OS의 메서드를 연결시켜주는 작업이 추가로 필요하다.

이 역할은 JNI(Java Native Interface)가 하는데, 관련 내용을 참조 링크로 남겨 놓겠다.

 

 

4. 애너테이션 타입 정의하기

직접 애너테이션을 만들어서 사용해보자.

새로운 애너테이션을 정의하는 방법은 아래와 같다. '@' 기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.

@interface 애너테이션이름 [
	타입요소이름();	// 애너테이션의 요소를 선언한다.
    ...
}

엄밀히 말해서 '@Override'는 애너테이션, 'Override'는 애너테이션의 타입' 이다.

 

애너테이션을 구성하는 요소와 마커 애너테이션, 애너테이션 요소와 규칙등에 대해서 다룹니다.

더보기

애너테이션의 요소

애너테이션 내에 선언된 메서드를 '애너테이션의 요소(element)'라고 하며, 아래에 선언된 TestInfo애너테이션은 다섯개의 요소를 갖는다.

 

Example Code

package kr.co.dong.annotation;

public @interface TestInfo {
	int count();
	String testedBy();
	String[] testTools();
	TestType testType();	// enum TestType { FIRST, FINAL }
	DateTime testDate();	// 자신이 아닌 다른 애너테이션(@DateTime)을 포함할 수 있다.
}

@interface DateTime {
	String yymmdd();
	String hhmmss();
}

애너테이션의 요소는 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다. 다만, 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 한다. 요소의 이름도 같이 적어주므로 순서는 상관없다.

 

@TestInfo(
	count = 3, testedBy = "kim",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST,
    testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)
public class NewClass { ... }

애너테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.

@interface TestInfo {
	int count() default 1;	// 기본값을 1로 지정
}
@TestInfo	// @TestInfo(count=1)과 동일
public class NewClass { ... }

애너테이션 요소가 오직 하나뿐이고 이름이 value인 경우, 애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다.

@Interface TestInfo {
	String value();
}

@TestInfo("passed")	// @TestInfo(value = "passed")와 동일
class NewClass { ... }

요소의 타입이 배열인 경우, 괄호 {}를 사용해서 여러 개의 값을 지정할 수 있다.

@interface TestInfo {
	String[] testTools();
}

@Test(testTools={"JUnit", "AutoTester"})	// 값이 여러 개인 경우
@Test(testTools="JUnit")			// 값이 하나일 때는 괄호{} 생략가능
@Test(testTools={})				// 값이 없을 때는 괄호 {}가 반드시 필요

 

기본값을 지정할 때도 마찬가지로 괄호 {}를 사용할 수 있다.

@interface TestInfo {
	String[] info() default {"aaa", "bbb"};	// 기본값이 여러 개인 경우. 괄호{} 사용
    String[] info2() default "ccc";		// 기본값이 하나인 경우, 괄호 생략 가능
}

@TestInfo	// @TestInfo(info= {"aaa", "bbb"}, info2 = "ccc")와 동일
@TestInfo(info2 = {})	// @TestInfo(info={"aaa", "bbb"}, info2 = {})와 동일
class NewClass { ... }

요소의 타입이 배열일 때도 요소의 이름이 value이면, 요소의 이름을 생략할 수 있다.

@interface SuppressWarnings {
	String[] value();
}

그래서 애너테이션을 적욕할 때 요소의 이름을 생략할 수 있는 것이다.

//	@SuppressWarnings(value = {"deprecation", "unchecked"})
	@SuppressWarnings({"deprecation", "unchecked"})
    class NewClass { ... }

java.lang.annotation.Annotation

모든 애너테이션의 조상은 Annotation이다. 그러나 애너테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없다.

@interface TestInfo extends Annotation { // 에러. 허용되지 않는 표현
	int count();
    	String testedBy();
    	...
}

Annotation은 애너테이션이 아닌 일반적인 인터페이스로 정의되어 있다.

package java.lang.annotation;

public interface Annotation {	// Annotation 자신은 인터페이스이다.
	boolean equals(Object obj);
    int hashCode();
    String toString();
    
    Class<? extends Annotation> annotationType();	//애너테이션의 타입을 반환
}

 

모든 애너테이션의 조상인 Annotation 인터페이스가 위와 같이 정의되어 있기 때문에, 모든 애너테이션 객체에 대해 equals(), hashCode(), toString()과 같은 메서드를 호출하는 것이 가능하다.

Class<AnnoationTest> cls = AnnotationTest.class;
Annotation[] annoArr = AnnotationTest.class.getAnnotations();

for(Annotation a : annoArr) {
	System.out.println("toString():"+a.toString());
    System.out.println("hashCode:"+a.hashCode());
    System.out.println("equals():"+a.equals());
    System.out.println("annotationType():"+a.annotationType());
}

 

마커 애너테이션 (Maker Annotation)

값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않을 수 있다.

Serializable이나 Clonealbe인터페이스처럼, 요소가 하나도 정의되지 않은 애너테이션을 마커 애너테이션이라 한다.

 

@Target(ElemtType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 마커 애너테이션. 정의된 요소가 하나도 없다.

@Target(ElemtType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Test {} // 마커 애너테이션. 정의된 요소가 하나도 없다.

 

애너테이션 요소의 규칙

애너테이션의 요소를 선언할 때 반드시 지켜야 하는 규칙은 다음과 같다.

- 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.

Example Code

@interface AnnoTest {
	int id = 100;	// OK. 상수 선언.
    String major(int i, int j);	// 에러. 매개변수를 선언할 수 없음
    String minor() throws Exception;	// 에러 예외를 선언할 수 없음
    ArrayList<T> list();	//에러. 요소의 타입에 타입 매개변수 사용불가
}

Annotation Example Code

package kr.co.dong.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Deprecated
@SuppressWarnings("1111")	// 유효하지 않은 애너테이션은 무시한다.
@TestInfo(testedBy = "aaa", testDate=@DateTime(yymmdd="160101", hhmmss="235959"))
public class AnnotationEx5 {
	public static void main(String[] args) {
		// AnnoationEx5의 Class객체를 얻는다.
		Class<AnnotationEx5> cls = AnnotationEx5.class;
		
		TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
		System.out.println("anno.testedBy()="+anno.testedBy());
		System.out.println("anno.testDate().yymmdd="+anno.testDate().yymmdd());
		System.out.println("anno.testDate().hhmmss="+anno.testDate().hhmmss());
		
		for(String str : anno.testTools()) {
			System.out.println("testTools="+str);
		}
		
		System.out.println();
		
		// AnnotationEx5에 적용된 모든 애너테이션을 가져온다.
		Annotation[] annoArr = cls.getAnnotations();
		
		for(Annotation a : annoArr) {
			System.out.println(a);
		}
	}
}

@Retention(RetentionPolicy.RUNTIME)	// 실행 시에 사용가능하도록 지정
@interface TestInfo {
	int count()	default 1;
	String testedBy();
	String[] testTools()	default "JUnit";
	TestType testType()	default TestType.FIRST;
	DateTime testDate();
}

@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정
@interface DateTime {
	String yymmdd();
	String hhmmss();
}

enum TestType { FIRST, FINAL }

/*
실행 결과
anno.testedBy()=aaa
anno.testDate().yymmdd=160101
anno.testDate().hhmmss=235959
testTools=JUnit

@java.lang.Deprecated(forRemoval=false, since="")
@kr.co.dong.annotation.TestInfo(count=1, testType=FIRST, testTools={"JUnit"}, testedBy="aaa", testDate=@kr.co.dong.annotation.DateTime(yymmdd="160101", hhmmss="235959"))

*/

'AnnotationEx5.class'는 클래스 객체를 의미하는 리터럴이다.

모든 클래스 파일은 클래스로더(Classloader)에 의해 메모리에 올라갈 때, 클래스에 대한 정보가 담긴 객체를 생성하는데 이 객체를 클래스 객체라고 한다. 이 객체를 참조 할 때는 '클래스이름.class'의 형식으로 사용한다.

클래스 객체에는 해당 클래스에 대한 모든 정보를 가지고 있는데, 애너테이션의 정보도 포함되어 있다.

클래스 객체가 가지고 있는 getAnnotation()이라는 메서드에 매개변수로 정보를 얻고자하는 애너테이션을 지정해주거나 getAnnotations()로 모든 애너테이션을 배열로 받아 올 수 있다.

 

 


references

https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4

 

자바 네이티브 인터페이스 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 자바 네이티브 인터페이스(Java Native Interface, JNI)는 자바 가상 머신(JVM)위에서 실행되고 있는 자바코드가 네이티브 응용 프로그램(하드웨어와 운영 체제 플랫폼

ko.wikipedia.org

https://hbase.tistory.com/82

 

[Java] JNI 사용법 및 예제

자바는 가상 머신인 JVM위에서 실행되도록 만들어진 언어다. 프로그래머가 자바 언어로 작성한 프로그램은 JVM 위에서 동작하도록 중간 언어인 바이트 코드로 컴파일 된다. 운영체제나 아키텍처

hbase.tistory.com