이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2018-03-16
일반적 상식으로는 final 변경자가 달린 변수는 값을 설정할 수 없다.
하지만 Reflection 을 잘 활용하면 final 변경자가 달린 변수에 멀쩡하게 값을 설정할 수 있다.
이 과정을 위해 필요한 것은 다음과 같다.
val field = findField(receiver.javaClass, fieldName) ?: return
private fun findField(cls: Class<*>, fieldName: String): Field? {
// Locate the field going back to parent.
// for extension uses.
// 확장성을 위해 부모 클래스까지 거슬러 올라가 필드를 찾음
var targetClass = cls
while (true) {
try {
val field = targetClass.getDeclaredField(fieldName)
// getDeclaredField checks for non-public scopes as well and it returns accurate results
// public 범위가 아닌 declaredField를 체크하고, 정확한 결과를 반환한다.
if (!Modifier.isPublic(field.modifiers)) {
field.isAccessible = true
}
return field
} catch (ex: NoSuchFieldException) {
// ignored
}
targetClass = cls.superclass
}
}
이미 필드 이름이 주어져 있으므로, 해당 클래스의 Class 객체에서 getDeclaredField 를 사용하면 된다.
만일 필드 이름이 public 가 아닌 경우에는 accessible 를 설정한다.
private fun removeFinalModifierAndSet(receiver: Any, field: Field, value: Any) {
try {
if (Modifier.isFinal(field.modifiers)) {
val modifiersField = Field::class.java.getDeclaredField("modifiers")
if (!modifiersField.isAccessible) {
modifiersField.isAccessible = true
}
try {
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
} finally {
if (!modifiersField.isAccessible) {
modifiersField.isAccessible = false
}
}
}
} catch (ex: NoSuchFieldException) {
// ignored
} catch (ex: IllegalAccessException) {
// ignored
}
}
Field 클래스에 선언된 modifiers 라는 필드가 public 가 아닐 경우 다시 한번 accessible를 설정하고, field.modifiers & ~Modifier.FINAL
를 적용한다.
여기서 사용된 비트 연산자는 & 연산자, ~ 연산자가 있는데, &는 그렇다 쳐도 ~는 잘 모르고 사용했던 기억이 있기 때문에 한번 정리해보려고 한다.
~ 연산자란?
~ 연산자, 비트 연산자, Bitwise operator라고 불리는 이 연산자는 bit 값이 1일 경우 0, 0일 경우 1로 하는 연산자이다. Kotlin에서는 inv() 메서드로 사용된다.
예를 들자면, 큿 의 사이즈를 바이너리 값으로 표현해보자.
72 = 01001000
그렇다면, 이 72에 ~를 적용해보자.
~72 = 10110111 = 183
이런 식으로 비트 단위로 직접 반대로 설정하게 하여 Final 값만 제거하고 설정하는 것이다.
if (Modifier.isPrivate(field.modifiers)) {
field.isAccessible = true
}
try {
field.set(receiver, value)
} finally {
if (Modifier.isPrivate(field.modifiers)) {
field.isAccessible = false
}
}
다시 한번, field 가 private 면 accessible를 true로 설정하고, 해당 필드에 value를 적용한다.
전체 코드는 다음과 같다.
package pyxis.uzuki.live.attribute.parser
import java.lang.reflect.Field
import java.lang.reflect.Modifier
/**
* FieldModifier will execute extremely evil things...
*
* 1. Find fields in Class<*> (in demo, it will be StyleViewAttributes)
* 2. remove final modifier of field
* 3. write value into field
*
*
* reference: https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java
*/
object FieldModifier {
@JvmStatic
fun process(receiver: Any, fieldName: String, value: Any) {
// It can be nullable cause field doesn't exists.
// for extension uses.
val field = findField(receiver.javaClass, fieldName) ?: return
removeFinalModifierAndSet(receiver, field, value)
}
private fun findField(cls: Class<*>, fieldName: String): Field? {
// Locate the field going back to parent.
// for extension uses.
var targetClass = cls
while (true) {
try {
val field = targetClass.getDeclaredField(fieldName)
// getDeclaredField checks for non-public scopes as well and it returns accurate results
if (!Modifier.isPublic(field.modifiers)) {
field.isAccessible = true
}
return field
} catch (ex: NoSuchFieldException) {
// ignored
}
targetClass = cls.superclass
}
}
private fun removeFinalModifierAndSet(receiver: Any, field: Field, value: Any) {
try {
if (Modifier.isFinal(field.modifiers)) {
val modifiersField = Field::class.java.getDeclaredField("modifiers")
if (!modifiersField.isAccessible) {
modifiersField.isAccessible = true
}
try {
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
} finally {
if (!modifiersField.isAccessible) {
modifiersField.isAccessible = false
}
}
}
if (Modifier.isPrivate(field.modifiers)) {
field.isAccessible = true
}
try {
field.set(receiver, value)
} finally {
if (Modifier.isPrivate(field.modifiers)) {
field.isAccessible = false
}
}
} catch (ex: NoSuchFieldException) {
// ignored
} catch (ex: IllegalAccessException) {
// ignored
}
}
}
사용할 때에는 FieldModifier.process(this, "test", "abc");
이렇게 사용하면 된다.
물론, 실제로 사용할 일이 없어야 하는 것은 맞지만서도(..)