목표
자바의 애노테이션에 대해 학습하세요.
학습할 것 (필수)
- 애노테이션 정의하는 방법
- @retention
- @target
- @documented
- 애노테이션 프로세서
자바 애노테이션을 처음 봤을 때.
흔히 할 수 있는 착각으로 자바 애노테이션에 동작 기능이 내포하고 있다고 생각할 수 있다.(나는 그랬다)
자바 애노테이션 내부에 동작하는 코드가 있음으로 애노테이션을 붙이면 기능이 생기는 줄 알았다.
어떻게 보면 맞는 말 같지만..
애노테이션에 내부적인 기능은 존재하지 않는다.
애노테이션은 어떠한 목적과 데이터를 가지고 있는 주석일 뿐이다.
즉 애노테이션을 정하여 컴파일, 런타임 과정에 코드를 어떻게 동작하고 조작할 것인지 나타내 주는 정보이다.
애노테이션 정의하는 방법
애노테이션을 정의하는 방법은 별다를 게 없다.
interface를 생성하는 키워드 앞에 @ 만 붙여주면 애노테이션을 정의할 수 있다.
앞서 말한 건 아무 설정하지 않은 아주아주 기본적인 애노테이션을 정의한 것이다.
public @interface Info {}
해당 애노테이션은 아무런 기능을 가지지 않고 있다.
이제 이 Info 애노테이션에 살을 붙여보자.
앞서 말한 것처럼 애노테이션은 값을 가질 수 있다고 설명했다.
애노테이션이 값을 들고 있을 수 있는 타입은 다음과 같다..
- 기본 타입(primitive type) -> int, dobule 등
- 박싱 된 기본 타입(reference type) -> String, Integer 등
- 기본 타입의 배열 -> int [] , String [] 등
public @interface Info {
String name();
int age();
String[] techStack();
}
이렇게 만들 수 있다.
또한
Info 애노테이션이 들고 있는 값들에 defalut 값을 설정할 수도 있다.
선언된 필드 뒤에 defalut 키워드와 기본 세팅해줄 값을 작성하면 된다.
public @interface Info {
String name() default "unKnow";
int age() default 99;
String[] techStack() default {"Spring,Java"};
}
이렇게 작성하게 되면 내가 이 애노테이션을 그냥 선언만 한다면 default 값으로 세팅이 된다.
그리고
클라이언트가 값을 설정하면 디폴드 값은 무시되고 클라이언트가 넣어준 값으로 세팅이 된다.
@Info(name = "jaejoon", techStack = {"java","jpa"}) //age 값은 defalut 값으로 셋팅됨.
public class User {
private String name;
private String[] techStack;
}
마지막으로 애노테이션 내부에 value라는 이름으로 필드 이름으로 작성하게 되면.
애노테이션을 사용할 때 해당 필드를 명시하지 않고 값을 세팅할 수 있다.
public @interface Info {
String value() default "??"; //value 라는 필드를 생성 후
String name() default "unKnow";
int age() default 99;
String[] techStack() default {"Spring,Java"};
}
@Info("value값으로 셋팅됩니다.") // value 값이라고 명시하지 않아도 됩니다.
public class User {
private String name;
private String techStack;
}
하지만 여러 개의 값을 세팅하려고 하면 value 필드도 명시를 해줘야 한다.
자 앞서 만들어본 Info라는 애노테이션을 다른 클래스들에 작성한다고 해서
특별한 기능을 가지고 있지 않다.
그냥 데이터를 들고 있는 주석일 뿐이다.
애노테이션을 붙인다고 해서 지금 User 필드에 잇는 name , techStack에 값을 바인딩해주지 않는다.
지금은 아무런 기능을 하지 않는다.
@Retention
이제 앞에서 만들어본 info 애노테이션을 리플렉션을 이용하여 조작해보자.
그전에 애노테이션의 Retention policy를 알아야 한다.
retention의 단어 뜻은 보유, 유지이다.
즉 애노테이션을 언제까지 유지할 건가?라고 생각하면 된다.
이제 정책을 알아보자.
retention policy는 3가지 정책이 있는데 다음과 같다.
- SOURCE
- CLASS
- RUNTIME
해당 정책들은 자세히 RetentionPolicy에 열거형 타입으로 정리되어있다.
간단하게 이런 뜻이다.
Source
컴파일러가 컴파일할 때 해당 애노테이션을 제외합니다.
Class
컴파일러가 컴파일에서는 해당 애노테이션을 가져가지만 런타임 시에는 사라지게 됩니다.
Runtime
런타임 시에도 해당 애노테이션이 유지됩니다.
그렇다면 우리는 해당 애노테이션을 리플렉션으로 조작하기 위해선 Runtime까지 유지하고 있어야 한다.
따라서 다음과 같이 코드를 변경해주자.
@Retention(RetentionPolicy.RUNTIME) //런타임시까지 해당 애노테이션을 유지하고있어야한다!
public @interface Info {
String name() default "unKnow";
int age() default 99;
String[] techStack() default {"Spring,Java"};
}
그럼 리플렉션을 이용하여
1. User클래스에 있는 애노테이션을 보고
2. 애노테이션이 들고 있는 값을 확인
3. User 인스턴스를 만들 때 필드에 애노테이션 값을 바인딩할 수 있다.
해당 코드는 다음과 같다.
info 애노테이션
@Retention(RetentionPolicy.RUNTIME) //런타임시까지 해당 애노테이션을 유지하고있어야한다!
public @interface Info {
String value();
}
User 클래스
public class User {
@Info("kjj")
private String name;
@Info("seoul")
private String local;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", local='" + local + '\'' +
'}';
}
}
main
public static void main(String[] args) {
User user = InstanceService.getObject(User.class);
System.out.println(user);
}
결과
public class InstanceService {
static <T> T getObject(Class<T>tClass){
T t = newInstance(tClass); // class 의 인스턴스를 생성
//클래스의 필드를 모두가져옴
Arrays.stream(tClass.getDeclaredFields()).forEach(f->{
f.setAccessible(true); // 접근 허용
Info annotation = f.getAnnotation(Info.class); //필드 에 붙어있는 애노테이션가져오기
try {
f.set(t,annotation.value()); //애노테이션 값을 해당 필드에 바인딩
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return t; // 인스턴스 반환
}
private static <T> T newInstance(Class<T> tClass) {
try {
return tClass.getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
리플렉션에 대해서는 설명하지 않겠다.
리플렉션을 알고 싶다면 인프런 더 자바:코드를 조작하는 다양한 방법을 추천한다.
자 만들어놓은 서비스를 통해 인스턴스를 생성하게 된다면 애노테이션에 설정해놓은 값 대로 해당 필드에 세팅이 되어 인스턴스가 만들어진다.
여기서 주의할 점은 내가 만든 애노테이션이 필드 외 다른 위치(메서드 , 생성자 등등)에 붙는다면?
작동하지 않을 것이다.
이러한 문제점을 해결하기 위해 애노테이션의 위치를 정해줄 수 있다.
바로 @Target이다.
@Target
target 은 앞서 발생할 수 있는 문제를 해결해줄 수 있다.
애노테이션이 어느 위치에서 적용해야 되는지 선택할 수 있기 때문이다.
위치 선택 방법은 다음과 같다.
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE
}
그럼 이번 글에 작성한 info 같은 경우 필드에서만 해당 애노테이션을 허용하고 싶다. 다음과 같이 하면 된다.
@Target(ElementType.FIELD)//필드에만 이 애노테이션을 허락함.
@Retention(RetentionPolicy.RUNTIME)
public @interface Info {
String value();
}
이렇게 애노테이션에 명시하게 되면 컴파일 시점에서 오류를 찾을 수 있다.
나머지 메타 애노테이션
@Documented
->자바 document 에 해당 애노테이션을 추가합니다.
@Inherited
-> 어노테이션 상속을 가능하게 해준다.
@Repeatable
-> (자바 1.8 부터 추가되었음) 어노테이션을 여러번 적용 가능하게 해준다.
빌트 인 애노테이션 (Bulit-in Annotation)
자바에서 기본적으로 제공해주는 애노테이션이다.
해당 애노테이션을 사용함으로써 사전에 컴파일 에러를 표현해줄수 있도록 해준다.
@Override
-> 메소드가 재정의 되었는지 확인합니다. 오버라이드 메서드가 아니면 컴파일 오류가 납니다.
@Deprecated
-> 메소드를 사용하지 않도록 경고합니다.
@SuppressWarnings
-> 컴파일 경고를 무시합니다.
해당 value 에 옵션을 주면되는데 옵션종류가 매우많다.
stackoverflow.com/questions/1205995/what-is-the-list-of-valid-suppresswarnings-warning-names-in-java
따로 정의되어있지않아 직접 타이핑 해야함으로 타입 세이프하지 않으니 잘보고 맞는 옵션을 잘 사용하자.
@FunctionalInterface
->람다 함수를 위한 인터페이스를 정의 할때사용 인터페이스내에 메소드가 없거나 두 개 이상일시 컴파일 오류가 나타남
애노테이션 프로세서
컴파일 시점에 특정한 애노테이션이 붙어있는 소스코드를 참조할 수 있게 해준다.
기본 공개된 API 로는 참조만 가능하고 소스코드를 추가하거나 변경할수 없다.
하지만 LomBok 같은 경우 해당 애노테이션 프로세서를 이용하여 참조 후 공개 되지않은 API 를이용하여
코드를 변경하고 추가해준다.
더 자세한 설명은 잘 이해가 되지않아 작성하지 못하였다.
'Java > live-study' 카테고리의 다른 글
백기선 라이브스터디 14주차 : 제네릭 (1) | 2021.02.26 |
---|---|
백기선라이브스터디 13주차 : I/O (0) | 2021.02.17 |
[Java] 백기선 라이브 스터디 11주차 과제: Enum (0) | 2021.01.28 |
[Java]백기선 라이브 스터디 10주차 과제: 멀티쓰레드 프로그래밍 (0) | 2021.01.20 |
[Java] 백기선 라이브스터디 9주차 : 예외 처리 (0) | 2021.01.14 |