JavaSvet - otvorena java zajednica

 
glavna stranica arr2javasvet  english version arr2java.net

Neke prljave upotrebe refleksije

Igor Spasić
19 Nov 2004

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.

Dobavljanje i pozivanje svih metoda

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.

Primer #1: Uzastopno pozivanje getMethod()

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?

getMethod0()

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.

Primer #2: dinamičko dodavanje elemenata na CLASSPATH u toku rada programa

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:)

Primer

Sors primer koji prati članak se može skinuti ovde.