本章重點介紹java.lang.reflect包下的接口和類
當程序使用某個類時,如果該類還沒有被加載到內存中,那么系統會通過加載,連接,初始化三個步驟來對該類進行初始化.
類的加載時指將類的class文件讀入內存,並為之創建一個java.lang.class對象,也就是說,當程序中使用任何類時,系統都會為之建立一個java.lang.Class對象.(幾乎所有的類都是java.lang.Class的實例);
所以JVM最先初始化的總是java.long.Object類.
在java中,一個類用其全限定類名(包括包名和類名)作為標識;但在JVM中,一個類用其全限定類名和類加載器作為其唯一標識.
打印根類加載器:
public class BootstrapTest { public static void main(String[] args) { // 獲取根類加載器所加載的全部URL數組 for (URL url : sun.misc.Launcher.getBootstrapClassPath().getURLs()) { // 遍歷、輸出根類加載器加載的全部URL System.out.println(url.toExternalForm()); } } } ---------------------------------------------------------------------- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/resources.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jar file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar
擴展類加載器,這個可以加入自己的jar包,挺好玩的.
系統類加載器:這個就是我們平常自定義類的父加載器了
開發者實現自定義的類加載器需要通過繼承ClassLoader來實現.
public static void main(String[] args) throws IOException { // 獲取系統類加載器 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系統類加載器:" + systemLoader); /* 獲取系統類加載器的加載路徑——通常由CLASSPATH環境變量指定 如果操作系統沒有指定CLASSPATH環境變量,默認以當前路徑作為 系統類加載器的加載路徑 */ Enumeration<URL> em1 = systemLoader.getResources(""); while(em1.hasMoreElements()) { System.out.println(em1.nextElement()); } // 獲取系統類加載器的父類加載器:得到擴展類加載器 ClassLoader extensionLader = systemLoader.getParent(); System.out.println("擴展類加載器:" + extensionLader); System.out.println("擴展類加載器的加載路徑:" + System.getProperty("java.ext.dirs")); System.out.println("擴展類加載器的parent: " + extensionLader.getParent()); }
自定義類加載器的例子:
由於java8.0.51的URLClassLoader里重寫了ClassLoader這個類里的下面這個方法,
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
所以,現在還沒弄明白原因,如果我們自己重寫findClass(String name),結果就是程序會先調用URLClassLoader里的findClass方法,如果這個方法找不到類,才會調用我們自己寫的findClass(String name).
比如我們要動態加載某個類(如果內存存在這個類,那么閑從內存取;如果內存中不存在,那么加載那個類的java文件並編譯(我不確定是不是要先檢查是否有class文件,看源碼和自己的測試結果是沒有檢查)).
例子
public class Hello { public static void main(String[] args) { System.out.println("tes22t2"); for (String arg : args) { System.out.println("運行Hello的參數:" + arg); } } }
import java.io.*; import java.lang.reflect.*; import java.net.URL; import java.security.CodeSigner; import java.security.CodeSource; import java.util.jar.Manifest; import sun.misc.Resource; import sun.misc.URLClassPath; import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf; /** * Description: * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a> * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee * <br/>This program is protected by copyright laws. * <br/>Program Name: * <br/>Date: * @author Yeeku.H.Lee kongyeeku@163.com * @version 1.0 */ public class CompileClassLoader extends ClassLoader { // 讀取一個文件的內容 private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int)len]; try( FileInputStream fin = new FileInputStream(file)) { // 一次讀取class文件的全部二進制數據 int r = fin.read(raw); if(r != len) throw new IOException("無法讀取全部文件:" + r + " != " + len); return raw; } } // 定義編譯指定Java文件的方法 private boolean compile(String javaFile) throws IOException { System.out.println("CompileClassLoader:正在編譯 " + javaFile + "..."); // 調用系統的javac命令 Process p = Runtime.getRuntime().exec("javac " + javaFile); try { // 其他線程都等待這個線程完成 p.waitFor(); } catch(InterruptedException ie) { System.out.println(ie); } // 獲取javac線程的退出值 int ret = p.exitValue(); // 返回編譯是否成功 return ret == 0; } // 重寫ClassLoader的findClass方法 @Override protected Class<?> findClass(String tmpName) throws ClassNotFoundException { System.out.println(tmpName); Class clazz = null; // 將包路徑中的點(.)替換成斜線(/); String className = tmpName.replace("." , "/").replace("1" , "");
// 這里因為我是在eclipse里編輯的,而java文件統一放到src里,class文件統一放到了bin里, 所以我需要加前綴, String javaFilename = "src/"+className + ".java"; String classFilename = "bin/"+className + ".class"; File javaFile = new File(javaFilename); System.out.println(javaFile.getAbsolutePath()); File classFile = new File(classFilename); // 當指定Java源文件存在,且class文件不存在、或者Java源文件 // 的修改時間比class文件修改時間更晚,重新編譯 if(javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // 如果編譯失敗,或者該Class文件不存在 if(!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException( "ClassNotFoundExcetpion:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } // 如果class文件存在,系統負責將該文件轉換成Class對象 if (classFile.exists()) { try { // 將class文件的二進制數據讀入數組 byte[] raw = getBytes(classFilename); // 調用ClassLoader的defineClass方法將二進制數據轉換成Class對象 clazz = defineClass(className,raw,0,raw.length); } catch(IOException ie) { ie.printStackTrace(); } } // 如果clazz為null,表明加載失敗,則拋出異常 if(clazz == null) { throw new ClassNotFoundException(className); } return clazz; } // 定義一個主方法 public static void main(String[] args) throws Exception { // 如果運行該程序時沒有參數,即沒有目標類 args = new String[]{"Hello","java瘋狂講義w"}; // 第一個參數是需要運行的類 String progClass = args[0]; // 剩下的參數將作為運行目標類時的參數, // 將這些參數復制到一個新數組中 String[] progArgs = new String[args.length-1]; System.arraycopy(args , 1 , progArgs , 0 , progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); // 加載需要運行的類 Class<?> clazz = ccl.loadClass(progClass); // 獲取需要運行的類的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null,argsArray); } }
輸出結果是
tes22t2
運行Hello的參數:java瘋狂講義w
打斷點可見,並沒有調用我們自定義的findClass(String tmpName)方法.
當我們把 args = new String[]{"Hello","java瘋狂講義w"}; 改為 args = new String[]{"Hello1","java瘋狂講義w"}; 時,由於URLClassLoader並沒有找到被加載的類Hello1,所以最后會調用我們自定義的findClass方法. 當然,更規范的是把 Class<?> clazz = ccl.loadClass(progClass); 改為 Class<?> clazz = ccl.findClass(progClass);
輸出結果:
Hello1 /Users/liuxin/work/workspace2/learnJava/src/Hello.java tes22t2 運行Hello的參數:java瘋狂講義w
這里我們可以看到,如果要動態加載某個類,不用自己覆寫findClass方法,只要如下代碼就好:
import java.io.*; import java.lang.reflect.*; import java.net.URL; import java.security.CodeSigner; import java.security.CodeSource; import java.util.jar.Manifest; import sun.misc.Resource; import sun.misc.URLClassPath; import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf; public class CompileClassLoader extends ClassLoader { public static void main(String[] args) throws Exception { // 如果運行該程序時沒有參數,即沒有目標類 args = new String[]{"Hello","java瘋狂講義w"}; // 第一個參數是需要運行的類 String progClass = args[0]; // 剩下的參數將作為運行目標類時的參數, // 將這些參數復制到一個新數組中 String[] progArgs = new String[args.length-1]; System.arraycopy(args , 1 , progArgs , 0 , progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); // 加載需要運行的類 Class<?> clazz = ccl.loadClass(progClass); // 獲取需要運行的類的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null,argsArray); } } ----------------輸出結果-------------------- tes22t2 運行Hello的參數:java瘋狂講義w
18.2.4 URLClassLoader類
java為ClassLoader提供了一個URLClassLoader實現類,該類也是系統類加載器和擴展類加載器的父類(此處的父類,就是指類與類之間的繼承關系).URLClassLoader功能強大,可以從本地或遠程主機獲取二進制文件來加載類.
下面是一個例子
import java.sql.*; import java.util.*; import java.net.*; /** * Description: * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a> * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee * <br/>This program is protected by copyright laws. * <br/>Program Name: * <br/>Date: * @author Yeeku.H.Lee kongyeeku@163.com * @version 1.0 */ public class URLClassLoaderTest { private static Connection conn; // 定義一個獲取數據庫連接方法 public static Connection getConn(String url , String user , String pass) throws Exception { if (conn == null) { // 創建一個URL數組 URL[] urls = {new URL( "file:mysql-connector-java-5.1.30-bin.jar")}; // 以默認的ClassLoader作為父ClassLoader,創建URLClassLoader URLClassLoader myClassLoader = new URLClassLoader(urls); // 加載MySQL的JDBC驅動,並創建默認實例 Driver driver = (Driver)myClassLoader. loadClass("com.mysql.jdbc.Driver").newInstance(); // 創建一個設置JDBC連接屬性的Properties對象 Properties props = new Properties(); // 至少需要為該對象傳入user和password兩個屬性 props.setProperty("user" , user); props.setProperty("password" , pass); // 調用Driver對象的connect方法來取得數據庫連接 conn = driver.connect(url , props); } return conn; } public static void main(String[] args)throws Exception { Connection temconn = getConn("jdbc:mysql://localhost:3306/mybatis" , "root" , "password"); try{ String sql = "INSERT INTO `mybatis`.`users` (`NAME`, `age`) VALUES ('java8', '2')"; java.sql.PreparedStatement stmt = temconn.prepareStatement(sql); stmt.execute(); stmt.close(); String sql2 = "select * from `mybatis`.`users`"; java.sql.PreparedStatement stmt2 = temconn.prepareStatement(sql2); ResultSet rs = stmt2.executeQuery(); ResultSetMetaData m=rs.getMetaData(); //顯示列,表格的表頭 int columns=m.getColumnCount(); for(int i=1;i<=columns;i++) { System.out.print(m.getColumnName(i)); System.out.print("\t\t"); } System.out.println(); //顯示表格內容 while(rs.next()) { for(int i=1;i<=columns;i++) { System.out.print(rs.getString(i)); System.out.print("\t\t"); } System.out.println(); } stmt2.close(); }catch(Exception e){ e.printStackTrace(); }finally{ temconn.close(); } } }
所以前面的動態加載類,可以用URLClassLoader改寫如下:(為了弄明白路徑,把Hello.java放到了learnJava包里去了)
public static void main(String[] args) throws Exception { args = new String[]{"learnJava.Hello","java瘋狂講義w"}; String progClass = args[0]; String[] progArgs = new String[args.length-1]; System.arraycopy(args , 1 , progArgs, 0 , progArgs.length); URL[] urls = {new URL("file:")}; Class<?> clazz = (new URLClassLoader(urls)).loadClass(progClass); // 獲取需要運行的類的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null,argsArray); }
18.3 通過反射來查看類的信息
什么時候會用到反射?
從Class中獲取信息,方法分以下幾類:
1.獲取構造器
2.獲取方法
3.獲取屬性
上面這三類方法,每一類都分4個方法,例如:(單個,多個) * (按權限,不顧權限)
4.獲取注解,這個太多:
5.獲取內部類
Class<?>[] getDeclaredClasses():返回該Class對象對應類包含的全部內部類
6.獲取外部類
Class<?>[] getDeclaringClasse():返回該Class對象對應類所在的外部類
7.獲取接口
Class<?>[] getInterfaces():返回該Class對象對應類所實現的全部接口
8.其他的如下:
例子:
// 定義可重復注解 @Repeatable(Annos.class) @interface Anno {} @Retention(value=RetentionPolicy.RUNTIME) @interface Annos { Anno[] value(); } // 使用4個注解修飾該類 @SuppressWarnings(value="unchecked") @Deprecated // 使用重復注解修飾該類 @Anno @Anno public class ClassTest { // 為該類定義一個私有的構造器 private ClassTest() { } // 定義一個有參數的構造器 public ClassTest(String name) { System.out.println("執行有參數的構造器"); } // 定義一個無參數的info方法 public void info() { System.out.println("執行無參數的info方法"); } // 定義一個有參數的info方法 public void info(String str) { System.out.println("執行有參數的info方法" + ",其str參數值:" + str); } // 定義一個測試用的內部類 class Inner { } public static void main(String[] args) throws Exception { // 下面代碼可以獲取ClassTest對應的Class Class<?> clazz = ClassTest.class; // 獲取該Class對象所對應類的全部構造器 Constructor[] ctors = clazz.getDeclaredConstructors(); System.out.println("ClassTest的全部構造器如下:"); for (Constructor c : ctors) { System.out.println(c); } // 獲取該Class對象所對應類的全部public構造器 Constructor[] publicCtors = clazz.getConstructors(); System.out.println("ClassTest的全部public構造器如下:"); for (Constructor c : publicCtors) { System.out.println(c); } // 獲取該Class對象所對應類的全部public方法 Method[] mtds = clazz.getMethods(); System.out.println("ClassTest的全部public方法如下:"); for (Method md : mtds) { System.out.println(md); } // 獲取該Class對象所對應類的指定方法 System.out.println("ClassTest里帶一個字符串參數的info()方法為:" + clazz.getMethod("info" , String.class)); // 獲取該Class對象所對應類的上的全部注解 Annotation[] anns = clazz.getAnnotations(); System.out.println("ClassTest的全部Annotation如下:"); for (Annotation an : anns) { System.out.println(an); } System.out.println("該Class元素上的@SuppressWarnings注解為:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class))); System.out.println("該Class元素上的@Anno注解為:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class))); // 獲取該Class對象所對應類的全部內部類 Class<?>[] inners = clazz.getDeclaredClasses(); System.out.println("ClassTest的全部內部類如下:"); for (Class c : inners) { System.out.println(c); } // 使用Class.forName方法加載ClassTest的Inner內部類 Class inClazz = Class.forName("ClassTest$Inner"); // 通過getDeclaringClass()訪問該類所在的外部類 System.out.println("inClazz對應類的外部類為:" + inClazz.getDeclaringClass()); System.out.println("ClassTest的包為:" + clazz.getPackage()); System.out.println("ClassTest的父類為:" + clazz.getSuperclass()); } }
18.3.3 java8 新增的方法參數反射
關於反射方法的參數的名字,這個比較麻煩,如下的例子
class Test { public void replace(String str, List<String> list){} } public class MethodParameterTest { public static void main(String[] args)throws Exception { // 獲取Tesy的類 Class<Test> clazz = Test.class; // 獲取Test類的帶兩個參數的replace()方法 Method replace = clazz.getMethod("replace" , String.class, List.class); // 獲取指定方法的參數個數 System.out.println("replace方法參數個數:" + replace.getParameterCount()); // 獲取replace的所有參數信息 Parameter[] parameters = replace.getParameters(); System.out.println((new File("")).getAbsolutePath()); int index = 1; // 遍歷所有參數 for (Parameter p : parameters) { if (p.isNamePresent()) { System.out.println("---第" + index++ + "個參數信息---"); System.out.println("參數名:" + p.getName()); System.out.println("形參類型:" + p.getType()); System.out.println("泛型類型:" + p.getParameterizedType()); } } } }
所以,上面這個例子在用eclipse編譯時,總是找不到參數名.沒找到設置的地方,只能用javac編譯
編譯命令如下: javac -parameters -encoding GBK -d . MethodParameterTest.java 因為瘋狂java講義里的源碼都是GBK編碼,而一般的電腦默認utf-8,所以需要指定編碼方式 運行: java MethodParameterTest ----------------------結果-------------------------- replace方法參數個數:2 /Users/liuxin/work/workspace2/learnJava/src ---第1個參數信息--- 參數名:str 形參類型:class java.lang.String 泛型類型:class java.lang.String ---第2個參數信息--- 參數名:list 形參類型:interface java.util.List 泛型類型:java.util.List<java.lang.String>
18.4 使用反射生成並操作對象
例子:這個個別地方沒理解,但是這是一個反射的典型應用,需要好好理解
import java.util.*; import java.io.*; import java.lang.reflect.*; /** * Description: * <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a> * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee * <br/>This program is protected by copyright laws. * <br/>Program Name: * <br/>Date: * @author Yeeku.H.Lee kongyeeku@163.com * @version 1.0 */ public class ExtendedObjectPoolFactory { // 定義一個對象池,前面是對象名,后面是實際對象 private Map<String ,Object> objectPool = new HashMap<>(); private Properties config = new Properties(); // 從指定屬性文件中初始化Properties對象 public void init(String fileName) { // File tmpFi = new File(fileName); // System.out.println(tmpFi.getAbsolutePath()); try(FileInputStream fis = new FileInputStream(fileName)) { config.load(fis); } catch (IOException ex) { ex.printStackTrace(); System.out.println("讀取" + fileName + "異常"); } } // 定義一個創建對象的方法, // 該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象 private Object createObject(String clazzName) throws InstantiationException , IllegalAccessException , ClassNotFoundException { // 根據字符串來獲取對應的Class對象 Class<?> clazz =Class.forName(clazzName); // 使用clazz對應類的默認構造器創建實例 return clazz.newInstance(); } // 該方法根據指定文件來初始化對象池, // 它會根據配置文件來創建對象 public void initPool()throws InstantiationException ,IllegalAccessException , ClassNotFoundException { for (String name : config.stringPropertyNames()) { // 每取出一對key-value對,如果key中不包含百分號(%) // 這就標明是根據value來創建一個對象 // 調用createObject創建對象,並將對象添加到對象池中 if (!name.contains("%")) { objectPool.put(name , createObject(config.getProperty(name))); } } } // 該方法將會根據屬性文件來調用指定對象的setter方法 public void initProperty()throws InvocationTargetException ,IllegalAccessException,NoSuchMethodException { for (String name : config.stringPropertyNames()) { // 每取出一對key-value對,如果key中包含百分號(%) // 即可認為該key用於控制調用對象的setter方法設置值, // %前半為對象名字,后半控制setter方法名 if (name.contains("%")) { // 將配置文件中key按%分割 String[] objAndProp = name.split("%"); // 取出調用setter方法的參數值 Object target = getObject(objAndProp[0]); // 獲取setter方法名:set + "首字母大寫" + 剩下部分 String mtdName = "set" + objAndProp[1].substring(0 , 1).toUpperCase() + objAndProp[1].substring(1); // 通過target的getClass()獲取它實現類所對應的Class對象 Class<?> targetClass = target.getClass(); // 獲取希望調用的setter方法 Method mtd = targetClass.getMethod(mtdName , String.class); // 通過Method的invoke方法執行setter方法, // 將config.getProperty(name)的值作為調用setter的方法的參數 mtd.invoke(target , config.getProperty(name)); } } } public Object getObject(String name) { // 從objectPool中取出指定name對應的對象。 return objectPool.get(name); } public static void main(String[] args) throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("src/extObj.txt"); epf.initPool(); epf.initProperty(); System.out.println(epf.getObject("a")); } }
例子二:使用指定的構造器來構造對象.
public class CreateJFrame { public static void main(String[] args) throws Exception { // 獲取JFrame對應的Class對象 Class<?> jframeClazz = Class.forName("javax.swing.JFrame"); // 獲取JFrame中帶一個字符串參數的構造器 Constructor ctor = jframeClazz .getConstructor(String.class); // 調用Constructor的newInstance方法創建對象 Object obj = ctor.newInstance("測試窗口"); // 輸出JFrame對象 System.out.println(obj); } }
18.4.3 訪問成員變量值
例子:
class Person { private String name; private int age; public String toString() { return "Person[name:" + name + " , age:" + age + " ]"; } } public class FieldTest { public static void main(String[] args) throws Exception { // 創建一個Person對象 Person p = new Person(); // 獲取Person類對應的Class對象 Class<Person> personClazz = Person.class; // 獲取Person的名為name的成員變量 // 使用getDeclaredField()方法表明可獲取各種訪問控制符的成員變量 Field nameField = personClazz.getDeclaredField("name"); // 設置通過反射訪問該成員變量時取消訪問權限檢查 nameField.setAccessible(true); // 調用set()方法為p對象的name成員變量設置值 nameField.set(p , "Yeeku.H.Lee"); // 獲取Person類名為age的成員變量 Field ageField = personClazz.getDeclaredField("age"); // 設置通過反射訪問該成員變量時取消訪問權限檢查 ageField.setAccessible(true); // 調用setInt()方法為p對象的age成員變量設置值 ageField.setInt(p , 30); System.out.println(p); } }
用java.lang.reflect包下的Array類操作數組
例子:
public class ArrayTest1 { public static void main(String args[]) { try { // 創建一個元素類型為String ,長度為10的數組 Object arr = Array.newInstance(String.class, 10); // 依次為arr數組中index為5、6的元素賦值 Array.set(arr, 5, "瘋狂Java講義"); Array.set(arr, 6, "輕量級Java EE企業應用實戰"); // 依次取出arr數組中index為5、6的元素的值 Object book1 = Array.get(arr , 5); Object book2 = Array.get(arr , 6); // 輸出arr數組中index為5、6的元素 System.out.println(book1); System.out.println(book2); } catch (Throwable e) { System.err.println(e); } } }
操作多維數組的例子:
public class ArrayTest2 { public static void main(String args[]) { /* 創建一個三維數組。 根據前面介紹數組時講的:三維數組也是一維數組, 是數組元素是二維數組的一維數組, 因此可以認為arr是長度為3的一維數組 */ Object arr = Array.newInstance(String.class, 3, 4, 10); // 獲取arr數組中index為2的元素,該元素應該是二維數組 Object arrObj = Array.get(arr, 2); // 使用Array為二維數組的數組元素賦值。二維數組的數組元素是一維數組, // 所以傳入Array的set()方法的第三個參數是一維數組。 Array.set(arrObj , 2 , new String[] { "瘋狂Java講義", "輕量級Java EE企業應用實戰" }); // 獲取arrObj數組中index為3的元素,該元素應該是一維數組。 Object anArr = Array.get(arrObj, 3); Array.set(anArr , 8 , "瘋狂Android講義"); // 將arr強制類型轉換為三維數組 String[][][] cast = (String[][][])arr; // 獲取cast三維數組中指定元素的值 System.out.println(cast[2][3][8]); System.out.println(cast[2][2][0]); System.out.println(cast[2][2][1]); } }
18.5 使用反射生成JDK動態代理
在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口,可生成JDK動態代理或動態代理對象.
動態代理的例子:
interface Person { void walk(); void sayHello(String name); } class MyInvokationHandler implements InvocationHandler { /* 執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法 其中: proxy:代表動態代理對象 method:代表正在執行的方法 args:代表調用目標方法時傳入的實參。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("----正在執行的方法:" + method); if (args != null) { System.out.println("下面是執行該方法時傳入的實參為:"); for (Object val : args) { System.out.println(val); } } else { System.out.println("調用該方法沒有實參!"); } return null; } } public class ProxyTest { public static void main(String[] args) throws Exception { // 創建一個InvocationHandler對象 InvocationHandler handler = new MyInvokationHandler(); // 使用指定的InvocationHandler來生成一個動態代理對象 Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader() , new Class[]{Person.class}, handler); // 調用動態代理對象的walk()和sayHello()方法 p.walk(); p.sayHello("孫悟空"); } }
18.5.2 動態代理和AOP
一個AOP的實現例子:
public interface Dog { // info方法聲明 void info(); // run方法聲明 void run(); }
public class GunDog implements Dog { // 實現info()方法,僅僅打印一個字符串 public void info() { System.out.println("我是一只獵狗"); } // 實現run()方法,僅僅打印一個字符串 public void run() { System.out.println("我奔跑迅速"); } }
public class DogUtil { // 第一個攔截器方法 public void method1() { System.out.println("=====模擬第一個通用方法====="); } // 第二個攔截器方法 public void method2() { System.out.println("=====模擬通用方法二====="); } }
public class MyInvokationHandler implements InvocationHandler { // 需要被代理的對象 private Object target; public void setTarget(Object target) { this.target = target; } // 執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Exception { DogUtil du = new DogUtil(); // 執行DogUtil對象中的method1。 du.method1(); // 以target作為主調來執行method方法 Object result = method.invoke(target , args); // 執行DogUtil對象中的method2。 du.method2(); return result; } }
public class MyProxyFactory { // 為指定target生成動態代理對象 public static Object getProxy(Object target) throws Exception { // 創建一個MyInvokationHandler對象 MyInvokationHandler handler = new MyInvokationHandler(); // 為MyInvokationHandler設置target對象 handler.setTarget(target); // 創建、並返回一個動態代理 return Proxy.newProxyInstance(target.getClass().getClassLoader() , target.getClass().getInterfaces() , handler); } }
public class Test { public static void main(String[] args) throws Exception { // 創建一個原始的GunDog對象,作為target Dog target = new GunDog(); // 以指定的target來創建動態代理 Dog dog = (Dog)MyProxyFactory.getProxy(target); dog.info(); dog.run(); } }
--------------結果----------------- =====模擬第一個通用方法===== 我是一只獵狗 =====模擬通用方法二===== =====模擬第一個通用方法===== 我奔跑迅速 =====模擬通用方法二=====
18.6 反射和泛型
例子1:泛型工廠類
public class CrazyitObjectFactory2 { public static <T> T getInstance(Class<T> cls) { try { return cls.newInstance(); } catch(Exception e) { e.printStackTrace(); return null; } } public static void main(String[] args) { // 獲取實例后無須類型轉換 Date d = CrazyitObjectFactory2.getInstance(Date.class); JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class); } }
例子2:使用反射來獲取泛型信息
public class GenericTest { private Map<String , Integer> score; public static void main(String[] args) throws Exception { Class<GenericTest> clazz = GenericTest.class; Field f = clazz.getDeclaredField("score"); // 直接使用getType()取出的類型只對普通類型的成員變量有效 Class<?> a = f.getType(); // 下面將看到僅輸出java.util.Map System.out.println("score的類型是:" + a); // 獲得成員變量f的泛型類型 Type gType = f.getGenericType(); // 如果gType類型是ParameterizedType對象 if(gType instanceof ParameterizedType) { // 強制類型轉換 ParameterizedType pType = (ParameterizedType)gType; // 獲取原始類型 Type rType = pType.getRawType(); System.out.println("原始類型是:" + rType); // 取得泛型類型的泛型參數 Type[] tArgs = pType.getActualTypeArguments(); System.out.println("泛型信息是:"); for (int i = 0; i < tArgs.length; i++) { System.out.println("第" + i + "個泛型類型是:" + tArgs[i]); } } else { System.out.println("獲取泛型類型出錯!"); } } }