[백기선님_자바_스터디] 12주차 - 어노테이션

시나브로·2021년 7월 27일
0

자바 라이브 스터디

목록 보기
12/15
post-thumbnail

1. 어노테이션 (Annotation)



@ 기호를 사용하는 문법 요소로 Java5부터 등장했다.

프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 어노테이션이다.

XML 설정 파일을 통해 명시하고 관리할 때, 프로그램 작성 때마다 많은 설정을 작성해야 하는 단점이 있다. 웹 애플리케이션이 커짐에 따라 이는 더 극대화되었고 해결하기 위해 나온 방법이 바로 어노테이션이다.

단어의 의미인 주석과는 비슷하지만 다른 역할로써 사용되는데 메서드/클래스 등에 의미를 단순히 컴파일러에게 알려주기 위한 표식이 아닌 컴파일타임 이나 런타임에 해석될 수 있다.

어노테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.



1.1 장점


기존 XML 관리 방식은 서비스의 규모가 클 수록 설정양이 많아지고 도메인 처리의 데이터들이 분산되어 있어 수정이 힘들었다.

어노테이션은 데이터 유효성 검사 등 직접 클래스에 명시해 줄 수 있게되어 수정이 필요할때 쉽게 파악할 수 있게 되었고 재사용도 가능해졌다.

AOP(관점 지향 프로그래밍)을 쉽게 구성할 수 있게 해준다.



2. 종류



어노테이션은 크게 총 2가지로 나뉜다.

  • Built-in Annotation : 자바에서 기본적으로 제공하는 어노테이션

  • Meta Annotation : 커스텀 어노테이션을 만들수 있게 제공된 어노테이션.


2.1 Built-in Annotation


종류설명
@Override선언한 메서드가 오버라이드 되었다는 것을 나타냄.
* 상위(부모)클래스(또는 인터페이스)에서 해당 메서드를 찾지 못하면 컴파일 에러 발생.
@Deprecated해당 메서드가 더이상 사용되지 않음을 표시합니다.
* 해당 메서드를 사용할 경우 컴파일 경고를 발생.
@SuppressWarnings선언한 곳의 컴파일 경고를 무시.
@SafeVarargs제네릭 가변인자 매개변수를 사용할 때의 경고를 무시한다.(Java 7 이상)
@FunctionalInterface람다 함수등을 위한 인터페이스 지정.(Java 8 이상)
* 메소드가 없거나 두개이상 되면 컴파일 오류 발생

2.2 Meta Annotation


종류설명
@Retention어노테이션이 유지되는 기간을 지정하는데 사용한다.(세가지 유지정책 사용)
@Documented해당 어노테이션을 javadoc에 포함시킨다.
@Target어노테이션이 적용할 위치를 지정한다. 여러 값일 경우 {} 사용
@Inherited어노테이션의 상속을 가능하게 한다.
@Repeatable연속적으로 어노테이션을 사용할 수 있게 해줍니다.



3. 사용법



public @interface MyAnnotation {

}
  • @interface는 애노테이션 타입(annotation type)을 선언하는 키워드다.
  • 필드 반환 유형은 Primitives, String, Class, enums, annotation, array로 제한된다.

3.1 @Retention


어노테이션이 유지되는 기간을 지정하는데 사용되는 Meta Annotation이다.

  • RetentionPolicy.Source : 컴파일 전까지만 유효
  • RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효(Default 값)
  • RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조 가능(리플렉션 가능)

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

}

3.2 @Target


@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 {
}

3.3 @Documented

@Documented는 해당 어노테이션을 javadoc 파일에 추가시킬지 여부이다.


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnnotation {
    String testDocument();
}



4. Java8에서의 어노테이션



  • 자바 8 부터 애노테이션을 타입 선언부에도 사용할 수 있게 되었다.
  • 자바 8 부터 애노테이션을 중복해서 사용할 수 있게 되었다.
  • 타입 선언부
    • 제네릭 타입
    • 변수 타입
    • 매개변수 타입
    • 예외 타입
    • ...

