add ts-proxy

This commit is contained in:
tursom 2021-08-25 14:31:58 +08:00
parent ebfeba583b
commit eb66444e23
12 changed files with 437 additions and 0 deletions

View File

@ -18,6 +18,7 @@ include("ts-core:ts-yaml")
include("ts-core:ts-json")
include("ts-core:ts-xml")
include("ts-core:ts-async-http")
include("ts-core:ts-proxy")
include("ts-socket")
include("ts-web")
include("ts-web:ts-web-netty")

View File

@ -0,0 +1,17 @@
plugins {
kotlin("jvm")
`maven-publish`
id("ts-gradle")
kotlin("plugin.allopen") version "1.5.21"
}
dependencies {
api(project(":ts-core"))
implementation("cglib:cglib:3.3.0")
implementation("org.apache.commons", "commons-lang3", "3.8.1")
testApi(group = "junit", name = "junit", version = "4.13.2")
}
artifacts {
archives(tasks["kotlinSourcesJar"])
}

View File

@ -0,0 +1,27 @@
package cn.tursom.proxy
class ListProxy<T : ProxyMethod> : MutableProxy<T> {
private val proxyList: MutableList<T> = ArrayList()
override fun addProxy(proxy: T): Int {
proxyList.add(proxy)
return proxyList.size - 1
}
override fun addAllProxy(proxy: Collection<T>?): Boolean {
return proxyList.addAll(proxy!!)
}
override fun removeProxy(proxy: T) {
proxyList.remove(proxy)
}
override fun removeProxy(index: Int) {
proxyList.removeAt(index)
}
override fun iterator(): MutableIterator<T> {
return proxyList.iterator()
}
}

View File

@ -0,0 +1,8 @@
package cn.tursom.proxy
interface MutableProxy<T : ProxyMethod> : Proxy<T> {
fun addProxy(proxy: T): Int
fun addAllProxy(proxy: Collection<T>?): Boolean
fun removeProxy(proxy: T)
fun removeProxy(index: Int)
}

View File

@ -0,0 +1,46 @@
package cn.tursom.proxy
import cn.tursom.core.uncheckedCast
interface Proxy<T : ProxyMethod> : Iterable<T> {
data class Result<out R>(
val result: R,
val success: Boolean = false,
)
companion object {
val failed: Result<*> = Result<Any?>(null, false)
fun <R> of(): Result<R?> {
return of(null)
}
/**
* 返回一个临时使用的 Result 对象
* 因为是临时对象所以不要把这个对象放到任何当前函数堆栈以外的地方
* 如果要长期储存对象请 new Result
*/
fun <R> of(result: R): Result<R> {
return Result(result, true)
}
fun <R> failed(): Result<R> {
return failed.uncheckedCast()
}
}
}
inline fun <T : ProxyMethod> Proxy<T>.forEachProxy(action: (T) -> Unit) {
for (t in this) {
action(t)
}
}
inline fun <R, T : ProxyMethod> Proxy<T>.forFirstProxy(action: (T) -> Proxy.Result<R>?): Proxy.Result<R> {
for (t in this) {
val result = action(t)
if (result != null && result.success) {
return result
}
}
return Proxy.failed()
}

View File

