자바 & Groovy & Scala & Kotlin - 33. Reflect 와 주석

13577 단어
Overview
이 장 은 주로 실제 프로 그래 밍 에서 매우 중요 한 반사 와 주해 의 두 가지 특성 을 소개 한다.
Java
주해
주 해 는 주로 메타 데 이 터 를 표시 하 는 데 쓰 인 다.자바 에서 주해 사용 기호 @interface 를 설명 합 니 다.
주석 만 들 기
예:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
    String name();
}

이상 에서 Bean 라 는 주 해 를 만 들 었 습 니 다.이 주석 에는 사용자 정의 주석 을 표시 하 는 두 개의 JVM 내 장 된 주석 RetentionTarget 이 사용 되 어 있 습 니 다.Rentention 이 주해 정 보 를 어디 에 저장 해 야 하 는 지 RUNTIME, CLASS, SOURCE 세 가지 가 있다 고 밝 혔 다.이 중 CLASS 은 기본 값 으로 RUNTIME 으로 표 시 된 주해 만 반 사 될 수 있다.Target 주 해 를 어디 에 두 어야 하 는 지, TYPE, FIELD, METHOD, PARAMETER 등 몇 가지 가 있다.즉, 성명 TYPE 일 때 주 해 를 클래스 나 인터페이스 에 놓 을 수 있 고, 성명 FIELD 은 방법 속성 에 만 적 용 됩 니 다.
위 에서 만 든 주해 에는 하나의 속성 name 이 포함 되 어 있 으 며, 이 주 해 를 사용 할 때 이 속성 을 동시에 정의 해 야 합 니 다.String name() default ""; 로 대체 하면 이 속성 을 정의 하지 않 고 기본 값 을 사용 할 수 있 습 니 다.
주 해 를 사용 하 다
세 개의 주석 만 들 기
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
    String name();
}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BeanField {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeanMethod {
    String alias() default "";
}

위 에서 만 든 주석 Bean 을 사용 할 때 name 속성 을 정의 해 야 합 니 다. BeanMethodalias 속성 은 기본 값 의 빈 문자열 이 있 기 때문에 정의 할 때 alias 속성 을 생략 할 수 있 습 니 다.
이상 의 주 해 를 사용 하 는 클래스 를 정의 합 니 다.
@Bean(name = "t_person")
class Person {

    public Person() {
    }

    public Person(int age) {
        this.age = age;
    }

    @BeanField
    private int age;

    @BeanMethod(alias = "trueAge")
    public int getAge() {
        return age;
    }

    public void setAge(final int age) {
        this.age = age;
    }

    @BeanMethod(alias = "hello")
    public void sayHello(String message) {
        System.out.println("hello " + message);
    }
}

반사
이른바 반사 란 운행 할 때 클래스 의 각종 메타 데이터 (클래스 자체, 클래스 중의 방법, 클래스 중의 속성 등) 를 얻 는 수단 이다.실제 개발 에서 반 사 는 주로 각종 도 구 를 작성 하 는 데 사용 되 는데, 우 리 는 스스로 반 사 를 작성 해 야 하 는 상황 이 매우 적다.
클래스 참조
하나의 종 류 를 반사 하기 위해 서 는 먼저 클래스 의 정 보 를 얻어 야 한다. 즉, 클래스 의 인용 을 얻 기 위해 자바 에 서 는 다음 과 같은 방법 을 사용 해 야 한다.
Class> clazz = Person.class;

방법의 인용
방법 은 클래스 에 존재 하기 때문에 방법의 인용 을 얻 기 전에 클래스 의 인용 을 먼저 얻어 야 한다.
Method setAgeMethod = clazz.getMethod("setAge", int.class);

이상 getMethod() 방법 을 사용 하여 Person 류 중의 setAge(int) 방법 을 인용 하 였 다.getMethod() 방법의 첫 번 째 매개 변 수 는 방법 명 이 고 그 다음 에 방법 을 나타 내 는 매개 변수 목록 의 변 삼 이다.getMethod() 유형 과 getDeclaredMethod() 라 는 방법 도 있다. 이들 의 가장 현저 한 차 이 는 전 자 는 공개 멤버 만 얻 을 수 있 고 후 자 는 사유 멤버 를 얻 을 수 있다 는 것 이다.기본적으로 방법, 속성, 주해 등의 인용 은 모두 이 두 가지 방법 이 있다.
방법의 인용 을 얻 은 후 invoke() 을 통 해 이 방법 을 집행 할 수 있다.invoke() 의 첫 번 째 매개 변 수 는 이 방법 을 실행 해 야 하 는 대상 이 고 그 다음 에 이 방법 에 들 어 오 는 매개 변수 목록 입 니 다.
Method setAgeMethod = clazz.getMethod("setAge", int.class);
setAgeMethod.invoke(person, 100);
Method getAgeMethod = clazz.getMethod("getAge");
int age = (int) getAgeMethod.invoke(person);
System.out.println("Age is " + age);    //  100

속성 참조
속성의 인용 은 기본적으로 같은 방법의 인용 이다.
Field ageField = clazz.getDeclaredField("age");

속성의 인용 을 통 해 속성의 값 을 얻 으 려 면 getXXX() 같은 방법 으로 만 얻 으 면 됩 니 다. 이 방법 은 매개 변 수 는 이 속성 을 가 진 대상 입 니 다.
ageField.setAccessible(true);
System.out.println("Age is " + ageField.getInt(person));

이상 age 은 성명 할 때 개인 변수 이기 때문에 먼저 setAccessible() 를 사용 해 야 접근 할 수 있 습 니 다.
구조 방법의 인용
구조 방법의 인용 도 다른 인용 과 차이 가 많 지 않 으 니 예 를 직접 보면 된다.
Constructor constructor = (Constructor) clazz.getConstructor(int.class);
Person person1 = constructor.newInstance(18);
System.out.println("Age is " + person1.getAge());

주해 의 인용
Bean bean = clazz.getAnnotation(Bean.class);
String name = bean.name();
System.out.println("Name is " + name);  //  t_person

스 트 리밍 멤버
실제 개발 에 서 는 반사 가 주로 도 구 를 만 드 는 데 사용 되 기 때문에 대부분의 경우 반사 되 는 클래스 의 구 조 를 모 르 기 때문에 클래스 의 구성원 을 옮 겨 다 니 며 이상 의 모든 방법 을 결합 해 야 합 니 다.다음은 간단 한 예 다.
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    Annotation[] annotations = field.getDeclaredAnnotations();
    if (annotations == null || annotations.length == 0) continue;
    System.out.println("Find field annotation " + annotations[0].annotationType().getSimpleName());
}

Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    Annotation[] annotations = method.getDeclaredAnnotations();
    if (annotations == null || annotations.length == 0) continue;
    System.out.println("Find method annotation " + annotations[0].annotationType().getSimpleName());  //  BeanMethod

    BeanMethod beanMethod = (BeanMethod) annotations[0];
    String alias = beanMethod.alias();
    System.out.println("Alias is " + alias);    //  trueAge

    if (method.getName().equals("sayHello")) {
        method.invoke(person, "world");
    }
    System.out.println("====================");
}

