Detaljno pogledajte Java Reflection API

U prošlomjesečnoj "Javnoj dubini" govorio sam o introspekciji i načinima na koje bi Java klasa s pristupom sirovim podacima klase mogla izgledati "unutar" klase i shvatiti kako je klasa konstruirana. Dalje, pokazao sam da bi se dodavanjem učitavača klasa te klase mogle učitati u pokrenuto okruženje i izvršiti. Taj je primjer oblik statičke introspekcije. Ovaj mjesec ću pogledati Java Reflection API, koji Java razredima daje mogućnost izvođenja dinamičke introspekcije: sposobnost gledanja u klase koje su već učitane.

Korisnost introspekcije

Jedna od jakih strana Jave je ta što je dizajniran s pretpostavkom da će se okruženje u kojem radi bilo dinamički mijenjati. Klase se dinamički učitavaju, povezivanje se vrši dinamički, a instance objekta stvaraju se dinamički u letu kada su potrebne. Ono što u prošlosti nije bilo vrlo dinamično je sposobnost manipulacije "anonimnim" klasama. U tom kontekstu, anonimna je klasa koja se učitava ili prezentira Java klasi tijekom izvođenja i čiji je tip Java program ranije bio nepoznat.

Anonimni satovi

Podrška anonimnim tečajevima teško je objasniti, a još teže dizajnirati za program. Izazov podrške anonimnoj klasi može se izreći ovako: "Napišite program koji, kada dobije Java objekt, može uključiti taj objekt u svoj kontinuirani rad." Opće je rješenje prilično teško, ali ograničavanjem problema mogu se stvoriti neka specijalizirana rješenja. Dva su primjera specijaliziranih rješenja za ovu klasu problema u 1.0 verziji Jave: Java apleti i inačica naredbenog retka Java interpretera.

Java apleti su Java klase koje učitava izvršeni Java virtualni stroj u kontekstu web preglednika i poziva ih. Ove su Java klase anonimne jer vrijeme izvođenja ne zna unaprijed potrebne informacije za pozivanje svake pojedine klase. Međutim, problem pozivanja određene klase riješen je pomoću klase Java java.applet.Applet.

Uobičajene superklase, poput Applet, i Java sučelja, poput AppletContext, rješavaju problem anonimnih klasa stvaranjem prethodno dogovorenog ugovora. Točnije, dobavljač okruženja za izvršavanje oglašava da može koristiti bilo koji objekt koji je u skladu s navedenim sučeljem, a potrošač okruženja za izvršavanje koristi to određeno sučelje u bilo kojem objektu koji namjerava pružiti vremenu izvođenja. U slučaju apleta, postoji točno određeno sučelje u obliku zajedničke superklase.

Loša strana uobičajenog rješenja superklase, posebno u nedostatku višestrukog nasljeđivanja, jest ta što se objekti izgrađeni za pokretanje u okruženju ne mogu koristiti u nekom drugom sustavu, osim ako taj sustav ne provodi cjelokupni ugovor. U slučaju Appletsučelja, hosting okruženje mora implementirati AppletContext. To znači za rješenje apleta da rješenje funkcionira samo kada učitavate aplete. Ako instancu Hashtableobjekta stavite na svoju web stranicu i usmerite svoj preglednik na nju, neće se uspjeti učitati jer sustav apleta ne može raditi izvan svog ograničenog dometa.

Uz primjer apleta, introspekcija pomaže u rješavanju problema koji sam spomenuo prošli mjesec: otkrivanje kako započeti izvršavanje u klasi koju je upravo učitala verzija naredbenog retka Java virtualnog stroja. U tom primjeru virtualni stroj mora pozvati neku statičku metodu u učitanoj klasi. Prema dogovoru, ta se metoda imenuje maini uzima jedan argument - niz Stringobjekata.

Motivacija za dinamičnije rješenje