@ -0,0 +1,115 @@
package cn.tursom.proxy
import cn.tursom.proxy.annotation.ForEachProxy
import cn.tursom.proxy.annotation.ForFirstProxy
import net.sf.cglib.proxy.MethodProxy
import org.apache.commons.lang3.StringUtils
import java.lang.reflect.Method
import java.util.*
interface ProxyContainer<T : ProxyMethod> {
@get:Throws(Throwable::class)
val proxy: Proxy<T>
/**
* will be call when proxy method invoke.
* 在代理方法被调用时该方法会被调用
*/
@Throws(Throwable::class)
fun onProxy(method: Method, args: Array<out Any?>, proxy: MethodProxy): Proxy.Result<*>? {
var handler = ProxyContainerHandlerCache.getHandler(method)
if (handler != null) {
return handler(this, method, args, proxy)
}
for (annotation in method.annotations) when (annotation) {
is ForEachProxy -> {
handler = onForeachProxy(annotation)
break
}
is ForFirstProxy -> {
handler = onForFirstProxy(annotation)
break
}
}
if (handler == null) {
handler = ProxyContainerHandlerCache.callSuper
}
ProxyContainerHandlerCache.setHandler(method, handler)
return handler(this, method, args, proxy)
}
companion object {
private val errMsgSearchList = arrayOf("%M", "%B", "%A")
fun onForFirstProxy(forFirstProxy: ForFirstProxy) = { o: Any, m: Method, a: Array<out Any?>, p: MethodProxy ->
onForFirstProxy(o, m, a, p, forFirstProxy, when (forFirstProxy.value.size) {
0 -> emptyList()
1 -> listOf(forFirstProxy.value[0].java)
else -> forFirstProxy.value.asSequence().map { it.java }.toSet()
})
}
private fun onForFirstProxy(
obj: Any,
method: Method,
args: Array<out Any?>,
proxy: MethodProxy?,
forFirstProxy: ForFirstProxy,
classes: Collection<Class<*>>,
): Proxy.Result<*> {
if (obj !is ProxyContainer<*>) return Proxy.failed<Any>()
val result = obj.proxy.forFirstProxy { p ->
if (classes.isEmpty() || classes.stream().anyMatch { c: Class<*> -> c.isInstance(p) }) {
return@forFirstProxy p.onProxy(obj, method, args, proxy)
} else {
return@forFirstProxy Proxy.failed()
}
}
if (result.success) {
return result
}
// when request not handled
if (forFirstProxy.must) {
// generate error message
var errMsg: String = forFirstProxy.errMsg
if (errMsg.isBlank()) {
errMsg = "no proxy handled on method %M"
}
val replacementList = arrayOfNulls<String>(errMsgSearchList.size)
// todo use efficient contains
if (errMsg.contains(errMsgSearchList[0])) {
replacementList[0] = method.toString()
}
if (errMsg.contains(errMsgSearchList[1])) {
replacementList[1] = obj.toString()
}
if (errMsg.contains(errMsgSearchList[2])) {
replacementList[2] = Arrays.toString(args)
}
errMsg = StringUtils.replaceEach(errMsg, errMsgSearchList, replacementList)
throw forFirstProxy.errClass.java.getConstructor(String::class.java).newInstance(errMsg)
}
return Proxy.failed
}
private fun onForeachProxy(forEachProxy: ForEachProxy) = onForeachProxy(when (forEachProxy.value.size) {
0 -> emptyList()
1 -> listOf(forEachProxy.value[0].java)
else -> forEachProxy.value.asSequence().map { it.java }.toSet()
})
private fun onForeachProxy(
classes: Collection<Class<*>>,
) = label@{ o: Any, m: Method, a: Array<out Any?>, proxy1: MethodProxy ->
if (o !is ProxyContainer<*>) return@label Proxy.failed
o.proxy.forEachProxy { p ->
if (classes.isEmpty() || classes.any { c: Class<*> -> c.isInstance(p) }) {
p.onProxy(o, m, a, proxy1)
}
}
Proxy.failed<Any?>()
}
}
}

View File

@ -0,0 +1,22 @@
package cn.tursom.proxy
import net.sf.cglib.proxy.MethodProxy
import java.lang.reflect.Method
import java.util.concurrent.ConcurrentHashMap
object ProxyContainerHandlerCache {
private val handlerMap: MutableMap<Method, (Any, Method, Array<out Any?>, MethodProxy) -> Proxy.Result<Any?>> =
ConcurrentHashMap()
val callSuper = { obj: Any, method: Method, args: Array<out Any?>, proxy: MethodProxy ->
Proxy.of<Any?>(proxy.invokeSuper(obj, args))
}
val empty = { obj: Any, method: Method, args: Array<out Any?>, proxy: MethodProxy -> Proxy.failed<Any?>() }
fun getHandler(method: Method): ((Any, Method, Array<out Any?>, MethodProxy) -> Proxy.Result<Any?>)? {
return handlerMap[method]
}
fun setHandler(method: Method, onProxy: ((Any, Method, Array<out Any?>, MethodProxy) -> Proxy.Result<Any?>)?) {
handlerMap[method] = onProxy ?: callSuper
}
}

View File

@ -0,0 +1,69 @@
package cn.tursom.proxy
import net.sf.cglib.proxy.MethodInterceptor
import net.sf.cglib.proxy.MethodProxy
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.*
class ProxyInterceptor : MethodInterceptor {
companion object {
private val HANDLE_DEQUE_THREAD_LOCAL = ThreadLocal<ArrayDeque<Any>>()
private val parameterTypes = arrayOf(Method::class.java, Array<Any>::class.java, MethodProxy::class.java)
private val parameterTypesField: Field = Method::class.java.getDeclaredField("parameterTypes").apply {
isAccessible = true
}
@Suppress("UNCHECKED_CAST")
private fun getParameterTypes(method: Method): Array<Class<*>> {
return parameterTypesField[method] as Array<Class<*>>
}
fun equalsMethod(method: Method, name: String?, parameterTypes: Array<Class<*>>?): Boolean {
return method.name == name && parameterTypes.contentEquals(getParameterTypes(method))
}
private fun isOnProxyMethod(method: Method): Boolean {
return equalsMethod(method, "onProxy", parameterTypes)
}
private val handleDeque: ArrayDeque<Any>
get() {
var objectArrayDeque = HANDLE_DEQUE_THREAD_LOCAL.get()
if (objectArrayDeque == null) {
objectArrayDeque = ArrayDeque()
HANDLE_DEQUE_THREAD_LOCAL.set(objectArrayDeque)
}
return objectArrayDeque
}
@Suppress("UNCHECKED_CAST")
fun <T> getHandle(): T {
return handleDeque.first as T
}
private fun push(obj: Any) {
handleDeque.push(obj)
}
private fun pop() {
handleDeque.pop()
}
}
@Throws(Throwable::class)
override fun intercept(obj: Any, method: Method, args: Array<out Any?>, proxy: MethodProxy): Any? {
push(obj)
return try {
if (obj is ProxyContainer<*> && !isOnProxyMethod(method)) {
val result = obj.onProxy(method, args, proxy)
if (result != null && result.success) {
return result.result
}
}
proxy.invokeSuper(obj, args)
} finally {
pop()
}
}
}

