Java - Class.getClass(), ClassLoader, .getMethod(), Method.invoke...

Java - Class.getClass(), ClassLoader, .getMethod(), Method.invoke...

심호흡하고,

먼저 .getClass()는 자신이 속한 클래스의 'Class 객체'를 반환하는 메서드이다.

Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.

그리고 클래스 파일이 '클래스 로더'에 의해 메모리에 올라갈 때, 자동으로 생성된다.

참고로 이 클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

Java의 클래스 로더는 자바 클래스를 자바 가상 머신(JVM)으로 동적 로드하는 자바 런타임 환경(JRE)의 일부이다.

일반적으로 클래스들은 요청 시 한 차례만 로드된다. 즉, 클래스 사용에 대한 요청이 없으면 로드되지 않는다.

따라서 어떤 경우에는 필요에 의해 클래스 로더를 받아 로드시키는 작업이 수행될 수도 있다. ex) .getClassLoader()

JRE는 클래스로더 덕분에 파일과 파일 시스템에 대해 알 필요가 없어진다.

1차적으로 클래스의 객체가 메모리에 존재하는지 확인하고 있다면 객체의 참조를 반환한다.

만약 없다면, classpath에 지정된 경로를 따라서 클래스 파일을 찾는다.

여기서도 없다면, 에러(ClassNotFoundException)가 발생하고 찾으면 그 클래스 파일을 읽은 후 Class 객체로 반환한다.

getClass에 의해 자동으로 반환되는 클래스 정보를 가진 객체는 다음과 같이 받는다.

Class myObj = myClass.class;

Class myObj = new myClass().getClass(); // 당연히 new myClass()는 객체를 의미한다.

Class myObj = Class.forName("myClass");

Class 클래스(class) 타입으로 참조한다는 것에 주목하자.

해당 myObj는 참조하는 객체에 대한 모든 클래스 정보를 얻는다. 이것을 통해 보다 동적인 코드를 작성할 수 있다.

굳이 왜 이걸 사용하냐는 의문이 든다면, 다이나믹한 프로그래밍이 그 목적이라고 답할 수 있다.

클래스의 정보를 코드 상에서, 스레드 간에 다이나믹하게 주고 받을 수 있기 때문이다.

예를 들어, 이런 식이다.

String myClassName = myObj.getName(); // myClass

추후에 살펴볼 메서드 invoke()가 그 동적인 구현의 연장선상에 있다.

Package java.lang.invoke Description

java.lang.invoke package contains dynamic language support provided directly by the Java core class libraries and virtual machine. Thejava.lang.invokepackage containsprovided directly by the Java core class libraries and virtual machine. As described in the Java Virtual Machine Specification, certain types in this package have special relations to dynamic language support in the virtual machine: The class MethodHandle contains signature polymorphic methods which can be linked regardless of their type descriptor. Normally, method linkage requires exact matching of type descriptors.

MethodHandle contains signature polymorphic methods which can be linked regardless of their type descriptor. Normally, method linkage requires exact matching of type descriptors. The JVM bytecode format supports immediate constants of the classes MethodHandle and MethodType.

(source: https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/package-summary.html)

영어권이라 반복을 싫어하는 오라클 형도 두 번이나 언급했다.

사실 이번 포스트는 이 invoke에 대해 기록하기 위해 작성되었다. 추후 계속 업데이트될 수 있다.

먼저 일반적인 .invoke()의 사용 방식은 다음과 같다.

Method.invoke( invoke할 객체, 요구되는 파라미터 )

정상인이라면, 이렇게 사용 방법만 봤을 때 무슨 말인지 이해가 1도 안 된다.

우선 독특한 점은 메서드를 Method(클래스 타입)로 가져와야 한다는 것이다. 이건 흡사... 말장난...?

다시 처음으로 돌아가서,

우리는 위에서 살펴본 .getClass()를 통해 얻어낸 Class가 있다.

이 메서드 뿐만 아니라, 위의 Method(클래스 타입)의 참조변수로 받을 객체를 뱉어주는 .getMethod()도 존재한다.

참고로 이 .getMethod()는 메서드명과 파라미터 타입 배열(Class[] parameterTypes)로 구성된 메서드를,

클래스에서 찾아 메서드 객체를 넘겨주는 역할을 한다.

사용법은 아래와 같다.

Method myMethod = myObj.getMethod(String 메서드명, Class[] 파라미터 타입)

이런 식으로 메서드를 받아온다. '메서드명'은 말 그대로 메서드 이름이니 볼 것 없고, ("print" 따위가 들어간다.)

뒤의 파라미터 타입이 문제인데,

Class 타입에 정의된 class 속성을 이용해 받은 값()에 대한 배열이다.

앞에서 getClass()를 이용해 얻어낸 Class 타입의 객체 myObj가 있는데, 이 객체의 속성을 .class로 꺼내면 된다.

myObj.class 혹은 그냥 클래스로 호출해서 myClass.class

