Programovanie

Zoznámte sa podrobne s rozhraním Java Reflection API

V minulotýždňovom „Java In-Depth“ som hovoril o introspekcii a spôsoboch, ako sa mohla trieda Java s prístupom k nespracovaným údajom triedy pozrieť „dovnútra“ triedy a zistiť, ako bola trieda skonštruovaná. Ďalej som ukázal, že po pridaní nakladača tried sa tieto triedy dajú načítať do bežiaceho prostredia a spustiť. Tento príklad je formou statický introspekcia. Tento mesiac sa pozriem na rozhranie Java Reflection API, ktoré umožňuje triedam Java vykonávať výkon dynamický introspekcia: schopnosť nahliadnuť do tried, ktoré sú už načítané.

Užitočnosť introspekcie

Jednou zo silných stránok Javy je, že bola navrhnutá za predpokladu, že prostredie, v ktorom bežala, sa bude dynamicky meniť. Triedy sa načítavajú dynamicky, väzba sa vykonáva dynamicky a inštancie objektov sa vytvárajú dynamicky za chodu, keď sú potrebné. Čo nebolo historicky veľmi dynamické, je schopnosť manipulovať s „anonymnými“ triedami. V tejto súvislosti je anonymná trieda taká, ktorá je načítaná alebo prezentovaná triede Java za behu a ktorej typ bol predtým programu Java neznámy.

Anonymné triedy

Podpora anonymných tried je v programe ťažko vysvetliteľná a ešte ťažšie navrhnuteľná. Výzva podpory anonymnej triedy môže byť vyjadrená takto: „Napíš program, ktorý po zadaní objektu Java dokáže tento objekt začleniť do svojej nepretržitej prevádzky.“ Všeobecné riešenie je dosť ťažké, ale obmedzením problému je možné vytvoriť niektoré špecializované riešenia. Existujú dva príklady špecializovaných riešení tejto triedy problémov vo verzii 1.0 Java: Java applety a verzia tlmočníka Java z príkazového riadku.

Applety Java sú triedy Java, ktoré sa načítajú spusteným virtuálnym strojom Java v kontexte webového prehľadávača a vyvolávajú sa. Tieto triedy Java sú anonymné, pretože čas spustenia nepozná vopred potrebné informácie na vyvolanie každej jednotlivej triedy. Problém vyvolania konkrétnej triedy je však vyriešený pomocou triedy Java java.applet.Applet.

Bežné supertriedy, ako Appleta rozhrania Java, napríklad AppletContext, riešiť problém anonymných tried vytvorením vopred dohodnutej zmluvy. Dodávateľ runtime prostredia konkrétne inzeruje, že môže používať akýkoľvek objekt, ktorý je v súlade so zadaným rozhraním, a spotrebiteľ runtime prostredia používa uvedené rozhranie v ľubovoľnom objekte, ktorý má v úmysle dodať za behu. V prípade appletov existuje presne špecifikované rozhranie vo forme spoločnej nadtriedy.

Nevýhodou spoločného riešenia nadtriedy, najmä pri absencii viacnásobného dedenia, je to, že objekty vytvorené na spustenie v prostredí nemožno použiť ani v inom systéme, pokiaľ tento systém neimplementuje celú zmluvu. V prípade Applet rozhrania, ktoré musí hostiteľské prostredie implementovať AppletContext. Čo to znamená pre riešenie appletu, je to, že riešenie funguje iba pri načítaní appletov. Ak dáte inštanciu a Hashtable objekt na svojej webovej stránke a nasmerujte na ňu prehliadač, načítanie by zlyhalo, pretože systém appletov nemôže fungovať mimo obmedzený rozsah.

Okrem príkladu appletu pomáha introspekcia vyriešiť problém, ktorý som spomenul minulý mesiac: prísť na to, ako spustiť vykonávanie v triede, ktorú práve načítala verzia príkazového riadku virtuálneho stroja Java. V tomto príklade musí virtuálny stroj vyvolať v načítanej triede nejakú statickú metódu. Podľa konvencie je táto metóda pomenovaná hlavný a berie jediný argument - pole String predmety.

Motivácia pre dynamickejšie riešenie

Výzvou súčasnej architektúry Java 1.0 je, že existujú problémy, ktoré by bolo možné vyriešiť pomocou dynamickejšieho introspekčného prostredia - napríklad načítateľné komponenty používateľského rozhrania, načítateľné ovládače zariadení v operačnom systéme založenom na prostredí Java a dynamicky konfigurovateľné editovacie prostredia. „Zabijáckou aplikáciou“ alebo problémom, ktorý spôsobil vytvorenie rozhrania Java Reflection API, bol vývoj modelu objektovej súčasti pre Javu. Tento model je teraz známy ako JavaBeans.

