Detaljno objašnjenje Java refleksije, čak i moja sestra je naučila.
„Erge, šta je refleksija?“ Sanmej je postavila pitanje vrlo direktno.
„Da biste znali šta je refleksija, prvo treba da razumemo šta je 'direktno gadanje'.“ Rekao sam Sanmej sa osmehom, „U normalnim okolnostima, pre nego što počnemo da koristimo određenu klasu, već znamo koja je to klasa, u ruke uzimamo i možemo direktno koristiti new ključnu reč za poziv konstruktora za inicijalizaciju, nakon toga koristimo objekat ove klase za operacije."
Writer writer = new Writer();
writer.setName("Chenmo Wang Er");Gore navedeni primer može se razumeti kao „direktno gadanje“. Refleksija znači da na početku ne znamo koju klasu treba inicijalizovati, tako da ne možemo direktno koristiti new ključnu reč za kreiranje objekta.
Znamo samo neke osnovne informacije o ovoj klasi, kao kada gledamo film, da bi uhvatili osumnjičenog, policija će pitati neke svedoke, na osnovu informacija koje su dali ovi svedoci, stručnjak će nacrtati izgled osumnjičenog — ovaj proces se može nazvati refleksija.
Class clazz = Class.forName("com.itwanger.s39.Writer");
Method method = clazz.getMethod("setName", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object,"Chenmo Wang Er");Gore navedeni primer može se razumeti kao „refleksija“.
„Refleksija je mnogo kompleksnija od direktnog gadanja!“ Sanmej je uzdahnula.
„Da, refleksija košta mnogo više nego direktno gadanje." Rekao sam, „Glavni nedostaci refleksije su dva."
- Oštećenje enkapsulacije: Budući da refleksija omogućava pristup privatnim poljima i metodama, može oštetiti enkapsulaciju i dovesti do sigurnosnih problema.
- Perfomansi trošak: Budući da refleksija uključuje dinamičko razrešavanje, ne može izvršiti optimizaciju Java virtuelne mašine, uz to je stil pisanja refleksije zaista mnogo komplikovaniji, stoga je perfomans mnogo gori od „direktnog gadanja“, u programima osetljivim na perfomans treba izbegavati refleksiju.
„Kakove su onda prednosti refleksije?“ pita Sanmej.
Glavni scenariji primene refleksije:
- Razvoj opštih framework-a: Kao Spring, radi održavanja opštosti, učitava različite objekte kroz konfiguracione datoteke, poziva različite metode.
- Dinamički proxy: U programiranju koje je orijentisano na aspekte, potrebno je presretanje određenih metoda, birće se način dinamičkog proxy-ja, a osnova dinamičkog proxy-ja je refleksija.
- Anotacije: Same anotacije igraju samo ulogu markera, treba koristiti mehanizam refleksije, prema markera izvršiti određeno ponašanje.
„Dobro, pogledajmo kompletn primer." Rekao sam Sanmej.
Klasa Writer, ima dva polja, i odgovarajuće getter/setter.
public class Writer {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Testna klasa:
public class ReflectionDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Writer writer = new Writer();
writer.setName("Chenmo Wang Er");
System.out.println(writer.getName());
Class clazz = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(object, "Chenmo Wang Er");
Method getNameMethod = clazz.getMethod("getName");
System.out.println(getNameMethod.invoke(object));
}
}Pogledaj rezultate:
Chenmo Wang Er
Chenmo Wang ErMeđutim, proces refleksije malo je krivudaviji.
Prvi korak, dobijanje Class objekta reflektovane klase:
Class clazz = Class.forName("com.itwanger.s39.Writer");U Javi, Class objekat je specijalan tip objekta, predstavlja klase i interfejse u programu.
Svaki tip u Javi (uključujući klase, interfejse, nizove i osnovne tipove) u JVM-u ima odgovarajući jedinstven Class objekat. Ovaj Class objekat se kreira u trenutku kada JVM učitava klasu, automatski ga dovršava JVM.
Class objekat sadržai mnogo informacija o klasi, kao što su ime klase, nadklasa, interfejsi koje klasa implementira, konstruktori klase, metode klase, polja klase itd. Ove informacije se obično nazivaju metapodaci (metadata).
Osim pomenutog, dobijanja Class objekta kroz potpuno ime klase, postoje još dva načina:
- Ako imate instancu klase, možete dobiti Class objekat pozivanjem metode
getClass()te instance. Na primer:String str = "Hello World"; Class cls = str.getClass(); - Ako imate literal klase (tj. samu klasu), možete direktno dobiti Class objekat. Na primer:
Class cls = String.class;
Drugi korak, dobijanje Constructor objekta konstruktora kroz Class objekat:
Constructor constructor = clazz.getConstructor();Treći korak, inicijalizacija reflektovane klase kroz Constructor objekat:
Object object = constructor.newInstance();Četvrti korak, dobijanje Method objekta metode koju treba pozvati:
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");Peti korak, izvršavanje kroz metodu invoke():
setNameMethod.invoke(object, "Chenmo Wang Er");
getNameMethod.invoke(object)„Sanmej, vidi, kroz ovih pet koraka, u osnovi si savladala način korišćenja refleksije.“ Rekao sam.
„Izgleda da refleksija nije naročito komplikovana!“ Rekla je Sanmej.
Prvo sam prijao Sanmej, zatim sam rekao: „Da, zaista nije teško savladati osnovnu upotrebu refleksije, ali za razumevanje celog mehanizma refleksije potrebno je da potrošiš malo vremena da razumeš mehanizam učitavanja klasa Java virtuelne mašine."
Da biste koristili refleksiju, prvo treba dobiti Class objekat reflektovane klase, svaka klasa, bez obzira koliko objekata na kraju kreira, ovi objekti će odgovarati samo jednom Class objektu, ovaj Class objekat kreira Java virtuelna mašina, on saznaje strukturne informacije cele klase.
To znači, java.lang.Class je ulaz za sve refleksione API.
A poziv refleksije metode, konačno se završava metodom invoke() Method objekta, pogledajmo izvorni kod (JDK 8 okruženje).
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
// Ako metoda ne dozvoljava prepisivanje, vrši se provera prava pristupa
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
// Provera da li pozivaoc ima pravo pristupa
checkAccess(caller, clazz, obj, modifiers);
}
}
// Dobavljač metoda pristupa (čita iz promenljive volatile)
MethodAccessor ma = methodAccessor;
if (ma == null) {
// Ako je dobavljač prazan, pokušaj da dobiješ dobavljača metoda
ma = acquireMethodAccessor();
}
// Koristi dobavljača metoda za poziv metode i vraća rezultat
return ma.invoke(obj, args);
}Dva ugnježdena if statement-a služe za proveru prava pristupa.
Metoda invoke() zapravo delegira MethodAccessor interfejsu da dovrši.