Groovy
주해
Groovy 용법 은 자바 와 같다.
주석 만 들 기
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Bean {
    String name()
}

주 해 를 사용 하 다
세 개의 주석 만 들 기
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Bean {
    String name()
}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
@interface BeanField {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface BeanMethod {
    String alias() default ""
}


이상 의 주 해 를 사용 하 는 클래스 를 정의 합 니 다.
@Bean(name = "t_person")
class Person {

    @BeanField
    def int age

    @BeanMethod(alias = "trueAge")
    def int getAge() {
        age
    }

    def void setAge(int age) {
        this.age = age
    }

    @BeanMethod(alias = "hello")
    def void sayHello(String message) {
        println("hello $message")
    }
}

반사
Groovy 의 반 사 는 자바 와 같 지만 Groovy 에 서 는 더 강력 한 메타 프로 그래 밍 을 가지 고 있 으 며 나중에 언급 할 것 입 니 다.
클래스 참조
def clazz = Person.class

방법의 인용
def setAgeMethod = clazz.getMethod("setAge", int.class)

이 방법 을 집행 하 다
def setAgeMethod = clazz.getMethod("setAge", int.class)
setAgeMethod.invoke(person, 100)
def getAgeMethod = clazz.getMethod("getAge")
int age = (int) getAgeMethod.invoke(person)
println("Age is " + age)    //  100

사용 invoke() 을 제외 하고 Groovy 는 또 다른 방식 으로 실행 하 는 방법 이 있 습 니 다. 사용 할 때 JavaScript 의 eval() 함수 처럼 보 입 니 다.
person."${setAgeMethod.name}"(20)

속성 참조
def ageField = clazz.getDeclaredField("age")

속성 값 획득
ageField.setAccessible(true)
println("Age is " + ageField.getInt(person))

구조 방법의 인용
만약 구조 방법의 인용 을 사용한다 면 반드시 필요 한 구조 방법 을 먼저 정의 해 야 하지만, 이렇게 하면 Groovy 구조 방법의 대명 파라미터 의 기능 을 상실 하 게 될 것 이다.
def constructor = clazz.getConstructor(int.class)
def person1 = constructor.newInstance(18)
println("Age is " + person1.getAge())

주해 의 인용
Bean bean = clazz.getAnnotation(Bean.class)
def name = bean.name()
println("name is " + name)

스 트 리밍 멤버
clazz.declaredFields.findAll {
    it.declaredAnnotations != null && it.declaredAnnotations.size() > 0
}.each {
    println("Find field annotation ${it.annotations[0].annotationType().simpleName}")
}

clazz.declaredMethods.findAll {
    it.declaredAnnotations != null && it.declaredAnnotations.size() > 0
}.each {
    println("Find method annotation ${it.annotations[0].annotationType().simpleName}")

    def alias = (it.annotations[0] as BeanMethod).alias()
    println("Alias is $alias")

    if (it.name == "sayHello") {
        it.invoke(person, "world")
    }
    println("====================")
}

Scala
주해
Scala 는 대부분 자바 의 주 해 를 직접 사용 하면 됩 니 다.스칼라 자체 도 스칼라 스타일 의 주석 기능 을 제공 하지만 기능 이 약해 자바 로 대체 할 수 있 습 니 다.
주석 만 들 기
Scala 가 주 해 를 만 들 려 면 ClassfileAnnotation 이나 StaticAnnotation 을 계승 해 야 합 니 다.전 자 는 자바 의 Runtime 과 유사 하여 반 사 될 수 있 고 후 자 는 반 사 될 수 없다.
class Bean(val name: String) extends ClassfileAnnotation

주 해 를 사용 하 다
세 개의 주석 만 들 기
class Bean(val name: String) extends ClassfileAnnotation
class BeanField extends StaticAnnotation
class BeanMethod(val alias: String = "") extends StaticAnnotation

이상 의 주 해 를 사용 하 는 클래스 를 정의 합 니 다.
@Bean(name = "t_person")
class Person {
    @BeanField
    private var privateAge: Int = 0

