@
기호를 사용하는 문법 요소로 Java5부터 등장했다.
프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 어노테이션이다.
XML 설정 파일을 통해 명시하고 관리할 때, 프로그램 작성 때마다 많은 설정을 작성해야 하는 단점이 있다. 웹 애플리케이션이 커짐에 따라 이는 더 극대화되었고 해결하기 위해 나온 방법이 바로 어노테이션이다.
단어의 의미인 주석과는 비슷하지만 다른 역할로써 사용되는데 메서드/클래스 등에 의미를 단순히 컴파일러에게 알려주기 위한 표식이 아닌 컴파일타임 이나 런타임에 해석될 수 있다.
어노테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.
기존 XML 관리 방식은 서비스의 규모가 클 수록 설정양이 많아지고 도메인 처리의 데이터들이 분산되어 있어 수정이 힘들었다.
어노테이션은 데이터 유효성 검사 등 직접 클래스에 명시해 줄 수 있게되어 수정이 필요할때 쉽게 파악할 수 있게 되었고 재사용도 가능해졌다.
AOP(관점 지향 프로그래밍)을 쉽게 구성할 수 있게 해준다.
어노테이션은 크게 총 2가지로 나뉜다.
Built-in Annotation : 자바에서 기본적으로 제공하는 어노테이션
Meta Annotation : 커스텀 어노테이션을 만들수 있게 제공된 어노테이션.
종류 | 설명 |
---|---|
@Override | 선언한 메서드가 오버라이드 되었다는 것을 나타냄. * 상위(부모)클래스(또는 인터페이스)에서 해당 메서드를 찾지 못하면 컴파일 에러 발생. |
@Deprecated | 해당 메서드가 더이상 사용되지 않음을 표시합니다. * 해당 메서드를 사용할 경우 컴파일 경고를 발생. |
@SuppressWarnings | 선언한 곳의 컴파일 경고를 무시. |
@SafeVarargs | 제네릭 가변인자 매개변수를 사용할 때의 경고를 무시한다.(Java 7 이상) |
@FunctionalInterface | 람다 함수등을 위한 인터페이스 지정.(Java 8 이상) * 메소드가 없거나 두개이상 되면 컴파일 오류 발생 |
종류 | 설명 |
---|---|
@Retention | 어노테이션이 유지되는 기간을 지정하는데 사용한다.(세가지 유지정책 사용) |
@Documented | 해당 어노테이션을 javadoc에 포함시킨다. |
@Target | 어노테이션이 적용할 위치를 지정한다. 여러 값일 경우 {} 사용 |
@Inherited | 어노테이션의 상속을 가능하게 한다. |
@Repeatable | 연속적으로 어노테이션을 사용할 수 있게 해줍니다. |
public @interface MyAnnotation {
}
@interface
는 애노테이션 타입(annotation type)을 선언하는 키워드다. 어노테이션이 유지되는 기간을 지정하는데 사용되는 Meta Annotation이다.
RetentionPolicy.Source
: 컴파일 전까지만 유효RetentionPolicy.CLASS
: 컴파일러가 클래스를 참조할 때까지 유효(Default 값)RetentionPolicy.RUNTIME
: 컴파일 이후에도 JVM에 의해 계속 참조 가능(리플렉션 가능)@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
@Target은 어노테이션이 적용될 위치를 지정하는 Meta Annotation이다.
ElementType.PACKAGE
: 패키지선언ElementType.TYPE
: 타입선언ElementType.ANNOTATION_TYPE
: 어노테이션 타입 선언ElementType.CONSRTUCTOR
: 생성자 선언ElementType.FIELD
: 멤버변수 선언ElementType.LOCAL_VARIABLE
: 지역 변수 선언ElementType.METHOD
: 메서드 선언ElementType.PARAMETER
: 전달인자 선언ElementType.TYPE_PARAMETER
: 전달인자 타입 선언ElementType.TYPE_USE
: 타입 선언@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface MyAnnotation {
}
or
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE,
ElementType.PACKAGE})
public @interface MyAnnotation {
}
@Documented는 해당 어노테이션을 javadoc 파일에 추가시킬지 여부이다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnnotation {
String testDocument();
}
타입에 사용할 수 있으려면 TYPE_PARAMETER
, TYPE_USE
으로 사용 가능하다.
@Repeatable
@Retention
및 @Target
이 같거나 더 넓어야 한다.@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface Chicken {
}
public class App {
public static void main(String[] args){
System.out.println("!");
}
static class FeelsLikeChicken<@Chicken T>{
}
}
어노테이션 프로세서는 소스코드 레벨에서 소스코드에 붙어있는 어노테이션을 읽어서 컴파일러가 컴파일 하는 중에 새로은 소스코드를 생성하거나 기존 소스코드를 바꿀 수 있다.
또는, 클래스(바이트코드) 도 생성할 수 있고 별개의 리소스파일을 생성할 수 있는 강력한 기능이다.
롬복 (기존코드를 변경한다), AutoService (리소스 파일을 생성해준다.), java.util.ServiceLoader 용 파일 생성 유틸리티, @Override 등이 있다.
장점
단점
public interface HelloService{
String hello();
}
위와 같은 Service를 다른 곳에서 각각 구현해서 사용가능하다 하자.
이 인터페이스의 구현체는 누군지 모른다. 이 구현체를 내가 지정하지 않고, Jar 파일만 바꿔끼면 동작하도록 만들 수 있도록 한게 Service Loader 이다.
Project : hello-service
public interface HelloService{
String hello();
}
-> jar 파일 생성
Project : my-hello
public class MyHello implements HelloService{
@Override
public String hello(){
return "홧팅!";
}
}
파일명 : resources/META-INF/services/me.cham.hello-service.HelloService
파일 내용 : me.cham.my-hello.MyHello
-> jar 파일 생성
Project : app
public static void main(String[] args){
ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
for(HelloService helloService : loader){
System.out.println(helloService.hello());
}
}
ServiceLoader<T> loader
형태로 반환된다.모든 클래스 파일은 클래스로더(Classloader)에 의해 메모리에 올라갈 때, 클래스에 대한 정보가 담긴 객체를 생성하는데 이 객체를 클래스 객체라고 한다.
이 객체를 참조할 때는 '클래스이름.class'의 형식을 사용한다.
메서드명 | 리턴타입 | 설명 |
---|---|---|
getFields() | Field[] | 접근 제어자가 public인 필드들을 Field 배열로 반환. 부모 클래스의 필드들도 함께 반환한다 |
getConstructors() | Constructor[] | 접근 제어자가 public인 생성자들을 Constructor 배열로 반환. 부모 클래스의 생성자들도 함께 반환합니다. |
getMethods() | Method[] | 접근 제어자가 public인 메서드들을 Method 배열로 반환. 부모 클래스의 메서드들도 함께 반환합니다. |
getDeclaredFields() | Field[] | 접근 제어자에 상관없이 모든 필드들을 Field배열로 반환. 부모 클래스의 필드들은 반환하지 않습니다. |
getDeclaredConstructors() | Constructor[] | 접근 제어자에 상관없이 모든 생성자들을 Constructor배열로 반환. 부모 클래스의 생성자들은 반환하지 않습니다. |
getDeclaredMethod() | Method[] | 접근 제어자에 상관없이 모든 메서드들을 Method배열로 반환. 부모 클래스의 메서드들은 반환하지 않습니다. |
애노테이션 정보를 얻기위한 메서드들
메서드명 | 리턴타입 | 설명 |
---|---|---|
isAnnotationPresent(Class<? Extends Annotation> annotationClass) | boolean | 지정한 애노테이션이 적용되었는지 여부를 확인. Class에서 호출했을 경우 상위 클래스에 적용된 경우에도 true를 리턴. |
getAnnotation(Class annotationClass) | Annotation | 지정한 애노테이션이 적용되어 있으면 애노테이션을 반환하고 그렇지 않은 경우 null을 반환합니다. Class에서 호출한 경우 상위 클래스에 적용된 애노테이션도 반환합니다. |
getAnnotations() | Annotation[] | 적용된 모든 애노테이션을 반환합니다. Class에 사용됐을 경우 상위 클래스에 적용된 애노테이션까지 전부 포함해서 반환합니다. 애노테이션이 없을 경우 길이가 0인 배열을 반환합니다. |
getDeclaredAnnotation() | Annotation[] | 직접 적용된 모든 애노테이션을 리턴합니다. Class에서 호출했을 경우 상위 클래스에 적용된 애노테이션은 포함되지 않습니다.(상위 클래스의 @Inherited가 붙은 애노테이션을 무시합니다.) |