타입에 사용할 수 있으려면 TYPE_PARAMETER, TYPE_USE 으로 사용 가능하다.


4.1 중복 가능한 어노테이션

  • @Repeatable
  • 어노테이션들을 감싸고 있을 컨테이너 어노테이션을 선언해야 한다.
  • 중복 사용한 어노테이션 만들기
    • 컨테이너 애노테이션은 중복 어노테이션과 @Retention@Target 이 같거나 더 넓어야 한다.
      • 컨테이너이기 떄문에, 이것은 접근 지시자의 범위와 유사한 개념이라고 볼 수 있다.

4.2 사용법


@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>{

    }
}



5. 어노테이션 프로세서



어노테이션 프로세서는 소스코드 레벨에서 소스코드에 붙어있는 어노테이션을 읽어서 컴파일러가 컴파일 하는 중에 새로은 소스코드를 생성하거나 기존 소스코드를 바꿀 수 있다.

또는, 클래스(바이트코드) 도 생성할 수 있고 별개의 리소스파일을 생성할 수 있는 강력한 기능이다.

롬복 (기존코드를 변경한다), AutoService (리소스 파일을 생성해준다.), java.util.ServiceLoader 용 파일 생성 유틸리티, @Override 등이 있다.


5.1 장단점


장점

  • 바이트코드에 대한 조작은 런타임에 발생되는 조작임으로 런타임에 대한 비용이 발생한다.
  • but. 애노테이션 프로세서는 애플리케이션을 구동하는 런타임 시점이 아니라, 컴파일 시점에 조작하여 사용함으로 런타임에 대한 비용이 제로가 된다.

단점

  • 기존의 코드를 고치는 방법은 현재로써는 public 한 API 가 없다.
  • 롬복 같은 경우 기존 코드를 변경하는 방법이지만 public 한 api를 이용한 것이 아님으로 해킹이라고 할 수 도 있다.



6. ServiceLoader



public interface HelloService{
	String hello();
}

위와 같은 Service를 다른 곳에서 각각 구현해서 사용가능하다 하자.

이 인터페이스의 구현체는 누군지 모른다. 이 구현체를 내가 지정하지 않고, Jar 파일만 바꿔끼면 동작하도록 만들 수 있도록 한게 Service Loader 이다.


6.1 사용법


Project : hello-service

public interface HelloService{
	String hello();
}

-> jar 파일 생성


Project : my-hello

  • 위 hello-service의 의존성 주입
public class MyHello implements HelloService{
	@Override
	public String hello(){
		return "홧팅!";
	}	
}
  • resources 하위에 META-INF/services 디렉토리 생성
    • 파일 생성 → 인터페이스의 풀패키지 경로로 파일을 생성한다.
      • 내용에는 구현체의 풀패키지 경로를 작성한다.
    파일명 : resources/META-INF/services/me.cham.hello-service.HelloService
    파일 내용 : me.cham.my-hello.MyHello

-> jar 파일 생성


Project : app

  • 위 my-hello 의존성 주입
    • my-hello는 hello-service 의존성을 주입하고 있음.
public static void main(String[] args){
	ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
	for(HelloService helloService : loader){
		System.out.println(helloService.hello());
	}
}
  • HelloService 인터페이스의 구현체가 뭐가 있는지 모르는 상황에서 위와 같이 사용할 수 있다.
  • loader는 얼마나 있을지도 모름으로 위와 같이 ServiceLoader<T> loader 형태로 반환된다.
  • loop를 돌면서 위 예제에선 HelloService 인터페이스 구현체가 모두 처리된다.



7. Java Reflection

모든 클래스 파일은 클래스로더(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가 붙은 애노테이션을 무시합니다.)








참조

profile
Be More!

0개의 댓글