EasyAndroid基礎整合元件庫之:EasyReflect 優雅的反射功能庫

Haoge發表於2018-06-11

什麼是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本身持有兩部分資料: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
複製程式碼

希望這些示例能讓大家在使用時有所幫助~

歡迎掃描下方二維碼關注我的公眾號?

EasyAndroid基礎整合元件庫之:EasyReflect 優雅的反射功能庫

相關文章