Izazov s postojećom arhitekturom Java 1.0 jest taj da postoje problemi koje bi moglo riješiti dinamičnije okruženje za introspekciju - poput učitanih komponenti korisničkog sučelja, učitanih upravljačkih programa uređaja u OS-u temeljenom na Javi i dinamički prilagodljivih okruženja za uređivanje. "Ubojita aplikacija" ili problem zbog kojeg je stvoren Java Reflection API bio je razvoj objektnog komponentnog modela za Javu. Taj je model sada poznat kao JavaBeans.

Komponente korisničkog sučelja idealna su točka za dizajn sustava za introspekciju jer imaju dva vrlo različita potrošača. S jedne strane, objekti komponenata povezani su zajedno kako bi stvorili korisničko sučelje kao dio neke aplikacije. Alternativno, mora postojati sučelje za alate koji manipuliraju korisničkim komponentama, a da ne moraju znati koje su komponente ili, što je još važnije, bez pristupa izvornom kodu komponenata.

Java Reflection API izrastao je iz potreba API-ja komponente korisničkog sučelja JavaBeans.

Što je refleksija?

U osnovi, Reflection API sastoji se od dvije komponente: objekata koji predstavljaju različite dijelove datoteke klase i sredstva za izdvajanje tih objekata na siguran i siguran način. Potonje je vrlo važno, jer Java pruža mnoge sigurnosne mjere zaštite, i ne bi imalo smisla pružati skup klasa koje bi te zaštitne mehanizme poništile.

Prva komponenta Reflection API-ja je mehanizam koji se koristi za dohvaćanje podataka o klasi. Ovaj je mehanizam ugrađen u klasu imenovanu Class. Posebna klasa Classje univerzalni tip za meta informacije koji opisuju objekte u Java sustavu. Učitavači klasa u sustavu Java vraćaju objekte tipa Class. Do sada su tri najzanimljivije metode u ovoj klasi bile:

  • forName, koji bi učitao klasu određenog imena, koristeći trenutni učitavač klase

  • getName, koji bi naziv klase vratio kao Stringobjekt, što je bilo korisno za identificiranje referenci na objekt prema imenu klase

  • newInstance, koji bi pozvao null konstruktor na klasi (ako postoji) i vratio vam instancu objekta te klase objekta

Ovim trima korisnim metodama Reflection API dodaje neke dodatne metode u klasu Class. To su kako slijedi:

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

Uz ove metode, dodane su mnoge nove klase koje predstavljaju objekte koje će te metode vratiti. Nove klase uglavnom su dio java.lang.reflectpaketa, ali neke od novih osnovne klase tipa ( Void, Byte, i tako dalje) su u java.langpaketu. Donesena je odluka da se nove klase stave tamo gdje jesu stavljanjem klasa koje su predstavljale meta-podatke u paket odraza i klasa koje su predstavljale tipove u jezičnom paketu.

Dakle, Reflection API predstavlja brojne promjene klase Classkoje vam omogućuju postavljanje pitanja o unutrašnjosti klase i hrpu klasa koje predstavljaju odgovore koje vam daju ove nove metode.

Kako mogu koristiti Reflection API?

Pitanje "Kako da koristim API?" je možda zanimljivije pitanje od "Što je razmišljanje?"

Reflection API je simetričan , što znači da ako držite Classobjekt, možete pitati za njegove unutarnje dijelove, a ako imate jedan od unutarnjih, možete ga pitati koja ga je klasa deklarirala. Tako se možete kretati naprijed-natrag iz klase u metodu u parametar u klasu u metodu i tako dalje. Jedna zanimljiva uporaba ove tehnologije je otkrivanje većine međuovisnosti između određene klase i ostatka sustava.

Radni primjer

Na praktičnijoj razini, međutim, možete koristiti Reflection API za izbacivanje predavanja, baš kao što je dumpclassto učinio moj razred u prošlomjesečnom stupcu.

