Java

Reflection 사용2

체리필터 2021. 6. 29. 09:22
728x90
반응형

Reflection이란 무엇인지, 그리고 기본적으로 어떻게 접근 가능한지 아래의 포스팅에서 다루었다.

2021.06.25 - [Java] - Reflection

 

Reflection

리플렉션에 대해 정확하게 무엇이다라고 정의 내리기가 쉽지 않다. 명확하게 캡슐화를 하여 접근하지 못하도록 정의 된 생성자를 포함하여 멤버변수라던가 메소드에 접근 가능하도록 해 주는

www.4te.co.kr

2021.06.25 - [Java] - Reflection 사용1

 

Reflection 사용1

2021.06.25 - [Java] - Reflection 에서 Reflection 이란 무엇인지, 그리고 왜 사용하는지, 어디에서 사용 되는지 등을 확인해 봤다. 이번 포스트에서는 실제로 Reflection을 직접 사용해 보고 어떻게 동작하는

www.4te.co.kr

 

이번 포스팅에서는 메소드의 호출, 필드의 값 변경, 그리고 정적 변수 및 메소드 호출 등에 대해 알아 보도록 하겠다.

메소드 호출

메소드의 호출은 다음과 같은 방법으로 할 수 있다. 우선 코드부터 보자.

우선 기존과 살짝 달라진 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와 같은 개념들이 어떻게 구현 되어 있는지 알 수 있는 기회가 되었다면 충분한 지식을 얻었다고 할 수 있을 것이다.

 

728x90
반응형

'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