反射是指計算機程序在運行時訪問、檢測和修改它自己狀態或行爲的一種能力,是一種元編程語言特性,有不少語言都提供了對反射機制的支持,它使程序可以編寫程序。Java的反射機制使得Java可以動態的獲取類的信息和調用對象的方法。java
在Java中,Class(類類型)是反射編程的起點,表明運行時類型信息(RTTI,Run-Time Type Identification)。java.lang.reflect包含了Java支持反射的主要組件,如Constructor、Method和Field等,分別表示類的構造器、方法和域,它們的關係以下圖所示。android
Constructor和Method與Field的區別在於前者繼承自抽象類Executable,是能夠在運行時動態調用的,而Field僅僅具有可訪問的特性,且默認爲不可訪問。下面瞭解下它們的基本用法:編程
// 1. 採用Class.forName獲取類的Class對象 Class clazz0 = Class.forName("com.yhthu.java.ClassTest"); System.out.println("clazz0:" + clazz0); // 2. 採用.class方法獲取類的Class對象 Class clazz1 = ClassTest.class; System.out.println("clazz1:" + clazz1); // 3. 採用getClass方法獲取類的Class對象 ClassTest classTest = new ClassTest(); Class clazz2 = classTest.getClass(); System.out.println("clazz2:" + clazz2); // 4. 判斷Class對象是否相同 System.out.println("Class對象是否相同:" + ((clazz0.equals(clazz1)) && (clazz1.equals(clazz2))));
注意:三種方式獲取的Class對象相同的前提是使用了相同的類加載器,好比上述代碼中默認採用應用程序類加載器(sun.misc.Launcher$AppClassLoader)。不一樣類加載器加載的同一個類,也會獲取不一樣的Class對象:後端
// 自定義類加載器 ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; // 採用自定義類加載器加載 Class clazz3 = Class.forName("com.yhthu.java.ClassTest", true, myLoader); // clazz0與clazz3並不相同 System.out.println("Class對象是否相同:" + clazz0.equals(clazz3));
// 返回申明爲public的方法,包含從基類中繼承的 for (Method method: String.class.getMethods()) { System.out.println(method.getName()); } // 返回當前類申明的全部方法,包含private的 for (Method method: String.class.getDeclaredMethods()) { System.out.println(method.getName()); }
註解(Annontation)是Java5引入的一種代碼輔助工具,它的核心做用是對類、方法、變量、參數和包進行標註,經過反射來訪問這些標註信息,以此在運行時改變所註解對象的行爲。Java中的註解由內置註解和元註解組成。內置註解主要包括:設計模式
這裏,咱們重點關注元註解,元註解位於java.lang.annotation包中,主要用於自定義註解。元註解包括:數組
自定義元註解需重點關注兩點:1)註解的數據類型;2)反射獲取註解的方法。首先,註解中的方法並不支持全部的數據類型,僅支持八種基本數據類型、String、Class、enum、Annotation和它們的數組。好比如下代碼會產生編譯時錯誤:緩存
@Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationTest { // 1. 註解數據類型不能是Object;2. 默認值不能爲null Object value() default null; // 支持的定義方式 String value() default ""; }
其次,上節中提到的反射相關類(Class、Constructor、Method和Field)和Package均實現了AnnotatedElement接口,該接口定義了訪問反射信息的方法,主要以下:網絡
// 獲取指定註解類型 getAnnotation(Class<T>):T; // 獲取全部註解,包括從父類繼承的 getAnnotations():Annotation[]; // 獲取指定註解類型,不包括從父類繼承的 getDeclaredAnnotation(Class<T>):T // 獲取全部註解,不包括從父類繼承的 getDeclaredAnnotations():Annotation[]; // 判斷是否存在指定註解 isAnnotationPresent(Class<? extends Annotation>:boolean
當使用上例中的AnnotationTest 標註某個類後,即可在運行時經過該類的反射方法訪問註解信息了。框架
@AnnotationTest("yhthu") public class AnnotationReflection { public static void main(String[] args) { AnnotationReflection ar = new AnnotationReflection(); Class clazz = ar.getClass(); // 判斷是否存在指定註解 if (clazz.isAnnotationPresent(AnnotationTest.class)) { // 獲取指定註解類型 Annotation annotation = clazz.getAnnotation(AnnotationTest.class); // 獲取該註解的值 System.out.println(((AnnotationTest) annotation).value()); } } }
當自定義註解只有一個方法value()時,使用註解可只寫值,例如:@AnnotationTest("yhthu")編程語言
代理是一種結構型設計模式,當沒法或不想直接訪問某個對象,或者訪問某個對象比較複雜的時候,能夠經過一個代理對象來間接訪問,代理對象向客戶端提供和真實對象一樣的接口功能。經典設計模式中,代理模式有四種角色:
在實現上,代理模式分爲靜態代理和動態代理,靜態代理的代理類二進制文件是在編譯時生成的,而動態代理的代理類二進制文件是在運行時生成並加載到虛擬機環境的。JDK提供了對動態代理接口的支持,開源的動態代理庫(Cglib、Javassist和Byte Buddy)提供了對接口和類的代理支持,本節將簡單比較JDK和Cglib實現動態代理的異同,後續章節會對Java字節碼編程作詳細分析。
JDK實現動態代理是經過Proxy類的newProxyInstance方法實現的,該方法的三個入參分別表示:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
一般的使用方法以下:
private Object getProxy() { return Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class<?>[]{Subject.class}, new MyInvocationHandler(new RealSubject())); } private static class MyInvocationHandler implements InvocationHandler { private Object realSubject; public MyInvocationHandler(Object realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Some thing before method invoke"); Object result = method.invoke(realSubject, args); System.out.println("Some thing after method invoke"); return result; } }
類加載器採用當前類的加載器,默認爲應用程序類加載器(sun.misc.Launcher$AppClassLoader);接口數組以Subject.class爲例,調用方法處理類MyInvocationHandler實現InvocationHandler接口,並在構造器中傳入Subject的真正的業務功能服務類RealSubject,在執行invoke方法時,能夠在實際方法調用先後織入自定義的處理邏輯,這也就是AOP(面向切面編程)的原理。
關於JDK動態代理,有兩個問題須要清楚:
// 1. 獲取代理類的Class對象 Class<?> cl = getProxyClass0(loader, intfs); // 2. 利用Class獲取Constructor,經過反射生成對象 cons.newInstance(new Object[]{h});
與反射獲取Class對象時搜索classpath路徑的.class文件不一樣的是,這裏的Class對象徹底是「無中生有」的。getProxyClass0根據類加載器和接口集合返回了Class對象,這裏採用了緩存的處理。
// 緩存(key, sub-key) -> value,其中key爲類加載器,sub-key爲代理的接口,value爲Class對象 private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); // 若是實現了代理接口的類已存在就返回緩存對象,不然就經過ProxyClassFactory生成 private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } return proxyClassCache.get(loader, interfaces); }
若是實現了代理接口的類已存在就返回緩存對象,不然就經過ProxyClassFactory生成。ProxyClassFactory又是經過下面的代碼生成Class對象的。
// 生成代理類字節碼文件 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); try { // defineClass0爲native方法,生成Class對象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }
generateProxyClass方法是用來生成字節碼文件的,根據生成的字節碼文件,再在native層生成Class對象。
public final class $Proxy extends Proxy implements Subject { // m1 = Object的equals方法 private static Method m1; // m2 = Object的toString方法 private static Method m2; // Subject的request方法 private static Method m3; // Object的hashCode方法 private static Method m0; // 省略m1/m2/m0,此處只列出request方法實現 public final void request() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
因爲生成的代理類繼承自Proxy,super.h便是Prxoy的InvocationHandler,即代理類的request方法直接調用了InvocationHandler的實現,這就回答了InvocationHandler的invoke方法是如何被調用的了。
Cglib的動態代理是經過Enhancer類實現的,其create方法生成動態代理的對象,有五個重載方法:
create():Object create(Class, Callback):Object create(Class, Class[], Callback):Object create(Class, Class[], CallbackFilter, Callback):Object create(Class[], Object):Object
經常使用的是第二個和第三個方法,分別用於動態代理類和動態代理接口,其使用方法以下:
private Object getProxy() { // 1. 動態代理類 return Enhancer.create(RealSubject.class, new MyMethodInterceptor()); // 2. 動態代理接口 return Enhancer.create(Object.class, new Class<?>[]{Subject.class}, new MyMethodInterceptor()); } private static class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Some thing before method invoke"); Object result = proxy.invokeSuper(obj, args); System.out.println("Some thing after method invoke"); return result; } }
從上小節可知,JDK只能代理接口,代理生成的類實現了接口的方法;而Cglib是經過繼承被代理的類、重寫其方法來實現的,如:create方法入參的第一個參數就是被代理類的類型。固然,Cglib也能代理接口,好比getProxy()方法中的第二種方式。
Dubbo是一款高性能的Java RPC框架,是服務治理的重量級中間件。Dubbo採用dubbo:service描述服務提供者,dubbo:reference描述服務消費者,其共同必填屬性爲interface,即Java接口。Dubbo正是採用接口來做爲服務提供者和消費者之間的「共同語言」的。
在移動網絡中,Android做爲服務消費者,通常經過HTTP網關調用後端服務。在國內的大型互聯網公司中,Java後端大多采用了Dubbo及其變種做爲服務治理、服務水平擴展的解決方案。所以,HTTP網關一般須要Android的網絡請求中提供調用的服務名稱、服務方法、服務版本、服務分組等信息,而後經過這些信息反射調用Java後端提供的RPC服務,實現從HTTP協議到RPC協議的轉換。
關於Android訪問網關請求,其分層結構可參考《基於Retrofit+RxJava的Android分層網絡請求框架》。
那麼,Android端可否以dubbo:reference化的方式申明須要訪問的網絡服務呢?如何這樣,將極大提升Android開發人員和Java後端開發之間的溝通效率,以及Android端的代碼效率。
首先,自定義服務的消費者註解Reference,經過該註解標記某個服務。
@Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Reference { // 服務接口名 String service() default ""; // 服務版本 String version() default ""; // 服務分組 String group() default ""; // 省略字段 }
其次,經過接口定義某個服務消費(若是能夠直接引入後端接口,此步驟可省略),在註解中指明該服務對應的後端服務接口名、服務版本、服務分組等信息;
@Reference(service = "com.yhthu.java.ClassTestService", group = "yhthu", version = "v_test_0.1") public interface ClassTestService { // 實例方法 Response echo(String pin); }
這樣就完成了服務的申明,接下來的問題是如何實現服務的調用呢?上述申明的服務接口如何定義實現呢?這裏就涉及依賴注入和動態代理。咱們先定義一個標記註解@Service,標識須要被注入實現的服務申明。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { } // 在須要使用服務的地方(好比Activity中)申明須要調用的服務 @Service private ClassTestService classTestService;
在調用classTestService的方法以前,須要注入該接口服務的實現,所以,該操做能夠在調用組件初始化的時候進行。
// 接口與對應實現的緩存 private Map<Class<?>, Object> serviceContainer = new HashMap<>(); // 依賴注入 public void inject(Object obj) { // 1. 掃描該類中全部添加@Service註解的域 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Service.class)) { Class<?> clazz = field.getType(); if (clazz.getAnnotation(Reference.class) == null) { Log.e("ClassTestService", "接口地址未配置"); continue; } // 2. 從緩存中取出或生成接口類的實現(動態代理) Object impl = serviceContainer.get(clazz); if (impl == null) { impl = create(clazz); serviceContainer.put(clazz, impl); } // 3. 設置服務接口實現 try { field.setAccessible(true); field.set(obj, impl); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
inject方法的關鍵有三步:
private <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 獲取服務信息 Annotation reference = service.getAnnotation(Reference.class); String serviceName = ((Reference) reference).service(); String versionName = ((Reference) reference).version(); String groupName = ((Reference) reference).group(); // 2. 獲取方法名 String methodName = method.getName(); // 3. 根據服務信息發起請求,返回調用結果 return Request.request(serviceName, versionName, groupName, methodName, param); } }); }
在HTTP網關獲得服務名稱、服務方法、服務版本、服務分組等信息以後,便可實現對後端服務的反射調用。總的來說,便可實現Android端dubbo:reference化的網絡訪問。
// 調用ClassTestService服務的方法 classTestService.echo("yhthu").callback(// ……);
上述代碼實現均爲僞代碼,僅說明解決方案思路。
在該案例中,綜合使用了自定義註解、反射以及動態代理,是對上述理論知識的一個具體應用。