Komponenty používateľského rozhrania sú ideálnym návrhovým bodom pre introspekčný systém, pretože majú dvoch veľmi odlišných spotrebiteľov. Na jednej strane sú objekty komponentov navzájom spojené a tvoria užívateľské rozhranie ako súčasť nejakej aplikácie. Alternatívne musí existovať rozhranie pre nástroje, ktoré manipulujú s užívateľskými komponentmi bez toho, aby museli vedieť, čo sú komponenty, alebo čo je dôležitejšie, bez prístupu k zdrojovému kódu komponentov.

Rozhranie Java Reflection API vyrastalo z potrieb rozhrania API komponentu užívateľského rozhrania JavaBeans.

Čo je to odraz?

Reflection API sa v zásade skladá z dvoch komponentov: objektov, ktoré predstavujú rôzne časti súboru triedy, a prostriedkov na bezpečnú a bezpečnú extrakciu týchto objektov. To posledné je veľmi dôležité, pretože Java poskytuje veľa bezpečnostných opatrení a nemalo by zmysel poskytovať sadu tried, ktoré tieto ochranné opatrenia zneplatňujú.

Prvou zložkou Reflection API je mechanizmus používaný na načítanie informácií o triede. Tento mechanizmus je zabudovaný do pomenovanej triedy Trieda. Špeciálna trieda Trieda je univerzálny typ pre meta informácie, ktoré popisujú objekty v systéme Java. Načítavače tried v systéme Java vracajú objekty typu Trieda. Doteraz boli v tejto triede tri najzaujímavejšie metódy:

  • preMeno, ktorá by pomocou aktuálneho zavádzača tried načítala triedu s rovnakým menom

  • getName, ktorý by vrátil názov triedy ako a String objekt, ktorý bol užitočný na identifikáciu odkazov na objekty podľa názvu ich triedy

  • novýInštancia, ktorý by vyvolal nulový konštruktor na triede (ak existuje) a vrátil by vám inštanciu objektu tejto triedy objektov

K týmto trom užitočným metódam Reflection API pridáva do triedy niektoré ďalšie metódy Trieda. Sú to tieto:

  • getConstructor, getConstructors, getDeclaredConstructor
  • getMethod, getMethods, getDeclaredMethods
  • getField, getFields, getDeclaredFields
  • getSuperclass
  • getInterfaces
  • getDeclaredClasses

Okrem týchto metód bolo pridaných veľa nových tried reprezentujúcich objekty, ktoré by tieto metódy vrátili. Nové triedy sú väčšinou súčasťou java.lang.reflect balíka, ale niektoré nové triedy základných typov (Neplatný, Byte, a tak ďalej) sú v java.lang balíček. Padlo rozhodnutie, aby sa nové triedy umiestnili tam, kde sú, a to umiestnením tried, ktoré predstavovali metaúdaje do balíka reflexií, a tried, ktoré predstavovali typy v jazykovom balíku.

Reflexné API teda predstavuje rad zmien v triede Trieda ktoré vám umožňujú pýtať sa na interné prvky triedy a na množstvo tried, ktoré predstavujú odpovede, ktoré vám tieto nové metódy poskytujú.

Ako môžem použiť Reflection API?

Otázka „Ako môžem použiť API?“ je možno zaujímavejšia otázka ako „Čo je to odraz?“

Reflection API je symetrický, čo znamená, že ak držíte a Trieda objektu, môžete sa opýtať na jeho vnútorné prvky, a ak máte jeden z vnútorných priestorov, môžete sa ho opýtať, ktorá trieda to vyhlásila. Takto sa môžete pohybovať tam a späť z triedy na metódu na parameter na triedu na metódu atď. Jedným zo zaujímavých použití tejto technológie je zistiť väčšinu vzájomných závislostí medzi danou triedou a zvyškom systému.

Pracovný príklad

Na praktickejšej úrovni však môžete použiť rozhranie Reflection API na vypísanie triedy, podobne ako mojej dumpclass triedy v stĺpci z minulého mesiaca.

Na demonštráciu Reflection API som napísal triedu s názvom ReflectClass to by trvalo triede známej z doby behu Javy (čo znamená, že je niekde vo vašej ceste k triede) a prostredníctvom rozhrania Reflection API vypustiť jej štruktúru do okna terminálu. Ak chcete experimentovať s touto triedou, musíte mať k dispozícii verziu JDK verzie 1.1.

Poznámka: Robte nie pokúste sa použiť čas chodu 1,0, pretože sa to stane zmätené, čo zvyčajne vedie k nekompatibilnej výnimke pre zmenu triedy.

Trieda ReflectClass začína takto:

