Programing

Java 리플렉션을 사용하여 개인 정적 최종 필드 변경

lottogame 2020. 2. 17. 22:07
반응형

Java 리플렉션을 사용하여 개인 정적 최종 필드 변경


private static final불행히도 런타임에 변경 해야하는 필드 가있는 클래스가 있습니다.

리플렉션을 사용하면이 오류가 발생합니다. java.lang.IllegalAccessException: Can not set static final boolean field

값을 변경하는 방법이 있습니까?

Field hack = WarpTransform2D.class.getDeclaredField("USE_HACK");
hack.setAccessible(true);
hack.set(null, true);

no SecurityManager로 인해이 작업을 수행 할 수 없다고 가정하면 수정자를 setAccessible해결 private하고 재설정하여 final실제로 private static final필드를 수정하는 데 사용할 수 있습니다.

예를 들면 다음과 같습니다.

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

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

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

아니오를 가정하면 SecurityException위의 코드가 인쇄 "Everything is true"됩니다.

여기서 실제로 수행되는 작업은 다음과 같습니다.

  • 프리미티브 booleantruefalse의가 main참조 형식으로되어 오토 박싱 Boolean"정수" Boolean.TRUEBoolean.FALSE
  • 반사를 변경하는 데 사용되는 public static final Boolean.FALSE받는 참조 Boolean하여 언급Boolean.TRUE
  • 결과적으로 이후에 false에 자동 박스가 추가 될 때마다는 Boolean.FALSE참조되는 것과 동일 Boolean하게 참조됩니다.Boolean.TRUE
  • "false"지금은 모든 것이"true"

관련 질문


경고

이와 같은 일을 할 때마다 매우주의해야합니다. SecurityManager존재 하기 때문에 작동하지 않을 수 있지만 사용 패턴에 따라 작동하지 않거나 작동하지 않을 수 있습니다.

JLS 17.5.3 최종 필드의 후속 수정

역 직렬화와 같은 일부 경우 시스템은 final시공 후 객체 필드 를 변경해야합니다 . final필드는 리플렉션 및 기타 구현 종속 수단을 통해 변경 될 수 있습니다. 이것이 합리적인 의미론을 갖는 유일한 패턴은 객체가 구성된 다음 final객체 필드가 업데이트되는 패턴입니다. 객체 필드에 대한 final모든 업데이트 final가 완료 될 때까지 객체를 다른 스레드에 표시하거나 필드를 읽지 않아야합니다 . final필드 고정은 final필드가 설정된 생성자의 끝 final리플렉션 또는 기타 특수 메커니즘을 통해 필드가 수정 될 때마다 발생 합니다.

그럼에도 불구하고 많은 합병증이 있습니다. final필드 선언에서 필드가 컴파일 타임 상수로 초기화 된 경우 해당 필드의 사용이 컴파일 타임 상수 로 컴파일 시간에 대체 final되므로 필드 변경 사항 이 관찰되지 않을 수 있습니다 final.

또 다른 문제는 사양이 final필드를 적극적으로 최적화 할 수 있다는 것 입니다. 스레드 내 final에서 생성자에서 발생하지 않는 최종 필드를 수정 하여 필드 읽기를 재정렬 할 수 있습니다.

또한보십시오

  • JLS 15.28 상수 표현
    • private static final boolean컴파일 타임 상수로 인라인 할 수 없으므로 "새로운"값을 관찰 할 수 없기 때문에이 기술이 프리미티브와 함께 작동 하지는 않습니다.

부록 : 비트 조작

본질적으로

field.getModifiers() & ~Modifier.FINAL

Modifier.FINALfrom에 해당하는 비트를 끕니다 field.getModifiers(). &비트 단위이며 ~비트 단위입니다.

또한보십시오


상수 표현식 기억

아직도이 문제를 해결할 수 없습니까? 제가했던 것처럼 우울증에 빠졌습니까? 코드가 다음과 같이 보입니까?

public class A {
    private final String myVar = "Some Value";
}

이 답변, @Pshemo에 의해 특별히 하나에 주석을 읽고, 그 생각 나게 상수 표현식 이 될 수 있도록 서로 다른 처리 불가능 을 수정할 수 있습니다. 따라서 다음과 같이 코드를 변경해야합니다.

public class A {
    private final String myVar;

    private A() {
        myVar = "Some Value";
    }
}

당신이 수업의 주인이 아니라면 ...

이 동작은 이유에 대한 자세한 내용은 이 글을 읽을 ?


