Java의 다른 클래스에서 개인 필드의 값을 읽는 방법은 무엇입니까?


482

제 3 자에 잘못 설계된 수업 JAR이 있으며 개인 분야 중 하나에 액세스해야 합니다. 예를 들어 왜 개인 필드를 선택해야합니까?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

리플렉션을 사용하여 값을 얻으려면 어떻게 stuffIWant해야합니까?

답변:


667

개인 필드에 액세스하려면 클래스의 선언 된 필드 에서 가져 와서 액세스 할 수 있어야합니다.

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

편집 : aperkins 가 댓글을 달았 듯이 필드에 액세스하고 액세스 가능으로 설정하고 값을 검색하면 Exceptions 를 던질 수 있습니다 . 단지 확인 해야 할 예외는 위에 설명되어 있습니다.

NoSuchFieldException당신이 선언 된 필드에 해당하지 않은 이름으로 필드에 대한 요구한다면 던져 질 것이다.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

IllegalAccessException분야에 액세스 할 수없는 있다면 비공개이며, 놓치고를 통해 액세스 가능하지 않은 경우 (예를 들어, 던져 질 것이다 f.setAccessible(true)선을.

RuntimeException발생 될 수 있습니다들 중 하나입니다 SecurityException(JVM의이 경우의 SecurityManager당신이 필드의 접근성을 변경할 수 없습니다), 또는 IllegalArgumentException당신이하지 필드의 클래스의 유형의 개체에 액세스에게 필드를 시도하고있는 경우, S :

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

4
예외 설명을 설명해 주시겠습니까? 코드가 실행되지만 예외가 발생합니까? 아니면 코드에서 예외가 발생할 수 있습니까?
Nir Levy

1
@Nir-아니오-모든 가능성에서 코드는 정상적으로 실행될 것입니다 (기본 SecurityManager는 필드의 적응성을 변경할 수 있기 때문에) – 확인 된 예외 를 처리 해야합니다 (잡거나 다시 던지도록 선언해야 함 ). 내 답변을 약간 수정했습니다. 당신이 놀러 작은 테스트 케이스를 작성하고 어떻게되는지하는 것이 좋은 수 있습니다
oxbow_lakes

3
죄송합니다. 답변이 혼란 스럽습니다. 아마도 일반적인 예외 처리의 예를 보여줄 것입니다. 클래스가 잘못 연결되었을 때 예외가 발생하는 것처럼 보입니다. 코드 샘플은 해당 llnes에서 예외가 발생하는 것처럼 보입니다.
LaFayette

2
getDeclaredField()필드가 부모 클래스에 정의 된 경우 필드를 찾지 못합니다. 또한 getDeclaredField()일치하는 항목을 찾거나 (나중에 호출 할 수 있음 setAccessible(true)) 또는 도달 할 때까지 각 클래스를 호출하여 부모 클래스 계층 구조를 반복해야 합니다 Object.
Luke Hutchison

1
@legend 보안 관리자를 설치하여 이러한 액세스를 금지 할 수 있습니다. Java 9 이후로, 이러한 규칙은 모듈 경계에서 작동하지 않아야합니다 (모듈이 명시 적으로 열려 있지 않는 한). 게다가 Reflection과 같은 추가 노력이 필요하면 항상 논 private필드 와 다른 점이 있습니다. 우발적 인 액세스는 불가능합니다.
Holger

159

FieldUtilsApache Commons-lang3에서 시도하십시오 .

FieldUtils.readField(object, fieldName, true);

76
commons-lang3의 몇 가지 방법을 모아서 세계의 대부분의 문제를 해결할 수 있다고 확신합니다.
카메론

@ yegor java가 왜 이것을 허용합니까? java가 개인 멤버에 액세스하는 것을 허용하는 이유는 무엇입니까?
Asif Mushtaq

1
@ yegor256 C # 및 C ++ 개인 멤버도 액세스 할 수 있습니다 .. !! 그때? 모든 언어 제작자는 약한가?
Asif Mushtaq

3
@ 알 수없는 자바는하지 않습니다. 자바 가상 머신과 같은 두 가지 다른 언어가 있습니다. 후자는 바이트 코드에서 작동합니다. 그리고 그것을 다루는 라이브러리가 있습니다. 따라서 Java 언어를 사용하면 범위 외부의 개인 필드를 사용할 수 없거나 최종 참조를 변경할 수 없습니다. 그러나 도구를 사용하여 그렇게 할 수 있습니다. 표준 라이브러리 안에있는 것입니다.
evgenii

3
@UnKnown 이유 중 하나는 Java에 DI, ORM 또는 XML / JSON 시리얼 라이저와 같은 인프라 프레임 워크를 통해서만 필드에 액세스 할 수있는 "친구"액세스 권한이 없기 때문입니다. 이러한 프레임 워크는 오브젝트의 내부 상태를 올바르게 직렬화하거나 초기화하기 위해 오브젝트 필드에 액세스해야하지만 비즈니스 로직에 대해 적절한 컴파일 시간 캡슐화 시행이 여전히 필요할 수 있습니다.
Ivan Gammel

26

리플렉션 만이 문제를 해결할 수있는 유일한 방법은 아닙니다 (클래스 / 컴포넌트의 개인 기능 / 행동에 액세스하는 것임)

다른 해결책은 .jar에서 클래스를 추출하고 Jode 또는 Jad를 사용하여 디 컴파일 하고 필드를 변경하거나 액세서를 추가 한 다음 원래 .jar에 대해 다시 컴파일하는 것입니다. 그런 다음 새 .class를 .jar클래스 경로 앞에 놓거나에 다시 삽입하십시오 .jar. jar 유틸리티를 사용하면 기존 .jar을 추출하고 다시 삽입 할 수 있습니다.

아래에 언급 된 것처럼, 이는 단순히 필드에 액세스 / 변경하는 것이 아니라 개인 상태에 액세스 / 변경하는 더 넓은 문제를 해결합니다.

이것은 필요 .jar하지 물론, 서명 할 수 있습니다.


36
이 방법은 단순한 분야에서 상당히 고통 스럽습니다.
Valentin Rocher

1
동의하지 않습니다. 필드에 액세스 할 수있을뿐만 아니라 필드에 액세스하는 것이 충분하지 않은 경우 필요한 경우 클래스를 변경할 수도 있습니다.
Brian Agnew

4
그런 다음 다시해야합니다. jar가 업데이트되고 더 이상 존재하지 않는 필드에 리플렉션을 사용하면 어떻게됩니까? 정확히 같은 문제입니다. 당신은 그것을 관리해야합니다.
브라이언 Agnew

4
a) 실용적인 대안으로 강조 표시됩니다. b) 필드의 가시성을 변경하는 것만으로는 충분하지 않은 시나리오에 적합합니다.
Brian Agnew

