클래스 외부에서 Java의 '개인 정적 최종'필드의 값을 수정하는 방법이 있습니까?

StackOverflow https://stackoverflow.com/questions/767008

  •  12-09-2019
  •  | 
  •  

문제

나는 이것이 보통 어리석은 것이라는 것을 알고 있지만 질문을 읽기 전에 나를 쏘지 마십시오. 나는 이것을해야 할 좋은 이유가 있다고 약속한다 :)

반사를 사용하여 Java의 정기적 인 개인 필드를 수정할 수는 있지만 Java는 동일한 작업을 수행하려고 할 때 보안 예외를 던집니다. final 필드.

나는 이것이 엄격하게 시행되었다고 생각하지만, 누군가가 이것을하기 위해 해킹을 알아 낸 경우를 대비하여 어쨌든 물어볼 것이라고 생각했다.

수업이있는 외부 라이브러리가 있다고 가정 해 봅시다. "SomeClass"

public class SomeClass 
{
  private static final SomeClass INSTANCE = new SomeClass()

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

  public Object doSomething(){
    // Do some stuff here 
  }
} 

나는 본질적으로 내 자신의 버전을 실행할 수 있도록 Monkey-Patch Someclass를 원합니다. doSomething(). (내 지식으로) Java에서 실제로 그렇게 할 수있는 방법이 없기 때문에 여기서 유일한 해결책은 다음의 가치를 바꾸는 것입니다. INSTANCE 따라서 수정 된 메소드로 내 버전의 클래스를 반환합니다.

본질적으로 보안 검사로 통화를 래핑 한 다음 원래 방법을 호출하고 싶습니다.

외부 라이브러리는 항상 사용합니다 getInstance() 이 클래스의 인스턴스를 얻으려면 (예 : 싱글 톤입니다).

편집 : 명확히하기 위해 getInstance() 내 코드가 아닌 외부 라이브러리에서 호출되므로 서브 클래싱만이 문제를 해결하지 못합니다.

그렇게 할 수 없다면 내가 생각할 수있는 유일한 솔루션은 전체 클래스를 복사하고 메소드를 수정하는 것입니다. 라이브러리 변경으로 포크를 최신 상태로 유지해야하므로 이상적이지 않습니다. 누군가가 조금 더 관리 가능한 무언가를 가지고 있다면 나는 제안에 개방적입니다.

도움이 되었습니까?

해결책

것이 가능하다. 나는 이것을 사용하여 WebApps에서 클래스 언로드를 방지하는 장난 꾸러기 스레드 로컬을 촉구했습니다. 반사를 사용하여 제거하면됩니다 final 수정 자, 그러면 필드를 수정할 수 있습니다.

이와 같은 것은 트릭을 수행합니다.

private void killThreadLocal(String klazzName, String fieldName) {
    Field field = Class.forName(klazzName).getDeclaredField(fieldName);
    field.setAccessible(true);  
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    int modifiers = modifiersField.getInt(field);
    modifiers &= ~Modifier.FINAL;
    modifiersField.setInt(field, modifiers);
    field.set(null, null);
}

주위에 약간의 캐싱이 있습니다 Field#set, 따라서 일부 코드가 실행되면 반드시 작동하지는 않습니다 ....

다른 팁

모든 AOP 프레임 워크는 귀하의 요구에 맞습니다

이를 통해 GetInstance 메소드에 대한 런타임 재정의를 정의하여 필요한 클래스에 적합한 클래스를 반환 할 수 있습니다.

JMockit은 ASM 프레임 워크를 내부적으로 사용하여 동일한 작업을 수행합니다.

다음을 시도해 볼 수 있습니다. 참고 : 전혀 스레드 안전하지 않으며 컴파일 타임에 알려진 일정한 프리미티브에는 작동하지 않습니다 (컴파일러에 의해 상거되므로)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);

JNI로 변경할 수 있어야합니다. 그것이 당신에게 옵션인지 확실하지 않습니다.

편집 : 가능하지만 좋은 생각은 아닙니다.

http://java.sun.com/docs/books/jni/html/pitfalls.html

10.9 액세스 제어 규칙 위반

JNI는 개인 및 최종과 같은 수정자를 사용하여 Java 프로그래밍 언어 수준에서 표현할 수있는 클래스, 필드 및 메소드 액세스 제어 제한을 시행하지 않습니다. Java 프로그래밍 언어 수준에서 그렇게하면 불법 행위 지출이 발생하더라도 객체의 필드에 액세스하거나 수정하기 위해 기본 코드를 작성할 수 있습니다. 기본 코드가 어쨌든 힙의 메모리 위치에 액세스하고 수정할 수 있다는 점을 감안할 때 JNI의 허용은 의식적인 설계 결정이었습니다.

