자바-12(annotation)

dragonappear·2021년 3월 21일
0

Java

목록 보기
12/22

# 학습할 것 (필수)
1. 애노테이션 정의하는 방법
2. 표준 어노테이션
3. 애노테이션 프로세서
4. 자바 리플랙션

  1. Getter and Setter 만들기

1. 애노테이션 정의하는 방법

어느테이션이란?

  • 애노테이션은 주석이다. 무언가 동작을 하거나 작동하는 코드가 아니라 단지 표시를 해놓은것이다.
  • 하지만, 일반주석과 큰 차이점은 코드를 작성할 수 있다는 것이 다르다.
  • 어노테이션으로 코드를 작성하여 뭔가를 할수있다는 뜻이다.
  • 어노테이션도 enum과 마찬가지로 1.5에 등장했다.

어느테이션을 정의하는 방법

인터페이스를 정의하는 것과 유사하다
@interface를 사용해서 어노테이션을 정의하며, 그 뒤에 사용할 어노테이션의 이름을 작성.

public @interface Make{
}

이렇게 정의하면 된다.

그러면 어노테이션의 조상은 뭘까? 바이트 코드를 통해 확인해보자.

public abstract @interface me/whiteship/livestudy/week12/annotation 
                             implements java/lang/annotation/Annotation {
    // compiled from: Make.java
}

확인결과 java.lang.annotation.Annotation에 라는것을 알 수 있다.

어노테이션은 인터페이스로 구성되었기 때문에 굳이 구현하려면 implements를 하던가 아니면 익명 클래스로 만들어야한다.

        Annotation annotation = new Annotation() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return null;
            }
        }

어노테이션 엘리먼트 규칙

  • 엘리먼트를 멤버로 가질 수 있다.
  • 각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.
  • 엘리먼트의 타입으로 기본형,String,eum,어노테이션,Class 그리고 이들의 배열 타입 만 허용된다.
  • 엘리먼트의 이름 뒤에는 메소드를 작성하는 것처럼 ()를 붙여야 한다.
  • ()안에 매개변수는 선언할 수 없다.
  • 예외를 선언할 수 없다.
  • 요소를 타입 매개변수로 정의할수없다.

annotation:

package me.whiteship.livestudy.week12;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.TYPE})
public @interface TestAnnotation {
    String element1();
    int element2() default 5;
    String[] element3() default("test");
}

AnnotationClassTest:

package me.whiteship.livestudy.week12;

@TestAnnotation(element1 = "test")
public class AnnotationClassTest {

    @TestAnnotation(element1 = "test1",element2 = 6)
    public void AnnotationMethodTest() {
    };

    @TestAnnotation(element1 = "test1",element2 = 6,element3 = {"test1,test2"})
    public void AnnotationMethodTest2() {
    }
}

어노테이션은 기본 엘리먼트인 value를 여러타입으로 가질수있다.

ValueAnnotation:

package me.whiteship.livestudy.week12;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
public @interface ValueAnnotation {
    String value();
    int element1() default 3;
}

ValueAnnoTest:

package me.whiteship.livestudy.week12;

@ValueAnnotation("hi")
public class ValueAnnoTest {
}

value 엘리먼트를 가진 어노테이션을 코드에서 적용할떄는 위와 같이 값만 기술할수있다.
이 값은 기본 엘리먼트인 value 값으로 자동설정된다.
만약 value 엘리먼트와 다른 엘리먼트에 값을 동시에 주고 싶다면 아래와 같이 이름을 지정하여 값을 부여해야 한다.

ValueAnnotation:

package me.whiteship.livestudy.week12;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
public @interface ValueAnnotation {
    int[] value();
    int element1() default 3;
}

ValueAnnoTest:

package me.whiteship.livestudy.week12;

@ValueAnnotation(value={1,2}, element1 = 5)
public class ValueAnnoTest {
}

싱글값 애노테이션

CustomUserAnnotation:

package me.whiteship.livestudy.week12;

import java.lang.annotation.*;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomUserAnnotation {
    String value();
}

