Programování

Podívejte se podrobně na rozhraní Java Reflection API

V minulém měsíci „Java In-Depth“ jsem hovořil o introspekci a způsobech, jak by mohla třída Java s přístupem k nezpracovaným datům třídy vypadat „uvnitř“ třídy a zjistit, jak byla třída konstruována. Dále jsem ukázal, že s přidáním zavaděče tříd lze tyto třídy načíst do běžícího prostředí a spustit. Tento příklad je formou statický introspekce. Tento měsíc se podívám na rozhraní Java Reflection API, které dává třídám Java schopnost hrát dynamický introspekce: schopnost nahlédnout do tříd, které jsou již načteny.

Užitečnost introspekce

Jednou ze silných stránek Javy je, že byla navržena s předpokladem, že prostředí, ve kterém běželo, se bude dynamicky měnit. Třídy se načítají dynamicky, vazba se provádí dynamicky a instance objektů se vytvářejí dynamicky za běhu, když jsou potřeba. Historicky příliš dynamická nebyla schopnost manipulovat s „anonymními“ třídami. V této souvislosti je anonymní třída taková, která je načtena nebo prezentována třídě Java za běhu a jejíž typ byl dříve pro program Java neznámý.

Anonymní třídy

Podpora anonymních tříd je v programu obtížně vysvětlitelná a ještě hůře navržitelná. Výzvu podpory anonymní třídy lze vyjádřit takto: „Napište program, který, když dostane objekt Java, může tento objekt začlenit do svého pokračujícího provozu.“ Obecné řešení je poměrně obtížné, ale omezením problému lze vytvořit některá specializovaná řešení. Existují dva příklady specializovaných řešení této třídy problémů ve verzi 1.0 Java: applety Java a verze interpretu Java z příkazového řádku.

Applety Java jsou třídy Java, které jsou načítány spuštěným virtuálním strojem Java v kontextu webového prohlížeče a vyvolány. Tyto třídy Java jsou anonymní, protože doba běhu předem nezná informace potřebné k vyvolání každé jednotlivé třídy. Problém vyvolání konkrétní třídy je však vyřešen pomocí třídy Java java.applet.Applet.

Běžné supertřídy, jako Appleta rozhraní Java, jako AppletContext, řešit problém anonymních tříd vytvořením předem dohodnuté smlouvy. Dodavatel konkrétně běhového prostředí inzeruje, že může používat jakýkoli objekt, který odpovídá zadanému rozhraní, a spotřebitel běhového prostředí používá toto zadané rozhraní v jakémkoli objektu, který hodlá dodat za běhu. V případě appletů existuje dobře specifikované rozhraní ve formě společné nadtřídy.

Nevýhodou společného řešení nadtřídy, zejména při absenci vícenásobné dědičnosti, je to, že objekty vytvořené pro běh v prostředí nelze také použít v nějakém jiném systému, pokud tento systém neimplementuje celou smlouvu. V případě Applet rozhraní, které musí hostitelské prostředí implementovat AppletContext. To pro řešení appletu znamená, že řešení funguje pouze při načítání appletů. Pokud vložíte instanci a Hashtable objekt na své webové stránce a nasměrovat na něj prohlížeč, načítání by selhalo, protože systém appletů nemůže fungovat mimo svůj omezený rozsah.

Kromě příkladu appletu pomáhá introspekce vyřešit problém, který jsem zmínil minulý měsíc: zjišťování, jak spustit spuštění ve třídě, kterou právě načetla verze příkazového řádku virtuálního stroje Java. V tomto příkladu musí virtuální stroj vyvolat nějakou statickou metodu v načtené třídě. Podle konvence je tato metoda pojmenována hlavní a vezme jediný argument - pole Tětiva předměty.

Motivace k dynamičtějšímu řešení

Úkolem stávající architektury Java 1.0 je, že existují problémy, které by mohly být vyřešeny dynamičtějším introspekčním prostředím - jako jsou načtitelné komponenty uživatelského rozhraní, načtitelné ovladače zařízení v operačním systému Java a dynamicky konfigurovatelná editační prostředí. „Zabijáckou aplikací“ nebo problémem, který způsobil vytvoření rozhraní Java Reflection API, byl vývoj modelu objektové komponenty pro Javu. Tento model je nyní známý jako JavaBeans.

