2017-12-26 6 views
2

반사를 사용하여 private static final 필드의 값을 변경하려고했습니다 (예, 처음에는 매우 나쁜 생각이었습니다). 예상대로 인쇄Java Reflection - "get"작업 앞에 "set"이있는 경우 IllegalAccessException

import java.lang.reflect.Field; 
import java.lang.reflect.Modifier; 

public class A { 

    public static void main(String[] args) throws ReflectiveOperationException { 
     System.out.println("Before :: " + B.get()); 
     Field field = B.class.getDeclaredField("arr"); 
     field.setAccessible(true); 
     // System.out.println("Peek :: " + ((String[]) field.get(null))[0]); 
     Field modifiersField = Field.class.getDeclaredField("modifiers"); 
     modifiersField.setAccessible(true); 
     modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 
     field.set(null, new String[] { "Good bye, World!" }); 
     System.out.println("After :: " + B.get()); 
    } 
} 

class B { 
    private static final String[] arr = new String[] { "Hello, World!" }; 

    public static String get() { 
     return arr[0]; 
    } 
} 

: 다음 코드 그리고, 물론, 대부분의 경우는 잘 사용하여 작동 내가 반사를 통해 get로 필드 값을하려고 할 때

Before :: Hello, World! 
After :: Good bye, World! 

문제가 발생 이전에 set 팅. 나는 위의 예제에서 주석 행의 주석을 해제하면, 나는 다음과 같은 수 있습니다 :

Before :: Hello, World! 
Peek :: Hello, World! 
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Ljava.lang.String; field B.arr to [Ljava.lang.String; 
     at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76) 
     at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80) 
     at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77) 
     at java.lang.reflect.Field.set(Field.java:764) 
     at A.main(A.java:14) 

왜 이런 일이? get을 호출 한 후 accessible 플래그를 다시 설정하려고 시도했지만 도움이되지 않습니다. 나는 도움이되지 않는 많은 다른 것들을 시도했다 ...

당신의 도움에 감사드립니다!


편집는 (@ ROGERIO에 의해 참조 "중요 업데이트") Using reflection to change static final File.separatorChar for unit testing? 답변의 요소가있다.

중요 업데이트 : 모든 경우에하지 작업을 수행 위의 솔루션입니다. 필드가 액세스 가능 해지고 Reflection을 통해 읽혀 재설정되기 전에 읽으면 IllegalAccessException이 발생합니다. Reflection API가 캐시되고 재사용되는 내부 FieldAccessor 오브젝트를 작성하기 때문에 실패합니다 (java.lang.reflect.Field # acquireFieldAccessor (boolean) 구현 참조). 실패 예제 테스트 코드 :

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null); 
// call setFinalStatic as before: throws IllegalAccessException 

을 슬프게도, 그것을 해결하는 방법에 대해 아무 말도하지 않고는 ... 어떻게 필드를 "재설정"합니까?

+0

최종적으로 설정할 수 없습니다. –

+0

흥미롭게도, 리플렉션을 통해 정상적인 방법으로 값을 얻는다면 나중에 리플렉션을 통해 설정하면 오류가 발생하지 않습니다. –

+0

흥미로운 질문입니다. 이 코드가 어디에서 왔는지 보려면 코드를 살펴보십시오. – Marco13

답변

2

정말 좋은 생각입니다.

이 질문에 대답 할 때 세부 사항에 얼마나 깊이 들어가야할지 모르겠습니다. 그러나 무슨 일이 발생하는지 간단히 요약하면 다음과 같습니다.

Field#get 전화를 걸면 내부적으로 (몇 가지 보안 검사를 거친 후) 전화가 sun.reflect.FieldAccessor으로 위임됩니다. 이것은 이름에서 알 수 있듯이 필드 값에 대한 액세스를 제공하는 내부 인터페이스입니다. 내부적으로 사용되는 FieldAccessor 인스턴스는 늦게 생성되고 나중에 사용하기 위해 "캐시 됨"이며 여러 Field 인스턴스간에 공유됩니다.

FieldAccessor 인터페이스에는 여러 가지 구현이 있습니다. 이러한 구현은 일반 필드, 정적 필드 또는 setAccessible(true)을 호출하여 액세스 할 수있는 개인 필드의 다양한 경우에 특화되어 있습니다. 예를 들어, 귀하의 경우에는 UnsafeQualifiedStaticObjectFieldAccessorImpl이 포함되어 있으며이 이름은 이미 수십개의 특수화 중 하나 일뿐입니다.