Sample:

package me.whiteship.livestudy.week12;

public class Sample {

    @CustomUserAnnotation("hi!")
    public void go(){
        System.out.println("annotationTest");
    }
}

어노테이션 멤버의 이름을 value()로 주었을경우, 외부에서 호출시()에 멤버 = 형식을 생략할수있다.

어노테이션에 값을 전달할 때 런타임중에 알아내어야 하는 값은 들어갈 수 없다. 컴파일러 수준에서 해석되는 완전 정적인 값만 들어가야한다.

package me.whiteship.livestudy.week12;

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는 완전 정적이다. 만약 여기서 final을 제거한다면 hello는 값이 바뀔수있게 되고 동적이기 때문에 hello는 더이상 @GetMapping에 사용될수없다.


2. 표준 어노테이션

자바에서 제공되는 어노테이션은 크게 2가지로 구성되어져있다.
하나는 자바코드를 작성할때 사용되는 어노테이션
다른 하나는 어노테이션을 정의하기 위해 필요한것들이다.

1. @Override

오버라이드 할 때 사용되며, 메서드가 오버라이드 되었는지 알려주는 역할을 한다.
이 어노테이션이 생략이 가능하지만, 존재하는 이유는 미연의 실수를 방지하기 위함이니 생략을 하지않는 것이 좋다.
왜 생략이 가능한지 이따가 살펴보자
그 전에 @Override 어노테이션 정보를 알아보자.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

2. @Deprecated

앞으로 사용되지 않을 대상에 붙여진다고 한다.
이 어노테이션이 존재하는 이유는 버전이 올라가면서 더이상 사용되지 않는것들이 존재하는데 그것을 삭제할수없기 때문이다.

예를 들어, Date 클래스가 있는데


@Deprecated
    public Date(int year, int month, int date) {
        this(year, month, date, 0, 0, 0);
    }
/*
Deprecated
As of JDK version 1.1, replaced by Calendar.set(year + 1900, month, date, hrs, min) or GregorianCalendar(year + 1900, month, date, hrs, min).
*/

Date 클래스를 살펴보면 생성자에 Deprecated가 있다는것을 확인할수있다.

아래 설명글을 읽어보면 이 클래스는 더이상 사용하지 말고, Calender클래스를 사용하라고 권장하고 있다.

그렇다면 이 클래스를 자바자체에서 삭제하면 되지않을까? 라고 생각할수있지만 그렇게 된다면 기존에 Date 클래스가 작성이 되있던 잘 동작하는 프로그램이 동작하지 않을수있다.
그러면 개발자가 일일이 그것을 찾아다니면서 수정해야된다

결국 호환성 때문에 존재하는 어노테이션이라는것을 알수있다.

어노테이션 정보:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

어노테이션은 인터페이스로 되어있기 때문에 구현이 되지않는것을 알수있다.


3. @FunctionalInterface

이 어노테이션은 이 인터페이스는 무조건 함수형으로 사용해야한다는 뜻이다.

함수형으로 작성할때는 몇가지 규칙이 존재하는데,

그중에 인터페이스는 단 하나의 메서드만 존재해야 된다.

package me.whiteship.livestudy.week12;

@FunctionalInterface
public interface Make {
    void hello();
    void hello2(); // 오류
}

그렇지 않으면 자바에서 함수형으로 만들수없다.

어노테이션 정보:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

4. @SuppressWarnings

컴파일러가 보여주는 경고 메시지가 보이지 않게 억제해준다.

package me.whiteship.livestudy.week12;

import java.lang.annotation.Annotation;
import java.util.ArrayList;

@SuppressWarnings("unchecked")
public class App {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("String");
        list.add(2);
    }
}

확인결과 노란 하이라이트로만 경고 메시지가 나오는 것을 확인할수있다.

어노테이션 정보:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

이 밖에도 제너릭 타입의 가변인자에 사용되는 @safeVarargs, native 메서드에서 참조되는 상수 앞에 붙이는 @Native가 존재한다.