static final boolean필드에 할당 된 값 이 컴파일 타임에 알려진 경우 상수입니다. 프리미티브 또는 String유형의 필드는 컴파일 타임 상수 일 수 있습니다. 필드를 참조하는 모든 코드에서 상수가 인라인됩니다. 필드는 실제로 런타임에 읽지 않으므로 필드를 변경해도 아무런 효과가 없습니다.

Java 언어 사양 이 말한다 :

필드가 상수 변수 (§4.12.4) 인 경우 키워드 final을 삭제하거나 값을 변경해도 기존 바이너리가 실행되지 않아 기존 바이너리와의 호환성이 손상되지 않지만 사용법에 대한 새로운 값은 표시되지 않습니다 다시 컴파일하지 않으면 필드의 사용법 자체가 컴파일 타임 상수 표현식이 아닌 경우에도 마찬가지입니다 (§15.28)

예를 들면 다음과 같습니다.

class Flag {
  static final boolean FLAG = true;
}

class Checker {
  public static void main(String... argv) {
    System.out.println(Flag.FLAG);
  }
}

디 컴파일 Checker하면을 참조하는 대신 Flag.FLAG코드가 단순히 true스택 에 1 ( ) 값을 푸시 한다는 것을 알 수 있습니다 (지침 # 3).

0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3:   iconst_1
4:   invokevirtual   #3; //Method java/io/PrintStream.println:(Z)V
7:   return

Java 언어 사양 17 장 17.5.4 "쓰기 방지 필드"에 대한 약간의 호기심 :

일반적으로 final 필드와 static 필드는 수정되지 않을 수 있습니다. 그러나 System.in, System.out 및 System.err는 정적 최종 필드이며, 레거시 이유로 System.setIn, System.setOut 및 System.setErr 메소드로 변경할 수 있어야합니다. 이러한 필드는 일반 최종 필드와 구별하기 위해 쓰기 방지 된 것으로 간주합니다.

출처 : http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.4


보안 관리자가있는 경우 사용할 수 있습니다 AccessController.doPrivileged

위의 허용 된 답변에서 동일한 예를 보았습니다.

import java.lang.reflect.*;

public class EverythingIsTrue {
    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");

        // wrapping setAccessible 
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                modifiersField.setAccessible(true);
                return null;
            }
        });

        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }

    public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);
      System.out.format("Everything is %s", false); // "Everything is true"
    }
}

람다 식에서을 다음과 AccessController.doPrivileged같이 단순화 할 수 있습니다.

AccessController.doPrivileged((PrivilegedAction) () -> {
    modifiersField.setAccessible(true);
    return null;
});

또한 joor 라이브러리 와 통합했습니다.

그냥 사용

      Reflect.on(yourObject).set("finalFieldName", finalFieldValue);

또한 override이전 솔루션이 누락 된 문제를 수정했습니다 . 그러나 다른 좋은 해결책이 없을 때만 이것을 신중하게 사용하십시오.


최고 순위의 답변과 함께 약간 간단한 접근 방식을 사용할 수 있습니다. Apache Commons FieldUtils클래스에는 이미 작업을 수행 할 수있는 특정 메소드가 있습니다. FieldUtils.removeFinalModifier방법을 살펴보십시오 . 대상 필드 인스턴스와 내게 필요한 옵션 강제 플래그를 지정해야합니다 (비공개 필드로 재생하는 경우). 자세한 내용은 여기를 참조하십시오 .


수락 된 답변은 JDK 1.8u91에 배포 될 때까지 효과적이었습니다. 그런 다음 메서드 field.set(null, newValue);를 호출하기 전에 리플렉션을 통해 값을 읽었을 때 줄 에서 실패했음을 깨달았습니다 setFinalStatic.

아마도 읽기로 인해 Java 리플렉션 내부의 설정이 달라졌 지만 (즉 , 성공 사례가 sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl아닌 실패 sun.reflect.UnsafeStaticObjectFieldAccessorImpl사례) 더 자세히 설명하지는 않았습니다.

이전 값을 기반으로 새 값을 임시로 설정하고 나중에 이전 값을 다시 설정해야했기 때문에 서명 기능을 약간 변경하여 외부에서 계산 기능을 제공하고 이전 값을 반환했습니다.