    @BeanMethod(alias = "trueAge")
    def age_=(pAge: Int) {
      privateAge = pAge
    }

    def age = privateAge

    @BeanMethod
    def sayHello(message: String) = println(s"hello $message")
}

반사
Scala 는 자체 반사 Api 가 있어 자바 보다 기능 이 풍부 하지만 개인 적 으로 사용 하기 가 매우 어렵 고 자바 를 직접 사용 하 는 것 이 편리 하 다.스칼라 의 Api 에 관심 이 있 는 사람 은 직접 홈 페이지 에 가서 문 서 를 볼 수 있다.
클래스 에 따라 대상 을 만 드 는 간단 한 스칼라 원생 Api 의 예 를 들 어 얼마나 어 려 운 지 체험 해 보 자.
val classLoaderMirror = runtimeMirror(getClass.getClassLoader)
val typePerson = typeOf[Person]
val classPerson = typePerson.typeSymbol.asClass
val classMirror = classLoaderMirror.reflectClass(classPerson)
val methodSymbol = typePerson.decl(termNames.CONSTRUCTOR).asMethod
val methodMirror = classMirror.reflectConstructor(methodSymbol)
val p: Person = methodMirror(10).asInstanceOf[Person]
p.age = 16
println(p.age)

Kotlin
주해
Kotlin 의 용법 은 자바 와 유사 하지만 큰 차이 가 있다.
주석 만 들 기AnnotationRetention 자바 와 유사 한 RetentionPolicy.AnnotationTarget 자바 와 유사 한 ElementType 이지 만 Kotlin 의 특성 상 그 값 은 FIELD, PROPERTY_GETTER 등 종류 가 있다.
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Repeatable
@MustBeDocumented
annotation class Bean(val name: String)

주 해 를 사용 하 다
세 개의 주석 만 들 기
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Repeatable
@MustBeDocumented
annotation class Bean(val name: String)

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FIELD)
annotation class BeanField

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION)
annotation class BeanMethod(val alias: String = "")

