反射是指計(jì)算機(jī)程序在運(yùn)行時(shí)訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的一種能力,是一種元編程語(yǔ)言特性,有很多語(yǔ)言都提供了對(duì)反射機(jī)制的支持,它使程序能夠編寫程序。Java的反射機(jī)制使得Java能夠動(dòng)態(tài)的獲取類的信息和調(diào)用對(duì)象的方法。
一、Java反射機(jī)制及基本用法
在Java中,Class(類類型)是反射編程的起點(diǎn),代表運(yùn)行時(shí)類型信息(RTTI,Run-Time Type Identification)。java.lang.reflect包含了Java支持反射的主要組件,如Constructor、Method和Field等,分別表示類的構(gòu)造器、方法和域,它們的關(guān)系如下圖所示。

Constructor和Method與Field的區(qū)別在于前者繼承自抽象類Executable,是可以在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用的,而Field僅僅具備可訪問(wèn)的特性,且默認(rèn)為不可訪問(wèn)。下面了解下它們的基本用法:

- 獲取Class對(duì)象有三種方式,Class.forName適合于已知類的全路徑名,典型應(yīng)用如加載JDBC驅(qū)動(dòng)。對(duì)同一個(gè)類,不同方式獲得的Class對(duì)象是相同的。
// 1. 采用Class.forName獲取類的Class對(duì)象
Class clazz0 = Class.forName("com.yhthu.java.ClassTest");
System.out.println("clazz0:" + clazz0);
// 2. 采用.class方法獲取類的Class對(duì)象
Class clazz1 = ClassTest.class;
System.out.println("clazz1:" + clazz1);
// 3. 采用getClass方法獲取類的Class對(duì)象
ClassTest classTest = new ClassTest();
Class clazz2 = classTest.getClass();
System.out.println("clazz2:" + clazz2);
// 4. 判斷Class對(duì)象是否相同
System.out.println("Class對(duì)象是否相同:" + ((clazz0.equals(clazz1)) && (clazz1.equals(clazz2))));
注意:三種方式獲取的Class對(duì)象相同的前提是使用了相同的類加載器,比如上述代碼中默認(rèn)采用應(yīng)用程序類加載器(sun.misc.Launcher$AppClassLoader)。不同類加載器加載的同一個(gè)類,也會(huì)獲取不同的Class對(duì)象:
// 自定義類加載器
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對(duì)象是否相同:" + clazz0.equals(clazz3));
- 通過(guò)Class的getDeclaredXxxx和getXxx方法獲取構(gòu)造器、方法和域?qū)ο?,兩者的區(qū)別在于前者返回的是當(dāng)前Class對(duì)象申明的構(gòu)造器、方法和域,包含修飾符為private的;后者只返回修飾符為public的構(gòu)造器、方法和域,但包含從基類中繼承的。
// 返回申明為public的方法,包含從基類中繼承的
for (Method method: String.class.getMethods()) {
System.out.println(method.getName());
}
// 返回當(dāng)前類申明的所有方法,包含private的
for (Method method: String.class.getDeclaredMethods()) {
System.out.println(method.getName());
}
- 通過(guò)Class的newInstance方法和Constructor的newInstance方法方法均可新建類型為Class的對(duì)象,通過(guò)Method的invoke方法可以在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用該方法,通過(guò)Field的set方法可以在運(yùn)行時(shí)動(dòng)態(tài)改變域的值,但需要首先設(shè)置其為可訪問(wèn)(setAccessible)。
二、 注解
注解(Annontation)是Java5引入的一種代碼輔助工具,它的核心作用是對(duì)類、方法、變量、參數(shù)和包進(jìn)行標(biāo)注,通過(guò)反射來(lái)訪問(wèn)這些標(biāo)注信息,以此在運(yùn)行時(shí)改變所注解對(duì)象的行為。Java中的注解由內(nèi)置注解和元注解組成。內(nèi)置注解主要包括:
- @Override - 檢查該方法是否是重載方法。如果發(fā)現(xiàn)其父類,或者是引用的接口中并沒(méi)有該方法時(shí),會(huì)報(bào)編譯錯(cuò)誤。
- @Deprecated - 標(biāo)記過(guò)時(shí)方法。如果使用該方法,會(huì)報(bào)編譯警告。
- @SuppressWarnings - 指示編譯器去忽略注解中聲明的警告。
- @SafeVarargs - Java 7 開始支持,忽略任何使用參數(shù)為泛型變量的方法或構(gòu)造函數(shù)調(diào)用產(chǎn)生的警告。
- @FunctionalInterface - Java 8 開始支持,標(biāo)識(shí)一個(gè)匿名函數(shù)或函數(shù)式接口。
這里,我們重點(diǎn)關(guān)注元注解,元注解位于java.lang.annotation包中,主要用于自定義注解。元注解包括:
- @Retention - 標(biāo)識(shí)這個(gè)注解怎么保存,是只在代碼中,還是編入class文件中,或者是在運(yùn)行時(shí)可以通過(guò)反射訪問(wèn),枚舉類型分為別SOURCE、CLASS和RUNTIME;
- @Documented - 標(biāo)記這些注解是否包含在用戶文檔中。
- @Target - 標(biāo)記這個(gè)注解應(yīng)該是哪種Java 成員,枚舉類型包括TYPE、FIELD、METHOD、CONSTRUCTOR等;
- @Inherited - 標(biāo)記這個(gè)注解可以繼承超類注解,即子類Class對(duì)象可使用getAnnotations()方法獲取父類被@Inherited修飾的注解,這個(gè)注解只能用來(lái)申明類。
- @Repeatable - Java 8 開始支持,標(biāo)識(shí)某注解可以在同一個(gè)聲明上使用多次。
自定義元注解需重點(diǎn)關(guān)注兩點(diǎn):1)注解的數(shù)據(jù)類型;2)反射獲取注解的方法。首先,注解中的方法并不支持所有的數(shù)據(jù)類型,僅支持八種基本數(shù)據(jù)類型、String、Class、enum、Annotation和它們的數(shù)組。比如以下代碼會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤:
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
// 1. 注解數(shù)據(jù)類型不能是Object;2. 默認(rèn)值不能為null
Object value() default null;
// 支持的定義方式
String value() default "";
}
其次,上節(jié)中提到的反射相關(guān)類(Class、Constructor、Method和Field)和Package均實(shí)現(xiàn)了AnnotatedElement接口,該接口定義了訪問(wèn)反射信息的方法,主要如下:
// 獲取指定注解類型
getAnnotation(Class<T>):T;
// 獲取所有注解,包括從父類繼承的
getAnnotations():Annotation[];
// 獲取指定注解類型,不包括從父類繼承的
getDeclaredAnnotation(Class<T>):T
// 獲取所有注解,不包括從父類繼承的
getDeclaredAnnotations():Annotation[];
// 判斷是否存在指定注解
isAnnotationPresent(Class<? extends Annotation>:boolean
當(dāng)使用上例中的AnnotationTest 標(biāo)注某個(gè)類后,便可在運(yùn)行時(shí)通過(guò)該類的反射方法訪問(wèn)注解信息了。
@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());
}
}
}
當(dāng)自定義注解只有一個(gè)方法value()時(shí),使用注解可只寫值,例如:@AnnotationTest("yhthu")
三、動(dòng)態(tài)代理
代理是一種結(jié)構(gòu)型設(shè)計(jì)模式,當(dāng)無(wú)法或不想直接訪問(wèn)某個(gè)對(duì)象,或者訪問(wèn)某個(gè)對(duì)象比較復(fù)雜的時(shí)候,可以通過(guò)一個(gè)代理對(duì)象來(lái)間接訪問(wèn),代理對(duì)象向客戶端提供和真實(shí)對(duì)象同樣的接口功能。經(jīng)典設(shè)計(jì)模式中,代理模式有四種角色:
- Subject抽象主題類——申明代理對(duì)象和真實(shí)對(duì)象共同的接口方法;
- RealSubject真實(shí)主題類——實(shí)現(xiàn)了Subject接口,真實(shí)執(zhí)行業(yè)務(wù)邏輯的地方;
- ProxySubject代理類——實(shí)現(xiàn)了Subject接口,持有對(duì)RealSubject的引用,在實(shí)現(xiàn)的接口方法中調(diào)用RealSubject中相應(yīng)的方法執(zhí)行;
- Cliect客戶端類——使用代理對(duì)象的類。

