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
728x90
반응형

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 사용2  (0) 2021.06.29
Reflection 사용1  (0) 2021.06.25
Reflection  (0) 2021.06.25
Java Random 함수의 동작 원리  (0) 2021.05.14