Existe alguma maneira de modificar o valor de um campo `privado final` estática em Java de fora da classe?

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

  •  12-09-2019
  •  | 
  •  

Pergunta

Eu sei que isto é normalmente bastante estúpido, mas não atire em mim antes de ler a pergunta. Eu prometo que tem uma boa razão para a necessidade de fazer isso:)

É possível modificar campos particulares regulares em Java usando reflexão, no entanto Java lança uma exceção de segurança ao tentar fazer o mesmo para campos final.

Eu diria que este é rigorosamente aplicada, mas percebi que eu perguntar de qualquer maneira apenas no caso de alguém tinha descoberto um truque para fazer isso.

Vamos apenas dizer que tenho uma biblioteca externa com um "SomeClass" class

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

I essencialmente quer macaco-Patch SomeClass para que eu possa executar minha própria versão de doSomething(). Uma vez que não é (a meu conhecimento) alguma maneira de realmente fazer isso em java, a minha única solução aqui é para alterar o valor de INSTANCE por isso retorna a minha versão da classe com o método modificado.

Essencialmente eu só quero embrulhar a chamada com uma verificação de segurança e, em seguida, chamar o método original.

A biblioteca externa sempre usa getInstance() para obter uma instância dessa classe (ou seja, é um singleton).

EDIT: Só para esclarecer, getInstance() é chamado pela biblioteca externa, não meu código, então, basta subclasse não vai resolver o problema.

Se eu não posso fazer isso a única solução que eu posso pensar é copiar e colar classe inteira e modificar o método. Este não é o ideal como eu vou ter que manter minha fork-se atualizado com as alterações na biblioteca. Se alguém tem algo um pouco mais sustentável estou aberto a sugestões.

Foi útil?

Solução

É possível. Eu usei isso para monkeypatch threadlocals impertinentes que foram impedindo classe descarga em webapps. Você só precisa usar a reflexão para remover o modificador final, em seguida, você pode modificar o campo.

Algo como isso irá fazer o truque:

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);
}

Há alguns cache bem em torno Field#set, por isso, se algum código foi executado antes que ela possa não necessariamente trabalho ....

Outras dicas

Qualquer quadro AOP iria atender às suas necessidades

Seria permitem que você defina uma substituição de tempo de execução para o método getInstance permitindo-lhe regressar ternos qualquer Classe sua necessidade.

JMockit usa a estrutura ASM internamente para fazer a mesma coisa.

Você pode tentar o seguinte. Nota: Não é de todo o segmento de seguros e isso não funciona para primitivas constante conhecida em tempo de compilação (como são inlined pelo compilador)

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

Você deve ser capaz de mudá-lo com JNI ... não tenho certeza se isso é uma opção para você.

EDIT: é possível, mas não é uma boa ideia

.

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

10,9 violar as regras de controle de acesso

A JNI não impõe classe, campo, e restrições de controle de acesso método que pode ser expressa no Java programação nível de linguagem através do usar de modificadores como privado e final. É possível escrever nativa código para acessar ou modificar campos de um objeto, mesmo que fazê-lo na nível linguagem de programação Java faria levar a um IllegalAccessException. permissividade da JNI foi um consciente decisão de projeto, uma vez que nativa código pode acessar e modificar qualquer memória localização na pilha de qualquer maneira.

código nativo que bypasses verificações de acesso em língua de nível de fonte pode ter efeitos indesejáveis ??sobre execução do programa. Por exemplo, uma inconsistência pode ser criado se um método nativo modifica um campo final depois de um (JIT) compilador just-in-time tem embutido acessos ao campo. Da mesma forma, métodos nativos não deve modificar objetos imutáveis ??como campos em casos de java.lang.String ou java.lang.Integer. Fazer isso pode levar à ruptura de invariantes na plataforma Java implementação.

Se você realmente deve (embora para o nosso problema que eu sugiro que você use a solução de CaptainAwesomePants), você pode ter um olhar para JMockit . Embora este é intented para ser usado em testes de unidade se permite redefinir métodos arbitrários. Isto é feito através da modificação do bytecode em tempo de execução.

I, prefaciar esta resposta, reconhecendo que este não é realmente uma resposta à sua pergunta declarada sobre como modificar um campo private static final. No entanto, no exemplo de código específico mencionado acima, eu posso de fato fazê-lo assim que você pode substituir doSomething (). O que você pode fazer é tirar proveito do fato de que getInstance () é um método público e subclasse:

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

Agora, basta invocar MySomeClass.getInstance () em vez de SomeClass.getInstance () e você está pronto para ir. Claro, isso só funciona se você é o único invocando getInstance () e não alguma outra parte do material unmodifiable você está trabalhando com.

Mockito é muito simples:

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());
    }
}

Este código imprime "algo mudou!"; você pode facilmente substituir as instâncias únicas. Meus 0,02 $ centavos.

Se não houver um corte externo disponível (pelo menos eu não estou ciente de) eu teria cortado a própria classe. Alterar o código, adicionando a verificação de segurança que você deseja. Como tal, a sua uma biblioteca externa, você não vai tomar as atualizações regularmente, também não muitos atualização acontece de qualquer maneira. Sempre que isso acontece Felizmente eu posso voltar a fazê-lo, pois não é uma grande tarefa de qualquer maneira.

Aqui, o problema é bom e velho Dependency Injection (aka inversão de controle). Seu objetivo deve ser para injetar sua implementação do SomeClass vez de monkeypatching-lo. E sim, esta abordagem exige algumas mudanças para seu projeto existente, mas pelas razões certas (nome do seu princípio de design favorito aqui) - especialmente o mesmo objeto não deve ser responsável tanto pela criação e utilização de outros objetos.

Eu assumo a maneira que você está usando olhares SomeClass mais ou menos assim:

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

    // some more stuff here...
  }
}

Em vez disso, o que você deve fazer é primeiro criar a sua classe que implementa a mesma interface ou estende SomeClass e depois passar essa instância para doEverything() assim que sua classe torna-se agnóstico a implementação de SomeClass. Neste caso, o código que chama doEverything é responsável por passar na implementação correta - se ser o SomeClass real ou seu MySomeClass monkeypatched.

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...
  }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top