2
@BrianAgnew 어쩌면 의미 론적이지만 리플렉션을 사용하지 않는 질문 (반사를 사용하여 개인 필드를 읽는 것)을 고수한다면 바로 자기 모순입니다. 그러나 나는 당신이 현장에 대한 접근을 제공한다는 데 동의합니다 ... 그러나 여전히 현장은 더 이상 개인이 아니므로 우리는 질문의 "개인 필드 읽기"를 고수하지 않습니다. 다른 각도에서 .jar 수정은 경우에 따라 작동하지 않을 수 있습니다 (서명 된 jar) .jar가 업데이트 될 때마다 수행해야하며 클래스 경로를 신중하게 조작해야합니다. 응용 컨테이너) 등
레미 모린

18

아직 언급되지 않은 다른 옵션 : Groovy 사용하십시오 . Groovy를 사용하면 언어 디자인의 부작용으로 프라이빗 인스턴스 변수에 액세스 할 수 있습니다. 현장에 게터가 있든 없든 상관없이

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
OP는 특별히 자바를 요구했다
Jochen

3
오늘날 많은 Java 프로젝트는 그루비를 통합합니다. 프로젝트에서 Spring의 그루비 DSL을 사용하면 충분하며 클래스 경로에 그루비가 표시됩니다. 이 경우이 답변이 유용하며 OP에 직접 답변하지는 않지만 많은 방문자에게 도움이됩니다.
Amir Abiri

10

Java 에서 Reflection을 사용하면 private/public한 클래스의 모든 필드와 메소드를 다른 클래스에 액세스 할 수 있지만 섹션 단점Oracle 문서 에 따라 다음과 같은 권장 사항이 있습니다.

"리플렉션을 사용하면 개인 필드 및 메소드에 액세스하는 것과 같이 비 반사 코드에서 불법적 인 작업을 코드에서 수행 할 수 있으므로 리플렉션을 사용하면 예기치 않은 부작용이 발생하여 코드가 제대로 작동하지 않아 이식성이 떨어질 수 있습니다. "추상화를 중단하고 플랫폼 업그레이드로 동작이 변경 될 수 있습니다."

다음은 리플렉션의 기본 개념을 보여주는 코드 스냅 샷입니다.

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

그것이 도움이되기를 바랍니다.


5

oxbow_lakes가 언급했듯이 리플렉션을 사용하여 액세스 제한을 피할 수 있습니다 (SecurityManager에서 허용한다고 가정).

