Reflection이란 무엇인지, 그리고 기본적으로 어떻게 접근 가능한지 아래의 포스팅에서 다루었다.
2021.06.25 - [Java] - Reflection
2021.06.25 - [Java] - Reflection 사용1
이번 포스팅에서는 메소드의 호출, 필드의 값 변경, 그리고 정적 변수 및 메소드 호출 등에 대해 알아 보도록 하겠다.
메소드 호출
메소드의 호출은 다음과 같은 방법으로 할 수 있다. 우선 코드부터 보자.
우선 기존과 살짝 달라진 A.java 파일은 다음과 같다.
package com.example.demo.reflection;
public class A {
private String ps1 = "aaa";
private String ps2;
public String ps3;
public static String staticString = "staticString";
private A() {
System.out.println("this is empty arg constructor");
}
private A(String ps2) {
this.ps2 = ps2;
}
public A(String ps1, String ps2) {
this.ps1 = ps1;
this.ps2 = ps2;
}
private void method1(String arg1, Integer arg2) {
System.out.println("this is mehtod1");
System.out.println("arg1 is : " + arg1);
System.out.println("arg2 is : " + arg2);
}
private void method2() {
System.out.println("this is mehtod2");
}
public void method3() {
System.out.println("this is method3");
}
public static void method4() {
System.out.println("this is static method4");
}
}
그리고 테스트 코드는 아래와 같다.
@Test
public void callMethodTest() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("com.example.demo.reflection.A");
Class constructorParam[] = new Class[2];
constructorParam[0] = String.class;
constructorParam[1] = String.class;
Constructor constructor3 = clazz.getConstructor(constructorParam);
A a = (A) constructor3.newInstance("string1", "string2");
Method method3 = clazz.getDeclaredMethod("method3");
method3.invoke(a);
System.out.println("=============================");
Class methodParam[] = new Class[2];
methodParam[0] = String.class;
methodParam[1] = Integer.class;
Method method1 = clazz.getDeclaredMethod("method1", methodParam);
method1.setAccessible(true);
Object paramObject[] = new Object[] {"test", 1};
method1.invoke(a, "test", 1);
}
우리가 실제로 메소드를 호출할 때 메소드만 독립적으로 띠어서 생각할 수 없고 생성된 instance 안에서 생각하고 호출해야 하듯이 Reflection 도 마찬가지이다.
따라서 위의 코드처럼 method3 라는 메소드를 호출하기 위해서는 A Class의 instance를 만들어야 한다. public 생성자는 String 2개를 파라미터로 받는 것이기에 해당 생성자를 이용하여 A Class의 instance a를 만들었다.
그리고 나서 method3를 가지고 안 다음 invoke를 이용해 호출할 때 instance a를 넘기면 된다.
private method의 경우에는 어떠한가?
위의 소스 코드를 보면 private method이면서 String 타입의 파라미터 2개를 받는 method1 를 사용하여 호출을 하였다.
그런데 method3과 달리 중간에 보면 method1.setAccessible(true)라는 부분이 보인다.
이는 접근할 수 없는 private 이기에 접근 권한을 만들어 주는 부분이다.
그리고 나서 invoke를 실행해 주면 아래와 같이 출력되게 된다.
this is method3
=============================
this is mehtod1
arg1 is : test
arg2 is : 1
method1.invoke 를 호출할 때 argument를 직접 나열해서 넣어도 되지만 정의된 Object paramObject[]를 넣어도 된다.
setAccessible를 true로 주지 않은 상태에서 호출하게 되면 어떻게 될까? 주석 처리를 하고 실행하면 아래와 같은 Exception일 발생하게 된다. 즉 private 이기 때문에 접근할 수 없다라고 오류가 생기게 된다.
java.lang.IllegalAccessException: Class com.example.demo.reflection.ReflectionTest can not access a member of class com.example.demo.reflection.A with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.example.demo.reflection.ReflectionTest.callMethodTest(ReflectionTest.java:125)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
필드값 변경
필드값을 가져와서 변경을 할 수 있다. 관련된 내용을 확인하기 위해 일단 소스코드를 보자.
@Test
public void fieldChangeTest() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName("com.example.demo.reflection.A");
Field ps1 = clazz.getDeclaredField("ps1");
ps1.setAccessible(true);
System.out.println("ps1 key is : " + ps1);
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
A a = (A) constructor.newInstance();
System.out.println("ps1 value is : " + ps1.get(a));
ps1.set(a, "bbb");
System.out.println("ps1 changed value is : " + ps1.get(a));
}
일단 Class 내에 있는 ps1 이란 멤버변수가 무엇인지 가져온다. 위의 메소드에서도 했던 것 처럼 멤버변수 역시 홀로 존재할 수 없고 instance 내에서 존재해야 하기 때문에 constructor를 이용하여 instance a를 만든다.
만든 다음 ps1.get(a)와 같이 field의 값을 가져와 보여줄 수 있다.
값을 변경하기 위해서는 set method를 사용할 수 있는데 ps1.set 으로 값을 변경해 준 다음 값이 변경 되었는지 확인해 보면 제대로 동작함을 알 수 있다.
물론 private 이기 때문에 멤버 변수, 생성자 등에 setAccessible을 true로 주어야 함을 잊지 않아야 한다.
실행 결과는 아래와 같다.
ps1 key is : private java.lang.String com.example.demo.reflection.A.ps1
this is empty arg constructor
ps1 value is : aaa
ps1 changed value is : bbb
값이 잘 변경되었음을 볼 수 있다.
정적 변수 및 메소드
instance가 있을 경우 invoke 및 set에 instance a를 넘겨 실행 하였다.
그렇다면 instance가 존재하지 않는 정적(static) 메소드 또는 변수일 경우에는 어떻게 해야 할까? 아래와 같이 할 수 있다.
@Test
public void staticTest() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName("com.example.demo.reflection.A");
Field field = clazz.getDeclaredField("staticString");
System.out.println("static field is : " + field.get(clazz));
Method method = clazz.getDeclaredMethod("method4");
method.invoke(clazz);
}
field의 값을 가져오는 get에 clazz 를 넘기고, invoke에서도 clazz를 넘기면 된다. 또는 clazz 대신 null을 넘겨도 된다.
이상 간단하게 Reflection에 대해 3개의 포스팅을 통해 알아보는 기회를 가지게 되었다.
Reflection은 캡슐화를 위반할 수 있게 하고, 속도 또한 느리기 때문에 일반적인 개발에서는 사용하면 안된다고 한다. 다만 이러한 기술을 통해 IoC, DI와 같은 개념들이 어떻게 구현 되어 있는지 알 수 있는 기회가 되었다면 충분한 지식을 얻었다고 할 수 있을 것이다.
'Java' 카테고리의 다른 글
Collection Test, Fail Fast, Fail Safe... (0) | 2021.07.02 |
---|---|
Public Interface의 품질에 영향을 미치는 요소... (0) | 2021.07.01 |
Reflection 사용1 (0) | 2021.06.25 |
Reflection (0) | 2021.06.25 |
Java Random 함수의 동작 원리 (0) | 2021.05.14 |