예를 들어 String.class를 찍으면 반환되는 값이 "class java.lang.String"이다.

따라서 이걸 요구되는 파라미터 타입 배열로 바꾸면 new Class[]{String.class}가 된다.

헷갈릴 수 있는데, 이럴 때는 과감하게 print로 한 번 값을 찍어보는 것도 좋다.

이런 파라미터 타입에 대한 배열을 어딘가에 참조 변수로 받아서 저장한 다음 넘겨주어야 하는 것이다.

더 궁금하다면 아래의 정리가 잘 된 문서를 참고

https://www.tutorialspoint.com/java/lang/class_getmethod.htm

그래서 다시 본론으로 돌아오면,

이제 대망의 invoke를 호출하여 메서드를 대신(?) 실행해줄 메서드 객체를 사용할 수 있다

우선 사용 방법에 있어 위에서 구한 Method 타입의 객체가 핵심이 된다.

Object result = myMethod.invoke( myClassObj(myClass 타입으로 생성한 객체), 파라미터);

return result; // 일반적으로 invoke한 결과 result를 반환한다! 즉 반환 타입 또한 Object

// myClassObj는myObj랑(얘는 Class 타입) 다른데, 작명을 처음부터 이상하게 해서 혼란이 올 수 있다...ㅠ

여기서 주목해야 할 것은 myClassObj라는 객체의 클래스가 실제로 myMethod가 나타내는 메서드를 가지고 있다는 것.

파라미터는 invoke를 사용할 때 메서드에서 사용되는 값으로, 타입이 Object[]인 배열이다.

Object로 받기 때문에 다 들어올 수 있다.

즉 파라미터는,

메서드를 호출할 때 해당 클래스의 어떤 메서드를 호출할지 모르기 때문에 ( 이게 바로 동적 프로그래밍이다 이 짜식아 )

이런 식으로 받는 것이다. 반환 타입 또한 Object라는 점을 상기하자.

< 추가 TMI 1 >

Spring에서는 AOP에서 proxy를 이용한 concern을 다룰 때 사용되기도 한다.

배경지식이 부족하면 사용에 어려움이 있다.

myClass myClassObj = new myClass(parameters);

myClass proxy = (myClass) Proxy.newproxyInstance(myClass.class.getClassLoader(),

new Class[] {myClass.class},

new InvocationHandler(){

@Override

Public Object invoke(Object proxy, Method method, Object[] args ) throws Throwable{

// Cross cutting concern (before)

// Core concern

Object result = method.invoke(myClassObj, args); // (obj, pa)

// Cross cutting concern (after)

return result;

}

}

)

proxy.[가지고_있는_아무_메서드명](해당 메서드가 필요한 파라미터들);

// ex) proxy.getSum(1, 2);

// 위의 실행 결과는 직관적으로 당연히 '3'

저 마지막 한 줄을 보고 끝에 이해가 빡- 되는 경우가 있는데,

proxy가 가지고 있는 메서드 중 아무거나(!) 불러도 정상적으로 실행된다. 파라미터만 정상적으로 넣어주자.

동적 프로그래밍이란 이런 것이다, -하는 느낌이 온다.

< 추가 TMI 2 >

우선 invoke는 리플렉션(Reflection)과 자주 엮인다.

리플렉션은 decompile에서 자주 사용되는 방법이다.

컴파일이 완료된 자바 코드에서 거꾸로 클래스를 불러 메소드(Method) 및 변수(Field)를 구해오는 방법으로,

클래스를 동적으로 로드하여 사용할 때 많이 사용된다. 역시 또 동적 로딩이 나온다.

< 추가 TMI 3 >

개념적으로 이 invoke 메서드는 멀티스레드에서 데이터 보호를 위해 사용되기도 한다.

프로그램을 실행하면 하나의 스레드가 생성되는데,

이것을 Main 스레드라고 한다. 우리가 아는 그 Main() 함수는 스레드의 시작점을 의미한다.

이 Main과는 별개로 외부 스레드가 데이터에 액세스를 하면 데이터가 깨질 수 있다.

그래서 이 외부 스레드는 관련 있는 객체, 메서드에 대한 정보를 메인 스레드에게 아예 위임해버린다.

이때 사용되는 메서드가 invoke이다.

그래서 invoke를 통해 메인 스레드가 흐름을 가져와 읽기, 쓰기의 작업을 대신 수행한다.

기타 참고할 만한 내용↓

https://lonelycat.tistory.com/399?category=21497

https://bbaddoroid.tistory.com/6

https://blog.naver.com/bajohw/40198155644

https://itisfun.tistory.com/10

https://blog.naver.com/bajohw/40198155644

본 포스트는 기록의 성질을 띠고 있습니다.

혹여 잘못된 점이나 추가하면 좋을 내용은 따로 언급해주시면 감사히 반영하겠습니다. ^______^

from http://verycrazy.tistory.com/31 by ccl(A) rewrite - 2021-12-04 16:27:56