Annotation 이란 ?
java (1.5) 부터 등장했으며 프로그램에 추가적인 정보를 제공하는 메타 데이터라고 할수있다.
메타데이터란 어플리케이션에서 처리해야 할 데이터가 아니라 컴파일 과정과 런타임에서 코드를 어떻게 컴파일하고 처리할 것인지에 대한 정보를 말한다.
이런 Annotation 옵셥(@Retention)에 따라 컴파일 전, 컴파일 시기, 런타임 시기에도 처리되도록 할수있다.
Java 의 리플렉션을 사용하여 런타임 시기에 어노테이션의 메타 데이터를 바탕으로 AOP를 구성하는데 큰 도움을 줄수있다.
이러한 메타 데이터를 활용하여 비지니스 로직과 분리하여 대상의 유효성 체크, 값 주입, 역활 주입 등을 수행할 수 있어 코드를 간결하게 작성할수 있다.
- 주의사항
Annotation 자체는 특별한 기능을 가지지 않고 다른 어플리케이션이나 AOP등에서 어노테이션의 메타 데이터를 기반으로 기능을 수행한다.
Annotation만 보았을때 어떤기능을 하는지 명확하지 않고 이러한 Annotation들이 많아지게 되면 비지니스 로직의 플로우를 이해하기 어렵고 관리가 어렵게 된다.
무분별한 Annotation 의 추가가 지금 당장의 개발속도를 끌어올릴수 있을지 모르지만 이해되지 않는 코드들이 많아진다면 직관적인 코드로 변경하는것도 고민해야한다.
Annotation 종류
빌트인 Annotation
Java 에서 기본적으로 제공하는 Annotation타입으로 자바코드에 적용
@Override
Interface Implement 하여 method에 구현체를 구성할때 인터페이스에 선언된 메소드를 구현하는 표현으로 오버라이드 한다고 한다.
부모클래스가 없는 method에 사용시 컴파일오류가 발생할수있고 최근에 개발 Tool에서는 Syntax오류라고 친절하게 알려주는 편이다.
@Deprecated
사용되지 않는 메소드를 표기하거나 앞으로 삭제예정으로 표기할때 해당 Annotation을 사용한다.
@SuppressWarning
컴파일시 발생하는 경고를 무시하도록 컴파일러에 알려주는기능으로 사용한다.
@SafeVarargs
Java7 부터 지원하며 제네릭 같은 가변인자의 매개변수 사용시 경고 무시
@Functionalinterface
Java8 부터 지원하며 함수형 인터페이스를 지정하는 어노테이션
만약 메소드가 존재하지 않거나, 1개 이상의 메소드(default 메소드 제외)가 존재할경우 컴파일 오류발생
메타 Annotation
다른 Annotation에 적용되기 위한 용도로 메타 Annotation을 사용하여 커스텀하게 만들수 있다.
@Retention
Annotation 정보를 어느범위까지 유지할것인지설정 하는기능
RetentionPolicy.SOURCE : 컴파일 전까지 유지
RetentionPolicy.CLASS : 컴파일 단계에서 컴파일러가 클래스를 참조하는 동안 유지 (기본값)
RetentionPolicy.RUNTIME : 런타임에도 유지하며 리플렉션을 사용하여 정보를 가져올수 있음
@Documented
JavaDoc 생성시 Document 에 포함되도록하는 기능
@Target
어노테이션 사용할수 있는 대상 지정 지정하지 않은경우 모든요소에서 사용가능
ElementType.PACKAGE : 패키지 선언시
ElementType.TYPE : 타입 선언시 (Class, interface, Enum)
ElementType.CONSTRUCTOR : 생성자 선언시
ElementType.FIELD : 맴버 변수 선언시
ElementType.METHOD : 메소드 선언시
ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언시
ElementType.LOCAL_VARIABLE : 지역 변수 선언시
@Inherited
상속 및 구현하는 클래스에도 어노테이션 적용
@Repeatable
Java8 부터 지원하며 연속적으로 어노테이션 선언하는것을 허용
Annotation 타입
마커 Annotation
정보를 표기할용도의 인터페이스 annotation
// 마커 어노테이션 정의
public @interface MakerAnnotation {
}
// 마커 어노테이션 사용
@MakerAnnotation
public class UsingMakerAnnotation {
}
싱글값 Annotation
하나의 값만 입력받는 Annotation
// 싱글값 어노테이션 정의
public @interface SingleValueAnnotation {
int id();
}
// 싱글값 어노테이션 사용
@SingleValueAnnotation(id = 1)
public class UsingSingleValueAnnotation {
}
멀티값 Annotation
여러개의 값을 입력 받을수 있는 Annotation Default 값을 지정할수 있음
// 멀티값 어노테이션 정의
public @interface MultiValueAnnotation {
int id();
String name() default "user”; //미지정시 기본 값으로 user가 지정된다
String[] roles() default {"anonymous"};
}
// 멀티값 어노테이션 사용
@MultiValueAnnotation(id = 2, name = "Hello", roles = {"admin", "users"})
public class UsingMultiValueAnnotation {
@MultiValueAnnotation(id = 10) //name = user, roles = {“anonymous’}로 지정된다
public void testMethod() {
}
}
커스텀 Annotation 만들기
@interface 선언
@interface 커스텀한 Annotation 선언
Retention 메타 어노테이션으로 런타임속성 추가
// 어노테이션 생성
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 런타임중에도 유효한 어노테이션임을 기술
public @interface Count100 { // 어노테이션은 @interface 인터페이스명으로 정의
}
// 커스텀 어노테이션을 메소드에 적용
public class MyHello {
@Count100
public void hello(){
System.out.println("hello");
}
}
// 어노테이션이 적용된 부분인지 체크하여 코드내에서 사용
import java.lang.reflect.Method;
public class MyHelloExam {
public static void main(String[] args) {
MyHello hello = new MyHello();
try{
Method method = hello.getClass().getDeclaredMethod("hello");
if(method.isAnnotationPresent(Count100.class)){
for(int i = 0; i < 100; i++){
hello.hello();
}
}else{
hello.hello();
}
}catch(Exception ex){
ex.printStackTrace();
}
}
}
아래는 Enum에 대한 vaild 체크하는 Custom Annotation 입니다.
이런식으로도 구현할수있어서 커스텀 Annotation만들때 참고하세요 :)
/**
* Enum Validator
*
* @NotNull 과 함께 사용하면 필수 체크 항목 입니다.
*
* @NotNull
* @EnumValid(enumClass = SampleEnum.class)
* private String sample;
*
*/
@Documented
@Constraint(validatedBy = {EnumValidator.class})
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValid {
String message() default "Invalid value. This is not permitted.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
boolean ignoreCase() default false;
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class EnumValidator implements ConstraintValidator<EnumValid, String> {
private EnumValid annotation;
@Override
public void initialize(EnumValid annotation) {
this.annotation = annotation;
}
@Override
public boolean isValid(String valueForValidation, ConstraintValidatorContext constraintValidatorContext) {
boolean result = false;
Object[] enumValues = this.annotation.enumClass().getEnumConstants();
if (enumValues != null) {
for (Object enumValue : enumValues) {
if (hasEnumValue(valueForValidation, enumValue, this.annotation.ignoreCase())) {
result = true;
break;
}
}
}
return result;
}
private boolean hasEnumValue(String valueForValidation, Object enumValue, boolean ignoreCase) {
return valueForValidation == null
|| valueForValidation.equals(enumValue.toString())
|| (ignoreCase && valueForValidation.equalsIgnoreCase(enumValue.toString()));
}
}