編程語言通常有各種不同的分類角度吼砂,動態(tài)類型和靜態(tài)類型就是其中一種分類角度,簡單區(qū)分就是語言類型信息是在運(yùn)行時檢查鼎文,還是在編譯期檢查渔肩。
與其近似的還有一個對比,就是所謂強(qiáng)類型和弱類型拇惋,就是不同類型變量賦值時周偎,是否需要顯式地(強(qiáng)制)進(jìn)行類型轉(zhuǎn)換。
通常認(rèn)為Java是靜態(tài)的強(qiáng)類型語言撑帖,但是因?yàn)樘峁┝祟愃品瓷涞葯C(jī)制蓉坎,也具備了部分動態(tài)類型語言的能力。
一磷仰、反射
反射機(jī)制的使用
反射機(jī)制是Java語言提供的一種基礎(chǔ)功能袍嬉,賦予程序在運(yùn)行時自省(introspect)的能力境蔼,通過反射我們可以直接操作類或者對象灶平,比如獲取某個對象的類定義,獲取類聲明的屬性和方法箍土,調(diào)用方法或者構(gòu)造對象逢享,甚至可以運(yùn)行時動態(tài)修改類定義。
反射提供的AccessibleObject.setAccessible(boolean flag)方法吴藻,它的子類也大都重寫了這個方法瞒爬,這里的所謂accessible可以理解成修飾成員的pulib、protected沟堡、private侧但、這意味著我們可以在運(yùn)行時修改成員訪問限制,入?yún)閠rue時表示反射對象禁止權(quán)限檢查航罗,
入?yún)閒asle時表示開啟反射對象權(quán)限檢查禀横。
setAccessible的應(yīng)用場景非常普遍,遍布我們的開發(fā)粥血、測試柏锄、依賴注入等各種框架中酿箭,比如在O/R Mapping框架中,我們?yōu)橐粋€Java實(shí)體對象運(yùn)行時自動生成setter趾娃、getter的邏輯缭嫡,這是加載或持久化數(shù)據(jù)非常必要的,框架通程疲可以利用反射實(shí)現(xiàn)這個功能而不需要開發(fā)者手動寫類似的代碼妇蛀。
在Java9以后,這個方法的使用可能會存在一些爭議笤成,因?yàn)镴igsaw項(xiàng)目新增的模塊化系統(tǒng)讥耗,出于強(qiáng)封裝性的考慮,對反射訪問進(jìn)行了限制疹启。Jigsaw引入了所謂Opend概念古程,只有當(dāng)被反射的模塊和指定的包對反射調(diào)用者模塊Open,才能使用setAccessible喊崖,否則被認(rèn)為是不合法的操作挣磨,如果我們的實(shí)體類是
定義在模塊里面,我們需要在模塊描述符中明確聲明:
module MyEntities {
// Open for reflection
opens com.mycorp to java.persistence;
}
可以使用下面參數(shù)顯式設(shè)置是否需要校驗(yàn)Open權(quán)限:
--illegal-access={ permit | warn | deny }
另外一個典型場景就是繞過API訪問限制荤懂,比如使用反射繞開ArrayList泛型限制:
ArrayList<Integer> integerArrayList = new ArrayList<Integer>();
integerArrayList.add(1);
integerArrayList.add(2);
Method method = integerArrayList.getClass().getMethod("add", Object.class);
method.invoke(integerArrayList, "hello");
System.out.println(JSON.toJSONString(integerArrayList));
反射的常見用法:
public class Apple {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Apple(int price) {
this.price = price;
}
public Apple() {
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//正常調(diào)用
Apple apple = new Apple();
apple.setPrice(5);
System.out.println("Apple Price:" + apple.getPrice());
//使用反射調(diào)用茁裙,無參構(gòu)造器
Class appleClass = Class.forName("math.foundation.job.java.core.reflect.Apple");
Constructor constructor = appleClass.getConstructor();
Object reflectApple = constructor.newInstance();
Method setMethod = appleClass.getMethod("setPrice", int.class);
setMethod.invoke(reflectApple, 6);
Method getMethod = appleClass.getMethod("getPrice");
System.out.println("reflectApple price:" + getMethod.invoke(reflectApple));
//有參構(gòu)造器
Constructor constructor1 = appleClass.getConstructor(int.class);
Apple apple1 = (Apple) constructor1.newInstance(7);
System.out.println("有參構(gòu)造器apple:" + JSON.toJSONString(apple1));
//getFields()可以獲取類的共有屬性,但是無法獲取類的私有屬性.與獲取類屬性一樣节仿,當(dāng)我們?nèi)カ@取類方法晤锥、類構(gòu)造器時,如果要獲取私有方法或私有構(gòu)造器廊宪,則必須使用有 declared 關(guān)鍵字的方法矾瘾。
Field[] fields = appleClass.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Field[] declaredFields = appleClass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field.getName());
}
}
}
反射源碼解析
java.lang.reflect.Method#invoke(Object obj, Object... args)方法表示在指定的對象上使用指定的參數(shù)來調(diào)用指定的方法,個別參數(shù)會自動展開去匹配原始參數(shù)箭启。
- 如果底層方法是靜態(tài)方法壕翩,那么第一個入?yún)bj會被忽略,可以為null
- 如果底層方法的參數(shù)個數(shù)為0傅寡,args的長度可以為0或者null
- 如果底層方法的返回值是原始類型則會被包裝成對象放妈,如果返回值是一個由原始類型組成的數(shù)組則不會進(jìn)行包裝。
invoke方法最終會調(diào)用sun.reflect.MethodAccessor#invoke方法荐操,MethodAccessor是從java.lang.reflect.Method#acquireMethodAccessor方法中獲取到的:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
在這里又會調(diào)用sun.reflect.ReflectionFactory#newMethodAccessor方法:
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if(noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;
}
}
在這個方法中會有一個判斷條件首先會判斷noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())是否為true芜抒,noInflation為true表示配置了,
sun.reflect.noInflation=true
這個配置表示不開啟類膨脹托启,且當(dāng)class不是匿名類時就直接使用sun.reflect.MethodAccessorGenerator#generateMethod方法來生成一個代理類宅倒,否則就使用sun.reflect.NativeMethodAccessorImpl#invoke方法:
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(var3);
}
return invoke0(this.method, var1, var2);
}
這個方法中會判斷調(diào)用次數(shù)是否超過了閾值(numInvocations),如果超過該閾值則調(diào)用MethodAccessorGenerator.generateMethod()來生成代理類驾中,否則使用native方式唉堪,閾值可以通過如下參數(shù)來進(jìn)行配置:
sun.reflect.inflationThreshold
這個閾值默認(rèn)為15模聋,源碼注釋上解釋這個JVM所做的一個優(yōu)化,通常初次加載字節(jié)碼實(shí)現(xiàn)反射唠亚,使用Method.invoke() 和 Constructor.newInstance() 加載花費(fèi)的時間是使用原生代碼加載花費(fèi)的3-4倍链方,這使得那些頻繁使用反射的應(yīng)用啟動會花費(fèi)更長的時間。為了避免這種痛苦所以在第一次加載
的時候使用JVM本地實(shí)現(xiàn)灶搜,之后超過閾值才會切換到字節(jié)碼方式實(shí)現(xiàn)祟蚀。
二、動態(tài)代理
動態(tài)代理是一種方便運(yùn)行時動態(tài)構(gòu)建代理割卖,動態(tài)處理代理方法調(diào)用的機(jī)制前酿,很多場景都是利用類似機(jī)制做到的,比如用來包裝RPC調(diào)用鹏溯,面向切面的編程(AOP)罢维。實(shí)現(xiàn)動態(tài)代理的方式很多,比如JDK自身提供的動態(tài)代理丙挽,就是主要利用了上面提到的反射機(jī)制肺孵。還有其他的實(shí)現(xiàn)方式,比如利用傳說中更高性能的字節(jié)碼操作機(jī)制颜阐,類似ASM平窘、cglig(基于ASM)、Javassist等凳怨。通過代理可以讓調(diào)用者和實(shí)現(xiàn)者之間解耦瑰艘,比如RPC調(diào)用,框架內(nèi)部的尋址肤舞、序列化紫新、反序列化等,對于調(diào)用者往往沒有太大意義萨赁,通過代理可以更加優(yōu)雅弊琴,例如:
JDK動態(tài)代理:
public class MyJDKDynamicProxy {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(helloImpl);
Hello hello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), myInvocationHandler);
hello.sayHello();
}
}
interface Hello {
void sayHello();
}
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target, args);
return result;
}
}
上面的JDK 動態(tài)代理的例子,非常簡單地實(shí)現(xiàn)了動態(tài)代理的構(gòu)建和代理操作杖爽,從API設(shè)計和實(shí)現(xiàn)的角度,這種實(shí)現(xiàn)仍然具有局限性紫皇,因?yàn)樗且越涌跒橹行牡奈堪玻喈?dāng)于添加了一種對于調(diào)用者沒有太大意義的限制。我們實(shí)例化的是Proxy對象聪铺,而不是真正的被調(diào)用類型化焕,這在實(shí)踐中還是可能帶來各種不便和能力退化。
如果被調(diào)用者沒有實(shí)現(xiàn)接口铃剔,而我們還是希望利用動態(tài)代理機(jī)制撒桨,那么可以考慮其他方式查刻。我們知道Spring AOP支持兩種模式的動態(tài)代理,JDK Proxy或者cglib凤类,如果我們選擇cglib方式就會發(fā)現(xiàn)對接口的依賴被克服了穗泵,cglib動態(tài)代理采用的是創(chuàng)建目標(biāo)類的子類的方式,因?yàn)槭亲宇惢瞻蹋覀兛梢赃_(dá)到近似使用被調(diào)用者本身的效果佃延,但是需要注意的是final方法不能被代理。
JDK動態(tài)代理原理簡單分析:
我們上面的例子中創(chuàng)建動態(tài)代理類的入口為:
Hello hello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), myInvocationHandler);
方法注釋說明這個方法的作用是返回一個指定接口的代理類實(shí)例夷磕,并將方法調(diào)用分派給指定的調(diào)用處理程序履肃。指定接口對應(yīng)的就是我們程序中的Hello,方法調(diào)用指的就是sayHello()方法坐桩,指定的調(diào)用處理程序?qū)?yīng)的是程序中的MyInvocationHandler尺棋。
方法聲明如果開啟securityManager,會去校驗(yàn)getClassLoader绵跷、checkPackageAccess陡鹃、ReflectPermission這三個權(quán)限是否滿足條件。
可以看到真正生成代理類的代碼為:
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
生成代理類之后會通過使用以入?yún)镮nvocationHandler.class類型的構(gòu)造器抖坪,將myInvocationHandler作為入?yún)?chuàng)建一個代理類的實(shí)例萍鲸,這個實(shí)例就是我們程序中使用到的hello對象。下面我們來看一下java.lang.reflect.Proxy#getProxyClass0方法:
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
這個方法強(qiáng)調(diào)自己的作用是用來生成代理類的擦俐,但是需要確保在調(diào)用自己之前一定要先調(diào)用checkProxyAccess通過權(quán)限驗(yàn)證脊阴,內(nèi)部使用弱引用的緩存來緩存代理類,如果代理類在緩存中存在就直接返回蚯瞧,如果不存在于緩存中就使用ProxyClassFactory來創(chuàng)建代理類嘿期。
WeakCache內(nèi)部實(shí)現(xiàn)了一個弱引用的二級緩存,java.lang.reflect.WeakCache.get(K key, P parameter)方法有兩個參數(shù)埋合,第一個參數(shù)表示一級緩存的key备徐,第二個參數(shù)表示二級緩存的key。
首先會使用
Object cacheKey = CacheKey.valueOf(key, refQueue);
根據(jù)一級緩存的入?yún)ey構(gòu)建一個CacheKey對象作為一級緩存真正的key甚颂,通過使用
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
來構(gòu)建二級緩存的key蜜猾,subKeyFactory實(shí)際上對應(yīng)的是java.lang.reflect.Proxy.KeyFactory:
/**
* A function that maps an array of interfaces to an optimal key where
* Class objects representing interfaces are weakly referenced.
*/
private static final class KeyFactory
implements BiFunction<ClassLoader, Class<?>[], Object>
{
@Override
public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
switch (interfaces.length) {
case 1: return new Key1(interfaces[0]); // the most frequent
case 2: return new Key2(interfaces[0], interfaces[1]);
case 0: return key0;
default: return new KeyX(interfaces);
}
}
}
apply方法會根據(jù)被代理的接口生成一個特殊的key,大部分的被代理類都只實(shí)現(xiàn)了一個接口振诬,所以大部分情況都會進(jìn)入
case 1: return new Key1(interfaces[0]); // the most frequent
分支蹭睡。二級緩存中如果沒有指定的key,會使用循環(huán)獲取的方式初始化一個java.lang.reflect.WeakCache.Factory對象赶么,直到初始化成功為止:
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
}
初始化成功之后會調(diào)用java.lang.reflect.WeakCache.Factory.get方法獲取代理類肩豁,內(nèi)部會調(diào)用java.lang.reflect.Proxy.ProxyClassFactory.apply來生成、定義給定代理類的Class。
生成的代理類如下:
public final class $proxy4 extends Proxy implements Hello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $proxy4(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
從生成代理類可以看到代理類已經(jīng)繼承了Proxy類清钥,實(shí)現(xiàn)了Hello接口琼锋,由于Java語言是單繼承的,所以JDK動態(tài)代理要求必須被代理類必須實(shí)現(xiàn)接口祟昭。
代理類的sayHello方法實(shí)現(xiàn)是:
super.h.invoke(this, m3, (Object[])null);
super.h對應(yīng)的正是MyInvocationHandler缕坎,所以實(shí)際調(diào)用的是MyInvocationHandler.invoke方法。
cglib動態(tài)代理:
public class MyCGLIBDynamicProxy {
public static void main(String[] args) {
//在指定目錄下生成代理類从橘,我們可以反編譯看一下里面到底是什么東西
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
//使用Enhancer對象念赶,類似于JDK動態(tài)代理的Proxy類
Enhancer enhancer = new Enhancer();
//設(shè)置目標(biāo)類的字節(jié)碼文件
enhancer.setSuperclass(Boy.class);
//設(shè)置回調(diào)函數(shù)
enhancer.setCallback(new MyMethodInterceptor());
//create方法就是正式創(chuàng)建代理類
Boy boy = (Boy) enhancer.create();
//調(diào)用代理類的play方法
boy.play();
boy.work();
}
}
class Boy {
public void play() {
System.out.println("boy play");
}
final public void work() {
System.out.println("boy work");
}
}
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("使用cglib對目標(biāo)類進(jìn)行增強(qiáng)");
//這里的方法調(diào)用不是使用反射
Object o1 = methodProxy.invokeSuper(o, objects);
return o1;
}
}
除了上面例子中的MethodInterceptor回調(diào)類型之外,cglib還支持一下集中回調(diào)類型:
InvocationHandler(與JDK動態(tài)代理InvocationHandler功能一樣)
NoOp(什么都不會做恰力,被代理類方法正常被執(zhí)行)
LazyLoader(只在第一次調(diào)用的時候會調(diào)用loadObject()方法叉谜,之后調(diào)用不會進(jìn)入loadObject()方法)
Dispatcher(功能與LazyLoader相似,不同的是每次調(diào)用都會進(jìn)入loadObject()方法)
ProxyRefDispatcher(與Dispatcher類似踩萎,不同的是會將代理對象作為入?yún)鬟f進(jìn)來)
FixedValue(調(diào)用代理類每次返回的都是固定的值)
JDK Proxy優(yōu)勢:
- 最小化依賴關(guān)系停局,減少依賴意味著簡化開發(fā)和維護(hù),JDK本身的支持香府,可能比cglib更加可靠董栽。
- 平滑進(jìn)行JDK版本升級,而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版Java上能夠使用
- 代碼實(shí)現(xiàn)簡單
cglib優(yōu)勢:
- 某些調(diào)用目標(biāo)可能不便實(shí)現(xiàn)額外接口企孩,從某種角度看锭碳,限定調(diào)用者實(shí)現(xiàn)接口是有些侵入性的實(shí)踐,類似cglib動態(tài)代理沒有這種限制勿璃。
- 只操作我們關(guān)心的類擒抛,不必為其他相關(guān)類增加工作量。
- 高性能
動態(tài)代理的應(yīng)用非常廣泛补疑,比如在不同模塊的特定階段做特定的事情歧沪,比如類似日志、用戶鑒權(quán)莲组、全局異常處理诊胞、性能監(jiān)控等。
目前在我們的應(yīng)用中使用了JDK動態(tài)代理進(jìn)行Rpc服務(wù)的容災(zāi)機(jī)房切換锹杈、以及服務(wù)降級的功能撵孤,因?yàn)镽pc服務(wù)天然實(shí)現(xiàn)了接口,使用JDK動態(tài)代理依賴較少嬉橙,維護(hù)相對來說也比較簡單早直。