이제 어노테이션을 정의하기 위한 어노테이션들을 살펴보자.


5. @target

어노테이션이 적용 가능한 대상을 지정하는데 사용된다.
위에서의 Override를 다시 확인해보자

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override는 메서드에서만 사용된다는 것을 알수있다.

즉, 다른 필드변수,생성자,파라미터 등에서는 사용이 불가능하다.

메서드,타입,필드,어노테이셥타입,생성자,지역변수,모듈,패키지,파라미터,타입 파라미터, 타입 유즈에서 사용할수있다고 한다.

예제에서는 한곳만 작성했지만, 배열을 이용하면 여러개도 추가가 가능하다.

어노테이션 정보:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

이것은 어노테이션 타입에만 사용할수있다는 것을 알수있다.


6. @retention

어노테이션이 유지되는 범위를 지정하는데 사용된다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

functionalInterface는 확인결과 runtime까지 유지가 된다고 한다.

  1. source로 사용되면 일반 주석처럼 사용된다는 뜻이다.

소스단에서만 사용하고 컴파일될때는 이 어노테이션이 필요 없다는 뜻이 된다.
컴파일하고나면 그 annotation의 정보가 사라진다.

대표적으로 @Overrride를 들수있다.

package me.whiteship.livestudy.week12;

public class Korea implements Great{
    @Override
    public String country() {
        return "Korea";
    }
}

분명히 Override로 오버라이드를 시도했지만, 이것을 컴파일 돌린 결과는

package me.whiteship.livestudy.week12;

public class Korea implements Great {
    public Korea() {
    }

    public String country() {
        return "Korea";
    }
}

override가 사라진것을 볼수있다.

  1. CLASS

컴파일될때까지 어노테이션을 유지한다는 것을 의미한다.
컴파일하고 나면 그 annotation의 정보가 사라진다.
바이트코드에 들어있다가 런타임때 클래스 로더가 메모리에 적재할때 class 어노테이션은 누락시킨다.-> reflection 불가능
아쉽게도 자바에서 제공한 어노테이션들은 class가 존재하지 않는다.
이것이 retation의 기본전략이라고 한다.

그래서 만들어보자

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Make {
}
package me.whiteship.livestudy.week12;

public class Korea implements Great{
    @Override
    @Make
    public String country() {
        return "Korea";
    }
}

컴파일을 돌려보았을때

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package me.whiteship.livestudy.week12;

public class Korea implements Great {
    public Korea() {
    }

    @Make
    public String country() {
        return "Korea";
    }
}

소스와 달리 컴파일시는 유지되는 것을 알수있다.
그러면 런타임시에는 유지가 될까?
이것의 바이트 코드를 확인해보면 대략적으로 짐작이 가능하다.

public country()Ljava/lang/String;
  @Lme/whiteship/livestudy/week12/Make;() // invisible
   L0
    LINENUMBER 7 L0
    LDC "Korea"
    ARETURN
   L1
    LOCALVARIABLE this Lme/whiteship/livestudy/week12/Korea; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

보면 invisible이라고 이 어노테이션은 보이지 않는다고 한다.

그러면 정책을 runtime으로 변경시키면 어떻게 나올까?

public country()Ljava/lang/String;
  @Lme/whiteship/livestudy/week12/Make;()
   L0
    LINENUMBER 7 L0
    LDC "Korea"
    ARETURN
   L1
    LOCALVARIABLE this Lme/whiteship/livestudy/week12/Korea; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

놀랍게도 CLASS에서 확인한 invisible이라는 주석은 보이지 않는다는 것을 확인할수있다.

어노테이션의 정보:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

7. @Documented

어노테이션 정보가 javadoc으로 작성된 문서에 포함된다고 한다.

자바docs에 올라간다는것이 아니라, 내가 직접 자바doc을 만들수있다는 뜻이다.

이런식으로 만들수있는데

Local 지역입력 ko_KR

other command line arguments : 한글깨짐 방지
-encoding UTF-8 -charset UTF-8 -docencoding UTF-8

