什麼是EasyReflect
EasyReflect是開源基礎元件整合庫EasyAndroid中的基礎元件之一。
其作用是:對基礎反射api進行封裝,精簡反射操作
EasyAndroid作為一款整合元件庫,此庫中所整合的元件,均包含以下特點,你可以放心使用~~
1. 設計獨立
元件間獨立存在,不相互依賴,且若只需要整合庫中的部分元件。也可以很方便的
只copy對應的元件檔案
進行使用
2. 設計輕巧
因為是元件整合庫,所以要求每個元件的設計儘量精練、輕巧。避免因為一個小功能而引入大量無用程式碼.
每個元件的方法數均
不超過100
. 大部分元件甚至不超過50
。
得益於編碼時的高內聚性
,若你只需要使用EasyReflect. 那麼可以直接去拷貝EasyReflect原始碼檔案到你的專案中,直接進行使用,也是沒問題的。
EasyAndroid開源庫地址:
https://github.com/yjfnypeu/EasyAndroid
EasyReflect元件地址:
https://github.com/yjfnypeu/EasyAndroid/blob/master/utils/src/main/java/com/haoge/easyandroid/easy/EasyReflect.kt
用法
1. 初識EasyReflect
class EasyReflect private constructor(val clazz: Class<*>, private var instance:Any?)
複製程式碼
可以看到:EasyReflect本身持有兩部分資料:clazz
與instance
.
- clazz: 此例項所繫結操作的clazz例項。永不為null
- instance: 此例項所繫結的instance例項,為clazz型別的具體例項。可能為null。
請注意:對於instance資料來說,當執行操作過程中需要使用此instance例項時(比如讀取某個成員變數),若此時instance為null,則將觸發使用預設的空構造器進行instance建立。
2. 建立EasyReflect的幾種姿勢
- 只使用Class進行建立:建立後只含有clazz資料
val reflect = EasyReflect.create(Test::class.java)
複製程式碼
- 使用類全名進行建立(可指定ClassLoader):建立後只含有clazz資料
val reflect = EasyReflect.create("com.haoge.sample.Test")
// 也可指定ClassLoader進行載入
val reflect = EasyReflect.create(className, classLoader)
複製程式碼
- 直接通過指定例項進行建立:建立後clazz與instance均存在, clazz為instance.javaClass資料
val reflect = EasyReflect.create(any)
複製程式碼
瞭解了EasyReflect的幾種建立方式與其區別後。我們就可以正式進行使用了
3. 呼叫指定構造器進行例項建立
val reflect:EasyReflect = createReflect()
// 通過預設空構造器進行物件建立. 通過get方法獲取建立出的物件
val instance = reflect.instance().get<Any>()
// 通過含參構造器進行物件建立
val instance2 = reflect.instance(arg0, arg1...argN).get<Any>()
複製程式碼
4. 欄位的賦值與取值
EasyReflect對欄位的操作,不用再去考慮它是否是靜態的
或者還是final修飾的
。更不用去管欄位是否是private
不可見的。我們只需要指定欄位名即可。這大大增強了便利性!
- 訪問指定欄位的值:
val value:Any = reflect.getFieldValue<Any>(fieldName)
複製程式碼
- 為指定欄位賦值:
reflect.setField(fieldName, newValue)
複製程式碼
- 使用指定欄位的值建立新的
EasyReflect
例項進行使用
val newReflect = reflect.transform(fieldName)
複製程式碼
5. 方法呼叫
與欄位操作類似,我們也不用去擔心方法的可見性問題。需要的只有此方法存在
即可
- 呼叫指定方法
// 呼叫無參方法
reflect.call(methodName)
// 呼叫有參方法
reflect.call(methodName, arg0, arg1...argN)
複製程式碼
- 呼叫指定方法並獲取返回值
val value = reflect.callWithReturn(methodName, arg0, arg1...argN).get()
複製程式碼
6. 傳參包括可變引數時的呼叫方式
可變引數會在編譯時。替換為陣列的形式。所以使用反射進行可變引數的傳參時,需要使用陣列的形式對引數進行構建:
以下方的兩個方法為例:
class Method{
fun onlyVararg(vararg names:String)
fun withVararg(preffix:Int, vararg names:String)
}
複製程式碼
此類中的兩個方法均為帶有可變引數的方法,在反射環境下。就應該將其中的可變引數看作為是對應的陣列進行使用,所以這個時候。我們需要使用如下方式進行引數傳遞:
// 1. 呼叫只含有可變引數的方法
reflect.call("onlyVararg", arrayOf("Hello", "World"))
// 2. 呼叫包含其他引數的可變引數方法
reflect.call("withVararg", 1, arrayOf("Hello", "World"))
複製程式碼
在這裡要特別提醒一下:由於java與kotlin的差異性,在java中呼叫只含有可變引數的方法時。需要對引數進行特殊處理(外層包裹一層Object[]
):
EasyReflect reflect = EasyReflect.create(Method.class)
reflect.call("onlyVararg", new Object[]{new String[]{"Hello", "World"}})
複製程式碼
而對於withVararg
這種帶有非可變引數的方法則不用進行此特殊處理:
reflect.call("withVararg", 1, new String[]{"Hello", "World"})
複製程式碼
這是因為在java中。若實參為一個陣列時。這個時候將會對此陣列進行解構平鋪,所以我們需要在外層再單獨套一層Object陣列。才能對引數型別做到正確匹配。而在kotlin中不存在此類問題。
7. 使用動態代理進行託管
當你需要對某個類進行訪問,但是又不想通過寫死名字的方式去呼叫時,可以使用此特性:
通過動態代理的方式,建立一個代理類來對反射操作進行託管
還是通過舉例來說明:
class Test {
private fun function0(name:String)
}
複製程式碼
對於上面所舉例的類來說。要通過反射呼叫它的function0方法,那麼需要這樣寫:
val reflect = EasyReflect.create(Test::class.java)
reflect.call("function0", "This is name")
複製程式碼
而若使用託管的方式。配置先建立代理介面,然後通過代理介面進行方法訪問:
// 建立代理介面
interface ITestProxy {
fun function0(name:String)
}
val reflect = EasyReflect.create(Test::class.java)
// 建立託管代理例項:
val proxy:ITestProxy = reflect.proxy(ITestProxy::class.java)
// 然後直接通過代理類進行操作:
proxy.function0("proxy name")
複製程式碼
當然,如果只能對方法進行託管那這功能就有點太弱逼了。託管方案也同時支援了對變數的操作:
class Test {
// 新增一個欄位
private val user:User
private fun function0(name:String)
}
// 建立代理介面
interface ITestProxy {
fun function0(name:String)
//==== 對user欄位進行賦值
// 方式一:使用setXXX方法進行賦值
fun setUser(user:User)
// 方式二:使用set(name, value)方法進行賦值
fun set(fieldName:String, value:String)
//==== 對user進行取值
// 方式一:使用getXXX方法進行取值
fun getUser()
// 方式二:使用get(name)方法進行取值
fun get(fieldName:String)
}
val reflect = EasyReflect.create(Test::class.java)
// 建立託管代理例項:
val proxy:ITestProxy = reflect.proxy(ITestProxy::class.java)
// 然後直接通過代理類進行操作:
proxy.setUser(user)// 方式一賦值
proxy.set("user", user)// 方式二賦值
proxy.getUser() // 方式一取值
proxy.get("user")// 方式二取值
複製程式碼
從某種程度上來說:代理託管方案會比一般的直接反射操作要繁瑣一點。但是帶來的便利也是顯而易見的:
首當其衝的優點就是:託管方式寫的程式碼比直接進行反射操作更加優雅~
其次,在某些使用環境下,比如在團隊協作中:可以將對外不方便直接提供例項的通過託管代理例項進行提供。
更多示例
為了方便大家更好的理解,這一節中我會列舉一些小案例,方便大家更清楚的知道應該怎麼去使用:
1. 使用指定構造器建立例項
假設我們有以下一個類需要建立:
data class Example private constructor(val name: String)
複製程式碼
那麼我們可以這樣來進行建立:
val example = EasyReflect.create(Example::class.java)// 指定實體類的class
.instance("Hello World")// 根據對應的構造器的入參型別進行傳參
.get<Example>() // 獲取建立好的實體類例項
複製程式碼
2. 對類中指定成員變數進行取值、賦值
假設我們需要對此類中name變數進行操作
class Example {
private name:String? = null
}
複製程式碼
因為是成員變數的操作。而成員變數是屬於具體例項
的。所以首先應該需要獲取到具體例項進行操作:
val example = Example()
val reflect = EasyReflect.create(example)
複製程式碼
獲取到例項後,即可直接操作了:
賦值
reflect.setField("name"/*變數名*/, "Hello"/*賦的值*/)
複製程式碼
取值
val name = reflect.getFieldValue<String>("name")
複製程式碼
3. 對類中的靜態變數進行取值、賦值
class Example {
companion object {
@JvmStatic
val staticName:String? = null
}
}
複製程式碼
因為靜態變數是隸屬於類本身的,所以與成員變數的訪問不同:靜態變數的訪問只需要使用類本身即可:
val reflect = EasyReflect.create(Example::class.java)
複製程式碼
當然,你也可以跟成員變數的操作一樣的,直接提供具體的類例項進行使用, 都是一樣的:
val reflect = EasyReflect.create(example)
複製程式碼
賦值
reflect.setField("staticName", "Haoge")
複製程式碼
取值
val staticName:String = reflect.getFieldValue<String>("staticName")
複製程式碼
4. 訪問成員方法
class Example {
private fun transform(name:String):User {...}
}
複製程式碼
與成員變數
類似,成員方法
也是隸屬於具體的類例項的,所以建立時需要提供具體的類例項
val reflect = EasyReflect.create(Example())
複製程式碼
然後,EasyReflect
中,對方法提供兩種操作:
1. 直接訪問指定的方法
reflect.call(
"transform"/*第一個引數:方法名*/,
"Haoge" /*可變引數,需要與具體方法的傳參型別一致*/
)
複製程式碼
2. 訪問指定方法,並使用返回值建立新的Reflect例項進行返回
val userReflect = reflect.callWithReturn(
"transform"/*第一個引數:方法名*/,
"Haoge" /*可變引數,需要與具體方法的傳參型別一致*/
)
// 獲取到返回的user例項
val user:User = userReflect.get<User>()
// 也可以根據返回的userReflect繼續對User類進行操作
...
複製程式碼
5. 訪問靜態方法:
靜態方法
也是隸屬於類本身
的,所以說:與靜態變數的訪問
類似,只需要指定操作的類即可直接訪問了:
val reflect = EasyReflect.create(Example::class.java)
複製程式碼
其他具體的操作方式與成員方法的操作均一致。
6. 使用指定Field的值建立新的EasyReflect例項提供使用
class Example {
val user:InternalUser
}
private class InternalUser {
val name:String
}
複製程式碼
類似上方程式碼:我們需要獲取變數user
中的name
資料,但是user的類是私有的,外部不能訪問。
所以,按照上面我們提供的方式。我們需要這樣來做一次中轉後再使用:
val reflect = EasyReflect.create(example)
// 因為外部不能訪問到InternalUser,所以用頂層類進行接收
val user:Any = reflect.getFieldValue<Any>("user")
// 再使用具體的user例項建立出新的操作類
val userReflect = EasyReflect.create(user)
// 最後再讀取InternalUser中的name欄位:
val name:String = userReflect.getFieldValue<String>("name")
複製程式碼
可以看到這種方式還是比較繁瑣的,所以EasyReflect
專門針對此種操作,提供了transform
方法,最終你可以使用下方的程式碼做到與上面同樣的效果:
val name:String = EasyReflect.create(example)
.transform("user")// 使用user變數的資料生成新的reflect例項
.get<String>("name")// 讀取InternalUser中的name資料。
複製程式碼
7. 使用動態代理進行託管管理
所謂的動態代理託管,即是通過動態代理
的方式,將一個代理介面
與實體類
進行繫結,使得可以通過操作繫結的代理類
做到操作對應實體類
的效果
1. 對映代理方法
class Example {
fun function() {}
}
複製程式碼
比如這裡我們需要使用動態代理託管
的方式訪問此function
方法,那麼首先,需要先建立一個代理介面
interface Proxy {
// 與Example.function方法對應,需要同名同引數才能正確對映匹配。
fun function();
}
複製程式碼
現在我們將此代理介面
與對應的被代理例項example
進行繫結:
val proxy:Proxy = EasyReflect.create(example).proxy(Proxy::class.java)
複製程式碼
繫結後即可通過自動建立的代理例項proxy
直接進行操作了:
proxy.function() ==等價於==> example.function()
複製程式碼
2. 對映代理變數
當然,也可以對被代理例項
中的變數進行代理,比如:
class Example {
val user:User = User()
}
複製程式碼
當我們需要對此類中的user
變數進行取值時。可以建立以下幾種的代理方法
- 第一種:方法名為
get
,且引數有且只有一個
,型別為String的:
fun get(name:String):Any?
複製程式碼
- 第二種:建立
getter
方法:
fun getUser():User? {}
複製程式碼
此兩種代理方法
都是等價的,所以通過代理介面進行操作時。他們的效果都是一致的:
proxy.get("user") ==等價於==> example.user
proxy.getUser() ==等價於==> example.user
複製程式碼
同樣的,當我們需要對類中變數進行賦值時,也提供兩種代理方法建立方式
- 第一種:方法名為
set
,且有錢僅有兩個
引數。第一個引數型別為String
:
fun set(name:String, value:Any)
複製程式碼
- 第二種:
setter
方法:
fun setUser(user:User)
複製程式碼
所以,使用此賦值方法的效果為:
proxy.set("user", newUser) ==等價於==> example.user = newUser
proxy.setUser(newUser) ==等價於==> example.user = newUser
複製程式碼
希望這些示例能讓大家在使用時有所幫助~
歡迎掃描下方二維碼關注我的公眾號?