Ovaj članak se bavi pomalo nestandardnim načinima upotrebe refleksije.
Ovakva upotreba možda nije opravdana i sigurno će postojati oni koji su
protiv izloženog i/ili kojima će članak biti nepotreban. Pošto "cilj
opravdava sredstvo" :) članak je ipak tu i daje dva primera upotrebe refleksije koji se
mogu koristiti u svakodnevnom radu: ubrzani getMethod() i
dodavanje elemenata na CLASSPATH za vreme rada programa.
Poznato je da Class ima metod
getMethod() kojim se vraća metod neke klase. Metod se
pronalazi na osnovu zadatog imena i tipova parametara. Ako se metod ne
nađe, baca se NoSuchMethodException.
Ograničenje getMethod() je što radi samo sa public
metodama. Ovako dobavljene metode se mogu pozvati sa invoke():
Object obj = ...
Class clazz = ... // obj.getClass();
Method m = clazz.getMethod("fooPublic", null);
m.invoke(obj, null);Za dobavljanje svih metoda služi getDeclaredMethod() metod klase Class.
Njime se dobijaju reference i na metode koje su deklarisane kao private, protected
i default. Međutim, da bi se neki ne-public metod dobijen na ovaj način mogao pozivati,
potrebno mu je prethodno dozvoliti pristup:
Method m = clazz.getMethod("fooPrivate", null);
m.setAccessible(true);
m.invoke(obj, null);Sledeća dva primera koriste ovu mogućnost.
Ovo je specifičan problem koji se, na primer, često može naći u bibliotekama koje rade sa bean-ovima. Zadatak je da se u zadatom objektu locira metod na osnovu imena i tipova parametara. Poređenje se u ovom slučaju ne radi sa samo jednim traženim oblikom metode, već sa čitavim skupom. Znači, prvo se proverava da li u objektu postoji metod koji odgovara prvom iz skupa tražnih metoda. Ako se takav metod ne nađe, provera se nastavlja sa sledećom traženom metodom iz skupa, i tako redom dok se konačno u objektu ne nađe metod ili dok se ne "potroše" sve tražene metode.
Standardno rešenje je prosto: za svako ime traženog metoda se poziva getMethod(). Ako metod odgovara,
sve je u redu. Ako ne, hvata se exception i prelazi se na sledeći traženi metod iz skupa:
Method m = null;
Class c = o.getClass();
try {
m = c.getMethod("getFoo", null);
...
} catch (Exception ex) {
if (m == null) {
try {
m = c.getMethod("get", new Class[] {String.class});
...
} catch (Exception e) {
}
}
}
Gornji kod bi mogao da se napiše malo lepše da se ne bi išlo u dubinu catch blokova, ali je poenta ista.
Problem sa ovim pristupom je upravo brzina. Ako se metod nađe iz prvog poređenja, brzina je OK. U suprotnom
slučaju se baca i hvata exception, što značajno usporava rad. Jednostavnim merenjem se utvrđuje
da je usporenje višestruko (na referentnoj konfiguraciji za ovaj članak i do 6-7 puta). Usporenje
bi bilo još veće kada bi se poredilo još metoda, a ne samo dve kao u ovom primeru.
Drugo rešenje se nameće samo: korišćenjem getMethods() se dobija niz postojećih metoda u zadatom
objektu (tj. njegovoj klasi), a zatim se jedan po jedan metod poredi po imenu (poređenjem Stringova imena metoda)
da li odgovara nekom od traženih metoda iz skupa.
Dodatno se mogu porediti i parametri, ali se to može i ostaviti i za kasnije, zavisno od konkretnog problema.
U svakom slučaju, ovakav pristup je bar nešto više nego 2 puta sporiji za uspešni prvi pokušaj. Međutim,
skoro 3 puta je brži kada se metod nalazi iz drugog pokušaja - što je i očekivano, jer nema skupog bacanja exception-a.
Ostaje odlučiti koje rešenje izabrati: prvo, koje je brže za uspešni prvi pokušaj, ali drastično sporije za drugi pokušaj, ili drugo, koje je manje-više podjednake brzine za oba pokušaja, a opet sporije za prvi pokušaj, koji je često najverovatniji. Postoji li rešenje koje bi bilo brzo i za prvi i za drugi pokušaj poređenja?
Da. Sors getMethod() metode izgleda otprilike ovako:
public Method getMethod(String name, Class[] parameterTypes) throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
Deluje pomalo nepraktično bacati NoSuchMethodException na kraju, kada se može vratiti
null kao oznaka da metoda nije nađena. Pitanje da li je to dobro ili ne ostaje otvoreno. Autor
članka je mišljenja da treba koristiti i jedno i drugo rešenje, ali da treba znati kada metoda treba da baca
exception, a kada da vraća informaciju o uspešnosti kroz return vrednost.
U svakom slučaju, getMethod() poziva getMethod0() koja praktično radi sav posao, ali koja samo
vraća null ako traženi metod nije nađen. I to bi upravo bilo rešenje problema, kada getMethod0()
ne bi bio private metod. Kako se ipak pokazalo, moguće je pozivati private metode, što konačno rešava problem:
Method getMethod0 = null;
try {
getMethod0 = Class.class.getDeclaredMethod("getMethod0", new Class[] {String.class, Class[].class});
getMethod0.setAccessible(true);
} catch (Exception e) {
...
}
...
Method m = null;
Class c = o.getClass();
try {
m = (Method) getMethod0.invoke(c, new Object[]{"getFoo", null});
if (m != null) {
...
} else {
m = (Method) getMethod0.invoke(c, new Object[]{"get", new Class[] {String.class}));
if (m != null) {
...
}
}
} catch (Exception ex) {
}Pokazuje se da su performanse najbolje, što je i očekivano. Za prvo poređenje, dobijaju se duplo brži rezultati nego
sa klasičnim getMethod(). Za drugo poređenje brzinski rezultati su za bar 20% bolji od String-ovskog
poređenja.
Vlada mišljenje da se CLASSPATH ne može dinamički menjati, jednom kada program počne da radi. Znači, nije moguće
dodati JAR fajl ili folder na CLASSPATH dinamički, pa onda, na primer, sa Class.forName() učitati
neku klasu iz novododatog foldera. Ovakav problem bi se rešavao korišćenjem class loadera.
Ipak, bilo bi ponekad zgodno kada bi se CLASSPATH mogao dopunjavati manipulisanjem baš sistemskog class loadera. On u jednom svom trenutku inicijalizacije mora da parsira CLASSPATH sistemsku varijablu odakle kupi informacije o tome šta sve spada u CLASSPATH.
Kako je sistemski class loader tipa URLClassLoader, on ima jednu
zgodnu metodu: addURL(), koja radi upravo to što i treba. Jedini problem je što je ova metoda
protected. Kako se pokazalo, to nije i nerešiv problem:
public static void addClassPath(String path) {
try {
URL url = new File(path).toURL();
URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
invokeEx(URLClassLoader.class, loader, "addURL", new Class[]{URL.class}, new Object[]{url});
} catch (Exception e) {
e.printStackTrace();
}
}invokeEx() metoda enkapsulira pozivanje private metoda, na već opisan način. I to je to:)
Sors primer koji prati članak se može skinuti ovde.