이상 의 주 해 를 사용 하 는 클래스 를 정의 합 니 다.
@Bean(name = "t_person")
class Person {
    @BeanField var age: Int = 0
        @BeanMethod(alias = "trueAge") get() = field

    @BeanMethod(alias = "hello") fun sayHello(message: String) {
        println("hello $message")
    }
}

반사
Kotlin 의 반 사 는 기호 :: 를 통 해 각 종류 와 구성원 을 직접 인용 할 수 있 지만 문자열 을 통 해 인용 하려 면 매우 번거롭다.
클래스 참조
val clazz = Person::class

함수 참조
val sayHello = Person::sayHello

이 함수 실행
println(sayHello.invoke(person, "world"))

클래스 의 함수 처럼 클래스 밖 에 정 의 된 함 수 를 직접 참조 하고 이 인용 을 매개 변수 로 전달 할 수 있 습 니 다.
fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

속성 참조
var name = Person::age

속성 값 획득
name.get(person)

마찬가지 로 클래스 밖의 속성 을 참조 할 수 있다.
var x = 2
println(::x.get())
::x.set(3)
println(x)

구조 방법의 인용
::Person

이 인용 사용
fun factory(f: () -> Person) {
    val p = f()
}
factory(::Person)

스 트 리밍 멤버
val bean = clazz.annotations.first {
    it.annotationType().typeName == Bean::class.qualifiedName
} as Bean
println("name is ${bean.name}") //  t_person

val properties = clazz.declaredMemberProperties
properties.filter {
    it.annotations.isNotEmpty()
}.forEach {
    println(it.annotations[0].annotationClass.simpleName)
}

val functions = clazz.declaredMemberFunctions
functions.filter {
    it.annotations.isNotEmpty()
}.forEach {
    println(it.name)
    println(it.annotations[0].annotationClass.simpleName)    //  BeanMethod

    val beanMethod = it.annotations[0] as BeanMethod
    println("alias is ${beanMethod.alias}") //  hello
}


Summary
  • 주 해 는 장면 을 많이 사용 하지만 보통 내 장 된 주해 의 역할 을 이해 하기 만 하면 스스로 주 해 를 정의 할 필요 가 없다
  • .
  • 반사 Api 는 대부분 사용 하기 어렵 지만 실제 사용 장면 이 많 지 않다
  • 문장 원본 코드 참조https://github.com/SidneyXu/JGSK 창고 의 _33_reflect_annotation 소절

    좋은 웹페이지 즐겨찾기