View File

@ -0,0 +1,71 @@
package cn.tursom.proxy
import cn.tursom.proxy.annotation.ForEachProxy
import cn.tursom.proxy.annotation.ForFirstProxy
import net.sf.cglib.proxy.MethodProxy
import java.lang.reflect.Method
import java.util.concurrent.ConcurrentHashMap
interface ProxyMethod {
@Throws(Throwable::class)
fun onProxy(obj: Any?, method: Method, args: Array<out Any?>, proxy: MethodProxy?): Proxy.Result<*>? {
val selfMethod: Method
val handlerCacheMap = getHandlerCacheMap(javaClass)
val methodResult = handlerCacheMap[method]
if (methodResult != null) {
return if (methodResult.success) {
Proxy.of(methodResult.result(this, *args))
} else {
Proxy.failed<Any>()
}
}
try {
var methodName = method.name
for (annotation in method.annotations) {
if (annotation is ForEachProxy) {
if (annotation.name.isNotEmpty()) {
methodName = annotation.name
break
}
} else if (annotation is ForFirstProxy) {
if (annotation.name.isNotEmpty()) {
methodName = annotation.name
break
}
}
}
selfMethod = javaClass.getMethod(methodName, *method.parameterTypes)
selfMethod.isAccessible = true
handlerCacheMap[method] = Proxy.Result(selfMethod, true)
} catch (e: Exception) {
handlerCacheMap[method] = Proxy.failed()
return Proxy.failed<Any>()
}
return Proxy.of<Any>(selfMethod(this, *args))
}
companion object {
private val handlerCacheMapMap: MutableMap<Class<out ProxyMethod>, MutableMap<Method, Proxy.Result<Method>>> =
HashMap()
fun getHandlerCacheMap(type: Class<out ProxyMethod>): MutableMap<Method, Proxy.Result<Method>> {
var handlerCacheMap = handlerCacheMapMap[type]
if (handlerCacheMap == null) synchronized(handlerCacheMapMap) {
handlerCacheMap = handlerCacheMapMap[type]
if (handlerCacheMap == null) {
handlerCacheMap = ConcurrentHashMap()
handlerCacheMapMap[type] = handlerCacheMap!!
}
}
return handlerCacheMap!!
}
fun getProxyMethod(clazz: Class<*>, name: String, vararg parameterTypes: Class<*>): Method? {
return try {
clazz.getDeclaredMethod(name, *parameterTypes)
} catch (e: NoSuchMethodException) {
throw RuntimeException(e)
}
}
}
}

View File

@ -0,0 +1,7 @@
package cn.tursom.proxy.annotation
import kotlin.reflect.KClass
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class ForEachProxy(vararg val value: KClass<*> = [], val name: String = "")

View File

@ -0,0 +1,17 @@
package cn.tursom.proxy.annotation
import kotlin.reflect.KClass
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class ForFirstProxy(
vararg val value: KClass<*> = [],
val name: String = "",
/**
* proxy objs must handle this method
* or will throw an exception
*/
val must: Boolean = false,
val errMsg: String = "",
val errClass: KClass<out RuntimeException> = RuntimeException::class,
)

View File

@ -0,0 +1,37 @@
package cn.tursom.proxy
import cn.tursom.proxy.Example.GetA
import cn.tursom.proxy.annotation.ForEachProxy
import net.sf.cglib.proxy.Enhancer
import org.junit.Test
class Example {
open class TestClass protected constructor() : ProxyContainer<ProxyMethod> {
@get:ForEachProxy
open val a: Int = 0
override val proxy = ListProxy<ProxyMethod>()
}
fun interface GetA : ProxyMethod {
fun getA(): Int
}
private val enhancer = Enhancer()
init {
enhancer.setSuperclass(TestClass::class.java)
enhancer.setCallback(ProxyInterceptor())
}
@Test
fun test() {
val testClass = enhancer.create() as TestClass
testClass.proxy.addProxy(GetA {
println("on proxy method")
0
})
println(testClass.javaClass)
println(testClass.a)
}
}