즉,이 클래스가 너무 나쁘게 설계되어 해커에게 의지하게되면 대안을 찾아야 할 것입니다. 이 작은 해킹으로 인해 몇 시간이 절약 될 수 있지만 비용이 얼마나 들까 요?


3
실제로 그보다 운이 좋았습니다.이 코드를 사용하여 일부 데이터를 추출 한 다음 휴지통으로 다시 던져 넣을 수 있습니다.
Frank Krueger

2
이 경우 해킹하십시오. :-)
Laurence Gonsalves

3
이것은 질문에 대한 답변을 제공하지 않습니다. 저자에게 비평을하거나 설명을 요청하려면 게시물 아래에 의견을 남겨주십시오.
Mureinik

@Mureinik- "반사를 사용할 수 있습니다"라는 단어로 질문에 대답합니다. 예나 더 큰 설명이나 방법이 없지만 대답입니다. 마음에 들지 않으면 공감하십시오.
ArtOfWarfare

4

Soot Java Optimization 프레임 워크를 사용하여 바이트 코드를 직접 수정하십시오. http://www.sable.mcgill.ca/soot/

그을음은 Java로 완전히 작성되었으며 새로운 Java 버전에서 작동합니다.


3

다음을 수행해야합니다.

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}

2

Spring을 사용하는 경우 ReflectionTestUtils 는 최소한의 노력으로 여기에 도움이되는 편리한 도구를 제공합니다. "단위 및 통합 테스트 시나리오에서 사용하기 위해" 설명되어 있습니다 . ReflectionUtils 라는 비슷한 클래스도 있지만 "내부 용으로 만 사용" 으로 설명되어 있습니다. 이것이 의미하는 바에 대한 해석 은 이 답변 을 참조하십시오 .

게시 된 예제를 해결하려면 다음을 수행하십시오.

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
Spring에서 Utils 클래스를 사용하기로 결정했다면 물론 테스트하지 않은 클래스 ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )를 대신 사용해야 합니다. 실제로 단위 테스트에 사용하고 있습니다.
동기화

해당 클래스는 "내부 전용"으로 설명되어있을 수 있습니다. 이에 대한 답변에 정보를 추가했습니다. ( ReflectionTestUtils내 경험 에서처럼 테스트 상황에서 이런 종류의 작업을 수행하는 데 필요한 예제를 계속 사용 했습니다.)
Steve Chambers

1

리플렉션에 대한 추가 참고 사항 : 일부 특수한 경우, 동일한 이름을 가진 여러 클래스가 다른 패키지에 존재할 때 최상위 답변에 사용 된 리플렉션이 객체에서 올바른 클래스를 선택하지 못할 수 있습니다. 따라서 객체의 package.class가 무엇인지 알고 있다면 다음과 같이 전용 필드 값에 액세스하는 것이 좋습니다.

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(이것은 저에게 효과적이지 않은 예제 수업입니다)


1

직접적이고 타입이 안전한 Java 리플렉션을 위해 Manifold의 @JailBreak 를 사용할 수 있습니다 .

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreak의 계층 구조에 foo있는 모든 멤버에 직접 액세스 할 수 있도록 컴파일러에서 로컬 변수를 잠금 해제합니다 Foo.

마찬가지로 jailbreak () 확장 메소드를 일회용으로 사용할 수 있습니다.

foo.jailbreak().stuffIWant = "123";

jailbreak()방법을 통해 Foo의 계층 구조에 있는 모든 멤버에 액세스 할 수 있습니다 .

두 경우 모두, 컴파일러는 공개 필드처럼 사용자에게 안전하게 필드 액세스를 해결하는 반면 매니 폴드는 효율적인 리플렉션 코드를 생성합니다.

매니 폴드 에 대해 자세히 알아보십시오 .


1

XrayInterface 도구를 사용하면 매우 쉽습니다 . 누락 된 게터 / 세터를 정의하십시오. 예 :

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

당신의 열악한 디자인 프로젝트를 xray :

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

내부적으로 이것은 반사에 의존합니다.


0

데이터를 설정 / 가져 오려는 calass가 자신의 클래스 중 하나 인 경우의 문제를 해결하십시오.

그냥 생성 public setter(Field f, Object value)하고 public Object getter(Field f)그 위해를. 이 멤버 함수 내에서 직접 보안 검사를 수행 할 수도 있습니다. 예를 들어 세터의 경우 :

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

물론, 지금은 알아 내야 java.lang.reflect.Field대한 sString이전 필드 값의 설정에.

이 기술은 일반적인 ResultSet-to-and-from-model-mapper에서 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.