소스 수준의 액세스 점검을 우회하는 기본 코드는 프로그램 실행에 바람직하지 않은 영향을 미칠 수 있습니다. 예를 들어, 기본 메소드가 JIT (Just-In-Time) 컴파일러가 필드에 접근 한 후 최종 필드를 수정하면 불일치가 생성 될 수 있습니다. 마찬가지로, 기본 방법은 java.lang.string 또는 java.lang.integer의 경우 필드와 같은 불변의 물체를 수정해서는 안됩니다. 그렇게하면 Java 플랫폼 구현에서 불변량이 파손될 수 있습니다.

당신이 정말로해야한다면 (우리의 문제에 대해서는 캡틴 awesomepants의 솔루션을 사용하는 것이 좋습니다) 당신은 살펴볼 수 있습니다. jmockit. 임의 방법을 재정의 할 수있는 경우 단위 테스트에서 사용하려는 의도가 있지만. 이것은 런타임에 바이트 코드를 수정하여 수행됩니다.

나는 이것이 개인 정적 최종 필드 수정에 대한 당신의 명시된 질문에 대한 답이 아니라는 것을 인정 함으로써이 답을 서두울 것입니다. 그러나 위에서 언급 한 특정 예제 코드에서는 실제로 Dosomething ()을 무시할 수 있도록 만들 수 있습니다. 당신이 할 수있는 것은 getinstance ()가 공개 방법이자 서브 클래스라는 사실을 활용하는 것입니다.

public class MySomeClass extends SomeClass
{
   private static final INSTANCE = new MySomeClass();

   public SomeClass getInstance() {
        return INSTANCE;
   }

   public Object doSomething() {
      //Override behavior here!
   }
}

이제 someclass.getInstance () 대신 mysomeclass.getInstance ()를 호출하면 가면 좋습니다. 물론, 이것은 당신이 getinstance ()를 호출하는 사람이고 당신이 함께 일하는 불가능한 것들의 다른 부분이 아닌 경우에만 효과가 있습니다.

~와 함께 모키토 매우 간단합니다 :

import static org.mockito.Mockito.*;

public class SomeClass {

    private static final SomeClass INSTANCE = new SomeClass();

    public static SomeClass getInstance() {
        return INSTANCE;
    }

    public Object doSomething() {
        return "done!";
    }

    public static void main(String[] args) {
        SomeClass someClass = mock(SomeClass.getInstance().getClass());
        when(someClass.doSomething()).thenReturn("something changed!");
        System.out.println(someClass.doSomething());
    }
}

이 코드는 "무언가가 변경되었습니다!"를 인쇄합니다. 싱글 톤 인스턴스를 쉽게 교체 할 수 있습니다. 내 0.02 $ 센트.

외부 해킹을 사용할 수 없다면 (적어도 나는 알지 못합니다) 나는 클래스 자체를 해킹했을 것입니다. 원하는 보안 검사를 추가하여 코드를 변경하십시오. 따라서 외부 라이브러리와 마찬가지로 업데이트를 정기적으로 수행하지 않으며 업데이트가 많지 않습니다. 그런 일이 일어날 때마다 나는 어쨌든 큰 작업이 아니기 때문에 행복하게 다시 할 수 있습니다.

여기서 귀하의 문제는 오래된 의존성 주입 (일명 제어의 역전)입니다. 귀하의 목표는 귀하의 구현을 주입하는 것입니다 SomeClass monkeypatching 대신. 그렇습니다.이 접근법은 기존 디자인에 약간의 변경이 필요하지만 올바른 이유 (여기에서 좋아하는 디자인 원칙을 지정하십시오). 특히 동일한 객체는 다른 개체를 만들고 사용하는 데 책임이 없어야합니다.

나는 당신이 사용하는 방식을 가정합니다 SomeClass 이것처럼 보입니다.

public class OtherClass {
  public void doEverything() {
    SomeClass sc = SomeClass.geInstance();
    Object o = sc.doSomething();

    // some more stuff here...
  }
}

대신, 당신이해야 할 일은 먼저 동일한 인터페이스를 구현하거나 확장하는 수업을 만드는 것입니다. SomeClass 그런 다음 해당 인스턴스를 전달합니다 doEverything() 따라서 귀하의 클래스는 구현에 적합 해집니다 SomeClass. 이 경우 호출되는 코드 doEverything 올바른 구현을 통과 할 책임이 있습니다. SomeClass 또는 당신의 monkeypatched MySomeClass.

public class MySomeClass() extends SomeClass {
  public Object doSomething() {
    // your monkeypatched implementation goes here
  }
}

public class OtherClass {
  public void doEveryting(SomeClass sc) {
    Object o = sc.doSomething();

    // some more stuff here...
  }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top