순간을 성실히, 화려함보단 꾸준함을

[리뷰]12주차 : 애노테이션 본문

백기선님과 함께 하는 자바 스터디

[리뷰]12주차 : 애노테이션

폭발토끼 2021. 7. 15. 23:33

애노테이션은 단순히 마크만 해놓은 것

애노테이션을 자세히 공부를 하지 않으면 무슨 기능이 있는 것처럼 생각할 수 있지만 그냥 주석과 다름이 없다.
이 말이 의미하는 바는 런타임 중에 알아내야 할 값은 들어가지 못한다

예시를 하나 들어보자

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private static final String hello = "hello";
    @GetMapping(hello)
    public String hello(){
        return "hello";
    }
}

위의 코드는 hello라는 변수를 static final을 이용하여 정적인 변수로 만들었다. 당연히 @GetMapping() 안에 hello라는 변수를 넣어도 아무 이상이 발생하지 않는다.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private static String hello = "hello";
    @GetMapping(hello)                        //Compile Error
    public String hello(){
        return "hello";
    }
}

그러나 final 키워드를 빼버리면 이렇게 컴파일 에러가 발생하는걸 확인할 수 있다.

@Retention 애노테이션의 SOURCE,CLASS,RUNTIME 의 의미

포스팅에 언급은 했지만 자세한 내용은 공부하지 않아 리뷰에 다시 공부하여 업로드를 한다.(굉장히 중요한 내용)

SOURCE

- @RetentionPolicySOURCE라는 뜻은 컴파일 이후에는 이 애노테이션을 더 이상 사용하지 않겠다 라는 의미이다. 바이트코드에 남아있지를 않는다. 단순히 소스코드에서만 눈으로 보는 용도로 쓰이는 것이다.
(예를 들어 @Override는 단순히 오버라이드가 되어 있는지 안되어 있는지를 확인 하는 용도)

CLASS

- 선언한 애노테이션의 정보를 바이트코드에 남겨 놓을거라는 뜻이다. 우리가 소스를 실행할때 클래스에 관한 정보를 클래스로더가 읽어들어 메모리에 적재를 한다. 이때 CLASS라고 선언되어 있으면 애노테이션의 정보를 메모리에 적재시킬 때 누락 시켜버린다. 따라서 이는 리플렉션이 불가하게 만들어 버린다.

리플렉션이란???

구체적인 타입을 알지는 못해도,그 클래스의 메소드,타입,변수들을 접근가능하게 해주는 API를 뜻한다.

RUNTIME