Komponenty uživatelského rozhraní jsou ideálním návrhovým bodem pro introspekční systém, protože mají dva velmi odlišné spotřebitele. Na jedné straně jsou objekty komponent spojeny dohromady a tvoří uživatelské rozhraní jako součást nějaké aplikace. Alternativně musí existovat rozhraní pro nástroje, které manipulují s uživatelskými komponentami, aniž by musely vědět, jaké jsou komponenty, nebo, co je důležitější, bez přístupu ke zdrojovému kódu komponent.

Rozhraní Java Reflection API vyrostlo z potřeb rozhraní API komponenty uživatelského rozhraní JavaBeans.

Co je to odraz?

Reflection API se v zásadě skládá ze dvou komponent: objektů, které představují různé části souboru třídy, a prostředků pro bezpečnou a bezpečnou extrakci těchto objektů. Druhá možnost je velmi důležitá, protože Java poskytuje mnoho bezpečnostních opatření a nemělo by smysl poskytovat sadu tříd, které tyto záruky zneplatnily.

První složkou Reflection API je mechanismus používaný k načtení informací o třídě. Tento mechanismus je zabudován do pojmenované třídy Třída. Speciální třída Třída je univerzální typ pro meta informace, která popisuje objekty v systému Java. Zavaděče tříd v systému Java vracejí objekty typu Třída. Až dosud byly v této třídě tři nejzajímavější metody:

  • forName, který by pomocí aktuálního zavaděče tříd načetl třídu daného jména

  • getName, což by vrátilo název třídy jako a Tětiva object, což bylo užitečné pro identifikaci odkazů na objekty podle jejich názvu třídy

  • novýInstance, který by vyvolal nulový konstruktor na třídě (pokud existuje) a vrátil vám instanci objektu této třídy objektu

K těmto třem užitečným metodám přidává Reflection API do třídy některé další metody Třída. Jedná se o následující:

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

Kromě těchto metod bylo přidáno mnoho nových tříd, které představují objekty, které by tyto metody vrátily. Nové třídy jsou většinou součástí java.lang.reflect balíček, ale některé nové třídy základních typů (Neplatné, Byte, a tak dále) jsou v java.lang balík. Bylo rozhodnuto umístit nové třídy tam, kde jsou, vložením tříd, které představovaly metadata v balíčku reflexe, a tříd, které představovaly typy v jazykovém balíčku.

Reflection API tedy představuje řadu změn třídy Třída které vám umožní klást otázky o vnitřních záležitostech třídy a spoustě tříd, které představují odpovědi, které vám tyto nové metody poskytují.

Jak mohu použít Reflection API?

Otázka „Jak mohu použít API?“ je možná zajímavější otázka než „Co je to odraz?“

Reflexní API je symetrický, což znamená, že pokud držíte a Třída objektu, můžete se zeptat na jeho interní prvky, a pokud máte jeden z interních, můžete se ho zeptat, která třída to deklarovala. Můžete se tedy pohybovat tam a zpět z třídy na metodu do parametru na třídu na metodu atd. Jedním zajímavým využitím této technologie je zjistit většinu vzájemných závislostí mezi danou třídou a zbytkem systému.

Pracovní příklad

Na praktičtější úrovni však můžete použít rozhraní Reflection API k vyřazení třídy, stejně jako mé dumpclass třída ve sloupci z minulého měsíce.

Abych předvedl Reflection API, napsal jsem třídu s názvem ReflectClass to by trvalo třídu známou době běhu Java (což znamená, že je někde ve vaší cestě ke třídě) a prostřednictvím rozhraní Reflection API vypsat její strukturu do okna terminálu. Chcete-li experimentovat s touto třídou, musíte mít k dispozici verzi JDK verze 1.1.

Poznámka: Dělejte ne zkuste použít dobu běhu 1,0, protože se to všechno zaměňuje, což obvykle vede k výjimce nekompatibilní změny třídy.

Třída ReflectClass začíná takto:

import java.lang.reflect. *; import java.util. *; veřejná třída ReflectClass { 

Jak vidíte výše, první věc, kterou kód udělá, je import tříd Reflection API. Dále skočí přímo do hlavní metody, která začíná, jak je znázorněno níže.

 public static void main (String args []) {Constructor cn []; Třída cc []; Metoda mm []; Pole ff []; Třída c = null; Třída supClass; Řetězec x, y, s1, s2, s3; Hashtable classRef = nový Hashtable (); if (args.length == 0) {System.out.println ("Na příkazovém řádku uveďte název třídy."); System.exit (1); } try {c = Class.forName (args [0]); } catch (ClassNotFoundException ee) {System.out.println ("Nelze najít třídu '" + args [0] + "'"); System.exit (1); } 

Metoda hlavní deklaruje pole konstruktorů, polí a metod. Pokud si vzpomínáte, jedná se o tři ze čtyř základních částí souboru třídy. Čtvrtou částí jsou atributy, ke kterým vám Reflection API bohužel nedává přístup. Po polích jsem provedl nějaké zpracování příkazového řádku. Pokud uživatel zadal název třídy, kód se pokusí načíst pomocí forName metoda třídy Třída. The forName metoda přebírá názvy tříd Java, nikoli názvy souborů, aby se mohla podívat dovnitř java.math.BigInteger třídy, jednoduše napíšete „java ReflectClass java.math.BigInteger,“ místo abyste poukazovali na to, kde je skutečně uložen soubor třídy.

Identifikace balíčku třídy

Za předpokladu, že je nalezen soubor třídy, pokračuje kód do kroku 0, který je uveden níže.

 / * * Krok 0: Pokud naše jméno obsahuje tečky, jsme v balíčku, takže nejprve 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 se název třídy načte pomocí getName metoda ve třídě Třída. Tato metoda vrací plně kvalifikovaný název a pokud název obsahuje tečky, můžeme předpokládat, že třída byla definována jako součást balíčku. Krok 0 je tedy oddělit část názvu balíčku od části názvu třídy a vytisknout část názvu balíčku na řádek, který začíná řetězcem „balíček ....“

Shromažďování odkazů na třídy z deklarací a parametrů

Když je o balíček postaráno, pokračujeme krokem 1, kterým je shromáždit všechny jiný názvy tříd, na které tato třída odkazuje. Tento proces shromažďování je uveden v kódu níže. Nezapomeňte, že tři nejčastější místa, na která se odkazuje na názvy tříd, jsou typy pro pole (proměnné instance), návratové typy pro metody a jako typy parametrů předávaných metodám a konstruktorům.

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

Ve výše uvedeném kódu pole násl je inicializován jako pole Pole předměty. Smyčka shromažďuje název typu z každého pole a zpracovává jej prostřednictvím tName metoda. The tName metoda je jednoduchý pomocník, který vrací zkratkový název pro typ. Tak řetězec java.lang se stává Tětiva. A zaznamenává do hashtable, které objekty byly viděny. V této fázi se kód více zajímá o shromažďování odkazů na třídy než o tisk.

Dalším zdrojem odkazů na třídy jsou parametry dodávané konstruktorům. Další část kódu, zobrazená níže, zpracovává každý deklarovaný konstruktor a shromažďuje odkazy ze seznamů parametrů.

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

Jak vidíte, použil jsem getParameterTypes metoda v Konstruktor třída, aby mi dala všechny parametry, které konkrétní konstruktor bere. Ty jsou poté zpracovány prostřednictvím tName metoda.

Zajímavostí, kterou je zde třeba poznamenat, je rozdíl mezi metodou getDeclaredConstructors a metoda getConstructors. Obě metody vracejí pole konstruktorů, ale getConstructors metoda vrátí pouze ty konstruktory, které jsou přístupné vaší třídě. To je užitečné, pokud chcete vědět, jestli ve skutečnosti můžete vyvolat konstruktor, který jste našli, ale pro tuto aplikaci to není užitečné, protože chci vytisknout všechny konstruktory ve třídě, veřejné nebo ne. Pole a reflektory metod mají také podobné verze, jednu pro všechny členy a jednu pouze pro veřejné členy.

Posledním krokem, který je zobrazen níže, je shromáždění odkazů ze všech metod. Tento kód musí získat odkazy jak z typu metody (podobně jako výše uvedená pole), tak z parametrů (podobně jako výše uvedené konstruktory).

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

Ve výše uvedeném kódu jsou dvě volání tName - jeden pro sběr návratového typu a jeden pro sběr typu každého parametru.