在實(shí)現(xiàn)上,代理模式分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代理類二進(jìn)制文件是在編譯時(shí)生成的,而動(dòng)態(tài)代理的代理類二進(jìn)制文件是在運(yùn)行時(shí)生成并加載到虛擬機(jī)環(huán)境的。JDK提供了對(duì)動(dòng)態(tài)代理接口的支持,開源的動(dòng)態(tài)代理庫(kù)(Cglib、Javassist和Byte Buddy)提供了對(duì)接口和類的代理支持,本節(jié)將簡(jiǎn)單比較JDK和Cglib實(shí)現(xiàn)動(dòng)態(tài)代理的異同,后續(xù)章節(jié)會(huì)對(duì)Java字節(jié)碼編程做詳細(xì)分析。
3.1 JDK動(dòng)態(tài)代理接口
JDK實(shí)現(xiàn)動(dòng)態(tài)代理是通過(guò)Proxy類的newProxyInstance方法實(shí)現(xiàn)的,該方法的三個(gè)入?yún)⒎謩e表示:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- ClassLoader loader,定義代理生成的類的加載器,可以自定義類加載器,也可以復(fù)用當(dāng)前Class的類加載器;
- Class<?>[] interfaces,定義代理對(duì)象需要實(shí)現(xiàn)的接口;
- InvocationHandler h,定義代理對(duì)象調(diào)用方法的處理,其invoke方法中的Object proxy表示生成的代理對(duì)象,Method表示代理方法, Object[]表示方法的參數(shù)。
通常的使用方法如下:
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;
}
}
類加載器采用當(dāng)前類的加載器,默認(rèn)為應(yīng)用程序類加載器(sun.misc.Launcher$AppClassLoader);接口數(shù)組以Subject.class為例,調(diào)用方法處理類MyInvocationHandler實(shí)現(xiàn)InvocationHandler接口,并在構(gòu)造器中傳入Subject的真正的業(yè)務(wù)功能服務(wù)類RealSubject,在執(zhí)行invoke方法時(shí),可以在實(shí)際方法調(diào)用前后織入自定義的處理邏輯,這也就是AOP(面向切面編程)的原理。
關(guān)于JDK動(dòng)態(tài)代理,有兩個(gè)問(wèn)題需要清楚:
- Proxy.newProxyInstance的代理類是如何生成的?Proxy.newProxyInstance生成代理類的核心分成兩步:
// 1. 獲取代理類的Class對(duì)象
Class<?> cl = getProxyClass0(loader, intfs);
// 2. 利用Class獲取Constructor,通過(guò)反射生成對(duì)象
cons.newInstance(new Object[]{h});
與反射獲取Class對(duì)象時(shí)搜索classpath路徑的.class文件不同的是,這里的Class對(duì)象完全是“無(wú)中生有”的。getProxyClass0根據(jù)類加載器和接口集合返回了Class對(duì)象,這里采用了緩存的處理。
// 緩存(key, sub-key) -> value,其中key為類加載器,sub-key為代理的接口,value為Class對(duì)象
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
// 如果實(shí)現(xiàn)了代理接口的類已存在就返回緩存對(duì)象,否則就通過(guò)ProxyClassFactory生成
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
如果實(shí)現(xiàn)了代理接口的類已存在就返回緩存對(duì)象,否則就通過(guò)ProxyClassFactory生成。ProxyClassFactory又是通過(guò)下面的代碼生成Class對(duì)象的。
// 生成代理類字節(jié)碼文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
// defineClass0為native方法,生成Class對(duì)象
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
generateProxyClass方法是用來(lái)生成字節(jié)碼文件的,根據(jù)生成的字節(jié)碼文件,再在native層生成Class對(duì)象。
- InvocationHandler的invoke方法是怎樣調(diào)用的?
回答這個(gè)問(wèn)題得先看下上面生成的Class對(duì)象究竟是什么樣的,將ProxyGenerator生成的字節(jié)碼保存成文件,然后反編譯打開(IDEA直接打開),可見生成的Proxy.class主要包含equals、toString、hashCode和代理接口的request方法實(shí)現(xiàn)。
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方法實(shí)現(xiàn)
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方法直接調(diào)用了InvocationHandler的實(shí)現(xiàn),這就回答了InvocationHandler的invoke方法是如何被調(diào)用的了。
3.2 Cglib動(dòng)態(tài)代理接口和類
Cglib的動(dòng)態(tài)代理是通過(guò)Enhancer類實(shí)現(xiàn)的,其create方法生成動(dòng)態(tài)代理的對(duì)象,有五個(gè)重載方法:
create():Object
create(Class, Callback):Object
create(Class, Class[], Callback):Object
create(Class, Class[], CallbackFilter, Callback):Object
create(Class[], Object):Object
常用的是第二個(gè)和第三個(gè)方法,分別用于動(dòng)態(tài)代理類和動(dòng)態(tài)代理接口,其使用方法如下:
private Object getProxy() {
// 1. 動(dòng)態(tài)代理類
return Enhancer.create(RealSubject.class, new MyMethodInterceptor());
// 2. 動(dòng)態(tài)代理接口
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;
}
}
從上小節(jié)可知,JDK只能代理接口,代理生成的類實(shí)現(xiàn)了接口的方法;而Cglib是通過(guò)繼承被代理的類、重寫其方法來(lái)實(shí)現(xiàn)的,如:create方法入?yún)⒌牡谝粋€(gè)參數(shù)就是被代理類的類型。當(dāng)然,Cglib也能代理接口,比如getProxy()方法中的第二種方式。
四、案例:Android端dubbo:reference化的網(wǎng)絡(luò)訪問(wèn)
Dubbo是一款高性能的Java RPC框架,是服務(wù)治理的重量級(jí)中間件。Dubbo采用dubbo:service描述服務(wù)提供者,dubbo:reference描述服務(wù)消費(fèi)者,其共同必填屬性為interface,即Java接口。Dubbo正是采用接口來(lái)作為服務(wù)提供者和消費(fèi)者之間的“共同語(yǔ)言”的。
在移動(dòng)網(wǎng)絡(luò)中,Android作為服務(wù)消費(fèi)者,一般通過(guò)HTTP網(wǎng)關(guān)調(diào)用后端服務(wù)。在國(guó)內(nèi)的大型互聯(lián)網(wǎng)公司中,Java后端大多采用了Dubbo及其變種作為服務(wù)治理、服務(wù)水平擴(kuò)展的解決方案。因此,HTTP網(wǎng)關(guān)通常需要Android的網(wǎng)絡(luò)請(qǐng)求中提供調(diào)用的服務(wù)名稱、服務(wù)方法、服務(wù)版本、服務(wù)分組等信息,然后通過(guò)這些信息反射調(diào)用Java后端提供的RPC服務(wù),實(shí)現(xiàn)從HTTP協(xié)議到RPC協(xié)議的轉(zhuǎn)換。
關(guān)于Android訪問(wèn)網(wǎng)關(guān)請(qǐng)求,其分層結(jié)構(gòu)可參考《基于Retrofit+RxJava的Android分層網(wǎng)絡(luò)請(qǐng)求框架》。
那么,Android端能否以dubbo:reference化的方式申明需要訪問(wèn)的網(wǎng)絡(luò)服務(wù)呢?如何這樣,將極大提高Android開發(fā)人員和Java后端開發(fā)之間的溝通效率,以及Android端的代碼效率。
首先,自定義服務(wù)的消費(fèi)者注解Reference,通過(guò)該注解標(biāo)記某個(gè)服務(wù)。
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Reference {
// 服務(wù)接口名
String service() default "";
// 服務(wù)版本
String version() default "";
// 服務(wù)分組
String group() default "";
// 省略字段
}
其次,通過(guò)接口定義某個(gè)服務(wù)消費(fèi)(如果可以直接引入后端接口,此步驟可省略),在注解中指明該服務(wù)對(duì)應(yīng)的后端服務(wù)接口名、服務(wù)版本、服務(wù)分組等信息;
@Reference(service = "com.yhthu.java.ClassTestService", group = "yhthu", version = "v_test_0.1")
public interface ClassTestService {
// 實(shí)例方法
Response echo(String pin);
}
這樣就完成了服務(wù)的申明,接下來(lái)的問(wèn)題是如何實(shí)現(xiàn)服務(wù)的調(diào)用呢?上述申明的服務(wù)接口如何定義實(shí)現(xiàn)呢?這里就涉及依賴注入和動(dòng)態(tài)代理。我們先定義一個(gè)標(biāo)記注解@Service,標(biāo)識(shí)需要被注入實(shí)現(xiàn)的服務(wù)申明。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
}
// 在需要使用服務(wù)的地方(比如Activity中)申明需要調(diào)用的服務(wù)
@Service
private ClassTestService classTestService;
在調(diào)用classTestService的方法之前,需要注入該接口服務(wù)的實(shí)現(xiàn),因此,該操作可以在調(diào)用組件初始化的時(shí)候進(jìn)行。
// 接口與對(duì)應(yīng)實(shí)現(xiàn)的緩存
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. 從緩存中取出或生成接口類的實(shí)現(xiàn)(動(dòng)態(tài)代理)
Object impl = serviceContainer.get(clazz);
if (impl == null) {
impl = create(clazz);
serviceContainer.put(clazz, impl);
}
// 3. 設(shè)置服務(wù)接口實(shí)現(xiàn)
try {
field.setAccessible(true);
field.set(obj, impl);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
inject方法的關(guān)鍵有三步:
- 掃描該類中所有添加@Service注解的字段,即可得到上述代碼示例中的ClassTestService字段;
- 從緩存中取出或生成接口類的實(shí)現(xiàn)。由于通過(guò)接口定義了服務(wù),并且實(shí)現(xiàn)不同服務(wù)的實(shí)現(xiàn)方式基本一致(即將服務(wù)信息發(fā)送HTTP網(wǎng)關(guān)),在生成實(shí)現(xiàn)上可選擇JDK的動(dòng)態(tài)代理。
- 設(shè)置服務(wù)接口實(shí)現(xiàn),完成為接口注入實(shí)現(xiàn)。
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. 獲取服務(wù)信息
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. 根據(jù)服務(wù)信息發(fā)起請(qǐng)求,返回調(diào)用結(jié)果
return Request.request(serviceName, versionName, groupName, methodName, param);
}
});
}
在HTTP網(wǎng)關(guān)得到服務(wù)名稱、服務(wù)方法、服務(wù)版本、服務(wù)分組等信息之后,即可實(shí)現(xiàn)對(duì)后端服務(wù)的反射調(diào)用??偟膩?lái)講,即可實(shí)現(xiàn)Android端dubbo:reference化的網(wǎng)絡(luò)訪問(wèn)。
// 調(diào)用ClassTestService服務(wù)的方法
classTestService.echo("yhthu").callback(// ……);
上述代碼實(shí)現(xiàn)均為偽代碼,僅說(shuō)明解決方案思路。
在該案例中,綜合使用了自定義注解、反射以及動(dòng)態(tài)代理,是對(duì)上述理論知識(shí)的一個(gè)具體應(yīng)用。