1)Java反射機制是在運行狀態中,對於任意一個類,都可以知道這個類中的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。簡單一句話:反射技術能夠對類進行解剖。
2)一個已經可使用的應用程序,由於程序已經作好能夠運行使用,不能再進行代碼的加入了。而當後期咱們新的功能加入程序時,該怎麼作呢?經常使用的做法,會提供一個配置文件,來供之後實現此程序的類來擴展功能。對外提供配置文件,讓後期出現的子類直接將類名字配置到配置文件中便可。該應用程序直接讀取配置文件中的內容。並查找和給定名稱相同的類文件。進行以下操做:
java
a) 加載這個類。 c++
b)建立該類的對象。 面試
c)調用該類中的內容。 spring
Note:應用程序使用的類不肯定時,能夠經過提供配置文件,讓使用者將具體的子類存儲到配置文件中。而後該程序經過反射技術,對指定的類進行內容的獲取。所以,反射技術大大提升了程序的擴展性。 apache
二、定義:反射就是把Java類中的各類成分映射成相應的java類。一個 Class 表明一個字節碼,一個 Method 表明一個字節碼中方法,一個 Constructor表明一個構造方法。 編程
例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。例如:汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來得到其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等等。
反射使得一個類中的每一個成員均可以用相應的反射API類的一個實例對象來表示,經過調用Class類的方法能夠獲得這些實例對象後,獲得這些實例對象後所作的事正是學習和應用反射的要點。
三、反射的基石——Class類
設計模式
1)全部的類文件都有共同屬性,因此能夠向上抽取,把這些共性內容封裝成一個類,這個類就叫Class(描述字節碼文件的對象)。Class類中就包含屬性有field(字段)、method(方法)、construction(構造函數)。而field中有修飾符、類型、變量名等複雜的描述內容,所以也能夠將字段封裝稱爲一個對象。用來獲取類中field的內容,這個對象的描述叫Field。同理方法和構造函數也被封裝成對象Method、Constructor。要想對一個類進行內容的獲取,必需要先獲取該字節碼文件的對象,該對象是Class類型。Class類描述的信息:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表,方法名稱的列表等。每個字節碼就是class的實例對象。 數組
2)Class和class的區別class:Java中的類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性,至於這個屬性的值是什麼,則由此類的實例對象肯定,不一樣的實例對象有不一樣的屬性值。 瀏覽器
Class:指的是Java程序中的各個Java類是屬於同一類事物,都是Java程序的類,這些類稱爲Class,即字節碼。例如人對應的是Person類,Java類對應的就是Class。 安全
3)獲取Class對象的三種方式加載XX.class文件進內存時就被封裝成了對象,該對象就是字節碼文件對象。下面就是獲取Class對象得三種方式:
方式一:
經過對象的getClass方法進行獲取。
如:Class clazz=new Person().getClass();//Person是一個類名
Note:每次都須要具體的類和該類的對象,以及調用getClass方法。
方式二:
任何數據類型都具有着一個靜態的屬性class,這個屬性直接獲取到該類型的對應Class對象。
如:Class clazz=Person.class;//Person是一個類名
Note:比第一種較爲簡單,不用建立對象,不用調用getClass方法,可是仍是要使用具體的類,和該類中的一個靜態屬性class完成。
方式三:
較爲簡單,只要知道類的名稱便可。不須要使用該類,也不須要去調用具體的屬性和行爲,就能夠獲取到Class對象了。
如:Class clazz=Class.forName("包名.Person");//Person是一個類名
Note:這種方式僅知道類名就能夠獲取到該類字節碼對象的方式,更有利於擴展。
拓展:
1)九個預約義的Class:
a)包括八種基本類型(byte、short、int、long、float、double、char、boolean)的字節碼對象和一種返回值爲void類型的void.class。
b)Integer.TYPE是Integer類的一個常量,它表明此包裝類型包裝的基本類型的字節碼,和int.class是相等。基本數據類型的字節碼均可以用與之對應的包裝類中的TYPE常量表示。
2)只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,能夠用Class.isArray()方法判斷是否爲數組類型的。
4)Class類中的方法
static Class forName(String className) // 返回與給定字符串名的類或接口的相關聯的Class對象。
Class getClass() //返回的是Object運行時的類,即返回Class對象即字節碼對象
Constructor getConstructor() //返回Constructor對象,它反映此Class對象所表示的類的指定公共構造方法。
Field getField(String name) //返回一個Field對象,它表示此Class對象所表明的類或接口的指定公共成員字段。
Field[] getFields() //返回包含某些Field對象的數組,表示所表明類中的成員字段。
Method getMethod(String name,Class… parameterTypes) //返回一個Method對象,它表示的是此Class對象所表明的類的指定公共成員方法。
Method[] getMehtods() //返回一個包含某些Method對象的數組,是所表明的的類中的公共成員方法。
String getName() //以String形式返回此Class對象所表示的實體名稱。
String getSuperclass() //返回此Class所表示的類的超類的名稱
boolean isArray() //斷定此Class對象是否表示一個數組
boolean isPrimitive() //判斷指定的Class對象是不是一個基本類型。
T newInstance() //建立此Class對象所表示的類的一個新實例。
5)經過Class對象獲取類實例
經過查看API咱們知道,Class類是沒有構造方法的,所以只能經過方法獲取類實例對象。
以前咱們用的已知類,建立對象的作法:
1)查找並加載XX.class文件進內存,並將該文件封裝成Class對象。
2)再依據Class對象建立該類具體的實例。
3)調用構造函數對對象進行初始化。
如:Person p=new Person();
如今用Class對象來獲取類實例對象的作法:
1)查找並加載指定名字的字節碼文件進內存,並被封裝成Class對象。
2)經過Class對象的newInstance方法建立該Class對應的類實例。
3)調用newInstance()方法會去使用該類的空參數構造函數進行初始化。
如:
String className="包名.Person";
Class clazz=Class.forName(className);
Object obj=clazz.newInstance();
示例://Person類 class Person { private String name; public int age; public Person(){ System.out.println("Person is run"); } public Person(String name,int age){ this.age=age; this.name=name; } public String toString(){ return name+":"+age; } } //示例 public class CreateClassDemo { public static void main(String[] args) throws Exception { createPersonClass(); } //經過Class對象建立類實例方法 public static void createPersonClass() throws Exception{ //獲取Person類的Class對象 String className="cn.itheima.Person"; Class clazz=Class.forName(className); //經過newInstance方法獲取類的無參構造函數實例 Person p=(Person)clazz.newInstance(); } }
四、Constructor類
1)概述:若是指定的類中沒有空參數的構造函數,或者要建立的類對象須要經過指定的構造函數進行初始化。這時就不能使用Class類中的newInstance方法了。既然要經過指定的構造函數進行對象的初始化,就必須先獲取這個構造函數——Constructor,而Constructor類表明某個類的構造方法。
2)獲取構造方法:
1)獲得這個類的全部構造方法:如獲得上面示例中Person類的全部構造方法
Constructor[] cons = Class.forName(「cn.itheima.Person」).getConstructors();
2)獲取某一個構造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
3)建立實例對象:
1)一般方式:Person p = new Person(「lisi」,30);
2)反射方式:Person p= (Person)con.newInstance(「lisi」,30);
Note:
1)建立實例時newInstance方法中的參數列表必須與獲取Constructor的方法getConstructor方法中的參數列表一致。
2)newInstance():構造出一個實例對象,每調用一次就構造一個對象。
3)利用Constructor類來建立類實例的好處是能夠指定構造函數,而Class類只能利用無參構造函數建立類實例對象。
示例:public class ConstructorDemo{ private static void main(String[] args) throws Exception { // 獲取構造器時————要根據參數類型和個數來肯定 Constructor<String> conString = String.class .getConstructor(StringBuffer.class); // 根據構造器獲取相應的對象時————也要肯定參數類型和個數(必須和獲取時的相同) String str = conString.newInstance(new StringBuffer("abc")); System.out.println(str.charAt(2)); } }
五、Field類
1)概述:Field類表明某個類中一個成員變量。
2)方法:
Field getField(String s); //只能獲取公有和父類中公有
Field getDeclaredField(String s); //獲取該類中任意成員變量,包括私有
setAccessible(ture); //若是是私有字段,要先將該私有字段進行取消權限檢查的能力,也稱暴力訪問。
set(Object obj, Object value); //將指定對象變量上此Field對象表示的字段設置爲指定的新值。
Object get(Object obj); //返回指定對象上Field表示的字段的值。
示例:
package reflect; /* * 反射點類ReflectPoint */ public class ReflectPoint { private int x; public int y; public String str1 = "ball"; public String str2 = "basketball"; public String str3 = "itheima"; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public ReflectPoint() { super(); } public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ReflectPoint other = (ReflectPoint) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public String toString() { return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]"; } } public class FileDemo{ private static void main(String[] args) throws Exception { // 對於源文件中的類成員變量public 只要用getField()方法就可得到Field對象 ReflectPoint rp = new ReflectPoint(2,5); Field fieldY = rp.getClass().getField("y"); System.out.println(fieldY.get(rp)); // 若是成員變量爲非公用,則必需要用getDeclearedField()得到Field對象 Field fieldX = rp.getClass().getDeclaredField("x"); // 暴力反射 fieldX.setAccessible(true); System.out.println(fieldX.get(rp)); } }
六、Method類
1)概述:Method類表明某個類中的一個成員方法。調用某個對象身上的方法,要先獲得方法,再針對某個對象調用。
2)方法:
Method[] getMethods();//只獲取公共和父類中的方法。
Method[] getDeclaredMethods();//獲取本類中包含私有。
Method getMethod("方法名",參數.class(若是是空參能夠寫null));
Object invoke(Object obj ,參數);//調用方法,若是方法是靜態,invoke方法中的對象參數能夠爲null。
如:獲取某個類中的某個方法(如String str =」abc」)
1)一般方式:str.charAt(1);
2)反射方式:
Method charAtMethod =Class.forName(「java.lang.String」).getMethod(「charAt」,int.class);
charAtMethod.invoke(str,1);
public class MethodDemo{ public static void main(String[] args){ getAllMethod(); } public static void getAllMethod() throws Exception { // 若是想要獲取方法,必須先要有對象。 Class clazz = Class.forName("reflect.ReflectPoint"); ReflectPoint p = (ReflectPoint) clazz.newInstance(); // 獲取因此方法 Method[] mes = clazz.getMethods();// 只獲取公共的和父類中的。 // mes=clazz.getDeclaredMethods();//獲取本類中包含私有。 for (Method me : mes) { System.out.println(me); } // 獲取單個方法 Method me = clazz.getMethod("toString", null); Object returnVaule = me.invoke(p, null); System.out.println(returnVaule); } }
七、數組的反射
1)具備相同維數和元素類型的數組屬於同一個類型,即具備相同的Class實例對象。數組字節碼的名字:有[和數組對應類型的縮寫,如int[]數組的名稱爲:[I
2)Object[]與String[]沒有父子關係,Object與String有父子關係,因此new Object[]{「aaa」,」bb」}不能強制轉換成new String[]{「aaa」,」bb」}; Object x =「abc」能強制轉換成String x =「abc」。
3)如何獲得某個數組中的某個元素的類型,
例:
int a = new int[3];Object[] obj=new Object[]{」ABC」,1};//沒法獲得某個數組的具體類型,只能獲得其中某個元素的類型,
例:
Obj[0].getClass().getName()獲得的是java.lang.String。
4)Array工具類用於完成對數組的反射操做。
Array.getLength(Object obj);//獲取數組的長度
Array.get(Object obj,int x);//獲取數組中的元素
5)基本類型的一維數組能夠被看成Object類型使用,不能看成Object[]類型使用;非基本類型的一維數組,既能夠當作Object類型使用,又能夠當作Object[]類型使用。
示例:import java.lang.reflect.Array; import java.util.Arrays; public class ArrayReflect { public static void main(String[] args) { int [] a1 = new int[]{1,2,3}; int [] a2 = new int[4]; int[][] a3 = new int[2][3]; String [] a4 = new String[]{"a","b","c"}; System.out.println(a1.getClass().equals(a2.getClass()));//true System.out.println(a1.getClass().equals(a3.getClass()));//false System.out.println(a1.getClass().equals(a4.getClass()));//false System.out.println(a1.getClass().getName());//[I System.out.println(a4.getClass().getName());//[Ljava.lang.String; System.out.println(a1.getClass().getSuperclass());//class java.lang.Object System.out.println(a4.getClass().getSuperclass());//class java.lang.Object Object obj1=a1; Object obj2=a3; Object obj3=a4; // Object[] obj11=a1;//這樣是不行的,由於a1中的元素是int類型,基本數據類型不是Object Object[] obj13=a3; Object[] obj14=a4;//這樣能夠,由於String數組中的元素屬於Object System.out.println(a1);//[I@4caaf64e System.out.println(a4);//[Ljava.lang.String;@6c10a234 System.out.println(Arrays.asList(a1));//[I@4caaf64e System.out.println(Arrays.asList(a4));//[a, b, c] //Array工具類用於完成對數組的反射操做。如打印任意數值 printObject(a1); printObject(a4); printObject("abc"); } //打印任意數值 private static void printObject(Object obj) { Class clazz=obj.getClass(); //若是傳入的是數組,則遍歷 if(clazz.isArray()){ int len =Array.getLength(obj);//Array工具類獲取數組長度方法 for(int x=0;x<len;x++){ System.out.println(Array.get(obj, x));//Array工具獲取數組元素 } } else System.out.println(obj); } }八、反射的應用——>實現框架功能
1)框架:經過反射調用Java類的一種方式。框架和工具類的區別:工具類被用戶類調用,而框架是調用用戶提供的類。
2)框架機器要解決的核心問題:在寫框架(造房子的過程)的時候,調用的類(安裝的門窗等)還未出現,那麼框架沒法知道要被調用的類名,因此在程序中沒法直接new其某個類的實例對象,而要用反射來作。
3)簡單框架程序的步驟:
a)建立一個配置文件如:config.properties,而後寫入配置信息。如鍵值對:className=java.util.ArrayList,等號右邊的配置鍵,右邊是值。
b)代碼實現,加載此文件:
將文件讀取到讀取流中,要寫出配置文件的絕對路徑。
如:InputStream is=new FileInputStream(「配置文件」);
用Properties類的load()方法將流中的數據存入集合。
關閉流:關閉的是讀取流,由於流中的數據已經加載進內存。
c)經過getProperty()方法獲取className,即配置的值,也就是某個類名。
d)用反射的方式,建立對象newInstance()。
e)執行程序主體功能
示例:package reflect; import java.io.InputStream; import java.util.Collection; import java.util.Properties; public class ReflectDemo2 { public static void main(String[] args) throws Exception { // 用完整的路徑,但不能是硬編碼而是運算得出的 // InputStream in = new FileInputStream("config.properties"); // InputStream in = // ReflectDemo1.class.getClassLoader().getResourceAsStream("reflect/config.properties"); InputStream in = ReflectDemo1.class .getResourceAsStream("config.properties"); Properties prop = new Properties(); prop.load(in); in.close(); String className = prop.getProperty("className"); Collection<ReflectPoint> al = (Collection<ReflectPoint>) Class.forName( className).newInstance(); // Collection al = new HashSet(); ReflectPoint rp1 = new ReflectPoint(3, 3); ReflectPoint rp2 = new ReflectPoint(6, 6); ReflectPoint rp3 = new ReflectPoint(3, 3); al.add(rp1); al.add(rp1); al.add(rp2); al.add(rp3); // 如下會出現內存泄漏,由於改變了rp1的y值,其hashCode也就隨之改變及內存地址改變,再用remove移除時就沒有成功 // rp1.y = 7; // al.remove(rp1); System.out.println(al.size()); } }2、內省
1)JavaBean是一種特殊的Java類,主要用於傳遞數據信息,這種Java類中的方法主要用於訪問私有的字段,且方法符合某種特殊的命名規則。
2)它是一種特殊的Java類,其中的方法符合特殊的規則。只要一個類中含有get或is和set打頭的方法,就能夠將其當作JavaBean使用。
3)字段和屬性:JavaBean的字段就是咱們定義的一些成員變量,如private String name等。JavaBean的屬性是根據其中的setter和getter方法來肯定的,而不是依據其中的變量,如方法名爲setId,則中文意思是設置Id,getId也是如此;去掉set或者get前綴,剩餘部分就是屬性名稱。若是剩餘部分的第二個字母小寫,則把剩餘部分改成小寫。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
總之,一個類被看成javaBean使用時,JavaBean的屬性是根據方法名推斷出來的,它根本看不到java類內部的成員變量。
三、做用:
若是要在兩個模板之間傳遞多個信息,可將這些信息封裝到一個JavaBean中,這種JavaBean的實例對象一般稱之爲值對象(Value Object,簡稱VO),這些信息在類中用私有字段來儲存,若是讀取或設置這些字段的值,則須要經過一些相應的方法來訪問。
四、JavaBean的好處:一個符合JavaBean特色的類當作普通類同樣可使用,可是把它當作JavaBean類用會帶來一些額外的好處:
1)在Java EE開發中,常常要使用到JavaBean。不少環境就要求按JavaBean方式進行操做,別人都這麼用和要求這麼作,那你就沒什麼挑選的餘地。
2)JDK中提供了對JavaBean進行操做的API,這套API稱爲內省,若要本身經過getX的方式來訪問私有x,可用內省這套API,操做JavaBean要比使用普通的方式更方便。
五、對JavaBean的複雜內省操做
一、在IntroSpector類中有getBeanInfo(Class cls)的方法,經過此方法獲取BeanInfo實例。參數是相應對象的字節碼,即Class對象。
二、BeanInfo類中有getPropertyDescriptors()的方法,可獲取全部的JavaBean類中的屬性信息,返回一個PropertyDescriptor[]。
三、在經過遍歷的形式,獲取與想要的那個屬性信息。
示例:
import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //接上述反射點類ReflectPoint繼續 import reflect.ReflectPoint; /* * 內省:操做的是JavaBean(特殊的類) * JavaBean類是含有get()、set()方法,其中的公有方法用於訪問私有成員變量,而且命名符合必定規則 * 例如:getAge——>age getCPU——>CPU * */ //接上述ReflectPoint類繼續 public class IntroSpectorDemo { public static void main(String[] args) throws Exception { ReflectPoint rp = new ReflectPoint(3, 4); String propertyName = "x"; // "x"-->"X"-->"getX"-->MethodGetX--> // 用內省的方式 // 獲取並getX方法 Object retval = getProperty1(rp, propertyName); System.out.println(retval); Object value = 5; // 獲取並調用setX方法 setProperty(rp, propertyName, value); System.out.println(rp.getX()); } // 獲取並調用setX方法 private static void setProperty(Object rp, String propertyName, Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd = new PropertyDescriptor(propertyName, rp.getClass());// 建立對象關聯 Method methodSetX = pd.getWriteMethod();// 獲取JavaBean類中的setX方法 methodSetX.invoke(rp, value);// 調用setX方法 } // 獲取並調用getX方法第一中方式 private static Object getProperty1(Object rp, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd = new PropertyDescriptor(propertyName, rp.getClass());// 建立對象關聯 Method methodGetX = pd.getReadMethod();// 獲取JavaBean類中的getX方法 Object retval = methodGetX.invoke(rp);// 調用getX方法 return retval; } // 獲取調用getX方法的第二種方式 public static Object getProperty(Object rp, String propertyName) throws Exception { BeanInfo beanInfo = Introspector.getBeanInfo(rp.getClass()); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); Object retVal = null; for (PropertyDescriptor pd : pds) { if (pd.getName().equals(propertyName)) { Method methodGetX = pd.getReadMethod(); retVal = methodGetX.invoke(rp); break; } } return retVal; } }
六、BeanUtils工具包
1)BeanUtils等工具包都是由阿帕奇提供的,爲了便於開發。
2)BeanUtils能夠將8種基本數據類型進行自動的轉換,所以對於非基本數據類型,就須要註冊轉換器Converter,這就須要ConverUtils包。
3)好處:
1)提供的set或get方法中,傳入的是字符串,返回的仍是字符串,由於在瀏覽器中,用戶輸入到文本框的都是以字符串的形式發送至服務器上的,因此操做的都是字符串。也就是說這個工具包的內部有自動將整數轉換爲字符串的操做。
2)支持屬性的級聯操做,即支持屬性鏈。如能夠設置:人的腦殼上的眼睛的眼珠的顏色。這種級聯屬性的屬性連若是本身用反射,那就很困難了,經過這個工具包就能夠輕鬆調用。
4)能夠和Map集合進行相互轉換:可將屬性信息經過鍵值對的形式做爲Map集合存儲(經過static java.util.Mapdescribe(java.lang.Object bean)的方法)。也能夠將Map集合轉換爲JavaBean中的屬性信息(經過static void populate(java.lang.Objectbean, java.util.Map properties)的方法)。
Note:要正常使用BeanUtils工具,還要將Apache公司的logging(日誌)的jar包也添加進Build Path。在工程中導入工具jar包。兩種方式:
a)右鍵項目--選擇Properties---Java Build Path--選擇Liberiers標籤。AddExternal Jars--選擇要導入的jar包便可。
這樣作有個問題就是若是jar路徑發生變化,項目就不能使用到這個jar包。
b)在項目中創建一個lib目錄,專門用於存放項目所使用到的jar工具包。將要使用到jar包複製粘貼進來,並在jar上點右鍵--選擇Builder Path---Add to BiuldPath,便可。
這時jar包中的對象,就可使用了。這樣作項目移動,jar隨項目移動。
5)BeanUtils工具包中還有一個工具類PropertyUtils,用法跟BeanUtils同樣。區別:
a)BeanUtils會對JavaBean的屬性的類型進行轉換,如屬性自己是integer,會轉換爲String。
b)PropertyUtils以屬性自己的類型進行操做。
示例:package cn.itheima.demo; //接前面的reflectPoint類繼續 import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; public class IntroSpectorDemo { public static void main(String[] args) throws Exception { ReflectPoint rp=new ReflectPoint(2,3); String propertyName="x"; //"x"-->"X"-->"getX"-->MethodGetX--> //用BeanUtils工具包的方法 System.out.println(BeanUtils.getProperty(hct, propertyName));//get BeanUtils.setProperty(rp, propertyName, "9");//set System.out.println(rp.getX()); //對於JavaBean中的屬性是對象的操做 BeanUtils.setProperty(rp, "birthday.time", "10");//set System.out.println(BeanUtils.getProperty(rp, "birthday.time"));//get } }3、類加載器
二、默認類加載器:
1)Java虛擬機中可安裝多個類加載器,系統默認的有三個主要的,每一個類負責加載特定位置的類:BootStrap、ExtClassLoader、AppClassLoader
2)類加載器自己也是Java類,由於它是Java類的加載器,自己也須要被類加載器加載,顯然必須有第一個類加載器而不是java類的,這正是BootStrap。它是嵌套在Java虛擬機內核中的,已啓動即出如今虛擬機中,是用c++寫的一段二進制代碼。因此不能經過java程序獲取其名字,得到的只能是null。
三、Java虛擬機中的全部類加載器採用父子關係的樹形結構進行組織,在實例化每一個類裝載器對象時,須要爲其指定一個父級類裝載器對象或者默認採用系統類裝載器爲其父級類加載。public class ClassLoaderDemo { public static void main(String[] args) throws Exception { System.out.println(ClassLoaderDemo.class.getClassLoader().getClass() .getName());//獲取當前類的類加載器---AppClassLoader System.out.println(System.class.getClassLoader());//獲取String類的類加載器,由於BootStrap不是Java語言編寫的,因此此處返回null } }五、委託機制
1)每一個ClassLoader自己只能分別加載特定位置和目錄中的類,但它們能夠委託其餘的類加載器去加載類,這就是類加載器的委託模式。
2)加載類的順序:
a)首先,當前線程的類加載器去加載線程中的第一個類。
b)若A引用類B(繼承或者使用了B),Java虛擬機將使用加載類的類加載器來加載類B。
c)還可直接調用ClassLoader的LoaderClass()方法,來指定某個類加載器去加載某個類。
2)每一個類加載器加載類時,又先委託給上級類加載器。類裝載器一級級委託到BootStrap類加載器,當BootStrap沒法加載當前所要加載的類時,而後才一級級回退到子孫類加載器去進行加載。當回退到最初的發起者類裝載器時,若是它本身也不能完成類的裝載,那就會拋出ClassNotFoundException異常。這時就不會再委託發起者加載器的子類去加載了,若是它還有子類的話。 簡單說,就是先由發起者將類一級級委託爲BootStrap,從父級開始找,找到了直接返回,沒找到再助劑讓其子級找,直到發起者,再沒找到就報異常。
3)委託機制的優勢:能夠集中管理,不會產生多字節碼重複的現象。
補充:(面試題)可不能夠本身寫個類爲:java.lang.System呢?
第一:一般是不能夠的,因爲類加載器的委託機制,會先將System這個類一級級委託給最頂級的BootStrap,因爲BootStrap在其指定的目錄中加載的是rt.jar中的類,且其中有System這個類,那麼就會直接加載本身目錄中的,也就是Java已經定義好的System這個類,而不會加載自定義的這個System。
第二:可是仍是有辦法加載這個自定義的System類的,此時就不能交給上級加載了,須要用自定義的類加載器加載,這就須要有特殊的寫法才能去加載這個自定義的System類的。
示例:
package reflect; public class ClassLoaderDemo { public static void main(String[] args) { ClassLoader loader=ClassLoaderDemo.class.getClassLoader(); while (loader!=null) {//循環獲取本類的類加載器和上級類加載器 System.out.println(loader.getClass().getName()); loader=loader.getParent();//將此loader的上級賦給loader } System.out.println(loader); } }
六、自定義類加載器
1)概述:自定義的類加載器必須繼承抽象類ClassLoader,要覆寫其中的findClass(String name)方法,而不用覆寫loadClass()方法。
2)覆寫findClass(Stringname)方法的緣由:
a)在loadClass()內部是會先委託給父級,當父級找不到後返回,再調用findClass(String name)方法,也就是你自定義的類加載器去找。因此只須要覆寫findClass方法,就能實現用自定義的類加載器加載類的目的。由於,通常自定義類加載器,會把須要加載的類放在本身指定的目錄中,而java中已有的類加載器是不知道你這個目錄的,因此會找不到。這樣纔會調用你複寫的findClass()方法,用你自定義的類加載器去指定的目錄加載類。
b)這是一種模板方法設計模式。這樣就保留了loadClass()方法中的流程(這個流程就是先找父級,找不到再調用自定義的類加載器),而咱們只需複寫findClass方法,實現局部細節就好了。ClassLoader提供了一個protected Class<?>defineClass(String name, byte[] b, int off, int len)方法,只須要將類對應的class文件傳入,就能夠將其變爲字節碼。
3)編程步驟:
a)編寫一個對文件內容進行簡單加密的程序
b)編寫好了一個本身的類加載器,可實現對加密過來的類進行加載和解密。
c)編寫一個程序,調用類加載器加載類,在源程序中不能用該類名定義引用變量,由於編譯器沒法識別這個類,程序中除了可以使用ClassLoader的loadClass方法外,還可使用設置線程的上下文類加載器或系統類加載器,而後再使用Class.forName。
4)操做步驟:
a)對不帶包名的class文件進行加密,加密結果存放到另一個目錄,例如: java MyClassLoader MyTest.class F:\itcast
b)運行加載類的程序,結果可以被正常加載,但打印出來的類裝載器名稱爲AppClassLoader:java MyClassLoader MyTest F:\itcast
c)用加密後的類文件替換CLASSPATH環境下的類文件,再執行上一步操做就出問題了,錯誤說明是AppClassLoader類裝載器裝載失敗。
d)刪除CLASSPATH環境下的類文件,再執行上一步操做就沒問題了。
示例:package classloader; import java.util.Date; //定義一個測試類,繼承Date,便於使用時加載 public class ClassLoaderAttachment extends Date{ //複寫toString方法 public String toString(){ return "Hello HeiMa!"; } } import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader extends ClassLoader{ public static void main(String[] args) throws Exception { String srcPath=args[0];//文件源 String destDir=args[1];//文件目的 InputStream ips=new FileInputStream(srcPath); String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1); String destFilePath=destDir+"\\"+destFileName; OutputStream ops=new FileOutputStream(destFilePath); cypher(ips,ops);//加密class字節碼 ips.close(); ops.close(); } //加密、解密方法 private static void cypher(InputStream ips,OutputStream ops) throws Exception{ int b=-1; while((b=ips.read())!=-1){ ops.write(b^0xff); } } @Override //覆蓋ClassLoader的findClass方法 protected Class<?> findClass(String name) throws ClassNotFoundException { name=name.substring(name.lastIndexOf(".")+1); String classFileName=classDir+"\\"+name+".class";//獲取class文件名 InputStream ips=null; try { ips=new FileInputStream(classFileName); ByteArrayOutputStream bos=new ByteArrayOutputStream();//定義字節數組流 cypher(ips,bos);//解密 ips.close(); byte[] buf=bos.toByteArray();//取出字節數組流中的數據 return defineClass(null, buf,0,buf.length);//加載進內存 } catch (Exception e) { e.printStackTrace(); } return null; } private String classDir; public MyClassLoader(){} //帶參數的構造函數 public MyClassLoader(String classDir){ this.classDir=classDir; } } import java.util.Date; public class ClassLoaderDemo { public static void main(String[] args) throws Exception { //將用自定義的類加載器加載.class文件 Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment"); Date d1 = (Date)clazz.newInstance();//獲取Class類的實例對象,由於使用ClassLoaderAttachment,編譯器會檢測出錯誤,因此用父類Date System.out.println(d1); } }Note:類加載器加載配置文件的方式:
一、概述:生活中的代理,就是常說的代理商,從廠商將商品賣給消費者,消費者不用很麻煩的跑到廠商那裏去購買。而程序中的代理,要爲已經存在的多個具備相同接口的目標類的各個方法增長一些系統功能,如異常處理、日誌、計算方法的運行時間、事物管理等等。
二、做用:代理爲已存在的多個具備相同接口的目標類的各個方法增長一些系統功能。
(例如: 編寫 一個與目標類具備相同接口的代理類, 代理類的每一個方法調用目標類的相同方法,並在 調用方法時加上系統功能的代碼。 )
三、代理架構圖
示例:
// 接口 interface A { void sayHello(); } // 目標類 class X implements A { @Override public void sayHello() { System.out.println("sayHello"); } } // 代理類 class Y implements A { @Override public void sayHello() { long startTime = System.currentTimeMillis();//獲取系統時間--程序開始點 System.out.println("sayHello"); long endTime = System.currentTimeMillis();//程序結束點 } }
目標類和代理類一般實現同一個或多個接口,通常用該接口來引用其子類(代理類),如:
Collection coll = new ArrayList();
四、代理類的優勢:
若是採用工廠模式和配置文件的方式進行管理,則不須要修改客戶端程序,在配置文件中配置是使用目標類仍是代理類。這樣之後很容易切換,若是想要日誌功能時,就配置代理類,不然配置目標類,這樣,增長系統功能很容易,之後運行一段時間後,又想換掉系統功能也很容易。
五、AOP
1)概述:AOP(AspectOriented Program)即面向方面的編程。
2)示意圖:(系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面)
安全 事務 日誌
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService -------|----------|------------|-------------
Note:安全、事務、日誌等功能要貫穿於好多個模塊中,因此他們就是交叉業務。
3)用具體的程序代碼描述交叉業務:
1)交叉業務的代碼實現
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
2)交叉業務的編程問題即爲面向方面的編程(Aspect orientedprogram ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。能夠採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是同樣的,以下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
Note:所以使用代理技術正好能夠解決這種問題,代理是實現AOP功能的核心和關鍵技術,只要是用到面向方面的編程,就涉及到代理,相似於模板方法。
六、動態代理1)由來:要爲系統中的各類接口的類增長代理功能,那將須要太多的代理類,這時就不能所有采用靜態代理的方式,由於要寫成百上千個代理類是很是麻煩的,這時就需用動態代理技術。
2)定義:JVM能夠在運行期,動態生成類的字節碼,這種動態生成的類(不是代理,只是拿來用做代理類)每每被用做代理類,即動態代理類。
Note:JVM生成的動態類必須實現一或多個接口,因此JVM生成的動態代理類只能用做具備相同接口的目標類代理。
3)CGLIB庫能夠動態生成一個類的子類,一個類的子類也能夠用做該類的代理。因此,若是要爲一個沒有實現接口的類生成動態代理類,那麼可使用CGLIB庫。
4)代理類各個方法一般除了調用目標相應方法和對外返回目標返回的結果外,還能夠在代理方法中的以下位置上加上系統功能代碼:
a)在調用目標方法以前
b)在調用目標方法以後
c)在調用目標方法先後
d)在處理目標方法異常的catch塊中。
5)分析JVM動態生成的類
以建立實現了Collection接口的代理類爲例:
a)用Proxy.getProxyClass方法建立實現了Collection接口的動態類和查看其名稱,分析getProxyClass方法的各個參數。
1)編碼列出動態類中的全部構造方法和參數簽名
2)編碼列出動態類中的全部方法和參數簽名
b)建立動態類的實例對象
1)用反射得到構造方法
2)編寫一個最簡單的InvocationHandler類(代理類構造方法接收的參數)
3)調用構造方法建立動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去
4)打印建立的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其餘有返回值的方法報告了異常。
5)將建立動態類的實例對象的代理改爲匿名內部類的形式編寫,鍛鍊匿名內部類的使用。
c)也能夠直接用Proxy.newInstance方法直接一步就建立出代理對象。
d)讓JVM建立動態類及其實例對象,須要提供的信息:
1)生成類中的哪些方法,經過讓其實現哪些接口的方式進行告知。
2)產生的類字節碼必須有一個關聯的類加載器對象
3)生成的類中的方法的代碼是怎麼樣的,也得由咱們本身提供。把咱們的代碼寫在一個約定好的子接口對象的方法中,把對象傳給它,它調用咱們的方法,即至關於插入了咱們本身的代碼。提供執行代碼的對象就是InvocationHandler對象,它是在建立動態類的實例對象的構造方法時傳遞進去的,在上面的InvocationHandler對象的invoke方法中,加一點代碼就能夠看到這些代碼被調用運行了。
6)總結分析動態代理類的統計原理和結構
1)怎樣將目標傳進去:
a)直接在InvocationHandler實現類中建立目標類的實例對象,可看運行效果和加入日誌代碼,可是沒有實際意義。
b)爲InvocationHandler實現類注入目標的實例對象,不能採用匿名內部類的形式了。
c)讓匿名內部類的InvocationHandler實現類訪問外面的方法中的目標類實例對象的final類型的引用變量。
2)動態代理的工做原理:
a)Client(客戶端)調用代理,代理的構造方法接收一個InvocationHandler,client調用代理的各個方法,代理的各個方法請求轉發給剛纔經過構造方法傳入的handler對象,又把各請求分發給目標的相應的方法。
示意圖:
b)將建立代理的過程改成一種更優雅的方式,eclipse重構出一個getProxy方法綁定接收目標,同時返回代理對象,讓調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API。在這裏將InvocationHandler加入到Proxy的構造方法中,所以,在建立出來的對象,就會存有構造方法中InvocationHandler的一些功能和信息,由於咱們把想要運行的代碼封裝在InvocationHandler對象,把它傳入到構造函數中,那麼就實現了代理對象每次調用與目標方法相同方法(由於實現了同一接口)時,都會調用咱們加入到InvocationHandler對象中的代碼。這就保證了每次調用代理時,能夠在目標上加入咱們本身加入的功能。
3)把系統功能代理模塊化,即切面代碼也改成經過參數形式提供,怎麼把要執行的系統功能代碼以參數的形式提供:
a)把要執行的代碼裝到一個對象的某個方法中,而後把此對象做爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外接提供的代碼。
b)爲getProxy方法增長一個Advice參數。
示例:package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class ProxyDemo { public static void main(String[] args) throws Exception{ final ArrayList target=new ArrayList();//指定目標 Advice advice=new MyAdvice();//將要添加的代碼封裝成對象 //使用Proxy提供的靜態newProxyInstance方法來一步到位的建立代理類實例對象 Collection proxy3 = (Collection)getProxy(target,advice); proxy3.add("it"); proxy3.add("hei"); proxy3.add("ma"); System.out.println(proxy3.size());//3 } //做爲一個通用的方法,就使用Object //傳入一個目標,並傳入一個接口,此接口做爲通訊的契約,才能調用額外的方法 private static Object getProxy(final Object target,final Advice advice) { Object proxy3=Proxy.newProxyInstance( //第一個參數,定義代理類的類加載器 target.getClass().getClassLoader(), //第二個參數,代理類要實現的接口列表,這裏要與target實現相同的接口 //new Class[]{Collection.class}, target.getClass().getInterfaces(), //第三個參數,代理類的構造函數的參數 new InvocationHandler() { @Override//複寫invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //使用約定的對象中的方法 advice.beforeMehod(); Object retval=method.invoke(target, args);//調用目標 advice.afterMethod(method); return retval; } }); return proxy3; } } import java.lang.reflect.Method; //只要實現Advice中的方法,裏面的代碼功能能夠隨便定義,調用代理時就會被使用 public class MyAdvice implements Advice { long startTime; @Override public void beforeMehod() { System.out.println("學習開始。。。"); startTime=System.currentTimeMillis(); } @Override public void afterMethod(Method method) { System.out.println("學習結束..."); long endTime=System.currentTimeMillis(); System.out.println(method.getName()+" running time:"+(endTime-startTime)); } } import java.lang.reflect.Method; //這裏用兩個做爲示例,建立Advice接口 public interface Advice { void beforeMehod(); void afterMethod(Method method); }
七、實現AOP功能的封裝與配置
工廠類BeanFactory
1)工廠類BeanFactory負責建立目標類或代理類的實例對象,並經過配置文件實現切換。
2)getBean方法根據參數字符串返回一個相應的實例對象,若是參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,不然返回該類示例對象的getProxy方法返回的對象。
3)BeanFactory的構造方法接收表明配置文件的輸入流對象的配置文件格式以下:
#xxx=java.util.ArrayList (#標識註釋此行)
xxx=proxy.aopframework.ProxyFactoryBean
xxx.advice=proxy.MyAdvice
xxx.target=java.util.ArrayList
4)ProxyFactoryBean充當封裝成動態的工廠,需爲工廠提供的配置參數信息包括:目標(target) 通知(advice)
5)BeanFactory和ProxyFactoryBean:
a)BeanFactory是一個純粹的bean工程,就是建立bean即相應的對象的工廠。
b)ProxyfactoryBean是BeanFactory中的一個特殊的Bean,是建立代理的工廠。
實現相似spring的可配置的AOP框架的思路
1)建立BeanFactory類:
a)構造方法:接受一個配置文件,經過Properties對象加載InputStream流對象得到。
b)建立getBean(String name)方法,接收Bean的名字,從上面加載後的對象得到。
c)經過其字節碼對象建立實例對象bean。
d)判斷bean是不是特殊的Bean即ProxyFactoryBean,若是是就要建立代理類,並設置目標和通告,分別獲得各自的實例對象,並返回代理類實例對象。不然返回普通類的實例對象。
2)建立ProxyFactoryBean(接口),此處直接定義爲類作測試,其中有一個getProxy方法,用於得到代理類對象。
3)編寫實現Advice接口的類和在配置文件中進行配置。
4)定義一個測試類:AopFrameworkTest,也稱客戶端,調用BeanFactory獲取對象。
示例:
package aopframework; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.Properties; /* * 配置文件config.properties中的信息以下: xxx=java.util.ArrayList xxx=aopframework.ProxyFactoryBean xxx.advice=proxy.MyAdvice xxx.target=java.util.ArrayList */ //兩個示例方法,封裝在Advice接口中 interface Advice { public void beforeMethod(Method method); public void afterMethod(Method method); } // 建立BeanFactory類,用於建立目標類或者代理類的實例對象。 class BeanFactory { // 定義properties集合用 Properties props = new Properties(); public BeanFactory(InputStream in) { try { props.load(in); } catch (IOException e) { e.printStackTrace(); } } // 從配置文件中獲取對象 public Object getBean(String name) { String className = props.getProperty(name);// 獲取配置文件中的值 Class<?> clazz = null; Object bean = null; try { clazz = Class.forName(className);// 經過反射建立對象 bean = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } // 若是建立的對象是ProxyFactoryBean類型,則經過getProxy方法獲取代理類對象 if (bean instanceof ProxyFactoryBean) { ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean; Advice advice = null; Object target = null; try { // 從配置文件中獲取代理類額外添加的代碼封裝成的對象 advice = (Advice) Class.forName( props.getProperty(name + ".advice")).newInstance(); // 從配置文件中獲取目標 target = Class.forName(props.getProperty(name + ".target")) .newInstance(); } catch (Exception e) { e.printStackTrace(); } proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); // 調用getProxy方法,獲取代理對象 Object proxy = (proxyFactoryBean).getProxy(); return proxy; } return bean; } } // 建立ProxyFactoryBean類,用於產生代理類實例對象 class ProxyFactoryBean { private Advice advice; private Object target; public Advice getAdvice() { return advice; } public void setAdvice(Advice advice) { this.advice = advice; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } Object getProxy() { // 第一個參數target.getClass().getClassLoader(),定義代理類的類加載器 // 第二個參數target.getClass().getInterfaces(),代理類要實現的接口列表,這裏要與target實現相同的接口 // 第三個參數new InvocationHandler() {...},代理類的構造函數的參數 Object proxy3 = Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override // 複寫invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 使用約定的對象中的方法 advice.beforeMethod(method); // 調用目標 Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return proxy3; }; } public class AopFrameworkTest { public static void main(String[] args) { InputStream in = AopFrameworkTest.class .getResourceAsStream("config.properties"); Object bean = new BeanFactory(in).getBean("xxx"); System.out.println(bean.getClass().getName()); ((Collection<?>) bean).clear(); } }至此,高新技術階段的主要知識:反射、內省、代理基本講說完畢,因爲和後期學習Java EE關係密切,因此知識點很重要。