EasyAndroid基礎集成組件庫之:EasyReflect 優雅的反射功能庫

什麼是EasyReflect

EasyReflect是開源基礎組件集成庫EasyAndroid中的基礎組件之一。java

其做用是:對基礎反射api進行封裝,精簡反射操做android

EasyAndroid做爲一款集成組件庫,此庫中所集成的組件,均包含如下特色,你能夠放心使用~~git

1. 設計獨立github

組件間獨立存在,不相互依賴,且若只須要集成庫中的部分組件。也能夠很方便的只copy對應的組件文件進行使用api

2. 設計輕巧數組

由於是組件集成庫,因此要求每一個組件的設計儘可能精練、輕巧。避免由於一個小功能而引入大量無用代碼.bash

每一個組件的方法數均不超過100. 大部分組件甚至不超過50ui

得益於編碼時的高內聚性,若你只須要使用EasyReflect. 那麼能夠直接去拷貝EasyReflect源碼文件到你的項目中,直接進行使用,也是沒問題的。編碼

EasyAndroid開源庫地址:spa

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自己持有兩部分數據:clazzinstance.

  • clazz: 此實例所綁定操做的clazz實例。永不爲null
  • instance: 此實例所綁定的instance實例,爲clazz類型的具體實例。可能爲null。

請注意:對於instance數據來講,當執行操做過程當中須要使用此instance實例時(好比讀取某個成員變量),若此時instance爲null,則將觸發使用默認的空構造器進行instance建立。

2. 建立EasyReflect的幾種姿式

  1. 只使用Class進行建立:建立後只含有clazz數據
val reflect = EasyReflect.create(Test::class.java)
複製代碼
  1. 使用類全名進行建立(可指定ClassLoader):建立後只含有clazz數據
val reflect = EasyReflect.create("com.haoge.sample.Test")
// 也可指定ClassLoader進行加載
val reflect = EasyReflect.create(className, classLoader)
複製代碼
  1. 直接經過指定實例進行建立:建立後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
複製代碼

但願這些示例能讓你們在使用時有所幫助~

歡迎掃描下方二維碼關注個人公衆號👇

相關文章
相關標籤/搜索