mirror of
https://github.com/tursom/TursomServer.git
synced 2024-12-27 13:20:35 +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-xml")
|
||||
include("ts-core:ts-async-http")
|
||||
include("ts-core:ts-proxy")
|
||||
include("ts-socket")
|
||||
include("ts-web")
|
||||
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