public static <T> T assignFinalField(Object object, Class<?> clazz, String fieldName, UnaryOperator<T> newValueFunction) {
    Field f = null, ff = null;
    try {
        f = clazz.getDeclaredField(fieldName);
        final int oldM = f.getModifiers();
        final int newM = oldM & ~Modifier.FINAL;
        ff = Field.class.getDeclaredField("modifiers");
        ff.setAccessible(true);
        ff.setInt(f,newM);
        f.setAccessible(true);

        T result = (T)f.get(object);
        T newValue = newValueFunction.apply(result);

        f.set(object,newValue);
        ff.setInt(f,oldM);

        return result;
    } ...

그러나 일반적인 경우에는 이것으로 충분하지 않습니다.


필드가 개인용 인 경우 다음을 수행 할 수 있습니다.

MyClass myClass= new MyClass();
Field aField= myClass.getClass().getDeclaredField("someField");
aField.setAccessible(true);
aField.set(myClass, "newValueForAString");

NoSuchFieldException 던지기 / 처리


도이면서 final필드는 전혀 문제 바이트 코드를 실행 static 초기화의 외부 (적어도 JVM 핫스팟) 변형 될 수있다.

문제는 Java 컴파일러가 이것을 허용하지 않지만을 사용하여 쉽게 우회 할 수 있다는 것 objectweb.asm입니다. 다음은 바이트 코드 확인을 통과하고 JVM HotSpot OpenJDK12에서 성공적으로로드 및 초기화 된 완벽하게 유효한 클래스 파일입니다.

ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
    FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
    fv.visitEnd();
}
{
    // public void setFinalField1() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_5);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
{
    // public void setFinalField2() { //... }
    MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
    mv.visitMaxs(2, 1);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
    mv.visitInsn(Opcodes.RETURN);
    mv.visitEnd();
}
cw.visitEnd();

Java에서 클래스는 대략 다음과 같이 보입니다.

public class Cl{
    private static final int fld;

    public static void setFinalField1(){
        fld = 5;
    }

    public static void setFinalField2(){
        fld = 2;
    }
}

로 컴파일 할 수 없지만 javacJVM에서로드하고 실행할 수 있습니다.

JVM HotSpot은 이러한 "상수"가 지속적인 폴딩에 참여하지 못하도록 이러한 클래스를 특별히 처리합니다. 이 점검은 클래스 초기화바이트 코드 재 작성 단계에서 수행됩니다 .

// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
   Symbol* field_name = cp->name_ref_at(bc_index);
   Symbol* field_sig = cp->signature_ref_at(bc_index);

   fieldDescriptor fd;
   if (klass->find_field(field_name, field_sig, &fd) != NULL) {
      if (fd.access_flags().is_final()) {
         if (fd.access_flags().is_static()) {
            if (!method->is_static_initializer()) {
               fd.set_has_initialized_final_update(true);
            }
          } else {
            if (!method->is_object_initializer()) {
              fd.set_has_initialized_final_update(true);
            }
          }
        }
      }
    }
}

JVM HotSpot이 점검하는 유일한 제한 사항 finalfinal필드가 선언 된 클래스 외부 에서 필드를 수정해서는 안된다는 것 입니다.


가능한 경우 리플렉션이나 런타임에 최종 변수를 변경할 수있는 경우 인터뷰 질문 중 하나에서 해당 질문을 보았습니다. 정말 관심있어서 내가 함께한 것이 :

 /**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class SomeClass {

    private final String str;

    SomeClass(){
        this.str = "This is the string that never changes!";
    }

    public String getStr() {
        return str;
    }

    @Override
    public String toString() {
        return "Class name: " + getClass() + " Value: " + getStr();
    }
}

최종 문자열 변수가있는 간단한 클래스. 메인 클래스에서 import java.lang.reflect.Field;

/**
 * @author Dmitrijs Lobanovskis
 * @since 03/03/2016.
 */
public class Main {


    public static void main(String[] args) throws Exception{

        SomeClass someClass = new SomeClass();
        System.out.println(someClass);

        Field field = someClass.getClass().getDeclaredField("str");
        field.setAccessible(true);

        field.set(someClass, "There you are");

        System.out.println(someClass);
    }
}

출력은 다음과 같습니다.

Class name: class SomeClass Value: This is the string that never changes!
Class name: class SomeClass Value: There you are

Process finished with exit code 0

설명서에 따르면 https://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html


final필드의 요점은 일단 설정 한 후에는 재 할당 할 수 없다는 것입니다. JVM은이 보증을 사용하여 다양한 위치 (예 : 외부 변수를 참조하는 내부 클래스)에서 일관성을 유지합니다. 그래서 안돼. 그렇게 할 수 있으면 JVM이 손상 될 수 있습니다!

해결책은 final처음에 그것을 선언하지 않습니다 .

참고 URL : https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection


반응형