적절하게 내용을 채운뒤 output directory에 경로를 입력해주면 끝이다.

  1. 코드:
package me.whiteship.livestudy.week12;

public class Korea implements Great{
    @Override
    @Make
    public String country() {
        return "Korea";
    }
}
  1. 없는거

  1. 있는거

어노테이션 정보:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

8. @Inherited

상속받은 클래스에도 어노테이션이 유지된다는뜻이다.

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Make { }
@Make
public class Korea implements Great{
    @Override
    public String country() {
        return "Korea";
    }

    public static void main(String[] args) {

    }
}
package me.whiteship.livestudy.week12;

public class Seoul extends Korea{
    @Override
    public String country() {
        return super.country();
    }
}

바이트코드,컴파일코드 전부 살펴봐도, Inherited가 사용되었다는 증거를 찾지 못했다.
그래서 리플랙션을 이용해서 어떤 어노테이션이 존재하는지 확인하였다.

public class Seoul extends Korea{
    @Override
    public String country() {
        return super.country();
    }

    public static void main(String[] args) {
        Seoul seoul = new Seoul();
        Annotation[] annotations = seoul.getClass().getAnnotations();

        Arrays.stream(annotations)
                .forEach(System.out::println);
    }
}

output:

@me.whiteship.livestudy.week12.Make()

Process finished with exit code 0

Seoul 클래스에 @Make를 달지않아도, @Inherited에 의해 리플렉션 결과는 make 어노테이션이 존재한다는것을 알수있다.

어노테이션 정보:

Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

이밖에도 어노테이션을 여러번 반복할수있는 @Repeatable이 존재한다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

9. meta annotation

위에서 살펴본 어노테이션들은 메타 어노테이션이라고 한다.

위 어노테이션들은 한가지 공통점이 존재한다

  • @Target이 전부 annotation-type으로 되어있다는점

결국 타겟이 어노테이션 타입으로 지정되어있으면 메타 어노테이션이라고 생각해도 될듯싶다.

DeclaredAnnotations

Hello:

package me.whiteship.livestudy.week12;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Hello {
}

HelloController:

package me.whiteship.livestudy.week12;

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

@RestController
@Hello
public class HelloController{
    private String privateName;
    private static final String hello = "hello";

    @GetMapping(hello)
    public String hello(){
        return "hello";
    }
}

MyHelloController:

package me.whiteship.livestudy.week12;

public class MyHelloController extends HelloController{
}

HelloMain:

package me.whiteship.livestudy.week12;

import java.lang.annotation.Annotation;

public class HelloMain {

