2009-04-20 8 views
10

Wiem, że to zwykle jest dość głupie, ale nie strzelaj do mnie przed przeczytaniem pytania. Obiecuję, że mam dobry powód, aby to zrobić :)Czy istnieje sposób modyfikowania wartości pola `private static final` w Javie spoza klasy?

Możliwe jest modyfikowanie zwykłych prywatnych pól w java za pomocą refleksji, jednak Java zgłasza wyjątek bezpieczeństwa, próbując zrobić to samo dla pól final.

Założę się, że jest to ściśle egzekwowane, ale pomyślałem, że zapytam w każdym razie na wypadek, gdyby ktoś wymyślił hack, aby to zrobić.

Powiedzmy mam zewnętrznej biblioteki z klasą „SomeClass

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

    public static SomeClass getInstance(){ 
     return INSTANCE; 
    } 

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

I zasadniczo chcą Monkey-Patch SomeClass tak, że mogę wykonać własną wersję doSomething(). Ponieważ nie ma (według mojej wiedzy) żadnego sposobu na zrobienie tego w Javie, moim jedynym rozwiązaniem jest tutaj zmiana wartości INSTANCE, więc zwraca ona moją wersję klasy za pomocą zmodyfikowanej metody.

Zasadniczo po prostu chcę zakończyć wywołanie sprawdzaniem bezpieczeństwa, a następnie wywołać oryginalną metodę.

Biblioteka zewnętrzna zawsze używa getInstance(), aby uzyskać wystąpienie tej klasy (tj. Jest to singleton).

EDYCJA: Aby wyjaśnić, getInstance() jest wywoływana przez bibliotekę zewnętrzną, nie mój kod, więc tylko podklasy nie rozwiąże problemu.

Jeśli nie mogę tego zrobić, jedyne inne rozwiązanie, jakie mogę wymyślić, to skopiować i wkleić całą klasę i zmodyfikować metodę. Nie jest to idealne rozwiązanie, ponieważ będę musiał aktualizować moje widły, wprowadzając zmiany w bibliotece. Jeśli ktoś ma coś więcej do utrzymania, jestem otwarty na sugestie.

+0

Szkoda, że ​​nie ma żadnych metod rozszerzania w Javie –

+0

Potrzebujesz pythona: P – Rexsung

+3

Mogłem dodać "static" do getInstance(), ponieważ prawdopodobnie tak to wygląda. –

Odpowiedz

9

To jest możliwe. Użyłem tego do monkeypatch niegrzecznych wątków, które zapobiegały rozładowywaniu klasy w aplikacjach internetowych. Wystarczy użyć odbicia, aby usunąć modyfikator final, a następnie można zmodyfikować pole.

Coś takiego rade:

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

Istnieją pewne buforowanie jak również wokół Field#set, więc jeśli jakiś kod został uruchomiony przed może nie koniecznie pracować ....

+0

Może gdybyś uruchomił ten wątek na wczesnym etapie aplikacji, rozwiąże to problem z buforowaniem? –

+0

Problem z buforowaniem nie jest problemem. myślę, że stanie się tak tylko wtedy, gdy inna metoda spróbuje ustawić pole za pomocą refleksji. – benmmurphy

+0

Dzięki, właśnie tego szukałem (chciałbym, aby mogłem cię bardziej pochwalić, jest to miłe i niejasne). –

0

Wstępnie przedstawię tę odpowiedź, uznając, że nie jest to właściwie odpowiedź na zadane pytanie dotyczące modyfikacji prywatnego statycznego pola końcowego. Jednak w podanym powyżej przykładowym kodzie, w rzeczywistości mogę uczynić go tak, aby można było zastąpić doSomething(). Co można zrobić, to skorzystać z faktu, że getInstance() to metoda publicznego i podklasy:

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

    public SomeClass getInstance() { 
     return INSTANCE; 
    } 

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

Teraz wystarczy powołać MySomeClass.getInstance() zamiast SomeClass.getInstance() i jesteś dobry, aby udać się. Oczywiście działa to tylko wtedy, gdy wywołujesz getInstance(), a nie jakąś inną część niemodyfikowalnych rzeczy, z którymi pracujesz.

+0

Niestety tak nie jest, getInstance jest w całości wywoływany przez wewnętrzny kod biblioteki. –

+0

