mirror of
https://github.com/tursom/TursomServer.git
synced 2025-03-30 14:11:11 +08:00
add ts-proxy
This commit is contained in:
parent
ebfeba583b
commit
eb66444e23
@ -18,6 +18,7 @@ include("ts-core:ts-yaml")
|
|||||||
include("ts-core:ts-json")
|
include("ts-core:ts-json")
|
||||||
include("ts-core:ts-xml")
|
include("ts-core:ts-xml")
|
||||||
include("ts-core:ts-async-http")
|
include("ts-core:ts-async-http")
|
||||||
|
include("ts-core:ts-proxy")
|
||||||
include("ts-socket")
|
include("ts-socket")
|
||||||
include("ts-web")
|
include("ts-web")
|
||||||
include("ts-web:ts-web-netty")
|
include("ts-web:ts-web-netty")
|
||||||
|
17
ts-core/ts-proxy/build.gradle.kts
Normal file
17
ts-core/ts-proxy/build.gradle.kts
Normal 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"])
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
46
ts-core/ts-proxy/src/main/kotlin/cn/tursom/proxy/Proxy.kt
Normal file
46
ts-core/ts-proxy/src/main/kotlin/cn/tursom/proxy/Proxy.kt
Normal 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()
|
||||||
|
}
|
@ -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?>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 = "")
|
@ -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,
|
||||||
|
)
|
37
ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt
Normal file
37
ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user