Da bih demonstrirao Reflection API, napisao sam klasu pod nazivom ReflectClasskoja bi uzela klasu poznatu po vremenu izvođenja Java (što znači da je negdje na putu do vaše klase) i, putem Reflection API-ja, izbacila svoju strukturu u prozor terminala. Da biste eksperimentirali s ovom klasom, morat ćete imati na raspolaganju 1.1 verziju JDK.

Napomena: Da li ne pokušati iskoristiti vrijeme 1,0 pokrenuti jer sve zbuni, obično rezultira nespojive izuzetkom promjene klase.

Predavanje ReflectClasszapočinje na sljedeći način:

uvoz java.lang.reflect. *; uvoz java.util. *; javni razred ReflectClass {

Kao što vidite gore, prvo što kod čini je uvoz klasa Reflection API. Dalje, skače točno u glavnu metodu, koja započinje kao što je prikazano u nastavku.

 public static void main(String args[]) { Constructor cn[]; Class cc[]; Method mm[]; Field ff[]; Class c = null; Class supClass; String x, y, s1, s2, s3; Hashtable classRef = new Hashtable(); if (args.length == 0) { System.out.println("Please specify a class name on the command line."); System.exit(1); } try { c = Class.forName(args[0]); } catch (ClassNotFoundException ee) { System.out.println("Couldn't find class '"+args[0]+"'"); System.exit(1); } 

The method main declares arrays of constructors, fields, and methods. If you recall, these are three of the four fundamental parts of the class file. The fourth part is the attributes, which the Reflection API unfortunately does not give you access to. After the arrays, I've done some command-line processing. If the user has typed a class name, the code attempts to load it using the forName method of class Class. The forName method takes Java class names, not file names, so to look inside the java.math.BigInteger class, you simply type "java ReflectClass java.math.BigInteger," rather than point out where the class file actually is stored.

Identifying the class's package

Assuming the class file is found, the code proceeds into Step 0, which is shown below.

 /* * Step 0: If our name contains dots we're in a package so put * that out first. */ x = c.getName(); y = x.substring(0, x.lastIndexOf(".")); if (y.length() > 0) { System.out.println("package "+y+";\n\r"); } 

In this step, the name of the class is retrieved using the getName method in class Class. This method returns the fully qualified name, and if the name contains dots, we can presume that the class was defined as part of a package. So Step 0 is to separate the package name part from the class name part, and print out the package name part on a line that starts with "package...."

Collecting class references from declarations and parameters

With the package statement taken care of, we proceed to Step 1, which is to collect all of the other class names that are referenced by this class. This collection process is shown in the code below. Remember that the three most common places where class names are referenced are as types for fields (instance variables), return types for methods, and as the types of the parameters passed to methods and constructors.

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

In the above code, the array ff is initialized to be an array of Field objects. The loop collects the type name from each field and process it through the tName method. The tName method is a simple helper that returns the shorthand name for a type. So java.lang.String becomes String. And it notes in a hashtable which objects have been seen. At this stage, the code is more interested in collecting class references than in printing.

The next source of class references are the parameters supplied to constructors. The next piece of code, shown below, processes each declared constructor and collects the references from the parameter lists.

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

As you can see, I've used the getParameterTypes method in the Constructor class to feed me all of the parameters that a particular constructor takes. These are then processed through the tName method.

An interesting thing to note here is the difference between the method getDeclaredConstructors and the method getConstructors. Both methods return an array of constructors, but the getConstructors method only returns those constructors that are accessible to your class. This is useful if you want to know if you actually can invoke the constructor you've found, but it isn't useful for this application because I want to print out all of the constructors in the class, public or not. The field and method reflectors also have similar versions, one for all members and one only for public members.

Posljednji korak, prikazan u nastavku, je prikupljanje referenci iz svih metoda. Ovaj kôd mora dobiti reference i iz vrste metode (slično gornjim poljima) i iz parametara (slično gornjim konstruktorima).

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

U gornjem kodu postoje dva poziva na tName- jedan za prikupljanje tipa povratka i jedan za prikupljanje tipa svakog parametra.