Ah, przepraszam, wtedy :( –

1

Jeśli naprawdę musisz (choć w naszym przypadku sugeruję użycie rozwiązania CaptainAwesomePants), możesz rzucić okiem na JMockIt. Chociaż jest to zamierzone do zastosowania w testach jednostkowych, jeśli pozwala na przedefiniowanie dowolnych metod. Odbywa się to poprzez modyfikację kodu bajtowego w środowisku wykonawczym.

1

Powinieneś być w stanie to zmienić za pomocą JNI ... nie wiesz, czy jest to opcja dla ciebie.

EDYCJA: jest to możliwe, ale nie jest to dobry pomysł.

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

10.9 Zasady Naruszenie Access Control

JNI nie wymusza klasowych, terenowych, i ograniczenia dostępu metoda kontroli które mogą być wyrażone na poziomie języka programowania Java przez użycia modyfikatorów, takich jak prywatne i końcowym. Możliwe jest zapisanie natywnego kodu w celu uzyskania dostępu do pól obiektu lub zmodyfikowania go, nawet jeśli uczyniłby to na poziomie języka programowania Java , co spowodowałoby, że doprowadziłoby do wyjątku IllegalAccessException. Przyzwolenie JNI było świadomą decyzją projektową, biorąc pod uwagę, że natywny kod może uzyskać dostęp i zmodyfikować dowolną lokalizację pamięci w sterty.

Kod macierzysty, który pomija sprawdzanie dostępu na poziomie języka źródłowego może mieć niepożądane skutki dla wykonania programu . Na przykład niespójność może zostać utworzona, jeśli natywna metoda modyfikuje końcowe pole po tym, jak kompilator JIT (just in time) uzyskał dostęp do pola. Podobnie metody natywne nie powinny modyfikować niezmiennych obiektów, takich jak , w obiektach java.lang.String lub java.lang.Integer. Może to doprowadzić do uszkodzenia niezmienników w implementacji platformy Java .

+0

Czy możesz opracować? Myślałem, że JNI był używany tylko do wywoływania bibliotek zewnętrznych (tj. C). –

+0

@ JamesDavies: Kod JNI może również dotrzeć z powrotem do JVM i uzyskać dostęp do obiektów i klas Java – mhsmith

5

Wszelkie ramy AOP będzie pasowała do Twoich potrzeb

Pozwoliłoby to na zdefiniowanie nadpisanie wykonawczego dla metody getInstance umożliwiając powrót cokolwiek klasa odpowiada jego potrzebom.

Jmockit używa wewnętrznej struktury ASM, aby zrobić to samo.

+0

Dobra odpowiedź Klucz nie polega na modyfikacji pola, ale na przechwyceniu wywołania, aby je odzyskać – skaffman

+0

Tak więc użyłbym tej samej sztuczki przepisywania kodu bajtowego, którą robi JMockit? –

+0

Nie jestem dokładnie ekspertem, ale istnieją co najmniej dwa sposoby Jeden używa API oprzyrządowania java 1.5, drugi używa ASM i bezpośredniego manipulowania kodami bajtowymi – Jean

-1

Jeśli nie ma dostępnego zewnętrznego hackera (przynajmniej nie jestem tego świadomy) zhakowałem samą klasę. Zmień kod, dodając odpowiednią kontrolę bezpieczeństwa. Jako taka jest to biblioteka zewnętrzna, nie będziesz regularnie pobierać aktualizacji, tak samo nie dzieje się tak wiele aktualizacji. Ilekroć to się dzieje, mogę z radością powtórzyć to, ponieważ i tak nie jest to duże zadanie.

1

Możesz wypróbować następujące rzeczy. Uwaga: To nie jest w ogóle wątku bezpieczne i to nie działa dla stałych prymitywów znane w czasie kompilacji (ponieważ są one wstawiane przez kompilator)

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

Niestety, Security Manager tego nie lubi - nawet z setAccessible (true) nie lubi modyfikowania ostatecznych wartości. –

+0

Możesz spróbować ustawić System. setSecurityManager (null) i sprawdź, czy możesz go tymczasowo wyłączyć.;) –

-1

Tutaj Twoim problemem jest staroświecka iniekcja zależności (inaczej Inwersja kontroli). Twoim celem powinno być wstrzyknięcie twojej implementacji SomeClass zamiast monkeypatchowania.I tak, to podejście wymaga pewnych zmian w istniejącym projekcie, ale z właściwych powodów (tutaj nazwij swoją ulubioną zasadę projektowania) - szczególnie ten sam obiekt nie powinien być odpowiedzialny za tworzenie i używanie innych obiektów.

Zakładam sposób używasz SomeClass wygląda nieco jak poniżej:

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

    // some more stuff here... 
    } 
} 

Zamiast tego, co należy zrobić, to najpierw utworzyć klasę, która implementuje ten sam interfejs lub rozciąga SomeClass a następnie przekazać tę instancję doEverything(), więc twoja klasa staje się agnostyczna wobec implementacji SomeClass. W tym przypadku kod wywołujący doEverything jest odpowiedzialny za przekazanie prawidłowej implementacji - niezależnie od tego, czy jest to rzeczywisty SomeClass, czy też 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... 
    } 
} 
+0

Dzięki, ale jak wspomniano w pytaniu, to nie zadziała, ponieważ nie wołam SomeClass bezpośrednio. Chcę zmienić instancję SomeClass używaną przez bibliotekę zewnętrzną - nigdy nie nazywam jej bezpośrednio z mojego kodu. –

0

z mockito jest bardzo prosta:

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

ten drukuje kod; "coś się zmieniło!" możesz łatwo zastąpić instancje singleton. Moje centy 0,02 $.

Powiązane problemy