Java Language
Refleksyjny interfejs API
Szukaj…
Wprowadzenie
Odbicie jest powszechnie stosowane w programach, które wymagają zdolności do sprawdzania lub modyfikowania działania środowiska wykonawczego aplikacji działających w JVM. Interfejs API Java Reflection jest wykorzystywany do tego celu, gdy umożliwia sprawdzanie klas, interfejsów, pól i metod w czasie wykonywania, bez znajomości ich nazw w czasie kompilacji. Umożliwia także tworzenie nowych obiektów i wywoływanie metod wykorzystujących odbicie.
Uwagi
Występ
Pamiętaj, że odbicie może obniżyć wydajność, używaj go tylko wtedy, gdy nie można wykonać zadania bez odbicia.
Z samouczka Java The Reflection API :
Ponieważ odbicie obejmuje typy rozwiązywane dynamicznie, nie można przeprowadzić pewnych optymalizacji maszyny wirtualnej Java. W rezultacie operacje odbijające mają mniejszą wydajność niż ich nieodblaskowe odpowiedniki i należy ich unikać w częściach kodu, które są często wywoływane w aplikacjach wrażliwych na wydajność.
Wprowadzenie
Podstawy
Reflection API pozwala sprawdzić strukturę klas kodu w czasie wykonywania i dynamicznie wywoływać kod. Jest to bardzo potężne, ale jest również niebezpieczne, ponieważ kompilator nie jest w stanie statycznie ustalić, czy wywołania dynamiczne są poprawne.
Prostym przykładem byłoby pobranie publicznych konstruktorów i metod danej klasy:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;
Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents
Dzięki tym informacjom możliwe jest dynamiczne tworzenie instancji obiektu i wywoływanie różnych metod.
Refleksja i typy ogólne
Ogólne informacje o typie są dostępne dla:
- parametry metody, przy użyciu
getGenericParameterTypes()
. - metody zwracają typy, używając
getGenericReturnType()
. - pola publiczne , używając
getGenericType
.
Poniższy przykład pokazuje, jak wyodrębnić ogólne informacje o typie we wszystkich trzech przypadkach:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class GenericTest {
public static void main(final String[] args) throws Exception {
final Method method = GenericTest.class.getMethod("testMethod", Map.class);
final Field field = GenericTest.class.getField("testField");
System.out.println("Method parameter:");
final Type parameterType = method.getGenericParameterTypes()[0];
displayGenericType(parameterType, "\t");
System.out.println("Method return type:");
final Type returnType = method.getGenericReturnType();
displayGenericType(returnType, "\t");
System.out.println("Field type:");
final Type fieldType = field.getGenericType();
displayGenericType(fieldType, "\t");
}
private static void displayGenericType(final Type type, final String prefix) {
System.out.println(prefix + type.getTypeName());
if (type instanceof ParameterizedType) {
for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
displayGenericType(subtype, prefix + "\t");
}
}
}
public Map<String, Map<Integer, List<String>>> testField;
public List<Number> testMethod(final Map<String, Double> arg) {
return null;
}
}
Daje to następujące wyniki:
Method parameter:
java.util.Map<java.lang.String, java.lang.Double>
java.lang.String
java.lang.Double
Method return type:
java.util.List<java.lang.Number>
java.lang.Number
Field type:
java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
java.lang.String
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
java.lang.Integer
java.util.List<java.lang.String>
java.lang.String
Wywoływanie metody
Za pomocą odbicia można wywołać metodę obiektu w czasie wykonywania.
Przykład pokazuje, jak wywołać metody obiektu String
.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
String s = "Hello World!";
// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"
// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"
Pobieranie i ustawianie pól
Za pomocą interfejsu API Reflection można zmienić lub uzyskać wartość pola w czasie wykonywania. Na przykład można użyć go w interfejsie API do pobierania różnych pól w oparciu o czynnik, na przykład system operacyjny. Możesz także usunąć modyfikatory, takie jak final
aby umożliwić modyfikowanie pól, które są ostateczne.
Aby to zrobić, musisz użyć metody Class # getField () w sposób taki jak pokazano poniżej:
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Get the field in class Field "modifiers". Note that it does not
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");
// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);
// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();
// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;
// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);
// Set it to be accessible. This overrides normal Java
// private/protected/package/etc access control checks.
nameField.setAccessible(true);
// Set the value of "NAME" here. Note the null argument.
// Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");
// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);
Uzyskiwanie pól jest znacznie łatwiejsze. Możemy użyć pola # get () i jego wariantów, aby uzyskać jego wartość:
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Set accessible for private fields
nameField.setAccessible(true);
// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);
Uwaga:
Korzystając z klasy # getDeclaredField , użyj jej, aby uzyskać pole w samej klasie:
class HackMe extends Hacked {
public String iAmDeclared;
}
class Hacked {
public String someState;
}
Tutaj HackMe#iAmDeclared
jest zadeklarowanym polem. Jednak HackMe#someState
nie jest zadeklarowanym polem, ponieważ jest dziedziczony z nadklasy Hacked.
Wywołanie konstruktora
Zdobywanie obiektu konstruktora
Możesz uzyskać klasę Constructor
z obiektu Class
następujący sposób:
Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();
Gdzie zmienna constructors
będzie miała jedną instancję Constructor
dla każdego publicznego konstruktora zadeklarowanego w klasie.
Jeśli znasz dokładne typy parametrów konstruktora, do którego chcesz uzyskać dostęp, możesz przefiltrować określony konstruktor. Następny przykład zwraca konstruktor publiczny danej klasy, który przyjmuje liczbę Integer
jako parametr:
Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});
Jeśli żaden konstruktor nie pasuje do podanych argumentów konstruktora, NoSuchMethodException
jest NoSuchMethodException
.
Nowa instancja za pomocą Konstruktora
Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));
Uzyskiwanie stałych wyliczenia
Podanie tego wyliczenia jako przykładu:
enum Compass {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270);
private int degree;
Compass(int deg){
degree = deg;
}
public int getDegree(){
return degree;
}
}
W Javie klasa wyliczeniowa jest jak każda inna klasa, ale ma określone stałe dla wartości wyliczeniowych. Dodatkowo ma pole, które jest tablicą, która przechowuje wszystkie wartości oraz dwie metody statyczne z nazwami values()
i valueOf(String)
.
Możemy to zobaczyć, jeśli użyjemy Reflection do wydrukowania wszystkich pól w tej klasie
for(Field f : Compass.class.getDeclaredFields())
System.out.println(f.getName());
wyjście będzie:
PÓŁNOC
WSCHÓD
POŁUDNIE
ZACHÓD
stopień
WARTOŚCI ENUM $
Możemy więc badać klasy wyliczeniowe z Refleksją, jak każda inna klasa. Ale Reflection API oferuje trzy metody specyficzne dla wyliczenia.
sprawdzenie wyliczenia
Compass.class.isEnum();
Zwraca wartość true dla klas reprezentujących typ wyliczeniowy.
pobieranie wartości
Object[] values = Compass.class.getEnumConstants();
Zwraca tablicę wszystkich wartości wyliczeniowych, takich jak Compass.values (), ale bez potrzeby wystąpienia instancji.
stała kontrola wyliczenia
for(Field f : Compass.class.getDeclaredFields()){
if(f.isEnumConstant())
System.out.println(f.getName());
}
Wyświetla wszystkie pola klas, które są wartościami wyliczonymi.
Uzyskaj klasę, podając jej (w pełni kwalifikowaną) nazwę
Biorąc pod uwagę String
zawierający nazwę klasy, do jej obiektu Class
można uzyskać dostęp za pomocą Class.forName
:
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
Można określić, czy klasa powinna zostać zainicjowana (drugi parametr forName
) i który ClassLoader
powinien zostać użyty (trzeci parametr):
ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
Wywołaj przeciążonych konstruktorów za pomocą odbicia
Przykład: Wywołaj różnych konstruktorów, przekazując odpowiednie parametry
import java.lang.reflect.*;
class NewInstanceWithReflection{
public NewInstanceWithReflection(){
System.out.println("Default constructor");
}
public NewInstanceWithReflection( String a){
System.out.println("Constructor :String => "+a);
}
public static void main(String args[]) throws Exception {
NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
}
}
wynik:
Default constructor
Constructor :String => StackOverFlow
Wyjaśnienie:
- Utwórz instancję klasy za pomocą
Class.forName
: Wywołuje domyślnego konstruktora - Wywołaj
getDeclaredConstructor
klasy, przekazując typ parametrów jakoClass array
- Po uzyskaniu konstruktora utwórz
newInstance
, przekazując wartość parametru jakoObject array
Niewłaściwe użycie interfejsu Reflection API do zmiany zmiennych prywatnych i końcowych
Odbicie jest przydatne, gdy jest właściwie używane we właściwym celu. Za pomocą refleksji można uzyskać dostęp do zmiennych prywatnych i ponownie zainicjować zmienne końcowe.
Poniżej znajduje się fragment kodu, który nie jest zalecany.
import java.lang.reflect.*;
public class ReflectionDemo{
public static void main(String args[]){
try{
Field[] fields = A.class.getDeclaredFields();
A a = new A();
for ( Field field:fields ) {
if(field.getName().equalsIgnoreCase("name")){
field.setAccessible(true);
field.set(a, "StackOverFlow");
System.out.println("A.name="+field.get(a));
}
if(field.getName().equalsIgnoreCase("age")){
field.set(a, 20);
System.out.println("A.age="+field.get(a));
}
if(field.getName().equalsIgnoreCase("rep")){
field.setAccessible(true);
field.set(a,"New Reputation");
System.out.println("A.rep="+field.get(a));
}
if(field.getName().equalsIgnoreCase("count")){
field.set(a,25);
System.out.println("A.count="+field.get(a));
}
}
}catch(Exception err){
err.printStackTrace();
}
}
}
class A {
private String name;
public int age;
public final String rep;
public static int count=0;
public A(){
name = "Unset";
age = 0;
rep = "Reputation";
count++;
}
}
Wynik:
A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25
Wyjaśnienie:
W normalnym scenariuszu nie można uzyskać dostępu do zmiennych private
poza zadeklarowaną klasą (bez metod pobierających i ustawiających). zmiennych final
nie można przypisać ponownie po inicjalizacji.
Reflection
przełamuje obie bariery, które można nadużywać, aby zmienić zarówno zmienne prywatne, jak i końcowe, jak wyjaśniono powyżej.
field.setAccessible(true)
jest kluczem do osiągnięcia pożądanej funkcjonalności.
Wywołanie konstruktora klasy zagnieżdżonej
Jeśli chcesz utworzyć instancję wewnętrznej klasy zagnieżdżonej, musisz podać obiekt klasy otaczającej klasy jako dodatkowy parametr w klasie # getDeclaredConstructor .
public class Enclosing{
public class Nested{
public Nested(String a){
System.out.println("Constructor :String => "+a);
}
}
public static void main(String args[]) throws Exception {
Class<?> clazzEnclosing = Class.forName("Enclosing");
Class<?> clazzNested = Class.forName("Enclosing$Nested");
Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
}
}
Jeśli zagnieżdżona klasa jest statyczna, nie będziesz potrzebować tej otaczającej instancji.
Dynamiczne proxy
Dynamiczne proxy nie mają tak naprawdę wiele wspólnego z Reflection, ale są częścią API. Jest to w zasadzie sposób na stworzenie dynamicznej implementacji interfejsu. Może to być pomocne podczas tworzenia usług makiet.
Dynamiczny serwer proxy to instancja interfejsu utworzona za pomocą tak zwanego modułu obsługi wywołań, który przechwytuje wszystkie wywołania metod i umożliwia ręczne ich wywoływanie.
public class DynamicProxyTest {
public interface MyInterface1{
public void someMethod1();
public int someMethod2(String s);
}
public interface MyInterface2{
public void anotherMethod();
}
public static void main(String args[]) throws Exception {
// the dynamic proxy class
Class<?> proxyClass = Proxy.getProxyClass(
ClassLoader.getSystemClassLoader(),
new Class[] {MyInterface1.class, MyInterface2.class});
// the dynamic proxy class constructor
Constructor<?> proxyConstructor =
proxyClass.getConstructor(InvocationHandler.class);
// the invocation handler
InvocationHandler handler = new InvocationHandler(){
// this method is invoked for every proxy method call
// method is the invoked method, args holds the method parameters
// it must return the method result
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.equals("someMethod1")){
System.out.println("someMethod1 was invoked!");
return null;
}
if(methodName.equals("someMethod2")){
System.out.println("someMethod2 was invoked!");
System.out.println("Parameter: " + args[0]);
return 42;
}
if(methodName.equals("anotherMethod")){
System.out.println("anotherMethod was invoked!");
return null;
}
System.out.println("Unkown method!");
return null;
}
};
// create the dynamic proxy instances
MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);
// and invoke some methods
i1.someMethod1();
i1.someMethod2("stackoverflow");
i2.anotherMethod();
}
}
Wynik tego kodu jest następujący:
someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!
Złe hacki Java z Reflection
Interfejs API Reflection może być używany do zmiany wartości pól prywatnych i końcowych nawet w domyślnej bibliotece JDK. Można to wykorzystać do manipulowania zachowaniem niektórych dobrze znanych klas, jak zobaczymy.
Co nie jest możliwe
Zacznijmy od pierwszego z jedynym ograniczeniem oznacza jedyne pole, którego nie możemy zmienić za pomocą Reflection. To jest Java SecurityManager
. Jest zadeklarowany w java.lang.System jako
private static volatile SecurityManager security = null;
Ale nie będzie wymieniony w klasie System, jeśli uruchomimy ten kod
for(Field f : System.class.getDeclaredFields())
System.out.println(f);
fieldFilterMap
tak z powodu fieldFilterMap
w sun.reflect.Reflection
który przechowuje samą mapę i pole bezpieczeństwa w System.class
i chroni je przed dostępem z Reflection. Dlatego nie mogliśmy dezaktywować SecurityManager
.
Crazy Strings
Każdy ciąg Java jest reprezentowany przez JVM jako instancja klasy String
. Jednak w niektórych sytuacjach JVM oszczędza miejsce na sterty, używając tej samej instancji dla ciągów. Dzieje się tak w przypadku literałów łańcuchowych, a także łańcuchów, które zostały „internowane” przez wywołanie String.intern()
. Jeśli więc w kodzie znajduje się "hello"
jest to zawsze ta sama instancja obiektu.
Ciągi mają być niezmienne, ale można je zmienić, używając „złego” odbicia. Poniższy przykład pokazuje, jak możemy zmienić znaki w ciągu, zastępując jego pole value
.
public class CrazyStrings {
static {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("hello", "you stink!".toCharArray());
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println("hello");
}
}
Więc ten kod wyświetli „śmierdzisz!”
1 = 42
Ten sam pomysł można zastosować z klasą całkowitą
public class CrazyMath {
static {
try {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.setInt(Integer.valueOf(1), 42);
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println(Integer.valueOf(1));
}
}
Wszystko jest prawdą
I zgodnie z tym postem przepełnienia stosu możemy użyć refleksji, aby zrobić coś naprawdę złego.
public class Evil {
static {
try {
Field field = Boolean.class.getField("FALSE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, true);
} catch (Exception e) {
}
}
public static void main(String args[]){
System.out.format("Everything is %s", false);
}
}
Zauważ, że to, co tutaj robimy, spowoduje, że JVM będzie zachowywać się w niewytłumaczalny sposób. To jest bardzo niebezpieczne.