    public static void main(String[] args) {
        Annotation[] annotations = MyHelloController.class.getAnnotations();

        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

output:

@me.whiteship.livestudy.week12.Hello()

Process finished with exit code 0

출력결과 @inherited를 가진 @Hello()를 출력한다.

package me.whiteship.livestudy.week12;

import java.lang.annotation.Annotation;

public class HelloMain {

    public static void main(String[] args) {
        Annotation[] annotations = MyHelloController.class.getDeclaredAnnotations();

        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

output:

Process finished with exit code 0

그냥 getAnnotations()를 사용하면 @Inherited가 출력되지만 getDeclaredAnnotations()를 사용하면 출력되지 않는다. private 하다는 말이 이러한 것이다. 본인 클래스에게 명시되어있는 어노테이션만 출력하기 때문이다.


3. 애노테이션 프로세서

어노테이션 프로세서란?

자바가 제공하는 기능으로 "컴파일 시점"에 특정한 애노테이션이 붙어있는 소스코드를 참조해서 애노테이션에 정의된 액션(새로운 소스코드를 생성하거나, 기존의 코드를 수정하거나, resource 파일생성) 등의 작업을 할수있는 기능이다.

즉, 컴파일 단계에서 어노테이션을 분석하고 처리하기 위해 자바 컴파일러에 동봉된 hook이다.

이러한 어노테이션 프로세서를 이용하면 컴파일 단계에서 소스를 조작할 수있게 되므로 이와 관련된 라이브러리는 Annotation processor를 사용한다고 생각하면 되고, 실제로 Lomkor, JPA 등등 꽤 많은 라이브러리에서 사용하고있다.
간단하게 애노테이션 프로세서를 사용하는 Lombok에 대해 알아보자.

먼저 프로젝트에 롬복 라이브러리를 추가해보자.

인텔리제이의 메뉴의 File>Project Structure에 들어간다.

왼쪽 Libraries 메뉴에 들어가서 + 버튼을 누른 다음 From Maven을 클릭한다.

롬복은 다음처럼 동작한다.

@Data
public class Test{
	private int num;
}

나는 클래스에 setter를 추가한적이없지만, 이상하게도 추가되어있다.

이것을 돌려보면

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package me.whiteship.livestudy.week12;

public class Test {
    private int num;

    public static void main(String[] args) {
        Test test = new Test();
        test.setNum(5);
    }

    public Test() {
    }

    public int getNum() {
        return this.num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Test)) {
            return false;
        } else {
            Test other = (Test)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                return this.getNum() == other.getNum();
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof Test;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        int result = result * 59 + this.getNum();
        return result;
    }

    public String toString() {
        return "Test(num=" + this.getNum() + ")";
    }
}

클래스 파일을 열어보니 내가 만들지도 않는 메서드들이 추가된것을 확인할수있다.
이것은 런타임시 생성된것이 아닌, 컴파일할때 생성되었다.

따라서 런타임에 비용이 추가되지 않는다.
하지만 단점은 알고쓰면 더없이 좋은데, 모르고 쓰면 의도하지 않는 동작을 할수도 있다.

위를 설명하자면 @Data라는 어노테이션이 붙으면 자바는 이 어노테이션이 붙은 곳을 찾는다.
그리고 마치 모자에서 토끼를 꺼내듯이 신기하게도 위 코드를 만들어줬다.

만약 똑같이 했는데 바이트 코드가 생기지 않는다면 인텔리제이에서 다음 설정을 활성화해야한다.

File > Settings

annotation processor를 검색해서 나온 메뉴에 들어가서 오른쪽에 Enable annotation processing이 활성화 되어있는지 확인해보자.

컴파일 타임에 annotation processor가 있는것을 컴파일러는 어떻게 알고 어노테이션에 대해 전처리를 할수있을까?

lombok 라이브러리를 좀 더 자세히 들여다보자.

라이브러리를 열고 들여가보면 META-INF 하위에 services 라는 폴더가 있는데 그안에 javax.annotation.processing.Processor 라는 파일이 있다.

이 이 파일을 열면 다음과 같은 내용이 있다

lombok.launch.AnnotationProcessorHider$AnnotationProcessor
lombok.launch.AnnotationProcessorHider$ClaimingProcessor

이번엔 lombok.launch.AnnotationProcessHider 클래스에 중첩클래스인 AnnotationProcessor를 열어보자.

파일에 작성되어있던 AnnotationProcessorHider 클래스에 중첩된 두개의 클래스를 볼수있는데 공통점이 있다. 두개다 AbstractProcessor 클래스를 상속받고있다.

결론은

Annotation Processor를 등록하려면

  1. META-INF 디렉토리 하위에 services 디렉토리를 만들고
  2. javax.annotation.processing.Processor 라는 파일을 만들고
  3. 그 안에 AbstractProcessor 클래스를 상속받아 구현하고 있는 클래스의 FQCN을 작성하면 된다.

그러면 컴파일러는 이 정보를 바탕으로 annotation processor 기능을 수행할 수 있게 된다.

어노테이션은 단독으로 사용되면 단순한 주석에 불과하지만, 어노테이션 프로세서라는 기술때문에 어노테이션을 다양하게 사용할수있다.

ServiceLoader

ServiceLoader?

public interface HelloService{
	String hello();
}

이렇게 인터페이스가 있고 나는 이러한 인터페이스만 제공한다고 했을때 구현체는 구글에서 만들수도있고, 네이버에서도 만들수있고, 카카오나 우형에서 만들 수도 있다. 제각각의 jar파일로 만들텐데, 인터페이스의 구현체를 내가 만들지 않고 jar파일만 바꾸어 끼우면 해당 인터페이스 타입을 구현한 구현체의 인스턴스를 가져올수있는 방법이 바로 ServiceLoader 이다.

그렇다면 이 서비스 구현체들을 어떻게 찾을것이냐? 인터페이스를 사용하는 구현체의 resources폴더에 META-INF/services를 만들고 그 안에 인터페이스의 풀패키지경로를 이름으로 하는 파일을 만들어준다. 그리고 내용으로 구현체의 풀패키지 경로를 작성한다. 다 되었다면 패키징을 한 후 사용할 프로젝트에서 dependecny로 추가해주자.

이렇게 추가해주면 직접 참조하지 않아도 사용할 수 있게 된다.
코드를 통해 확인해보자.

package me.whiteship.livestudy.week12;

import java.lang.annotation.Annotation;
import java.util.ServiceLoader;

public class HelloMain {

    public static void main(String[] args) {
        ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);

        for (HelloService helloService : loader) {
            System.out.println(helloService.hello());
        }
 }
}

4. Java Reflection

reflection은 자바의 특징 중 하나로 객체를 통해 클래스의 정보를 분석하는 프로그램 기법이다. 쉽게 말해서 구체적인 클래스 타입을 알지못해도 해당 클래스의 객체 생성,메소드,타입,변수들에 접근할 수 있도록 도와주는 API이다.

자바에서는 구체적인 클래스 타입을 알지 못하는 상태에서는 메소드를 호출할수는 없다


public class Fruit{
	public void Apple(){
    	System.out.println("사과"
);
    }
}

public class Main(){
	public static void main(String[] args){
    	Object fruit = new Fruit();
        fruit.Apple(); // 컴파일 에러
    }
}

모든 클래스의 조상인 Object 타입은 Fruit 클래스의 인스턴스를 담을 수는 있지만 사용하지는 못하기 때문이다.

이런 식으로 구체적인 타입의 클래스를 모를때 사용하는게 리플렉션이다.

리플렉션이 어떻게 가능한 것일까?

우리가 작성한 소스코드가 JVM이라는 프로그램 위에서 실행된다는 특성을 갖고있다.
우리가 자바 코드의 컴파일과 실행을 시도하면, JVM 상에 우리가 작성한 코드들(.java)는 .class라는 바이트 코드로 변환된 후 jvm상에 로드된다. 그리고 클래스에 대한 정보는 jvm의 runtime Data Area의 클래스 영역이라는 메모리 공간에 저장된다. 즉 런타임 시점에는 로드된 클래스들의 정보를 가져올수있기 때문에 이러한 리플렉션이 가능한것이다.
참고링크

  • ClassName
  • Class Modifiers(public, private, synchronized 등등)
  • Package Info
  • Superclass
  • Implemented Interface
  • Constructors
  • MethodsFields
  • Annotations
package me.whiteship.livestudy.week12;


import java.lang.annotation.Annotation;

@Make
public class Korea implements Great{
    @Override
    public String country() {
        return "Korea";
    }

    public static void main(String[] args) {
        Annotation[] annotations = Korea.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

필드값 변경:

package me.whiteship.livestudy.week12;

import java.lang.reflect.*;

@Make
public class Korea implements Great{
    public int num = 5;

    @Override
    public String country() {
        return "Korea";
    }


    public static void main(String[] args) {
        try {
            Class c = Class.forName("me.whiteship.livestudy.week12.Korea");
            Field f= c.getField("num");
            Korea korea = new Korea();
            System.out.println(korea.num);
            f.setInt(korea,8);
            System.out.println(korea.num);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e ){
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

5. Getter and Setter 만들기

참고링크
나중에 직접해보자


참고

참고링크
참고링크
참고링크

0개의 댓글