많은 이들 FieldAccessor 구현은 필드의 일부 속성을 설명하는 내부 상태을 저장합니다. 예를 들어 필드가 "읽기 전용"인지 또는 final (!)인지 여부를 지정합니다.

요점은 다음과 같습니다. 반사 Field#get 호출을 수행 할 때 생성되는 FieldAccessor은 나중에 반사 Field#set 호출에 사용되는 것과 동일합니다. 이 생성 될 때이 생성되면 해당 필드는 여전히 final으로 간주됩니다. 다음에 만 변경하면 FieldAccessor을 만들었습니다.

가장 쉬운 해결책은 필드의 final 상태가 으로 변경된 후 전에이 첫 번째 반영 호출을하는 것입니다. 나는 이것을 언급 안

import java.lang.reflect.Field; 
import java.lang.reflect.Modifier; 

public class A { 

    public static void main(String[] args) throws Exception { 
     System.out.println("Before :: " + B.get()); 
     Field field = B.class.getDeclaredField("arr"); 
     field.setAccessible(true); 

     Field modifiersField = Field.class.getDeclaredField("modifiers"); 
     modifiersField.setAccessible(true); 
     modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 

     System.out.println("Peek :: " + ((String[]) field.get(null))[0]); 
     field.set(null, new String[] { "Good bye, World!" }); 
     System.out.println("After :: " + B.get()); 
    } 
} 

class B { 
    private static final String[] arr = new String[] { "Hello, World!" }; 

    public static String get() { 
     return arr[0]; 
    } 
} 

:이 방법은, 내부적으로 생성 된 FieldAccessor는 "비 최종"필드에서 작동 하나입니다. 사람들은 마십시오. 그러나 그러나 :

: 그것은 처음 필드의 final 버전을 생성하더라도,

기술적으로, 그것은 brutallyHackYourWayThroughInternalClasses로도 가능하고 나중에 필드를 수정 할 수 있도록 내부적으로 생성 된 FieldAccessor을 수정

import java.lang.reflect.Field; 
import java.lang.reflect.Modifier; 

public class A { 

    public static void main(String[] args) throws Exception { 
     System.out.println("Before :: " + B.get()); 
     Field field = B.class.getDeclaredField("arr"); 
     field.setAccessible(true); 

     System.out.println("Peek :: " + ((String[]) field.get(null))[0]); 
     brutallyHackYourWayThroughInternalClasses(field); 

     Field modifiersField = Field.class.getDeclaredField("modifiers"); 
     modifiersField.setAccessible(true); 
     modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 

     field.set(null, new String[] { "Good bye, World!" }); 
     System.out.println("After :: " + B.get()); 
    } 

    private static void brutallyHackYourWayThroughInternalClasses(Field field) 
     throws Exception 
    { 
     Field overrideFieldAccessorField = 
      Field.class.getDeclaredField("overrideFieldAccessor"); 
     overrideFieldAccessorField.setAccessible(true); 
     Object overrideFieldAccessorValue = 
      overrideFieldAccessorField.get(field); 

     Class<?> unsafeFieldAccessorImplClass = 
      Class.forName("sun.reflect.UnsafeFieldAccessorImpl"); 
     Field isFinalField = 
      unsafeFieldAccessorImplClass.getDeclaredField("isFinal"); 
     isFinalField.setAccessible(true); 
     isFinalField.set(overrideFieldAccessorValue, false); 

     Class<?> unsafeQualifiedStaticFieldAccessorImplClass = 
      Class.forName("sun.reflect.UnsafeQualifiedStaticFieldAccessorImpl"); 
     Field isReadOnlyField = 
      unsafeQualifiedStaticFieldAccessorImplClass.getDeclaredField(
       "isReadOnly"); 
     isReadOnlyField.setAccessible(true); 
     isReadOnlyField.set(overrideFieldAccessorValue, false); 
    } 
} 

class B { 
    private static final String[] arr = new String[] { "Hello, World!" }; 

    public static String get() { 
     return arr[0]; 
    } 
} 
+0

위대한 대답, 감사합니다 @ Marco13! 매력처럼 작동합니다. 나는 잔인한 해킹이 필요하다고 생각하지 않는다 ;-) –

관련 문제