Annotation
자바 어노테이션은 MeteData를 소스 코드(class, method, field)에 붙여 Marker interface 역할을 하여, 특정 동작을 표시하는 데 사용합니다.
Marker Interface란
Marker Interface는 구현 클래스에 특별한 동작을 부여하거나 특정 속성을 나타내기 위해 아무 메서드도 포함하지 않는 인터페이스입니다.
public interface Marker {
// 아무 메서드도 정의하지 않음
}
어노테이션 주의 사항
- @interface로 정의해야 합니다.
- 모든 어노테이션은 기본적으로 java.lang.Annotation 인터페이스를 상속하기 때문에 다른 클래스나 인터페이스를 상속할 수 없다.
- Parameter 멤버들의 접근자는 public or default만 가능합니다.
- 클래스 메소드와 필드에 관한 어노테이션 정보를 얻고 싶으면, 리플렉션을 이용해서 얻어야 합니다.
- 다른 방법으로는 어노테이션 객체를 얻을 수 없습니다.
- Parameter 멤버들은 byte, short, char, int, float, double, boolean 등 기본 타입과 String, Enum, Class, annotation만 사용할 수 있습니다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserProvider {
int id() default -1;
String name() default "";
String address() default "";
}
id(), name(), address()는 어노테이션에서 요소(element)라고 불리는 메서드입니다.
자바에서 어노테이션에 정의할 수 있는 필드는 메서드 형태로 선언됩니다.
어노테이션은 메서드의 반환 값을 통해 값을 설정하여 사용합니다.
@UserProvider(id = 1,name = "HomePlus",address = "Seoul")
private String appleProvider;
- id, name, address에 값을 넣어주면 @UserProvider 어노테이션에 값이 설정됩니다.
- 값을 설정하지 않으면, default 값으로 설정됩니다.
- 메서드처럼 보이지만 실행할 수 있는 것이 아님.
- 어노테이션 처리 시점에서 데이터를 제공하기 위한 요소이다.
Annotation 종류
1. Marker Annotations
매개변수 없이 마크 표시만 하는 어노테이션을 의미합니다.
필요한 곳에 적으면 됩니다. (ex: @Override)
2. Single value Annotations
하나의 멤버를 가지는 어노테이션을 의미합니다.
사용할 때 멤버에 값을 넣어주어야 합니다. (값을 넣을 멤버 이름은 생략 가능합니다.)
ex: @CustomAnnotation(”testing”)
3. Full Annotations
여러 개의 멤버를 가지는 어노테이션을 의미합니다.
ex: @CustomAnnotation(owner="kim", value="developer")
- Meta Annotations은 새로운 어노테이션을 만들 때 사용하는 어노테이션입니다.
- 메타 데이터 정보를 설정하는 것이라 생각할 수 있습니다.
Meta Annotation
@Target
@Target은 어노테이션이 어디에 위치할 수 있는지 제한해줍니다.
@Target(ElementType.TYPE)
@interface CustomAnnotation {}
- class, interface, enum에 작성할 수 있습니다.
실습
예시 1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassAnnotation {
String value() default "Can annotate a class";
}
ElementType.TYPE으로 class, interface, enum 에 작성할 수 있습니다.
- 변수에 작성하게 되면 컴파일 에러가 발생합니다.
예시 2
@Target({ElementType.METHOD, ElementType.TYPE,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@interface MultipleElementTypeAnnotation {
String value() default "Can annotate a class, method, "
+ "annotation, or constructor";
}
- method, class, interface, enum, annotation, constructor에 작성할 수 있습니다.
@ClassAnnotation
public class LombokStudy {
// @ClassAnnotation 붙일 시 컴파일 오류 (빨간색 밑줄) 발생
@MultipleElementTypeAnnotation
public void myMethod() {
}
}
실행하기
public static void main(String[] args) throws NoSuchMethodException {
SpringApplication.run(SpringMvc1Application.class, args);
LombokStudy obj = new LombokStudy();
Annotation a[] = obj.getClass().getAnnotations();
System.out.println(a[0]);
Class<? extends LombokStudy> className = obj.getClass();
Annotation b[] = className.getMethod("myMethod").getAnnotations();
System.out.println(b[0]);
}
- getClass()를 통해 객체의 클래스 정보를 가져옵니다.
- getAnnotations()는 해당 클래스의 어노테이션 정보를 가져옵니다.
- getClass(). getMethod()를 통해 특정 메서드를 가져올 수 있습니다.
실행결과
@mvc1.springmvc1.annotation.ClassAnnotation("Can annotate a class")
@mvc1.springmvc1.annotation.MultipleElementTypeAnnotation("Can annotate a class, method, annotation, or constructor")
-> 각 에노테이션에 설정된 필드 값을 반환합니다.
@Retention
코드를 실행할 때 언제 이 어노테이션을 제거할지 결정하는 meta annotation입니다.
- @Retention(RetentionPolicy.SOURCE) : runtime 때 제거
- @Retention(RetentionPolicy.CLASS). class 파일엔 적혀있지만 runtime 때 제거. Default 값
- @Retention(RetentionPolicy.RUNTIME) runtime 때까지 접근 가능. ⭐️
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface RetentionClassAnnotation{
String value() default "Can not access during runtime";
}
RetentionPolicy.CLASS로 설정되어. class 파일에는 존재하지만, 런타임시에는 없어집니다.
public static void main(String[] args) throws NoSuchMethodException {
SpringApplication.run(SpringMvc1Application.class, args);
LombokStudy obj = new LombokStudy();
Annotation c[] = obj.getClass().getMethod("myMethod2")
.getAnnotations();
System.out.println(c.length); // 0 출력
}
실행결과
0
Annotation 커스텀하기
메서드의 실행 시간을 출력해 주는 어노테이션을 만들어 보겠습니다. - @LogExecutionTime
메서드의 실행 시점에 따라 처리가 필요하므로 AOP의 Aspect를 사용합니다.
1. Annotation class 생성
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
2. AOP를 이용해 메서드 실행시간 카운트하기
@Aspect
@Component
@Slf4j
public class TimeAspect {
@Around("@annotation(mvc1.springmvc1.annotation.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Call LogExecutionTime");
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
log.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
aop 의존성을 추가해야 @Aspect, @Around를 사용할 수 있습니다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
AOP를 적용하기 위해서는 스프링 컨테이너의 빈으로 등록하고 등록된 빈을 사용해야 합니다.!!
참고자료
https://velog.io/@anak_2/Java-annotations-이란-설명-활용
https://donghyeon.dev/spring/2020/08/18/Spring-Annotation의-원리와-Custom-Annotation-만들어보기/