Java反射機制主要提供瞭如下功能:java
不少框架都用到了反射機制,包括大名鼎鼎的Spring。所以,瞭解反射也能夠說是爲以後學習框架源碼而打下堅實的基礎。編程
即使編譯時不知道類型和方法名稱,也能使用反射。反射使用類對象提供的基本元數據,能從類對象中找出方法或字段的名稱,而後獲取表示方法或字段的對象。數組
在Java中,靜態成員和普通數據類型不是對象,其餘皆是。瀏覽器
那麼問題來了,類是誰的對象?緩存
是java.lang.Class
的實例對象。安全
Class.forName(ClassName)//能夠動態加載類——也就是運行時加載
(使用 Class::newInstance() 或另外一個構造方法)建立實例時也能讓實例具備反射功能。若是有一個能反射的對象和一個 Method 對象,咱們就能在以前類型未知的對象上調用任何方法。架構
反射出來的對象信息是幾乎未知的,因此反射也並非那麼的好用。框架
不少,也許是多數 Java 框架都會適度使用反射。若是編寫的架構足夠靈活,在運行時以前都不知道要處理什麼代碼,那麼一般都須要使用反射。例如,插入式架構、調試器、代碼瀏覽器和 REPL 類環境每每都會在反射的基礎上實現。ide
反射在測試中也有普遍應用,例如,JUnit 和 TestNG 庫都用到了反射,並且建立模擬對象也要使用反射。若是你用過任何一個 Java 框架,即使沒有意識到,也幾乎能夠肯定,你使用的是具備反射功能的代碼。函數
常見的反射手段有JDK
反射和cglib
反射。
在本身的代碼中使用反射 API 時必定要知道,獲取到的對象幾乎全部信息都未知,所以處理起來可能很麻煩。
只要知道動態加載的類的一些靜態信息(例如,加載的類實現一個已知的接口),與這個類交互的過程就能大大簡化,減輕反射操做的負擔。
使用反射時有個常見的誤區:試圖建立能適用於全部場合的反射框架。正確的作法是,只處理當前領域當即就能解決的問題。
使用反射的第一步就是獲取Class對象,Class對象裏存儲了不少關鍵信息——畢竟這是用來描述類的class。
咱們能夠這樣來獲取Class信息:
Class<?> clz = Class.forName(obj.getClazz()); //經過class生成相應的實例 Object newObj = clz.newInstance
從Java1.5開始,Class類就支持泛型化了。好比:String.class就是Class<String>類型。
在反射最經常使用的API就是Method了。
類對象中包含該類中每一個方法的 Method 對象。這些 Method 對象在類加載以後惰性建立,因此在 IDE 的調試器中不會當即出現。
Method對象中保存的方法和元數據:
private Class<?> clazz; private int slot; // This is guaranteed to be interned by the VM in the 1.4 // reflection implementation private String name; private Class<?> returnType; private Class<?>[] parameterTypes; private Class<?>[] exceptionTypes private int modifiers; // Generics and annotations support private transient String signature; // Generic info repository; lazily initialized private transient MethodRepository genericInfo; private byte[] annotations; private byte[] parameterAnnotations; private byte[] annotationDefault; private volatile MethodAccessor methodAccessor;
咱們能夠經過getMethod得到對象的方法:
Object rcvr = "str"; try { Class<?>[] argTypes = new Class[] { }; //其實這個參數沒有也不要緊,由於hashCode方法不須要參數 Object[] args = null; Method hasMeth = rcvr.getClass().getMethod("hashCode", argTypes); Object ret = hasMeth.invoke(rcvr,args); System.out.println(ret); } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }
若是要調用非公開方法,必須使用 getDeclaredMethod() 方法才能獲取非公開方法的引用,並且還要使用 setAccessible() 方法覆蓋 Java 的訪問控制子系統,而後才能執行:
public class MyCache { private void flush() { // 清除緩存…… } } Class<?> clz = MyCache.class; try { Object rcvr = clz.newInstance(); Class<?>[] argTypes = new Class[]{}; Object[] args = null; Method meth = clz.getDeclaredMethod("flush", argTypes); meth.setAccessible(true); meth.invoke(rcvr, args); } catch (IllegalArgumentException | NoSuchMethodException | InstantiationException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }
Java 的反射 API 每每是處理動態加載代碼的惟一方式,不過 API 中有些讓人頭疼的地方,處理起來稍微有點困難:
void 就是個明顯的問題——雖然有 void.class,但沒堅持用下去。Java 甚至不知道 void 是否是一種類型,並且反射 API 中的某些方法使用 null 代替 void。
這很難處理,並且容易出錯,尤爲是稍微有點冗長的數組句法,更容易出錯。
Java反射的API中還提供了動態代理。動態代理是實現了一些接口的類(擴展 java.lang.reflect.Proxy 類)。這些類在運行時動態建立,並且會把全部調用都轉交給 InvocationHandler 對象處理:
InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws javaThrowable { String name = method.getName(); System.out.println("Called as: "+ name); switch (name) { case "isOpen": return false; case "close": return null; } return null; } }; Channel c = (Channel) Proxy.newProxyInstance(Channel.class.getClassLoader(), new Class[] { Channel.class }, h); c.isOpen(); c.close();
代理能夠用做測試的替身對象(尤爲是測試使用模擬方式實現的對象)。
代理的另外一個做用是提供接口的部分實現,或者修飾或控制委託對象的某些方面:
public class RememberingList implements InvocationHandler { private final List<String> proxied = new ArrayList<>(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); switch (name) { case "clear": return null; case "remove": case "removeAll": return false; } return method.invoke(proxied, args); } } RememberingList hList = new RememberingList(); List<String> l = (List<String>) Proxy.newProxyInstance(List.class.getClassLoader(), new Class[] { List.class }, hList); l.add("cat"); l.add("bunny"); l.clear(); System.out.println(l);
Java7中提供了方法句柄,比起「傳統」的反射機制。更爲好用,並且性能更好。
以以前的反射hashCode爲例
Object rcvr = "str"; try { MethodType mt = MethodType.methodType(int.class); MethodHandles.Lookup l = MethodHandles.lookup(); MethodHandle hashMeth = l.findVirtual(rcvr.getClass(), "hashCode", mt); int result; try { result = (int) hashMeth.invoke(rcvr); System.out.println(result); } catch (Throwable t) { t.printStackTrace(); } } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); }
MethodType.methodType(int.class);
指定了方法的返回類型,其實不止如此。methodType還能夠填入其函數參數經過MethodHandles.Lookup
能夠得到當前執行方法的上下文對象,在這個對象上能夠調用幾個方法(方法名都以 find 開頭),查找須要的方法,包括findVirtual()
、findConstructor()
` 和 findStatic()
findGetter()
和 findSetter()
方法,分別能夠生成讀取字段和更新字段的方法句柄。MethodHandles.Lookup.findVirtual()
得到了方法句柄(MethodHandle)通常來講,invoke() 方法會調用 asType() 方法轉換參數。轉換的規則以下:
方法句柄提供的動態編程功能和反射同樣,但處理方式更清晰明瞭。並且,方法句柄能在 JVM 的低層執行模型中很好地運轉,所以,性能比反射好得多。