- 리플렉션이 가능해 진다.(https://brunch.co.kr/@kd4/8)

getFields() 와 getDeclaredFields()

@RestController
public class HelloController {
    private String privatename;

    public String publicname;

    private static final String hello = "hello";
    @GetMapping(hello)
    public String hello(){
        return "hello";
    }
}

HelloController 라는 클래스가 이런 형태로 선언되어 있다.

import java.lang.reflect.Field;

public class HelloMain {
    public static void main(String[] args) {
        Field[] fields = HelloController.class.getFields();
        for(Field field : fields)
            System.out.println(field);
    }
}
//출력
public java.lang.String com.example.demo.HelloController.publicname

getFields()는 외부에서 접근할 수 있는 필드값만을 가져와서 보여준다.

public class HelloMain {
    public static void main(String[] args) {
        Field[] fields = HelloController.class.getDeclaredFields();
        for(Field field : fields)
            System.out.println(field);
    }
}
//출력
private java.lang.String com.example.demo.HelloController.privatename
public java.lang.String com.example.demo.HelloController.publicname
private static final java.lang.String com.example.demo.HelloController.hello

getDeclaredFields()는 선언되어 있는 모든 필드값을 가져와 보여준다.

때문에 백기선님 말씀에 의하면 Getter Setter 에 대해 너무 집착할 필요가 없다고 말씀하셨다.
이런 방법으로도 충분히 private한 필드 값들을 가져올 수 있기 때문이다.

여기서 한단계 더 들어가 보자

public class MyHelloController extends HelloController{
    private String myName;
}

MyHelloController라는 클래스는 HelloController 클래스를 상속받고 있다.
이때,

import java.lang.reflect.Field;

public class HelloMain {
    public static void main(String[] args) {
        Field[] fields = MyHelloController.class.getDeclaredFields();
        for(Field field : fields)
            System.out.println(field);
    }
}

는 어떤 값을 보여줄까?
정답은 private java.lang.String com.example.demo.MyHelloController.myName이다. 그 이유는 getDeclaredFields()는 선언되어 있는 필드값만을 보여주는데 현재 MyHelloController에는 어떠한 애노테이션도 선언되어 있지 않기 때문이다.

그럼 getFields()는 어떨까?
public java.lang.String com.example.demo.HelloController.publicname 이렇게 출력이 된다.
왜 이럴까? getFields()는 외부에서 접근을 할 수 있는 필드값만을 보여줄 수 있다고 하였다. 그럼 당연히 private 한 필드값들은 보여주지를 않는 것이다.

Service Loader

- Service Loader란?

주로 어플리케이션 내부에서 플러그인을 제공할 때 쓰이는 개념이다.
전체적인 개념은 특정 기능을 제공하는 인터페이스가 존재하고, 다양한 회사들이 이 인터페이스를 자신들의 서비스에 맞게 구현한다고 해보자.
그러면 사용자 입장에서 이 공통된 인터페이스만 가지고 있다면, 각 회사들이 제공하는 서비스들을 비교해가며 원하는 구현체만 골라서 쓸 수 있는 것이다. 이러한 인터페이스를 SPI(Service Provider Interface) 라고 한다.

길고 읽기 귀찮은 여정이 되겠지만 한번 Service Loader를 구현해 보자.

먼저 필요한 프로젝트는 총 3개이다.
필자는 demo,demo1,demo2 이렇게 3가지의 패키지를 사용을 했다.

demo 에는 HelloService라는 인터페이스를 구현해주고, 패키징을 해보자(오른쪽 maven 버튼 클릭 후 clean->install 을 누르자). 이때 주의해야 할 점은 pom.xml 파일에서 dependencybuild 들을 삭제해준 후 패키징을 실행해야 한다.

이후에 demo에 존재하는 groupId,artifactId,version 들을 복사하여 demo1pom.xml에 넣어주자.

그리고 MyHello라는 클래스를 선언한 후 HelloService의 구현체를 만들어주 주자.

구현체를 만들고 난 후에 resources 밑에 META-INFdummy 파일을 만들어주자. 그리고 META-INF 안에 services를 만들어 준다음에 HelloService의 풀경로를 파일형식으로 생성해 주자.(빨간색으로 표시한걸 유심히 봐라)

그 파일 안에는 MyHello의 풀경로를 적어주자

그 다음에 위에서 했던 거와 똑같이 패키징을 수행해주자 (clean -> install)

그리고 또 다시 groupId,artifactId,version 를 이번엔 demo2pom.xml에 넣어준 후
main 함수를 구현해 주자.

우린 현재 무엇을 한건지 다시 상기시켜보면, 현재 우린 HelloService가 어떻게 구현되어 있는지 구현체를 전혀 알지 못하는 상태이고 단순히 인터페이스만 알고 있는 상태이다. 그러나 실행시켜보면 홧팅!! 이라고 잘 출력되는 것을 확인할 수 있다.

출처 : https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html

Refernce

https://velog.io/@adduci/Java-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%A1%9C%EB%8D%94ServiceLoader

'백기선님과 함께 하는 자바 스터디' 카테고리의 다른 글

[리뷰] 13주차 : I/O  (0) 2021.07.25
13주차 과제: I/O  (0) 2021.07.21
12주차 과제: 애노테이션  (0) 2021.07.12
[리뷰] 11주차 : Enum  (0) 2021.07.10
11주차 과제: Enum  (0) 2021.07.10