MethodAccessor interfejs ima tri implementacije, MethodAccessorImpl je apstraktna klasa, druge dve konkretne implementacije nasleđuju ovu apstraktnu klasu.

- NativeMethodAccessorImpl: Realizuje poziv refleksije kroz native metode;
- DelegatingMethodAccessorImpl: Realizuje poziv refleksije kroz delegirajući obrazac;
Kroz debug način uđemo u metodu invoke(), može se videti da prvi poziv refleksije generiše delegirajuću implementaciju DelegatingMethodAccessorImpl, pri generisanju prosleđuje nativnu implementaciju NativeMethodAccessorImpl.

To znači, pri izvršavanju metode invoke(), prvo će pozvati DelegatingMethodAccessorImpl, zatim NativeMethodAccessorImpl, konačno poziva stvarnu metodu.
„Zašto ne pozvati direktno nativnu implementaciju?“ pita Sanmej.
„Razlog za korišćenje delegirajuće implementacije je mogućnost prebacivanja između nativne i dinamičke implementacije. Dinamička implementacija je još jedan mehanizam poziva refleksije, realizuje se kroz generisanje bajt koda. Ako je broj poziva refleksije veći, efikasnost dinamičke implementacije će biti viša, zato što nativna implementacija zahteva prolazak iz Jave u C/C++ pa natrag u Javu, a dinamička implementacija ne; ali ako je broj poziva refleksije manji, naprotiv, nativna implementacija je brža." Rekao sam.
„Gde je ta granična tačka?“ pita Sanmej.
„Podrazumevano je 15 puta." Rekao sam, „Može se podesiti parametrom -Dsun.reflect.inflationThreshold."
Pogledaj sledeći primer.
Method setAgeMethod = clazz.getMethod("setAge", int.class);
for (int i = 0;i < 20; i++) {
setAgeMethod.invoke(object, 18);
}U metodu invoke() dodajte breakpoint i uđite u debug način, kada i = 15, to jest pri 16. izvršavanju, ući će u if granu, promeniti delegirajući oblik DelegatingMethodAccessorImpl delegate u (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(), dok je prethodni delegirajući oblik delegate bio NativeMethodAccessorImpl.

„Sada razumemš? Sanmej." Rekao sam, „Sledeće, upoznajmo još češće korišćene API u refleksiji."
1) Dobavljanje Class objekta reflektovane klase
Class.forName(), parametar je potpuno kvalifikovano ime reflektovane klase.
Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
System.out.println(c1.getCanonicalName());
Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());
Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());Pogledaj rezultate:
com.itwanger.s39.ReflectionDemo3
double[]
java.lang.String[][]Ime klase + .class, pogodno samo ako se pre kompajliranja zna koja se Class operira.
Class c1 = ReflectionDemo3.class;
System.out.println(c1.getCanonicalName());
Class c2 = String.class;
System.out.println(c2.getCanonicalName());
Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());Pogledaj rezultate:
com.itwanger.s39.ReflectionDemo3
java.lang.String
int[][][]2) Kreiranje objekta reflektovane klase
Postoje dva načina za kreiranje objekata kroz refleksiju:
- Koristeći metodu
newInstance()Class objekta. - Koristeći metodu
newInstance()Constructor objekta.
Class c1 = Writer.class;
Writer writer = (Writer) c1.newInstance();
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Object object = constructor.newInstance();3) Dobavljanje konstruktora
Class objekat pruža sledeće metode za dobavljanje Constructor objekta konstruktora:
getConstructor(): Vraća navedeni public konstruktor reflektovane klase, može se proslediti parametar, parametar je Class objekat odgovarajućeg parametra konstruktora; kada se izostavi, vraća podrazumevani konstruktor.getDeclaredConstructor(): Vraća navedeni konstruktor reflektovane klase, ne ograničeno na public.getConstructors(): Vraća sve public konstruktore klase.getDeclaredConstructors(): Vraća sve konstruktore klase, ne ograničeno na public.
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Constructor[] constructors1 = String.class.getDeclaredConstructors();
for (Constructor c : constructors1) {
System.out.println(c);
}**4) Dobavljanje polja
U osnovi slično dobavljanju konstruktora, samo zamenite ključnu reč Constructor sa Field.
Field[] fields1 = System.class.getFields();
Field fields2 = System.class.getField("out");5) Dobavljanje metoda
U osnovi slično dobavljanju konstruktora, samo zamenite ključnu reč Constructor sa Method.
Method[] methods1 = System.class.getDeclaredMethods();
Method[] methods2 = System.class.getMethods();„Pažnja, Sanmej, ako želiš da reflektivno pristupaš privatnim poljima i (konstruktorskim) metodama, treba koristiti Constructor/Field/Method.setAccessible(true) da bi zaobišla ograničenja pristupa Java jezika." Rekao sam.
„Dobro, Erge. Da li ima još materijala za referencu?“ pita Sanmej.
„Ima, dva članka su vrlo dobro napisani, možeš koristiti kao referencu pri učenju refleksije." Rekao sam.
Prvi: Duboko razumevanje Java refleksije i dinamičkog proxy-ja
Link: https://dunwu.github.io/javacore/basics/java-reflection.html
Drugi: Java refleksija uplainim jezikom: uvod, upotreba, princip:
Link: https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
Ovaj jednostavan zaključak.
Refleksija je moćna karakteristika u Javi, omogućava inspekciju i manipulaciju klasa, interfejsa, poljai metoda u vreme izvršavanja. Refleksija je ključna komponenta Jave, podržava implementaciju raznih framework-a i biblioteka, kao što su Spring, Hibernate itd. Korišćenjem refleksije, može se dinamički kreirati objekte, pozivati metode i pristupati poljima u vreme izvršavanja, bez potrebe da se zna specifična implementacija tih objekata u vreme kompajliranja.
Glavne klase refleksije nalaze se u paketu java.lang.reflect, uključujući sledeće ključne klase:
- Class: Predstavlja klasu ili interfejs, sadrži strukturne informacije klase (kao što su ime, konstruktori, metode, polja itd.). Kroz Class objekat može se dobiti metapodaci klase i manipulisati instancama klase.
- Constructor: Predstavlja konstruktorklase, koristi se za kreiranje instanci klase.
- Method: Predstavlja metodu klase, može se pozvati metoda instance klase kroz nju.
- Field: Predstavlja polje klase, može dobiti ili modifikovati vrednost polja.
- Modifier: Sadrži modifikatore pristupaza metode, polja i klase (kao public, private itd.).
Pri korišćenju refleksije treba obratiti pažnju na sledeće:
- Perfomans: Operacije refleksije su obično sporije od direktnih operacija nad metodama i poljima objekta, zato što uključuju dodatne indirektne pozive i dinamičko razrešavanje. Stoga, u scenama gde se vodi računa o perfomansi, pažljivo koristite refleksiju.
- Sigurnost: Kroz refleksiju može se pristupiti i manipulisati privatnim poljima i metodama klase, što može dovesti do sigurnosnih problema. Stoga, prilikom korišćenja refleksije osigurajte sigurnost koda.
- Održavanje: Refleksija čini kod kompleksnijim, može dovesti do teškog održavanja. Pri korišćenju refleksije osigurajte čitljivost i održivost koda.
Uprkos ovim problemima, u određenim scenama (kao razvoj framework-a, dinamički proxy itd.), i dalje je vrlo koristan alat.
Evo jednog kompletnog demo primera.
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void privateMethod() {
System.out.println("Privatna metoda");
}
}
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// Dobavljanje Class objekta klase Person
Class<?> personClass = Class.forName("com.github.paicoding.forum.test.javabetter.importance.Person");
// Dobavljanje i štampanje imena klase
System.out.println("Ime klase: " + personClass.getName());
// Dobavljanje konstruktora
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
// Korišćenje konstruktora za kreiranje instance Person objekta
Object personInstance = constructor.newInstance("Chenmo Wang Er", 30);
// Dobavljanje i pozivanje getName metode
Method getNameMethod = personClass.getMethod("getName");
String name = (String) getNameMethod.invoke(personInstance);
System.out.println("Ime: " + name);
// Dobavljanje i pozivanje setAge metode
Method setAgeMethod = personClass.getMethod("setAge", int.class);
setAgeMethod.invoke(personInstance, 35);
// Dobavljanje i pristup polju age
Field ageField = personClass.getDeclaredField("age");
ageField.setAccessible(true);
int age = ageField.getInt(personInstance);
System.out.println("Starost: " + age);
// Dobavljanje i pozivanje privatne metode
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(personInstance);
}
}U ovom primeru, prvo smo dobili Class objekat klase Person kroz metodu Class.forName(). Zatim smo dobili konstruktor, metode i polja klase Person, i koristili ove refleksione objekte za kreiranje instance, pozivanje metoda i pristup poljima. Obratite pažnju, prilikom pristupa privatnim metodama i poljima, moramo pozvati metodu setAccessible(true) da bismo dozvolili pristup.
„Dobro, Sanmej, o refleksiji ćemo ovde završiti."
