詳解Java中的反射機(jī)制和動(dòng)態(tài)代理
一、反射概述
反射機(jī)制指的是Java在運(yùn)行時(shí)候有一種自觀的能力,能夠了解自身的情況為下一步做準(zhǔn)備,其想表達(dá)的意思就是:在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠獲取到這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性(包括私有的方法和屬性),這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能就稱為java語言的反射機(jī)制。通俗點(diǎn)講,通過反射,該類對(duì)我們來說是完全透明的,想要獲取任何東西都可以,這是一種動(dòng)態(tài)獲取類的信息以及動(dòng)態(tài)調(diào)用對(duì)象方法的能力。
想要使用反射機(jī)制,就必須要先獲取到該類的字節(jié)碼文件對(duì)象(.class),通過該類的字節(jié)碼對(duì)象,就能夠通過該類中的方法獲取到我們想要的所有信息(方法,屬性,類名,父類名,實(shí)現(xiàn)的所有接口等等),每一個(gè)類對(duì)應(yīng)著一個(gè)字節(jié)碼文件也就對(duì)應(yīng)著一個(gè)Class類型的對(duì)象,也就是字節(jié)碼文件對(duì)象。
Java提供的反射機(jī)制,依賴于我們下面要講到的Class類和java.lang.reflect類庫。我們下面要學(xué)習(xí)使用的主要類有:①Class表示類或者接口;②java.lang.reflect.Field表示類中的成員變量;③java.lang.reflect.Method表示類中的方法;④java.lang.reflect.Constructor表示類的構(gòu)造方法;⑤Array提供動(dòng)態(tài)數(shù)組的創(chuàng)建和訪問數(shù)組的靜態(tài)方法。
二、反射之Class類
2.1、初識(shí)Class類
在類Object下面提供了一個(gè)方法:
public final native Class<?> getClass()
此方法將會(huì)被所有的子類繼承,該方法的返回值為一個(gè)Class,這個(gè)Class類就是反射的源頭。那么Class類是什么呢?Class類是Java中用來表達(dá)運(yùn)行時(shí)類型信息的對(duì)應(yīng)類,我們剛剛也說過所有類都會(huì)繼承Object類的getClass()方法,那么也體現(xiàn)了著Java中的每個(gè)類都有一個(gè)Class對(duì)象,當(dāng)我們編寫并編譯一個(gè)創(chuàng)建的類就會(huì)產(chǎn)生對(duì)應(yīng)的class文件并將類的信息寫到該class文件中,當(dāng)我們使用正常方式new一個(gè)對(duì)象或者使用類的靜態(tài)字段時(shí)候,JVM的累加器子系統(tǒng)就會(huì)將對(duì)應(yīng)的Class對(duì)象加載到JVM中,然后JVM根據(jù)這個(gè)類型信息相關(guān)的Class對(duì)象創(chuàng)建我們需要的實(shí)例對(duì)象或者根據(jù)提供靜態(tài)變量的引用值。將Class類稱為類的類型,一個(gè)Class對(duì)象稱為類的類型對(duì)象。
2.2、Class有下面的幾個(gè)特點(diǎn)
①Class也是類的一種(不同于class,class是一個(gè)關(guān)鍵字);
②Class類只有一個(gè)私有的構(gòu)造函數(shù)
private Class(ClassLoader loader)
只有JVM能夠創(chuàng)建Class類的實(shí)例;
③對(duì)于同一類的對(duì)象,在JVM中只存在唯一一個(gè)對(duì)應(yīng)的Class類實(shí)例來描述其信息;
④每個(gè)類的實(shí)例都會(huì)記得自己是由哪個(gè)Class實(shí)例所生成;
⑤通過Class可以完整的得到一個(gè)類中的完整結(jié)構(gòu);
2.3、獲取Class類實(shí)例
剛剛說到過Class只有一個(gè)私有的構(gòu)造函數(shù),所以我們不能通過new創(chuàng)建Class實(shí)例,有下面這幾種獲取Class實(shí)例的方法:
①Class.forName("類的全限定名"),該方法只能獲取引用類型的類類型對(duì)象。該方法會(huì)拋出異常(a.l類加載器在類路徑中沒有找到該類 b.該類被某個(gè)類加載器加載到JVM內(nèi)存中,另外一個(gè)類加載器有嘗試從同一個(gè)包中加載)
//Class<T> clazz = Class.forName("類的全限定名");這是通過Class類中的靜態(tài)方法forName直接獲取一個(gè)Class的對(duì)象 Class<?> clazz1 = null; try { clazz1 = Class.forName("reflect.Person"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(clazz1); //class reflect.Person
②如果我們有一個(gè)類的對(duì)象實(shí)例,那么通過這個(gè)對(duì)象的getClass()方法可以獲得他的Class對(duì)象,如下所示
//Class<T> clazz = xxx.getClass(); //通過類的實(shí)例獲取類的Class對(duì)象 Class<?> clazz3 = new Person().getClass(); System.out.println(clazz3); //class reflect.Person Class<?> stringClass = "string".getClass(); System.out.println(stringClass); //class java.lang.String /** * [代表數(shù)組, * B代表byte; * I代表int; * Z代表boolean; * L代表引用類型 * 組合起來就是指定類型的一維數(shù)組,如果是[[就是二維數(shù)組 */ Class<?> arrClass = new byte[20].getClass(); System.out.println(arrClass); //class [B Class<?> arrClass1 = new int[20].getClass(); System.out.println(arrClass1); //class [I Class<?> arrClass2 = new boolean[20].getClass(); System.out.println(arrClass2); //class [Z Class<?> arrClass3 = new Person[20].getClass(); System.out.println(arrClass3); //class [Lreflect.Person; Class<?> arrClass4 = new String[20].getClass(); System.out.println(arrClass4); //class [Ljava.lang.String;
③通過類的class字節(jié)碼文件獲取,通過類名.class獲取該類的Class對(duì)象
//Class<T> clazz = XXXClass.class; 當(dāng)類已經(jīng)被加載為.class文件時(shí)候, Class<Person> clazz2 = Person.class; System.out.println(clazz2); System.out.println(int [][].class);//class [[I System.out.println(Integer.class);//class java.lang.Integer
2.4、關(guān)于包裝類的靜態(tài)屬性
我們知道,在Java中對(duì)于基本類型和void都有對(duì)應(yīng)的包裝類。在包裝類中有一個(gè)靜態(tài)屬性TYPE保存了該類的類類型。如下所示
/** * The {@code Class} instance representing the primitive type * {@code int}. * * @since JDK1.1 */ @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
我們使用這個(gè)靜態(tài)屬性來獲得Class實(shí)例,如下所示
Class c0 = Byte.TYPE; //byte Class c1 = Integer.TYPE; //int Class c2 = Character.TYPE; //char Class c3 = Boolean.TYPE; //boolean Class c4 = Short.TYPE; //short Class c5 = Long.TYPE; //long Class c6 = Float.TYPE; //float Class c7 = Double.TYPE; //double Class c8 = Void.TYPE; //void
2.5、通過Class類的其他方法獲取
①public native Class<? super T> getSuperclass():獲取該類的父類
Class c1 = Integer.class; Class par = c1.getSuperclass(); System.out.println(par); //class java.lang.Number
②public Class<?>[] getClasses():獲取該類的所有公共類、接口、枚舉組成的Class數(shù)組,包括繼承的;
③public Class<?>[] getDeclaredClasses():獲取該類的顯式聲明的所有類、接口、枚舉組成的Class數(shù)組;
④(Class/Field/Method/Constructor).getDeclaringClass():獲取該類/屬性/方法/構(gòu)造器所在的類
三、Class類的API
這是下面測(cè)試用例中使用的Person類和實(shí)現(xiàn)的接口
package reflect; interface Test { String test = "interface"; } public class Person implements Test{ private String id; private String name; public void sing(String name) { System.out.println(getName() + "會(huì)唱" + name +"歌"); } private void dance(String name) { System.out.println(getName() + "會(huì)跳" + name + "舞"); } public void playBalls() { System.out.println(getName() + "會(huì)打籃球"); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Person() { } public Person(String id, String name) { this.id = id; this.name = name; } public Person(String id) { this.id = id; } @Override public String toString() { return "Person{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } //Person
3.1、創(chuàng)建實(shí)例對(duì)象
public void test4() throws Exception{ Class clazz =Class.forName("reflect.Person"); Person person = (Person)clazz.newInstance(); System.out.println(person); }
創(chuàng)建運(yùn)行時(shí)類的對(duì)象,使用newInstance(),實(shí)際上就是調(diào)用運(yùn)行時(shí)指定類的無參構(gòu)造方法。這里也說明要想創(chuàng)建成功,需要對(duì)應(yīng)的類有無參構(gòu)造器,并且構(gòu)造器的權(quán)限要足夠,否則會(huì)拋出下面的異常。
①我們顯示聲明Person類一個(gè)帶參構(gòu)造,并沒有無參構(gòu)造,這種情況會(huì)拋出InstantiationException
②更改無參構(gòu)造器訪問權(quán)限為private
3.2、獲取構(gòu)造器
(1)獲取指定可訪問的構(gòu)造器創(chuàng)建對(duì)象實(shí)例
上面我們所說的使用newInstance方法創(chuàng)建對(duì)象,如果不指定任何參數(shù)的話默認(rèn)是調(diào)用指定類的無參構(gòu)造器的。那么如果沒有無參構(gòu)造器,又想創(chuàng)建對(duì)象實(shí)例怎么辦呢,就使用Class類提供的獲取構(gòu)造器的方法,顯示指定我們需要調(diào)用哪一個(gè)無參構(gòu)造器。
@Test public void test5() throws Exception { Class clazz = Class.forName("reflect.Person"); //獲取帶參構(gòu)造器 Constructor constructor = clazz.getConstructor(String.class, String .class); //通過構(gòu)造器來實(shí)例化對(duì)象 Person person = (Person) constructor.newInstance("p1", "person"); System.out.println(person); }
當(dāng)我們指定的構(gòu)造器全部不夠(比如設(shè)置為private),我們?cè)谡{(diào)用的時(shí)候就會(huì)拋出下面的異常
(2)獲得全部構(gòu)造器
@Test public void test6() throws Exception { Class clazz1 = Class.forName("reflect.Person"); Constructor[] constructors = clazz1.getConstructors(); for (Constructor constructor : constructors) { Class[] parameters = constructor.getParameterTypes(); System.out.println("構(gòu)造函數(shù)名:" + constructor + "\n" + "參數(shù)"); for (Class c: parameters) { System.out.print(c.getName() + " "); } System.out.println(); } }
運(yùn)行結(jié)果如下
3.3、獲取成員變量并使用Field對(duì)象的方法
(1)Class.getField(String)方法可以獲取類中的指定字段(可見的), 如果是私有的可以用getDeclaedField("name")方法獲取,通過set(對(duì)象引用,屬性值)方法可以設(shè)置指定對(duì)象上該字段的值, 如果是私有的需要先調(diào)用setAccessible(true)設(shè)置訪問權(quán)限,用獲取的指定的字段調(diào)用get(對(duì)象引用)可以獲取指定對(duì)象中該字段的值。
@Test public void test7() throws Exception { Class clazz1 = Class.forName("reflect.Person"); //獲得實(shí)例對(duì)象 Person person = (Person) clazz1.newInstance(); /** * 獲得類的屬性信息 * 使用getField(name),通過指定的屬性name獲得 * 如果屬性不是public的,使用getDeclaredField(name)獲得 */ Field field = clazz1.getDeclaredField("id"); //如果是private的,需要設(shè)置權(quán)限為可訪問 field.setAccessible(true); //設(shè)置成員變量的屬性值 field.set(person, "person1"); //獲取成員變量的屬性值,使用get方法,其中第一個(gè)參數(shù)表示獲得字段的所屬對(duì)象,第二個(gè)參數(shù)表示設(shè)置的值 System.out.println(field.get(person)); //這里的field就是id屬性,打印person對(duì)象的id屬性的值 }
(2)獲得全部成員變量
@Test public void test8() throws Exception{ Class clazz1 = Class.forName("reflect.Person"); //獲得實(shí)例對(duì)象 Person person = (Person) clazz1.newInstance(); person.setId("person1"); person.setName("person1_name"); Field[] fields = clazz1.getDeclaredFields(); for (Field f : fields) { //打開private成員變量的可訪問權(quán)限 f.setAccessible(true); System.out.println(f+ ":" + f.get(person)); } }
3.4、獲取方法并使用method
(1)使用Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法可以獲取類中的指定方法,如果為私有方法,則需要打開一個(gè)權(quán)限。setAccessible(true);用invoke(Object, Object...)可以調(diào)用該方法。如果是私有方法而使用的是getMethod方法來獲得會(huì)拋出NoSuchMethodException
@Test public void test9() throws Exception{ Class clazz1 = Class.forName("reflect.Person"); //獲得實(shí)例對(duì)象 Person person = (Person) clazz1.newInstance(); person.setName("Person"); //①不帶參數(shù)的public方法 Method playBalls = clazz1.getMethod("playBalls"); //調(diào)用獲得的方法,需要指定是哪一個(gè)對(duì)象的 playBalls.invoke(person); //②帶參的public方法:第一個(gè)參數(shù)是方法名,后面的可變參數(shù)列表是參數(shù)類型的Class類型 Method sing = clazz1.getMethod("sing",String.class); //調(diào)用獲得的方法,調(diào)用時(shí)候傳遞參數(shù) sing.invoke(person,"HaHaHa..."); //③帶參的private方法:使用getDeclaredMethod方法 Method dance = clazz1.getDeclaredMethod("dance", String.class); //調(diào)用獲得的方法,需要先設(shè)置權(quán)限為可訪問 dance.setAccessible(true); dance.invoke(person,"HaHaHa..."); }
(2)獲得所有方法(不包括構(gòu)造方法)
@Test public void test10() throws Exception{ Class clazz1 = Class.forName("reflect.Person"); //獲得實(shí)例對(duì)象 Person person = (Person) clazz1.newInstance(); person.setName("Person"); Method[] methods = clazz1.getDeclaredMethods(); for (Method method: methods) { System.out.print("方法名" + method.getName() + "的參數(shù)是:"); //獲得方法參數(shù) Class[] params = method.getParameterTypes(); for (Class c : params) { System.out.print(c.getName() + " "); } System.out.println(); } }
3.5、獲得該類的所有接口
Class[] getInterfaces():確定此對(duì)象所表示的類或接口實(shí)現(xiàn)的接口,返回值:接口的字節(jié)碼文件對(duì)象的數(shù)組
@Test public void test11() throws Exception{ Class clazz1 = Class.forName("reflect.Person"); Class[] interfaces = clazz1.getInterfaces(); for (Class inter : interfaces) { System.out.println(inter); } }
3.6、獲取指定資源的輸入流
InputStreamgetResourceAsStream(String name),返回值:一個(gè) InputStream 對(duì)象;如果找不到帶有該名稱的資源,則返回null;參數(shù):所需資源的名稱,如果以"/"開始,則絕對(duì)資源名為"/"后面的一部分。
@Test public void test12() throws Exception { ClassLoader loader = this.getClass().getClassLoader(); System.out.println(loader);//sun.misc.Launcher$AppClassLoader@18b4aac2 ,應(yīng)用程序類加載器 System.out.println(loader.getParent());//sun.misc.Launcher$ExtClassLoader@31befd9f ,擴(kuò)展類加載器 System.out.println(loader.getParent().getParent());//null ,不能獲得啟動(dòng)類加載器 Class clazz = Person.class;//自定義的類 ClassLoader loader2 = clazz.getClassLoader(); System.out.println(loader2);//sun.misc.Launcher$AppClassLoader@18b4aac2 //下面是獲得InputStream的例子 ClassLoader inputStreamLoader = this.getClass().getClassLoader(); InputStream inputStream = inputStreamLoader.getResourceAsStream("person.properties"); Properties properties = new Properties(); properties.load(inputStream); System.out.println("id:" + properties.get("id")); System.out.println("name:" + properties.get("name")); }
其中properties文件內(nèi)容
id = person001
name = person-name1
四、反射的應(yīng)用之動(dòng)態(tài)代理
代理模式在Java中應(yīng)用十分廣泛,它說的是使用一個(gè)代理將對(duì)象包裝起來然后用該代理對(duì)象取代原始對(duì)象,任何原始對(duì)象的調(diào)用都需要通過代理對(duì)象,代理對(duì)象決定是否以及何時(shí)將方法調(diào)用轉(zhuǎn)到原始對(duì)象上。這種模式可以這樣簡單理解:你自己想要做某件事情(被代理類),但是覺得自己做非常麻煩或者不方便,所以就叫一個(gè)另一個(gè)人(代理類)來幫你做這個(gè)事情,而你自己只需要告訴要做啥事就好了。上面我們講到了反射,在下面我們會(huì)說一說java中的代理
4.1、靜態(tài)代理
靜態(tài)代理其實(shí)就是程序運(yùn)行之前,提前寫好被代理類的代理類,編譯之后直接運(yùn)行即可起到代理的效果,下面會(huì)用簡單的例子來說明。在例子中,首先我們有一個(gè)頂級(jí)接口(ProductFactory),這個(gè)接口需要代理類(ProxyTeaProduct)和被代理類(TeaProduct)都去實(shí)現(xiàn)它,在被代理類中我們重寫需要實(shí)現(xiàn)的方法(action),該方法會(huì)交由代理類去選擇是否執(zhí)行和在何處執(zhí)行;被代理類中主要是提供頂級(jí)接口的的一個(gè)引用但是引用實(shí)際指向的對(duì)象則是實(shí)現(xiàn)了該接口的代理類(使用多態(tài)的特點(diǎn),在代理類中提供構(gòu)造器傳遞實(shí)際的對(duì)象引用)。分析之后,我們通過下面這個(gè)圖理解一下這個(gè)過程。
package proxy; /** * 靜態(tài)代理 */ //產(chǎn)品接口 interface ProductFactory { void action(); } //一個(gè)具體產(chǎn)品的實(shí)現(xiàn)類,作為一個(gè)被代理類 class TeaProduct implements ProductFactory{ @Override public void action() { System.out.println("我是生產(chǎn)茶葉的......"); } } //TeaProduct的代理類 class ProxyTeaProduct implements ProductFactory { //我們需要ProductFactory的一個(gè)實(shí)現(xiàn)類,去代理這個(gè)實(shí)現(xiàn)類中的方法(多態(tài)) ProductFactory productFactory; //通過構(gòu)造器傳入實(shí)際被代理類的對(duì)象,這時(shí)候代理類調(diào)用action的時(shí)候就可以在其中執(zhí)行被代理代理類的方法了 public ProxyTeaProduct(ProductFactory productFactory) { this.productFactory = productFactory; } @Override public void action() { System.out.println("我是代理類,我開始代理執(zhí)行方法了......"); productFactory.action(); } } public class TestProduct { public static void main(String[] args) { //創(chuàng)建代理類的對(duì)象 ProxyTeaProduct proxyTeaProduct = new ProxyTeaProduct(new TeaProduct()); //執(zhí)行代理類代理的方法 proxyTeaProduct.action(); } }
那么程序測(cè)試的輸出結(jié)果也很顯然了,代理類執(zhí)行自己實(shí)現(xiàn)的方法,而在其中有調(diào)用了被代理類的方法
那么我們想一下,上面這種稱為靜態(tài)代理的方式有什么缺點(diǎn)呢?因?yàn)槊恳粋€(gè)代理類只能為一個(gè)借口服務(wù)(因?yàn)檫@個(gè)代理類需要實(shí)現(xiàn)這個(gè)接口,然后去代理接口實(shí)現(xiàn)類的方法),這樣一來程序中就會(huì)產(chǎn)生過多的代理類。比如說我們現(xiàn)在又來一個(gè)接口,那么是不是也需要提供去被代理類去實(shí)現(xiàn)它然后交給代理類去代理執(zhí)行呢,那這樣程序就不靈活了。那么如果有一種方式,就可以處理新添加接口的以及實(shí)現(xiàn)那不就更加靈活了嗎,在java中反射機(jī)制的存在為動(dòng)態(tài)代理創(chuàng)造了機(jī)會(huì)
4.2、JDK中的動(dòng)態(tài)代理
動(dòng)態(tài)代理是指通過代理類來調(diào)用它對(duì)象的方法,并且是在程序運(yùn)行使其根據(jù)需要?jiǎng)?chuàng)建目標(biāo)類型的代理對(duì)象。它只提供一個(gè)代理類,我們只需要在運(yùn)行時(shí)候動(dòng)態(tài)傳遞給需要他代理的對(duì)象就可以完成對(duì)不同接口的服務(wù)了??聪旅娴睦?。(JDK提供的代理正能針對(duì)接口做代理,也就是下面的newProxyInstance返回的必須要是一個(gè)接口)
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JDK中的動(dòng)態(tài)代理 */ //第一個(gè)接口 interface TargetOne { void action(); } //第一個(gè)接口的被代理類 class TargetOneImpl implements TargetOne{ @Override public void action() { System.out.println("我會(huì)實(shí)現(xiàn)父接口的方法...action"); } } //動(dòng)態(tài)代理類 class DynamicProxyHandler implements InvocationHandler { //接口的一個(gè)引用,多態(tài)的特性會(huì)使得在程序運(yùn)行的時(shí)候,它實(shí)際指向的是實(shí)現(xiàn)它的子類對(duì)象 private TargetOne targetOne; //我們使用Proxy類的靜態(tài)方法newProxyInstance方法,將代理對(duì)象偽裝成那個(gè)被代理的對(duì)象 /** * ①這個(gè)方法會(huì)將targetOne指向?qū)嶋H實(shí)現(xiàn)接口的子類對(duì)象 * ②根據(jù)被代理類的信息返回一個(gè)代理類對(duì)象 */ public Object setObj(TargetOne targetOne) { this.targetOne = targetOne; // public static Object newProxyInstance(ClassLoader loader, //被代理類的類加載器 // Class<?>[] interfaces, //被代理類實(shí)現(xiàn)的接口 // InvocationHandler h) //實(shí)現(xiàn)InvocationHandler的代理類對(duì)象 return Proxy.newProxyInstance(targetOne.getClass().getClassLoader(),targetOne.getClass().getInterfaces(),this); } //當(dāng)通過代理類的對(duì)象發(fā)起對(duì)接口被重寫的方法的調(diào)用的時(shí)候,都會(huì)轉(zhuǎn)換為對(duì)invoke方法的調(diào)用 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("這是我代理之前要準(zhǔn)備的事情......"); /** * 這里回想一下在靜態(tài)代理的時(shí)候,我們顯示指定代理類需要執(zhí)行的是被代理類的哪些方法; * 而在這里的動(dòng)態(tài)代理實(shí)現(xiàn)中,我們并不知道代理類會(huì)實(shí)現(xiàn)什么方法,他是根據(jù)運(yùn)行時(shí)通過反射來 * 知道自己要去指定被代理類的什么方法的 */ Object returnVal = method.invoke(this.targetOne,args);//這里的返回值,就相當(dāng)于真正調(diào)用的被代理類方法的返回值 System.out.println("這是我代理之后要處理的事情......"); return returnVal; } } public class TestProxy { public static void main(String[] args) { //創(chuàng)建被代理類的對(duì)象 TargetOneImpl targetOneImpl = new TargetOneImpl(); //創(chuàng)建實(shí)現(xiàn)了InvocationHandler的代理類對(duì)象,然后調(diào)用其中的setObj方法完成兩項(xiàng)操作 //①將被代理類對(duì)象傳入,運(yùn)行時(shí)候調(diào)用的是被代理類重寫的方法 //②返回一個(gè)類對(duì)象,通過代理類對(duì)象執(zhí)行接口中的方法 DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(); TargetOne targetOne = (TargetOne) dynamicProxyHandler.setObj(targetOneImpl); targetOne.action(); //調(diào)用該方法運(yùn)行時(shí)都會(huì)轉(zhuǎn)為對(duì)DynamicProxyHandler中的invoke方法的調(diào)用 } }
運(yùn)行結(jié)果如下。現(xiàn)在我們對(duì)比jdk提供的動(dòng)態(tài)代理和我們剛剛實(shí)現(xiàn)的靜態(tài)代理,剛剛說到靜態(tài)代理對(duì)于新添加的接口需要定義對(duì)應(yīng)的代理類去代理接口的實(shí)現(xiàn)類。而上面的測(cè)試程序所使用的動(dòng)態(tài)代理規(guī)避了這個(gè)問題,即我們不需要顯示的指定每個(gè)接口對(duì)應(yīng)的代理類,有新的接口添加沒有關(guān)系,只需要在使用的時(shí)候傳入接口對(duì)應(yīng)的實(shí)現(xiàn)類然后返回代理類對(duì)象(接口實(shí)現(xiàn)類型),然后調(diào)用被代理類的方法即可。
五、動(dòng)態(tài)代理與AOP簡單實(shí)現(xiàn)
5.1、AOP是什么
AOP(Aspect Orient Programming)我們一般稱之為面向切面編程,作為一種面向?qū)ο蟮难a(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志記錄等。AOP實(shí)現(xiàn)的關(guān)鍵在于AOP的代理(實(shí)際實(shí)現(xiàn)上有靜態(tài)代理和動(dòng)態(tài)代理),我們下面使用JDK的動(dòng)態(tài)代理的方式模擬實(shí)現(xiàn)下面的場(chǎng)景。
5.2、模擬實(shí)現(xiàn)AOP
我們先考慮下面圖中的情況和說明。然后我們使用動(dòng)態(tài)代理的思想模擬簡單實(shí)現(xiàn)一下這個(gè)場(chǎng)景
package aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //基于jdk的針對(duì)接口實(shí)現(xiàn)動(dòng)態(tài)代理,要求的接口 interface Target { void login(); void logout(); } //被代理類 class TargetImpl implements Target { @Override public void login() { System.out.println("log......"); } @Override public void logout() { System.out.println("logout......"); } } class Util { public void printLog() { System.out.println("我是記錄打印日志功能的方法......"); } public void getProperties() { System.out.println("我是獲取配置文件信息功能的方法......"); } } //實(shí)現(xiàn)了InvocationHandler的統(tǒng)一代理類 class DynamicProxyHandler implements InvocationHandler { private Object object; /** * 參數(shù)為obj,是應(yīng)對(duì)對(duì)不同的被代理類,都能綁定與該代理類的代理關(guān)系 * 這個(gè)方法會(huì)將targetOne指向?qū)嶋H實(shí)現(xiàn)接口的子類對(duì)象,即當(dāng)前代理類實(shí)際要去代理的那個(gè)類 */ public void setObj(Object obj) { this.object = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Util util = new Util(); util.getProperties(); Object object = method.invoke(this.object, args); //這個(gè)方法是個(gè)動(dòng)態(tài)的方法,可以是login,可以是logout,具體在測(cè)試調(diào)用中調(diào)用不同方法 util.printLog(); return object; } } //該類的主要作用就是動(dòng)態(tài)的創(chuàng)建一個(gè)代理類的對(duì)象,同時(shí)需要執(zhí)行被代理類 class MyDynamicProxyUtil { //參數(shù)obj表示動(dòng)態(tài)的傳遞進(jìn)來被代理類的對(duì)象 public static Object getProxyInstance(Object object) { //獲取代理類對(duì)象 DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(); dynamicProxyHandler.setObj(object); //設(shè)置好代理類與被代理類之間的關(guān)系后,返回一個(gè)代理類的對(duì)象 return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), dynamicProxyHandler); } } public class TestAop { public static void main(String[] args) { //獲得被代理類 Target target = new TargetImpl(); //通過代理類工具類,設(shè)置實(shí)際與代理類綁定的被代理類,并返回一個(gè)代理類對(duì)象執(zhí)行實(shí)際的方法 Target execute = (Target) MyDynamicProxyUtil.getProxyInstance(target); execute.login(); execute.logout(); } }
現(xiàn)在來分析一下上面的代碼,首先我們看一下下面的這個(gè)圖。在圖中動(dòng)態(tài)代理增加的通用日志方法、配置文件方法就是增加的方法,他在執(zhí)行用戶實(shí)際自己開發(fā)的方法之前、之后調(diào)用。對(duì)應(yīng)于上面的程序就是Target接口的實(shí)現(xiàn)類實(shí)現(xiàn)的login、logout方法被代理類動(dòng)態(tài)的調(diào)用,在他們執(zhí)行之前會(huì)調(diào)用日志模塊和配置文件模塊的功能。
以上就是詳解Java中的反射機(jī)制和動(dòng)態(tài)代理的詳細(xì)內(nèi)容,更多關(guān)于Java 反射機(jī)制 動(dòng)態(tài)代理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot Redis配置Fastjson進(jìn)行序列化和反序列化實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot Redis配置Fastjson進(jìn)行序列化和反序列化實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10教你如何測(cè)試Spring Data JPA的Repository
Spring Data JPA 提供了一些便捷的方式來測(cè)試這種持久層的代碼,常見的兩種測(cè)試類型是集成測(cè)試和單元測(cè)試,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫
這篇文章主要介紹了學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫,這個(gè)日歷小程序僅用簡單的算法實(shí)現(xiàn)沒有用到date類等,但是帶有圖形界面,需要的朋友可以參考下2016-02-02SpringBoot多環(huán)境配置及配置文件分類實(shí)例詳解
這篇文章主要介紹了SpringBoot多環(huán)境配置及配置文件分類,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10Java中的NoSuchMethodException異常原因以及解決方案詳解
這篇文章主要介紹了Java中的NoSuchMethodException異常原因以及解決方案詳解,NoSuchMethodException是Java反射機(jī)制中的異常,在嘗試通過反射獲取方法時(shí),找不到指定的方法,通常發(fā)生在調(diào)用?Class?對(duì)象的方法時(shí),當(dāng)方法名或方法參數(shù)不匹配時(shí)拋出該異常,需要的朋友可以參考下2024-02-02IntelliJ IDEA2020.2.2創(chuàng)建Servlet方法及404問題
這篇文章主要介紹了IntelliJ IDEA2020.2.2創(chuàng)建Servlet方法及404問題,這里小編使用的2020.2.2企業(yè)破解版本,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09IntelliJ?IDEA?2021.3永久最新激活至2099年(親測(cè)有效)
最新版idea2021.3已出來,很多網(wǎng)友迫不及待的要升級(jí)idea2021最新版,今天小編抽空給大家整理了一篇教程關(guān)于idea2021.3最新激活教程,本文以idea2021.2.3為例通過圖文并茂的形式給大家分享激活詳細(xì)過程,感興趣的朋友參考下吧2020-12-12