import java.lang.reflect. *; import java.util. *; verejná trieda ReflectClass { 

Ako vidíte vyššie, prvá vec, ktorú kód urobí, je importovanie tried Reflection API. Ďalej skočí priamo do hlavnej metódy, ktorá sa spustí, ako je to znázornené nižšie.

 public static void main (String args []) {Constructor cn []; Trieda cc []; Metóda mm []; Pole ff []; Trieda c = null; Trieda supClass; Reťazec x, y, s1, s2, s3; Hashtable classRef = nový Hashtable (); if (args.length == 0) {System.out.println ("Prosím, zadajte názov triedy na príkazovom riadku."); System.exit (1); } try {c = Class.forName (args [0]); } catch (ClassNotFoundException ee) {System.out.println ("Nemohol som nájsť triedu '" + args [0] + "'")); System.exit (1); } 

Metóda hlavný deklaruje polia konštruktorov, polí a metód. Ak si spomínate, jedná sa o tri zo štyroch základných častí súboru triedy. Štvrtou časťou sú atribúty, ku ktorým vám Reflection API bohužiaľ nedáva prístup. Po poliach som urobil nejaké spracovanie z príkazového riadku. Ak užívateľ zadal názov triedy, kód sa ho pokúsi načítať pomocou forName metóda triedy Trieda. The forName metóda berie názvy tried Java, nie názvy súborov, aby sa pozrela dovnútra java.math.BigInteger triedy, jednoducho napíšete „java ReflectClass java.math.BigInteger,“ namiesto toho, aby ste poukazovali na to, kde je súbor triedy v skutočnosti uložený.

Identifikácia balíka triedy

Za predpokladu, že sa nájde súbor triedy, kód postúpi do kroku 0, ktorý je zobrazený nižšie.

 / * * Krok 0: Ak naše meno obsahuje bodky, sme v balíčku, takže najskôr vložte *. * / x = c.getName (); y = x.substring (0, x.lastIndexOf (".")); if (y.length ()> 0) {System.out.println ("balíček" + y + "; \ n \ r"); } 

V tomto kroku je názov triedy získaný pomocou getName metóda v triede Trieda. Táto metóda vráti úplný názov a ak názov obsahuje bodky, môžeme predpokladať, že trieda bola definovaná ako súčasť balíka. Krok 0 je teda oddeliť časť s názvom balíka od časti s názvom triedy a vytlačiť časť s názvom balíka na riadok, ktorý začína „balíkom ....“

Zhromažďovanie odkazov na triedy z vyhlásení a parametrov

Keď je o vyhlásenie o balíku postarané, pokračujeme krokom 1, ktorý má zhromaždiť všetko iné názvy tried, na ktoré táto trieda odkazuje. Tento proces zhromažďovania je uvedený v kóde nižšie. Pamätajte, že tri najbežnejšie miesta, na ktoré sa odkazuje na názvy tried, sú typy pre polia (premenné inštancie), návratové typy pre metódy a ako typy parametrov odovzdávaných metódam a konštruktorom.

 ff = c.getDeclaredFields (); pre (int i = 0; i <ff.length; i ++) {x = tName (ff [i] .getType (). getName (), classRef); } 

Vo vyššie uvedenom kóde pole a nasl je inicializovaný ako pole Lúka predmety. Smyčka zhromažďuje názov typu z každého poľa a spracúva ho cez tName metóda. The tName metóda je jednoduchý pomocník, ktorý vráti stenografický názov typu. Takže java.lang.String sa stáva String. A zaznamená do hashtable, ktoré objekty boli videné. V tejto fáze sa kód zaujíma skôr o zhromažďovanie odkazov na triedy ako o tlač.

Ďalším zdrojom odkazov na triedy sú parametre dodávané konštruktorom. Ďalšia časť kódu, ktorá je zobrazená nižšie, spracuje každý deklarovaný konštruktor a zhromaždí odkazy zo zoznamov parametrov.

 cn = c.getDeclaredConstructors (); for (int i = 0; i 0) {for (int j = 0; j <cx.length; j ++) {x = tName (cx [j] .getName (), classRef); }}} 

Ako vidíte, použil som getParameterTypes metóda v Konštruktér triedy, aby mi poskytli všetky parametre, ktoré berie konkrétny konštruktor. Tieto sa potom spracúvajú prostredníctvom tName metóda.

Je zaujímavé si tu uvedomiť rozdiel medzi metódou getDeclaredConstructors a metóda getConstructors. Obe metódy vracajú pole konštruktorov, ale getConstructors metóda vráti iba tie konštruktory, ktoré sú prístupné pre vašu triedu. To je užitočné, ak chcete vedieť, či skutočne môžete vyvolať konštruktor, ktorý ste našli, ale nie je to užitočné pre túto aplikáciu, pretože chcem vytlačiť všetky konštruktory v triede, verejné alebo nie. Reflektory poľa a metódy majú tiež podobné verzie, jednu pre všetkých členov a jednu iba pre členov verejnosti.

Posledným krokom, ktorý je uvedený nižšie, je zhromaždenie referencií zo všetkých metód. Tento kód musí získavať odkazy z typu metódy (podobne ako v poliach vyššie), tak z parametrov (podobne ako vyššie uvedené konštruktory).

 mm = c.getDeclaredMethods (); for (int i = 0; i 0) {for (int j = 0; j <cx.length; j ++) {x = tName (cx [j] .getName (), classRef); }}} 

Vo vyššie uvedenom kóde sú dve volania na číslo tName - jeden na zhromažďovanie návratového typu a jeden na zhromažďovanie typov jednotlivých parametrov.

$config[zx-auto] not found$config[zx-overlay] not found