Merge branch 'dev' of github.com:synebula-myths/gaea into dev
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
.*
|
.*
|
||||||
gradlew*
|
gradlew*
|
||||||
build
|
build
|
||||||
gradle
|
|
||||||
|
|
||||||
!.gitignore
|
!.gitignore
|
||||||
27
build.gradle
27
build.gradle
@@ -1,11 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
kotlin_version = '1.3.72'
|
kotlin_version = '1.6.10'
|
||||||
|
spring_version = "2.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,41 +15,33 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
|
||||||
group 'com.synebula'
|
|
||||||
version version
|
|
||||||
}
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
ext {
|
group 'com.synebula'
|
||||||
version '0.13.1'
|
version '1.3.0'
|
||||||
spring_version = "2.3.0.RELEASE"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'idea'
|
apply plugin: 'idea'
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'maven'
|
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
testApi group: 'junit', name: 'junit', version: '4.12'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Mon May 18 17:21:26 CST 2020
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
rootProject.name = 'myths.gaea'
|
rootProject.name = 'gaea'
|
||||||
include 'src:gaea'
|
include 'src:gaea'
|
||||||
include 'src:gaea.app'
|
include 'src:gaea.app'
|
||||||
include 'src:gaea.mongo'
|
include 'src:gaea.mongodb'
|
||||||
|
include 'src:gaea.spring'
|
||||||
|
include 'src:gaea.spring'
|
||||||
|
findProject(':src:gaea.spring')?.name = 'gaea.spring'
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,21 @@ buildscript {
|
|||||||
apply plugin: 'kotlin-spring'
|
apply plugin: 'kotlin-spring'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(":src:gaea")
|
api project(":src:gaea")
|
||||||
compile("org.springframework.boot:spring-boot-starter-web:$spring_version")
|
api project(":src:gaea.spring")
|
||||||
compile("org.springframework.boot:spring-boot-starter-aop:$spring_version")
|
|
||||||
compile("org.springframework.boot:spring-boot-starter-mail:$spring_version")
|
api("org.springframework.boot:spring-boot-starter-web:$spring_version")
|
||||||
compile("org.springframework.boot:spring-boot-starter-security:$spring_version")
|
api("org.springframework.boot:spring-boot-starter-mail:$spring_version")
|
||||||
compile group: 'net.sf.dozer', name: 'dozer', version: '5.5.1'
|
api("org.springframework.boot:spring-boot-starter-security:$spring_version")
|
||||||
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0'
|
api group: 'net.sf.dozer', name: 'dozer', version: '5.5.1'
|
||||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
api group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0'
|
||||||
compile group: 'com.google.guava', name: 'guava', version: '30.1.1-jre'
|
api group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||||
compile group: 'com.auth0', name: 'java-jwt', version: '3.14.0'
|
api group: 'com.auth0', name: 'java-jwt', version: '3.14.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
publish(MavenPublication) {
|
publish(MavenPublication) {
|
||||||
group 'com.synebula'
|
|
||||||
artifactId 'gaea.app'
|
|
||||||
version "$version"
|
|
||||||
from components.java
|
from components.java
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ import javax.annotation.Resource
|
|||||||
* @param query 业务查询服务
|
* @param query 业务查询服务
|
||||||
* @param logger 日志组件
|
* @param logger 日志组件
|
||||||
*/
|
*/
|
||||||
open class Application<TCommand : ICommand, TView, TKey>(
|
open class Application<TCommand : ICommand, TView, ID>(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var clazz: Class<TView>,
|
override var service: IService<ID>,
|
||||||
override var service: IService<TKey>,
|
override var query: IQuery<TView, ID>,
|
||||||
override var query: IQuery,
|
override var logger: ILogger,
|
||||||
override var logger: ILogger?
|
) : ICommandApp<TCommand, ID>, IQueryApp<TView, ID> {
|
||||||
) : ICommandApp<TCommand, TKey>, IQueryApp<TView, TKey> {
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
override var jsonSerializer: IJsonSerializer? = null
|
override var jsonSerializer: IJsonSerializer? = null
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.synebula.gaea.app
|
package com.synebula.gaea.app
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.log.ILogger
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
@@ -16,7 +16,7 @@ interface IApplication {
|
|||||||
/**
|
/**
|
||||||
* 日志组件
|
* 日志组件
|
||||||
*/
|
*/
|
||||||
var logger: ILogger?
|
var logger: ILogger
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,11 +26,11 @@ interface IApplication {
|
|||||||
val msg = HttpMessage()
|
val msg = HttpMessage()
|
||||||
try {
|
try {
|
||||||
process(msg)
|
process(msg)
|
||||||
logger?.debug(this, "$name business execute success")
|
logger.debug(this, "$name business execute success")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
msg.status = Status.Error
|
msg.status = Status.Error
|
||||||
msg.message = if (error.isEmpty()) ex.message ?: "" else "$error: ${ex.message}"
|
msg.message = if (error.isEmpty()) ex.message ?: "" else "$error: ${ex.message}"
|
||||||
logger?.error(this, ex, "[$name]$error: ${ex.message}")
|
logger.error(this, ex, "[$name]$error: ${ex.message}")
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
@@ -42,9 +42,9 @@ interface IApplication {
|
|||||||
val msg = HttpMessage()
|
val msg = HttpMessage()
|
||||||
try {
|
try {
|
||||||
process(msg)
|
process(msg)
|
||||||
logger?.debug(this, "$name business execute success")
|
logger.debug(this, "$name business execute success")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logger?.error(this, ex, "[$name]$error。异常消息将抛出!: ${ex.message}")
|
logger.error(this, ex, "[$name]$error。异常消息将抛出!: ${ex.message}")
|
||||||
throw RuntimeException(error, ex)
|
throw RuntimeException(error, ex)
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
@@ -59,12 +59,12 @@ interface IApplication {
|
|||||||
val authentication = SecurityContextHolder.getContext().authentication.principal.toString()
|
val authentication = SecurityContextHolder.getContext().authentication.principal.toString()
|
||||||
try {
|
try {
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
return gson.fromJson<T>(authentication, clazz)
|
return gson.fromJson(authentication, clazz)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logger?.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}")
|
logger.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}")
|
||||||
}
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logger?.error(this, ex, "[$name]获取用户信息异常!${ex.message}")
|
logger.error(this, ex, "[$name]获取用户信息异常!${ex.message}")
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
package com.synebula.gaea.app
|
package com.synebula.gaea.app
|
||||||
|
|
||||||
import com.synebula.gaea.app.cmd.ILazyCommandApp
|
import com.synebula.gaea.app.cmd.ISimpleCommandApp
|
||||||
import com.synebula.gaea.app.query.IQueryApp
|
import com.synebula.gaea.app.query.IQueryApp
|
||||||
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
||||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
import com.synebula.gaea.domain.service.ILazyService
|
import com.synebula.gaea.domain.service.ISimpleService
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.log.ILogger
|
||||||
import com.synebula.gaea.query.IQuery
|
import com.synebula.gaea.query.IQuery
|
||||||
import javax.annotation.Resource
|
import javax.annotation.Resource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 联合服务,同时实现了ILazyCommandApp和IQueryApp接口
|
* 简单的服务, 取消了Command对象
|
||||||
*
|
*
|
||||||
* @param name 业务名称
|
* @param name 业务名称
|
||||||
* @param service 业务domain服务
|
* @param service 业务domain服务
|
||||||
* @param query 业务查询服务
|
* @param query 业务查询服务
|
||||||
* @param logger 日志组件
|
* @param logger 日志组件
|
||||||
*/
|
*/
|
||||||
open class LazyApplication<TRoot : IAggregateRoot<TKey>, TKey>(
|
open class SimpleApplication<TRoot : IAggregateRoot<ID>, ID>(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var clazz: Class<TRoot>, //view class type
|
override var service: ISimpleService<TRoot, ID>,
|
||||||
override var service: ILazyService<TRoot, TKey>,
|
override var query: IQuery<TRoot, ID>,
|
||||||
override var query: IQuery,
|
override var logger: ILogger,
|
||||||
override var logger: ILogger?
|
) : ISimpleCommandApp<TRoot, ID>, IQueryApp<TRoot, ID> {
|
||||||
) : ILazyCommandApp<TRoot, TKey>, IQueryApp<TRoot, TKey> {
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
override var jsonSerializer: IJsonSerializer? = null
|
override var jsonSerializer: IJsonSerializer? = null
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.synebula.gaea.app.autoconfig.service
|
||||||
|
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Factory
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Proxy
|
||||||
|
import org.springframework.beans.factory.BeanFactory
|
||||||
|
|
||||||
|
class ServiceFactory(
|
||||||
|
supertype: Class<*>,
|
||||||
|
var beanFactory: BeanFactory,
|
||||||
|
) : Factory(supertype) {
|
||||||
|
override fun createProxy(): Proxy {
|
||||||
|
return ServiceProxy(supertype, this.beanFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.synebula.gaea.app.autoconfig.service
|
||||||
|
|
||||||
|
import com.synebula.gaea.bus.IBus
|
||||||
|
import com.synebula.gaea.data.serialization.IObjectMapper
|
||||||
|
import com.synebula.gaea.domain.repository.IRepository
|
||||||
|
import com.synebula.gaea.domain.repository.IRepositoryFactory
|
||||||
|
import com.synebula.gaea.domain.service.Domain
|
||||||
|
import com.synebula.gaea.domain.service.IService
|
||||||
|
import com.synebula.gaea.domain.service.Service
|
||||||
|
import com.synebula.gaea.exception.NoticeUserException
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Proxy
|
||||||
|
import org.springframework.beans.factory.BeanFactory
|
||||||
|
import org.springframework.core.ResolvableType
|
||||||
|
import java.io.InvalidClassException
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
class ServiceProxy(
|
||||||
|
private var supertype: Class<*>,
|
||||||
|
private var beanFactory: BeanFactory
|
||||||
|
) : Proxy() {
|
||||||
|
|
||||||
|
private var service: IService<*>
|
||||||
|
|
||||||
|
init {
|
||||||
|
// 如果没有实现类, 使用Service类代理
|
||||||
|
// 如果没有实现类并且没有ServiceDependency注解, 则抛出异常
|
||||||
|
if (!this.supertype.declaredAnnotations.any { it.annotationClass == Domain::class }) {
|
||||||
|
throw InvalidClassException(
|
||||||
|
"interface ${this.supertype.name} must has implementation class or annotation by ${Domain::class.qualifiedName}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val domain = this.supertype.getDeclaredAnnotation(Domain::class.java)
|
||||||
|
|
||||||
|
// repository工厂对象
|
||||||
|
val defaultRepositoryFactory = this.beanFactory.getBean(IRepositoryFactory::class.java)
|
||||||
|
val mapper = this.beanFactory.getBean(IObjectMapper::class.java)
|
||||||
|
|
||||||
|
val constructor = Service::class.java.getConstructor(
|
||||||
|
Class::class.java, IRepository::class.java, IObjectMapper::class.java
|
||||||
|
)
|
||||||
|
this.service =
|
||||||
|
constructor.newInstance(
|
||||||
|
domain.clazz.java,
|
||||||
|
defaultRepositoryFactory.createRawRepository(domain.clazz.java),
|
||||||
|
mapper
|
||||||
|
)
|
||||||
|
|
||||||
|
// 尝试注入IBus对象
|
||||||
|
val bus = Service::class.java.getDeclaredField("bus")
|
||||||
|
val iBusObjectProvider = this.beanFactory.getBeanProvider<IBus<*>>(ResolvableType.forField(bus))
|
||||||
|
iBusObjectProvider.ifAvailable { busBean ->
|
||||||
|
bus.isAccessible = true
|
||||||
|
bus.set(this.service, busBean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代理方法
|
||||||
|
*
|
||||||
|
* @param proxy 代理对象
|
||||||
|
* @param method 需要执行的方法
|
||||||
|
* @param args 参数列表
|
||||||
|
* @return 方法执行结果
|
||||||
|
*/
|
||||||
|
override fun exec(proxy: Any, method: Method, args: Array<Any>): Any? {
|
||||||
|
try {
|
||||||
|
val proxyMethod = this.service.javaClass.getMethod(method.name, *method.parameterTypes)
|
||||||
|
return proxyMethod.invoke(this.service, *args)
|
||||||
|
} catch (ex: NoSuchMethodException) {
|
||||||
|
throw NoSuchMethodException("method [${method.toGenericString()}] not implements in class [${this.service::class.java}], you must implements interface [${this.supertype.name}] ")
|
||||||
|
} catch (ex: InvocationTargetException) {
|
||||||
|
if (ex.cause is Error || ex.cause is NoticeUserException) {
|
||||||
|
throw ex.targetException!!
|
||||||
|
}
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.synebula.gaea.app.autoconfig.service
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.service.IService
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Register
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition
|
||||||
|
import org.springframework.core.annotation.AnnotationAttributes
|
||||||
|
import org.springframework.core.type.AnnotationMetadata
|
||||||
|
|
||||||
|
class ServiceRegister : Register() {
|
||||||
|
override fun scan(metadata: AnnotationMetadata): Map<String, BeanDefinition> {
|
||||||
|
val result = mutableMapOf<String, BeanDefinition>()
|
||||||
|
|
||||||
|
// 获取注解参数信息:basePackages
|
||||||
|
val attributes = AnnotationAttributes(
|
||||||
|
metadata.getAnnotationAttributes(
|
||||||
|
ServiceScan::class.java.name
|
||||||
|
) ?: mapOf()
|
||||||
|
)
|
||||||
|
val basePackages = attributes.getStringArray("basePackages")
|
||||||
|
val beanDefinitions = this.doScan(basePackages, arrayOf(this.interfaceFilter(arrayOf(IService::class.java))))
|
||||||
|
beanDefinitions.forEach { beanDefinition ->
|
||||||
|
// 获取实际的bean类型
|
||||||
|
val beanClazz: Class<*> = try {
|
||||||
|
Class.forName(beanDefinition.beanClassName)
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取实际继承类型
|
||||||
|
val implBeanDefinitions = this.doScan(basePackages, arrayOf(this.interfaceFilter(arrayOf(beanClazz))))
|
||||||
|
if (implBeanDefinitions.isNotEmpty()) {
|
||||||
|
implBeanDefinitions.forEach {
|
||||||
|
result[it.beanClassName!!] = it
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 构造BeanDefinition
|
||||||
|
val builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz)
|
||||||
|
builder.addConstructorArgValue(beanClazz)
|
||||||
|
builder.addConstructorArgValue(this._beanFactory)
|
||||||
|
val definition = builder.rawBeanDefinition as GenericBeanDefinition
|
||||||
|
definition.beanClass = ServiceFactory::class.java
|
||||||
|
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
|
||||||
|
result[beanClazz.name] = definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.synebula.gaea.app.autoconfig.service
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@MustBeDocumented
|
||||||
|
@Inherited
|
||||||
|
@Import(ServiceRegister::class)
|
||||||
|
annotation class ServiceScan(val basePackages: Array<String> = [])
|
||||||
@@ -13,11 +13,11 @@ import javax.annotation.Resource
|
|||||||
* @param service 业务domain服务
|
* @param service 业务domain服务
|
||||||
* @param logger 日志组件
|
* @param logger 日志组件
|
||||||
*/
|
*/
|
||||||
open class CommandApp<TCommand : ICommand, TKey>(
|
open class CommandApp<TCommand : ICommand, ID>(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var service: IService<TKey>,
|
override var service: IService<ID>,
|
||||||
override var logger: ILogger?
|
override var logger: ILogger,
|
||||||
) : ICommandApp<TCommand, TKey> {
|
) : ICommandApp<TCommand, ID> {
|
||||||
@Resource
|
@Resource
|
||||||
override var jsonSerializer: IJsonSerializer? = null
|
override var jsonSerializer: IJsonSerializer? = null
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.synebula.gaea.app.cmd
|
package com.synebula.gaea.app.cmd
|
||||||
|
|
||||||
import com.synebula.gaea.app.IApplication
|
import com.synebula.gaea.app.IApplication
|
||||||
import com.synebula.gaea.app.component.aop.annotation.MethodName
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
||||||
import com.synebula.gaea.domain.service.ICommand
|
import com.synebula.gaea.domain.service.ICommand
|
||||||
import com.synebula.gaea.domain.service.IService
|
import com.synebula.gaea.domain.service.IService
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Method
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,27 +16,27 @@ import org.springframework.web.bind.annotation.*
|
|||||||
* @version 0.1
|
* @version 0.1
|
||||||
* @since 2020-05-15
|
* @since 2020-05-15
|
||||||
*/
|
*/
|
||||||
interface ICommandApp<TCommand : ICommand, TKey> : IApplication {
|
interface ICommandApp<TCommand : ICommand, ID> : IApplication {
|
||||||
var jsonSerializer: IJsonSerializer?
|
var jsonSerializer: IJsonSerializer?
|
||||||
|
|
||||||
var service: IService<TKey>
|
var service: IService<ID>
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@MethodName("添加")
|
@Method("添加")
|
||||||
fun add(@RequestBody command: TCommand): HttpMessage {
|
fun add(@RequestBody command: TCommand): HttpMessage {
|
||||||
return HttpMessage(service.add(command))
|
return HttpMessage(service.add(command))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id:.+}")
|
@PutMapping("/{id:.+}")
|
||||||
@MethodName("更新")
|
@Method("更新")
|
||||||
fun update(@PathVariable id: TKey, @RequestBody command: TCommand): HttpMessage {
|
fun update(@PathVariable id: ID, @RequestBody command: TCommand): HttpMessage {
|
||||||
this.service.update(id, command)
|
this.service.update(id, command)
|
||||||
return HttpMessage()
|
return HttpMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id:.+}")
|
@DeleteMapping("/{id:.+}")
|
||||||
@MethodName("删除")
|
@Method("删除")
|
||||||
fun remove(@PathVariable id: TKey): HttpMessage {
|
fun remove(@PathVariable id: ID): HttpMessage {
|
||||||
val msg = HttpMessage()
|
val msg = HttpMessage()
|
||||||
try {
|
try {
|
||||||
msg.data = this.service.remove(id)
|
msg.data = this.service.remove(id)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.synebula.gaea.app.cmd
|
package com.synebula.gaea.app.cmd
|
||||||
|
|
||||||
import com.synebula.gaea.app.IApplication
|
import com.synebula.gaea.app.IApplication
|
||||||
import com.synebula.gaea.app.component.aop.annotation.MethodName
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
||||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
import com.synebula.gaea.domain.service.ILazyService
|
import com.synebula.gaea.domain.service.ISimpleService
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Method
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,27 +16,27 @@ import org.springframework.web.bind.annotation.*
|
|||||||
* @version 0.1
|
* @version 0.1
|
||||||
* @since 2020-05-15
|
* @since 2020-05-15
|
||||||
*/
|
*/
|
||||||
interface ILazyCommandApp<TRoot : IAggregateRoot<TKey>, TKey> : IApplication {
|
interface ISimpleCommandApp<TRoot : IAggregateRoot<ID>, ID> : IApplication {
|
||||||
var jsonSerializer: IJsonSerializer?
|
var jsonSerializer: IJsonSerializer?
|
||||||
|
|
||||||
var service: ILazyService<TRoot, TKey>
|
var service: ISimpleService<TRoot, ID>
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@MethodName("添加")
|
@Method("添加")
|
||||||
fun add(@RequestBody entity: TRoot): HttpMessage {
|
fun add(@RequestBody entity: TRoot): HttpMessage {
|
||||||
return HttpMessage(service.add(entity))
|
return HttpMessage(service.add(entity))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id:.+}")
|
@PutMapping("/{id:.+}")
|
||||||
@MethodName("更新")
|
@Method("更新")
|
||||||
fun update(@PathVariable id: TKey, @RequestBody entity: TRoot): HttpMessage {
|
fun update(@PathVariable id: ID, @RequestBody entity: TRoot): HttpMessage {
|
||||||
this.service.update(id, entity)
|
this.service.update(id, entity)
|
||||||
return HttpMessage()
|
return HttpMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id:.+}")
|
@DeleteMapping("/{id:.+}")
|
||||||
@MethodName("删除")
|
@Method("删除")
|
||||||
fun remove(@PathVariable id: TKey): HttpMessage {
|
fun remove(@PathVariable id: ID): HttpMessage {
|
||||||
val msg = HttpMessage()
|
val msg = HttpMessage()
|
||||||
try {
|
try {
|
||||||
msg.data = this.service.remove(id)
|
msg.data = this.service.remove(id)
|
||||||
@@ -2,9 +2,7 @@ package com.synebula.gaea.app.cmd
|
|||||||
|
|
||||||
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
||||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
import com.synebula.gaea.domain.service.ICommand
|
import com.synebula.gaea.domain.service.ISimpleService
|
||||||
import com.synebula.gaea.domain.service.ILazyService
|
|
||||||
import com.synebula.gaea.domain.service.IService
|
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.log.ILogger
|
||||||
import javax.annotation.Resource
|
import javax.annotation.Resource
|
||||||
|
|
||||||
@@ -15,11 +13,11 @@ import javax.annotation.Resource
|
|||||||
* @param service 业务domain服务
|
* @param service 业务domain服务
|
||||||
* @param logger 日志组件
|
* @param logger 日志组件
|
||||||
*/
|
*/
|
||||||
open class LazyCommandApp<TRoot : IAggregateRoot<TKey>, TKey>(
|
open class SimpleCommandApp<TRoot : IAggregateRoot<ID>, ID>(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var service: ILazyService<TRoot, TKey>,
|
override var service: ISimpleService<TRoot, ID>,
|
||||||
override var logger: ILogger?
|
override var logger: ILogger,
|
||||||
) : ILazyCommandApp<TRoot, TKey> {
|
) : ISimpleCommandApp<TRoot, ID> {
|
||||||
@Resource
|
@Resource
|
||||||
override var jsonSerializer: IJsonSerializer? = null
|
override var jsonSerializer: IJsonSerializer? = null
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.synebula.gaea.app.component
|
package com.synebula.gaea.app.component
|
||||||
|
|
||||||
import com.synebula.gaea.data.IObjectConverter
|
import com.synebula.gaea.data.serialization.IObjectMapper
|
||||||
import org.dozer.DozerBeanMapper
|
import org.dozer.DozerBeanMapper
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class DozerConverter : IObjectConverter {
|
class DozerConverter : IObjectMapper {
|
||||||
private val converter = DozerBeanMapper()
|
private val converter = DozerBeanMapper()
|
||||||
|
|
||||||
override fun <T> convert(src: Any, dest: Class<T>): T = converter.map(src, dest)
|
override fun <T> deserialize(src: Any, targetClass: Class<T>): T = converter.map(src, targetClass)
|
||||||
}
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.synebula.gaea.app.component
|
|
||||||
|
|
||||||
import com.google.common.eventbus.AsyncEventBus
|
|
||||||
import com.google.common.eventbus.EventBus
|
|
||||||
import com.synebula.gaea.event.IEvent
|
|
||||||
import com.synebula.gaea.event.IEventBus
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class EventBus : IEventBus {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步事件总线
|
|
||||||
*/
|
|
||||||
var eventBus = EventBus()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步事件总线
|
|
||||||
*/
|
|
||||||
var asyncEventBus = AsyncEventBus(Executors.newFixedThreadPool(2))
|
|
||||||
|
|
||||||
override fun register(obj: Any) {
|
|
||||||
eventBus.register(obj)
|
|
||||||
asyncEventBus.register(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun unregister(obj: Any) {
|
|
||||||
eventBus.unregister(obj)
|
|
||||||
asyncEventBus.unregister(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun publish(event: IEvent) {
|
|
||||||
eventBus.post(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun publishAsync(event: IEvent) {
|
|
||||||
asyncEventBus.post(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
/**
|
/**
|
||||||
* 使用 log4j進行日志记录。
|
* 使用 log4j進行日志记录。
|
||||||
*
|
*
|
||||||
* @author reize
|
* @author alex
|
||||||
* @version 0.0.1
|
* @version 0.0.1
|
||||||
* @since 2016年9月18日 下午2:13:43
|
* @since 2016年9月18日 下午2:13:43
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.synebula.gaea.app.component.aop.annotation
|
|
||||||
|
|
||||||
import com.synebula.gaea.app.component.aop.handler.AnnotationHandler
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.ANNOTATION_CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class Handler(val value: KClass<out AnnotationHandler>)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.synebula.gaea.app.component.aop.annotation
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模块的业务名称
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class ModuleName(val value: String)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.synebula.gaea.app.component.aop.handler
|
|
||||||
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
|
|
||||||
interface AnnotationHandler {
|
|
||||||
fun handle(clazz: Class<Any>, func: Method, args: Array<Any>, exception: Exception? = null)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.synebula.gaea.app.component.bus
|
||||||
|
|
||||||
|
import com.synebula.gaea.bus.Bus
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class EventBus : Bus<Any>()
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.synebula.gaea.app.component
|
package com.synebula.gaea.app.component.bus
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe
|
import com.synebula.gaea.bus.IBus
|
||||||
import com.synebula.gaea.event.IEventBus
|
import com.synebula.gaea.bus.Subscribe
|
||||||
import org.springframework.beans.BeansException
|
import org.springframework.beans.BeansException
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor
|
import org.springframework.beans.factory.config.BeanPostProcessor
|
||||||
@@ -14,7 +14,7 @@ class EventBusSubscriberProcessor : BeanPostProcessor {
|
|||||||
|
|
||||||
// 事件总线bean由Spring IoC容器负责创建,这里只需要通过@Autowired注解注入该bean即可使用事件总线
|
// 事件总线bean由Spring IoC容器负责创建,这里只需要通过@Autowired注解注入该bean即可使用事件总线
|
||||||
@Autowired
|
@Autowired
|
||||||
var eventBus: IEventBus? = null
|
var bus: IBus<Any>? = null
|
||||||
|
|
||||||
@Throws(BeansException::class)
|
@Throws(BeansException::class)
|
||||||
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
|
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
|
||||||
@@ -28,18 +28,21 @@ class EventBusSubscriberProcessor : BeanPostProcessor {
|
|||||||
val methods: Array<Method> = bean.javaClass.methods
|
val methods: Array<Method> = bean.javaClass.methods
|
||||||
for (method in methods) {
|
for (method in methods) {
|
||||||
// check the annotations on that method
|
// check the annotations on that method
|
||||||
val annotations: Array<Annotation> = method.getAnnotations()
|
val annotations: Array<Annotation> = method.annotations
|
||||||
for (annotation in annotations) {
|
for (annotation in annotations) {
|
||||||
// if it contains the Subscribe annotation
|
// if it contains Subscribe annotation
|
||||||
if (annotation.annotationClass == Subscribe::class) {
|
if (annotation.annotationClass == Subscribe::class) {
|
||||||
// 如果这是一个Guava @Subscribe注解的事件监听器方法,说明所在bean实例
|
// 如果这是一个Guava @Subscribe注解的事件监听器方法,说明所在bean实例
|
||||||
// 对应一个Guava事件监听器类,将该bean实例注册到Guava事件总线
|
// 对应一个Guava事件监听器类,将该bean实例注册到Guava事件总线
|
||||||
eventBus?.register(bean)
|
val subscribe = annotation as Subscribe
|
||||||
|
if (subscribe.topics.isEmpty())
|
||||||
|
bus?.register(bean, method)
|
||||||
|
else
|
||||||
|
bus?.register(subscribe.topics, bean, method)
|
||||||
return bean
|
return bean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bean
|
return bean
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ object Excel {
|
|||||||
//声明列对象
|
//声明列对象
|
||||||
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
|
// 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制
|
||||||
var row = sheet.createRow(0)
|
var row = sheet.createRow(0)
|
||||||
row.height = 25 * 20
|
row.height = (25 * 20).toShort()
|
||||||
var cell: HSSFCell
|
var cell: HSSFCell
|
||||||
//创建标题
|
//创建标题
|
||||||
for (col in data.columnNames.indices) {
|
for (col in data.columnNames.indices) {
|
||||||
@@ -69,7 +69,7 @@ object Excel {
|
|||||||
for (i in data.data.indices) {
|
for (i in data.data.indices) {
|
||||||
try {
|
try {
|
||||||
row = sheet.createRow(i + 1)
|
row = sheet.createRow(i + 1)
|
||||||
row.height = 20 * 20
|
row.height = (20 * 20).toShort()
|
||||||
col = 0
|
col = 0
|
||||||
while (col < data.data[i].size) {
|
while (col < data.data[i].size) {
|
||||||
cell = row.createCell(col)
|
cell = row.createCell(col)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.synebula.gaea.app.component.security
|
package com.synebula.gaea.app.component.security
|
||||||
|
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
|
||||||
import com.synebula.gaea.app.struct.exception.TokenCloseExpireException
|
import com.synebula.gaea.app.struct.exception.TokenCloseExpireException
|
||||||
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package com.synebula.gaea.app.component.security
|
package com.synebula.gaea.app.component.security
|
||||||
|
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy
|
import org.springframework.security.config.http.SessionCreationPolicy
|
||||||
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.cors.CorsConfiguration
|
import org.springframework.web.cors.CorsConfiguration
|
||||||
import org.springframework.web.cors.CorsConfigurationSource
|
import org.springframework.web.cors.CorsConfigurationSource
|
||||||
@@ -16,38 +16,41 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
|||||||
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@EnableWebSecurity
|
class WebSecurity {
|
||||||
class WebSecurity : WebSecurityConfigurerAdapter() {
|
|
||||||
@Autowired
|
@Autowired
|
||||||
lateinit var tokenManager: TokenManager
|
lateinit var tokenManager: TokenManager
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var authenticationManager: AuthenticationManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全配置
|
* 安全配置
|
||||||
*/
|
*/
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun configure(http: HttpSecurity) {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
// 跨域共享
|
// 跨域共享
|
||||||
http.cors()
|
http.cors()
|
||||||
.and().csrf().disable() // 跨域伪造请求限制无效
|
.and().csrf().disable() // 跨域伪造请求限制无效
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.anyRequest().authenticated()// 资源任何人都可访问
|
.anyRequest().authenticated()// 资源任何人都可访问
|
||||||
.and()
|
.and()
|
||||||
.addFilter(WebAuthorization(authenticationManager(), tokenManager))// 添加JWT鉴权拦截器
|
.addFilter(WebAuthorization(authenticationManager, tokenManager))// 添加JWT鉴权拦截器
|
||||||
.sessionManagement()
|
.sessionManagement()
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
|
||||||
.and()
|
.and()
|
||||||
.exceptionHandling()
|
.exceptionHandling()
|
||||||
.authenticationEntryPoint { _, response, _ ->
|
.authenticationEntryPoint { _, response, _ ->
|
||||||
response.status = Status.Success
|
response.status = Status.Success
|
||||||
response.characterEncoding = "utf-8"
|
response.characterEncoding = "utf-8"
|
||||||
response.contentType = "text/javascript;charset=utf-8"
|
response.contentType = "text/javascript;charset=utf-8"
|
||||||
response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!"))
|
response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!"))
|
||||||
}
|
}
|
||||||
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun configure(web: WebSecurity) {
|
fun filterChain(): WebSecurityCustomizer {
|
||||||
web.ignoring().antMatchers("/sign/**")
|
return WebSecurityCustomizer { web -> web.ignoring().antMatchers("/sign/**") }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,50 +1,45 @@
|
|||||||
package com.synebula.gaea.app.query
|
package com.synebula.gaea.app.query
|
||||||
|
|
||||||
import com.synebula.gaea.app.IApplication
|
import com.synebula.gaea.app.IApplication
|
||||||
import com.synebula.gaea.app.component.aop.annotation.MethodName
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
|
||||||
import com.synebula.gaea.query.IQuery
|
import com.synebula.gaea.query.IQuery
|
||||||
import com.synebula.gaea.query.Params
|
import com.synebula.gaea.query.Params
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Method
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
|
|
||||||
interface IQueryApp<TView, TKey> : IApplication {
|
interface IQueryApp<TView, ID> : IApplication {
|
||||||
/**
|
/**
|
||||||
* 查询服务
|
* 查询服务
|
||||||
*/
|
*/
|
||||||
var query: IQuery
|
var query: IQuery<TView, ID>
|
||||||
|
|
||||||
/**
|
@Method("获取数据")
|
||||||
* 查询的View类型
|
|
||||||
*/
|
|
||||||
var clazz: Class<TView>
|
|
||||||
|
|
||||||
@MethodName("获取数据")
|
|
||||||
@GetMapping("/{id:.+}")
|
@GetMapping("/{id:.+}")
|
||||||
fun get(@PathVariable id: TKey): HttpMessage {
|
fun get(@PathVariable id: ID): HttpMessage {
|
||||||
val data = this.query.get(id, clazz)
|
val data = this.query.get(id)
|
||||||
val msg = HttpMessage()
|
val msg = HttpMessage()
|
||||||
msg.data = data
|
msg.data = data
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@MethodName("获取列表数据")
|
@Method("获取列表数据")
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun list(@RequestParam params: LinkedHashMap<String, Any>): HttpMessage {
|
fun list(@RequestParam params: LinkedHashMap<String, Any>): HttpMessage {
|
||||||
val data = this.query.list(params, clazz)
|
val data = this.query.list(params)
|
||||||
return HttpMessage(data)
|
return HttpMessage(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MethodName("获取分页数据")
|
@Method("获取分页数据")
|
||||||
@GetMapping("/segments/{size}/pages/{page}")
|
@GetMapping("/size/{size}/pages/{page}")
|
||||||
fun paging(
|
fun paging(
|
||||||
@PathVariable size: Int,
|
@PathVariable size: Int,
|
||||||
@PathVariable page: Int,
|
@PathVariable page: Int,
|
||||||
@RequestParam parameters: LinkedHashMap<String, Any>
|
@RequestParam parameters: LinkedHashMap<String, Any>
|
||||||
): HttpMessage {
|
): HttpMessage {
|
||||||
val params = Params(page, size, parameters)
|
val params = Params(page, size, parameters)
|
||||||
val data = this.query.paging(params, clazz)
|
val data = this.query.paging(params)
|
||||||
return HttpMessage(data)
|
return HttpMessage(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,8 @@ import com.synebula.gaea.query.IQuery
|
|||||||
* @param query 业务查询服务
|
* @param query 业务查询服务
|
||||||
* @param logger 日志组件
|
* @param logger 日志组件
|
||||||
*/
|
*/
|
||||||
open class QueryApp<TView, TKey>(
|
open class QueryApp<TView, ID>(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var clazz: Class<TView>,
|
override var query: IQuery<TView, ID>,
|
||||||
override var query: IQuery,
|
override var logger: ILogger,
|
||||||
override var logger: ILogger?
|
) : IQueryApp<TView, ID>
|
||||||
) : IQueryApp<TView, TKey>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
compile project(":src:gaea")
|
|
||||||
compile("org.springframework.boot:spring-boot-starter-data-mongodb:$spring_version")
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
publish(MavenPublication) {
|
|
||||||
group 'com.synebula'
|
|
||||||
artifactId 'gaea.mongo'
|
|
||||||
version "$version"
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package com.synebula.gaea.mongo.repository
|
|
||||||
|
|
||||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
|
||||||
import com.synebula.gaea.domain.repository.IRepository
|
|
||||||
import com.synebula.gaea.mongo.where
|
|
||||||
import com.synebula.gaea.mongo.whereId
|
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate
|
|
||||||
import org.springframework.data.mongodb.core.query.Query
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实现ITypedRepository的mongo仓储类
|
|
||||||
* @param repo MongoRepo对象
|
|
||||||
*/
|
|
||||||
open class MongoRepository(private var repo: MongoTemplate) : IRepository {
|
|
||||||
|
|
||||||
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> remove(id: TKey, clazz: Class<TAggregateRoot>) {
|
|
||||||
this.repo.remove(whereId(id), clazz)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get(
|
|
||||||
id: TKey,
|
|
||||||
clazz: Class<TAggregateRoot>
|
|
||||||
): TAggregateRoot? {
|
|
||||||
return this.repo.findOne(whereId(id), clazz)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> update(
|
|
||||||
obj: TAggregateRoot,
|
|
||||||
clazz: Class<TAggregateRoot>
|
|
||||||
) {
|
|
||||||
this.repo.save(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: TAggregateRoot, clazz: Class<TAggregateRoot>) {
|
|
||||||
this.repo.save(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: List<TAggregateRoot>, clazz: Class<TAggregateRoot>) {
|
|
||||||
this.repo.insert(obj, clazz)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int {
|
|
||||||
val query = Query()
|
|
||||||
return this.repo.count(query.where(params, clazz), clazz).toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
src/gaea.mongodb/build.gradle
Normal file
13
src/gaea.mongodb/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
dependencies {
|
||||||
|
api project(":src:gaea")
|
||||||
|
api project(":src:gaea.spring")
|
||||||
|
api("org.springframework.boot:spring-boot-starter-data-mongodb:$spring_version")
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
publish(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.synebula.gaea.mongo
|
package com.synebula.gaea.mongodb
|
||||||
|
|
||||||
import com.synebula.gaea.data.date.DateTime
|
import com.synebula.gaea.data.date.DateTime
|
||||||
import com.synebula.gaea.query.annotation.Where
|
import com.synebula.gaea.query.Operator
|
||||||
import com.synebula.gaea.query.type.Operator
|
import com.synebula.gaea.query.Order
|
||||||
import com.synebula.gaea.query.type.Order
|
import com.synebula.gaea.query.Where
|
||||||
import org.springframework.data.domain.Sort
|
import org.springframework.data.domain.Sort
|
||||||
import org.springframework.data.mongodb.core.query.Criteria
|
import org.springframework.data.mongodb.core.query.Criteria
|
||||||
import org.springframework.data.mongodb.core.query.Query
|
import org.springframework.data.mongodb.core.query.Query
|
||||||
@@ -30,9 +30,9 @@ fun Query.select(fields: Array<String>): Query {
|
|||||||
* @param onWhere 获取字段查询方式的方法
|
* @param onWhere 获取字段查询方式的方法
|
||||||
*/
|
*/
|
||||||
fun Query.where(
|
fun Query.where(
|
||||||
params: Map<String, Any>?,
|
params: Map<String, Any>?,
|
||||||
onWhere: ((v: String) -> Where?) = { null },
|
onWhere: ((v: String) -> Where?) = { null },
|
||||||
onFieldType: ((v: String) -> Class<*>?) = { null }
|
onFieldType: ((v: String) -> Class<*>?) = { null }
|
||||||
): Query {
|
): Query {
|
||||||
val list = arrayListOf<Criteria>()
|
val list = arrayListOf<Criteria>()
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
@@ -55,7 +55,7 @@ fun Query.where(
|
|||||||
list.add(tryRangeWhere(param.key, value, onFieldType))
|
list.add(tryRangeWhere(param.key, value, onFieldType))
|
||||||
} else {
|
} else {
|
||||||
//判断执行查询子元素还是本字段
|
//判断执行查询子元素还是本字段
|
||||||
val field = if (where.children.isEmpty()) key else where.children
|
val field = where.children.ifEmpty { key }
|
||||||
var criteria = Criteria.where(field)
|
var criteria = Criteria.where(field)
|
||||||
criteria = when (where.operator) {
|
criteria = when (where.operator) {
|
||||||
Operator.eq -> criteria.`is`(value)
|
Operator.eq -> criteria.`is`(value)
|
||||||
@@ -116,7 +116,7 @@ fun Query.where(params: Map<String, Any>?, clazz: Class<*>): Query {
|
|||||||
*
|
*
|
||||||
* @param id 业务ID
|
* @param id 业务ID
|
||||||
*/
|
*/
|
||||||
fun <TKey> whereId(id: TKey): Query = Query.query(Criteria.where("_id").`is`(id))
|
fun <ID> whereId(id: ID): Query = Query.query(Criteria.where("_id").`is`(id))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取排序对象
|
* 获取排序对象
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.synebula.gaea.mongodb.autoconfig
|
||||||
|
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Factory
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Proxy
|
||||||
|
import org.springframework.beans.factory.BeanFactory
|
||||||
|
|
||||||
|
class MongodbRepoFactory(
|
||||||
|
supertype: Class<*>,
|
||||||
|
var beanFactory: BeanFactory,
|
||||||
|
) : Factory(supertype) {
|
||||||
|
override fun createProxy(): Proxy {
|
||||||
|
return MongodbRepoProxy(supertype, this.beanFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.synebula.gaea.mongodb.autoconfig
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.repository.IRepository
|
||||||
|
import com.synebula.gaea.mongodb.query.MongodbQuery
|
||||||
|
import com.synebula.gaea.mongodb.repository.MongodbRepository
|
||||||
|
import com.synebula.gaea.query.IQuery
|
||||||
|
import com.synebula.gaea.reflect.getGenericInterface
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Proxy
|
||||||
|
import org.springframework.beans.factory.BeanFactory
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
class MongodbRepoProxy(
|
||||||
|
private var supertype: Class<*>, private var beanFactory: BeanFactory
|
||||||
|
) : Proxy() {
|
||||||
|
|
||||||
|
private var mongodbRepo: Any
|
||||||
|
|
||||||
|
init {
|
||||||
|
// 判断接口类型
|
||||||
|
val clazz: Class<*> // 代理服务类型
|
||||||
|
val interfaceClazz: Class<*> // 代理服务接口
|
||||||
|
if (this.supertype.interfaces.any { it == IRepository::class.java }) {
|
||||||
|
clazz = MongodbRepository::class.java
|
||||||
|
interfaceClazz = IRepository::class.java
|
||||||
|
} else {
|
||||||
|
clazz = MongodbQuery::class.java
|
||||||
|
interfaceClazz = IQuery::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
val constructor = clazz.getConstructor(Class::class.java, MongoTemplate::class.java)
|
||||||
|
this.mongodbRepo = constructor.newInstance(
|
||||||
|
this.supertype.getGenericInterface(interfaceClazz)!!.actualTypeArguments[0],
|
||||||
|
this.beanFactory.getBean(MongoTemplate::class.java)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代理方法
|
||||||
|
*
|
||||||
|
* @param proxy 代理对象
|
||||||
|
* @param method 需要执行的方法
|
||||||
|
* @param args 参数列表
|
||||||
|
* @return 方法执行结果
|
||||||
|
*/
|
||||||
|
override fun exec(proxy: Any, method: Method, args: Array<Any>): Any? {
|
||||||
|
try {
|
||||||
|
val proxyMethod: Method = this.mongodbRepo.javaClass.getMethod(method.name, *method.parameterTypes)
|
||||||
|
return proxyMethod.invoke(this.mongodbRepo, *args)
|
||||||
|
} catch (ex: NoSuchMethodException) {
|
||||||
|
throw NoSuchMethodException("method [${method.toGenericString()}] not implements in class [${this.mongodbRepo.javaClass}], you must implements interface [${this.supertype.name}] ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.synebula.gaea.mongodb.autoconfig
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.repository.IRepository
|
||||||
|
import com.synebula.gaea.query.IQuery
|
||||||
|
import com.synebula.gaea.spring.autoconfig.Register
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition
|
||||||
|
import org.springframework.core.annotation.AnnotationAttributes
|
||||||
|
import org.springframework.core.type.AnnotationMetadata
|
||||||
|
|
||||||
|
class MongodbRepoRegister : Register() {
|
||||||
|
override fun scan(metadata: AnnotationMetadata): Map<String, BeanDefinition> {
|
||||||
|
val result = mutableMapOf<String, BeanDefinition>()
|
||||||
|
|
||||||
|
// 获取注解参数信息:basePackages
|
||||||
|
val attributes = AnnotationAttributes(
|
||||||
|
metadata.getAnnotationAttributes(
|
||||||
|
MongodbRepoScan::class.java.name
|
||||||
|
) ?: mapOf()
|
||||||
|
)
|
||||||
|
val basePackages = attributes.getStringArray("basePackages")
|
||||||
|
val beanDefinitions = this.doScan(
|
||||||
|
basePackages,
|
||||||
|
arrayOf(this.interfaceFilter(arrayOf(IRepository::class.java, IQuery::class.java)))
|
||||||
|
)
|
||||||
|
beanDefinitions.forEach { beanDefinition ->
|
||||||
|
// 获取实际的bean类型
|
||||||
|
val beanClazz: Class<*> = try {
|
||||||
|
Class.forName(beanDefinition.beanClassName)
|
||||||
|
} catch (ex: ClassNotFoundException) {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取实际继承类型
|
||||||
|
val implBeanDefinitions = this.doScan(basePackages, arrayOf(this.interfaceFilter(arrayOf(beanClazz))))
|
||||||
|
|
||||||
|
if (implBeanDefinitions.isNotEmpty()) {
|
||||||
|
implBeanDefinitions.forEach {
|
||||||
|
result[it.beanClassName!!] = it
|
||||||
|
}
|
||||||
|
} else { // 构造BeanDefinition
|
||||||
|
val builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz)
|
||||||
|
builder.addConstructorArgValue(beanClazz)
|
||||||
|
builder.addConstructorArgValue(this._beanFactory)
|
||||||
|
val definition = builder.rawBeanDefinition as GenericBeanDefinition
|
||||||
|
definition.beanClass = MongodbRepoFactory::class.java
|
||||||
|
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
|
||||||
|
result[beanClazz.name] = definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDefaultProxyBean(result: MutableMap<String, BeanDefinition>) {
|
||||||
|
// IRepository proxy
|
||||||
|
val builder = BeanDefinitionBuilder.genericBeanDefinition(IRepository::class.java)
|
||||||
|
builder.addConstructorArgValue(IRepository::class.java)
|
||||||
|
builder.addConstructorArgValue(this._beanFactory)
|
||||||
|
builder.addConstructorArgValue(emptyArray<String>())
|
||||||
|
val definition = builder.rawBeanDefinition as GenericBeanDefinition
|
||||||
|
definition.beanClass = MongodbRepoFactory::class.java
|
||||||
|
definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE
|
||||||
|
result[IRepository::class.java.name] = definition
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.synebula.gaea.mongodb.autoconfig
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@MustBeDocumented
|
||||||
|
@Inherited
|
||||||
|
@Import(MongodbRepoRegister::class)
|
||||||
|
annotation class MongodbRepoScan(val basePackages: Array<String> = [])
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.synebula.gaea.mongodb.query
|
||||||
|
|
||||||
|
import com.synebula.gaea.ext.fieldNames
|
||||||
|
import com.synebula.gaea.ext.firstCharLowerCase
|
||||||
|
import com.synebula.gaea.mongodb.order
|
||||||
|
import com.synebula.gaea.mongodb.select
|
||||||
|
import com.synebula.gaea.mongodb.where
|
||||||
|
import com.synebula.gaea.mongodb.whereId
|
||||||
|
import com.synebula.gaea.query.IQuery
|
||||||
|
import com.synebula.gaea.query.Page
|
||||||
|
import com.synebula.gaea.query.Params
|
||||||
|
import com.synebula.gaea.query.Table
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
import org.springframework.data.mongodb.core.query.Criteria
|
||||||
|
import org.springframework.data.mongodb.core.query.Query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现IQuery的Mongodb查询类
|
||||||
|
* @param template MongodbRepo对象
|
||||||
|
*/
|
||||||
|
|
||||||
|
open class MongodbQuery<TView, ID>(override var clazz: Class<TView>, var template: MongoTemplate) :
|
||||||
|
IQuery<TView, ID> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用View解析是collection时是否校验存在,默认不校验
|
||||||
|
*/
|
||||||
|
var validViewCollection = false
|
||||||
|
|
||||||
|
override fun get(id: ID): TView? {
|
||||||
|
return this.template.findOne(whereId(id), clazz, this.collection(clazz))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun list(params: Map<String, Any>?): List<TView> {
|
||||||
|
val fields = this.fields(clazz)
|
||||||
|
val query = Query()
|
||||||
|
query.where(params, clazz)
|
||||||
|
query.select(fields)
|
||||||
|
return this.find(query, clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun count(params: Map<String, Any>?): Int {
|
||||||
|
val query = Query()
|
||||||
|
return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paging(params: Params): Page<TView> {
|
||||||
|
val query = Query()
|
||||||
|
val fields = this.fields(clazz)
|
||||||
|
val result = Page<TView>(params.page, params.size)
|
||||||
|
result.total = this.count(params.parameters)
|
||||||
|
//如果总数和索引相同,说明该页没有数据,直接跳到上一页
|
||||||
|
if (result.total == result.index) {
|
||||||
|
params.page -= 1
|
||||||
|
result.page -= 1
|
||||||
|
}
|
||||||
|
query.select(fields)
|
||||||
|
query.where(params.parameters, clazz)
|
||||||
|
query.with(order(params.orders))
|
||||||
|
query.skip(params.index).limit(params.size)
|
||||||
|
result.data = this.find(query, clazz)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun range(field: String, params: List<Any>): List<TView> {
|
||||||
|
return this.find(Query.query(Criteria.where(field).`in`(params)), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun find(query: Query, clazz: Class<TView>): List<TView> {
|
||||||
|
return this.template.find(query, clazz, this.collection(clazz))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun fields(clazz: Class<TView>): Array<String> {
|
||||||
|
val fields = mutableListOf<String>()
|
||||||
|
fields.addAll(clazz.fieldNames())
|
||||||
|
var parent = clazz.superclass
|
||||||
|
while (parent != Any::class.java) {
|
||||||
|
fields.addAll(clazz.superclass.fieldNames())
|
||||||
|
parent = parent.superclass
|
||||||
|
}
|
||||||
|
return fields.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取collection
|
||||||
|
*/
|
||||||
|
fun <TView> collection(clazz: Class<TView>): String {
|
||||||
|
val table = clazz.getDeclaredAnnotation(Table::class.java)
|
||||||
|
return if (table != null) table.name
|
||||||
|
else {
|
||||||
|
val name = clazz.simpleName.removeSuffix("View").firstCharLowerCase()
|
||||||
|
if (!validViewCollection || this.template.collectionExists(name)) name
|
||||||
|
else throw RuntimeException("找不到名为[${clazz.name}]的集合")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.synebula.gaea.mongodb.query
|
||||||
|
|
||||||
|
import com.synebula.gaea.query.IQuery
|
||||||
|
import com.synebula.gaea.query.IQueryFactory
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
|
||||||
|
class MongodbQueryFactory(var template: MongoTemplate) : IQueryFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建IQuery接口类型
|
||||||
|
*/
|
||||||
|
override fun createRawQuery(clazz: Class<*>): IQuery<*, *> {
|
||||||
|
val constructor = MongodbQuery::class.java.getConstructor(Class::class.java, MongoTemplate::class.java)
|
||||||
|
return constructor.newInstance(clazz, this.template)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建IQuery接口类型
|
||||||
|
*/
|
||||||
|
override fun <T, I> createQuery(clazz: Class<T>): IQuery<T, I> {
|
||||||
|
return MongodbQuery(clazz, template)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
package com.synebula.gaea.mongo.query
|
package com.synebula.gaea.mongodb.query
|
||||||
|
|
||||||
|
|
||||||
import com.synebula.gaea.ext.fieldNames
|
import com.synebula.gaea.ext.fieldNames
|
||||||
import com.synebula.gaea.ext.firstCharLowerCase
|
import com.synebula.gaea.ext.firstCharLowerCase
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.mongodb.order
|
||||||
import com.synebula.gaea.mongo.order
|
import com.synebula.gaea.mongodb.select
|
||||||
import com.synebula.gaea.mongo.select
|
import com.synebula.gaea.mongodb.where
|
||||||
import com.synebula.gaea.mongo.where
|
import com.synebula.gaea.mongodb.whereId
|
||||||
import com.synebula.gaea.mongo.whereId
|
import com.synebula.gaea.query.IUniversalQuery
|
||||||
import com.synebula.gaea.query.IQuery
|
|
||||||
import com.synebula.gaea.query.Page
|
import com.synebula.gaea.query.Page
|
||||||
import com.synebula.gaea.query.Params
|
import com.synebula.gaea.query.Params
|
||||||
import com.synebula.gaea.query.annotation.Table
|
import com.synebula.gaea.query.Table
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
import org.springframework.data.mongodb.core.query.Criteria
|
import org.springframework.data.mongodb.core.query.Criteria
|
||||||
import org.springframework.data.mongodb.core.query.Query
|
import org.springframework.data.mongodb.core.query.Query
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实现IQuery的Mongo查询类
|
* 实现IQuery的Mongodb查询类
|
||||||
* @param template MongoRepo对象
|
* @param template MongodbRepo对象
|
||||||
*/
|
*/
|
||||||
|
open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery {
|
||||||
open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null) : IQuery {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用View解析是collection时是否校验存在,默认不校验
|
* 使用View解析是collection时是否校验存在,默认不校验
|
||||||
*/
|
*/
|
||||||
var validViewCollection = false
|
var validViewCollection = false
|
||||||
|
|
||||||
override fun <TView, TKey> get(id: TKey, clazz: Class<TView>): TView? {
|
override fun <TView, ID> get(id: ID, clazz: Class<TView>): TView? {
|
||||||
return this.template.findOne(whereId(id), clazz, this.collection(clazz))
|
return this.template.findOne(whereId(id), clazz, this.collection(clazz))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null)
|
|||||||
val query = Query()
|
val query = Query()
|
||||||
query.where(params, clazz)
|
query.where(params, clazz)
|
||||||
query.select(fields)
|
query.select(fields)
|
||||||
return this.template.find(query, clazz, this.collection(clazz))
|
return this.find(query, clazz)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <TView> count(params: Map<String, Any>?, clazz: Class<TView>): Int {
|
override fun <TView> count(params: Map<String, Any>?, clazz: Class<TView>): Int {
|
||||||
@@ -58,12 +57,16 @@ open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null)
|
|||||||
query.where(params.parameters, clazz)
|
query.where(params.parameters, clazz)
|
||||||
query.with(order(params.orders))
|
query.with(order(params.orders))
|
||||||
query.skip(params.index).limit(params.size)
|
query.skip(params.index).limit(params.size)
|
||||||
result.data = this.template.find(query, clazz, this.collection(clazz))
|
result.data = this.find(query, clazz)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <TView> range(field: String, params: List<Any>, clazz: Class<TView>): List<TView> {
|
override fun <TView> range(field: String, params: List<Any>, clazz: Class<TView>): List<TView> {
|
||||||
return this.template.find(Query.query(Criteria.where(field).`in`(params)), clazz, this.collection(clazz))
|
return this.find(Query.query(Criteria.where(field).`in`(params)), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <TView> find(query: Query, clazz: Class<TView>): List<TView> {
|
||||||
|
return this.template.find(query, clazz, this.collection(clazz))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <TView> fields(clazz: Class<TView>): Array<String> {
|
fun <TView> fields(clazz: Class<TView>): Array<String> {
|
||||||
@@ -81,19 +84,12 @@ open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null)
|
|||||||
* 获取collection
|
* 获取collection
|
||||||
*/
|
*/
|
||||||
fun <TView> collection(clazz: Class<TView>): String {
|
fun <TView> collection(clazz: Class<TView>): String {
|
||||||
val table: Table? = clazz.getDeclaredAnnotation(
|
val table = clazz.getDeclaredAnnotation(Table::class.java)
|
||||||
Table::class.java
|
return if (table != null) table.name
|
||||||
)
|
|
||||||
return if (table != null)
|
|
||||||
return table.name
|
|
||||||
else {
|
else {
|
||||||
this.logger?.info(this, "视图类没有标记[Collection]注解,无法获取Collection名称。尝试使用View<${clazz.name}>名称解析集合")
|
|
||||||
val name = clazz.simpleName.removeSuffix("View").firstCharLowerCase()
|
val name = clazz.simpleName.removeSuffix("View").firstCharLowerCase()
|
||||||
if (!validViewCollection || this.template.collectionExists(name))
|
if (!validViewCollection || this.template.collectionExists(name)) name
|
||||||
name
|
else throw RuntimeException("找不到名为[${clazz.name}]的集合")
|
||||||
else {
|
|
||||||
throw RuntimeException("找不到名为[$table]的集合")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.synebula.gaea.mongodb.repository
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
import com.synebula.gaea.domain.repository.IRepository
|
||||||
|
import com.synebula.gaea.mongodb.where
|
||||||
|
import com.synebula.gaea.mongodb.whereId
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
import org.springframework.data.mongodb.core.query.Query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现[IRepository]的Mongodb仓储类
|
||||||
|
* @param repo MongodbRepo对象
|
||||||
|
*/
|
||||||
|
open class MongodbRepository<TAggregateRoot : IAggregateRoot<ID>, ID>(
|
||||||
|
override var clazz: Class<TAggregateRoot>,
|
||||||
|
protected var repo: MongoTemplate
|
||||||
|
) : IRepository<TAggregateRoot, ID> {
|
||||||
|
|
||||||
|
override fun add(obj: TAggregateRoot) {
|
||||||
|
this.repo.save(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(list: List<TAggregateRoot>) {
|
||||||
|
this.repo.insert(list, clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(id: ID) {
|
||||||
|
this.repo.remove(whereId(id), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(id: ID): TAggregateRoot? {
|
||||||
|
return this.repo.findOne(whereId(id), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(obj: TAggregateRoot) {
|
||||||
|
this.repo.save(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(list: List<TAggregateRoot>) {
|
||||||
|
this.repo.save(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun count(params: Map<String, Any>?): Int {
|
||||||
|
val query = Query()
|
||||||
|
return this.repo.count(query.where(params, clazz), clazz).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.synebula.gaea.mongodb.repository
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
import com.synebula.gaea.domain.repository.IRepository
|
||||||
|
import com.synebula.gaea.domain.repository.IRepositoryFactory
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
|
||||||
|
class MongodbRepositoryFactory(var template: MongoTemplate) : IRepositoryFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建IRepository接口类型
|
||||||
|
*/
|
||||||
|
override fun createRawRepository(clazz: Class<*>): IRepository<*, *> {
|
||||||
|
val constructor = MongodbRepository::class.java.getConstructor(Class::class.java, MongoTemplate::class.java)
|
||||||
|
return constructor.newInstance(clazz, this.template)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建IRepository接口类型
|
||||||
|
*/
|
||||||
|
override fun <T : IAggregateRoot<I>, I> createRepository(clazz: Class<T>): IRepository<T, I> {
|
||||||
|
return MongodbRepository(clazz, template)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.synebula.gaea.mongodb.repository
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
import com.synebula.gaea.domain.repository.IUniversalRepository
|
||||||
|
import com.synebula.gaea.mongodb.where
|
||||||
|
import com.synebula.gaea.mongodb.whereId
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
|
import org.springframework.data.mongodb.core.query.Query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现ITypedRepository的Mongodb仓储类
|
||||||
|
* @param repo MongodbRepo对象
|
||||||
|
*/
|
||||||
|
open class MongodbUniversalRepository(private var repo: MongoTemplate) : IUniversalRepository {
|
||||||
|
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> remove(id: ID, clazz: Class<TAggregateRoot>) {
|
||||||
|
this.repo.remove(whereId(id), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> get(
|
||||||
|
id: ID,
|
||||||
|
clazz: Class<TAggregateRoot>,
|
||||||
|
): TAggregateRoot? {
|
||||||
|
return this.repo.findOne(whereId(id), clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(
|
||||||
|
root: TAggregateRoot,
|
||||||
|
clazz: Class<TAggregateRoot>,
|
||||||
|
) {
|
||||||
|
this.repo.save(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新多个个对象。
|
||||||
|
*
|
||||||
|
* @param roots 需要更新的对象。
|
||||||
|
*/
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(
|
||||||
|
roots: List<TAggregateRoot>,
|
||||||
|
clazz: Class<TAggregateRoot>
|
||||||
|
) {
|
||||||
|
this.repo.save(roots)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(root: TAggregateRoot, clazz: Class<TAggregateRoot>) {
|
||||||
|
this.repo.save(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(
|
||||||
|
roots: List<TAggregateRoot>,
|
||||||
|
clazz: Class<TAggregateRoot>,
|
||||||
|
) {
|
||||||
|
this.repo.insert(roots, clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int {
|
||||||
|
val query = Query()
|
||||||
|
return this.repo.count(query.where(params, clazz), clazz).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/gaea.spring/build.gradle
Normal file
24
src/gaea.spring/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin-spring'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(":src:gaea")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web:$spring_version")
|
||||||
|
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||||
|
|
||||||
|
api("org.springframework.boot:spring-boot-starter-aop:$spring_version")
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
publish(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package com.synebula.gaea.app.component.aop
|
package com.synebula.gaea.spring.aop
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.synebula.gaea.app.IApplication
|
import com.synebula.gaea.data.message.HttpMessage
|
||||||
import com.synebula.gaea.app.component.aop.annotation.Handler
|
|
||||||
import com.synebula.gaea.app.component.aop.annotation.MethodName
|
|
||||||
import com.synebula.gaea.app.component.aop.annotation.ModuleName
|
|
||||||
import com.synebula.gaea.app.struct.HttpMessage
|
|
||||||
import com.synebula.gaea.data.message.Status
|
import com.synebula.gaea.data.message.Status
|
||||||
import com.synebula.gaea.exception.NoticeUserException
|
import com.synebula.gaea.exception.NoticeUserException
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.log.ILogger
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Handler
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Method
|
||||||
|
import com.synebula.gaea.spring.aop.annotation.Module
|
||||||
import org.aspectj.lang.ProceedingJoinPoint
|
import org.aspectj.lang.ProceedingJoinPoint
|
||||||
import org.aspectj.lang.annotation.Around
|
import org.aspectj.lang.annotation.Around
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
@@ -16,7 +15,7 @@ import org.springframework.context.ApplicationContext
|
|||||||
import org.springframework.core.DefaultParameterNameDiscoverer
|
import org.springframework.core.DefaultParameterNameDiscoverer
|
||||||
|
|
||||||
abstract class AppAspect {
|
abstract class AppAspect {
|
||||||
private var paramDiscover = DefaultParameterNameDiscoverer()
|
private var parameterNameDiscoverer = DefaultParameterNameDiscoverer()
|
||||||
|
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ abstract class AppAspect {
|
|||||||
*/
|
*/
|
||||||
@Around("func()")
|
@Around("func()")
|
||||||
fun around(point: ProceedingJoinPoint): Any? {
|
fun around(point: ProceedingJoinPoint): Any? {
|
||||||
val clazz = point.`this`.javaClass //获取实际对象的类型避免获取到父类
|
|
||||||
val func = point.signature.declaringType.methods.find {
|
val func = point.signature.declaringType.methods.find {
|
||||||
it.name == point.signature.name
|
it.name == point.signature.name
|
||||||
}!!//获取声明类型中的方法信息
|
}!!//获取声明类型中的方法信息
|
||||||
@@ -46,42 +44,32 @@ abstract class AppAspect {
|
|||||||
var funcName = func.name
|
var funcName = func.name
|
||||||
//遍历方法注解
|
//遍历方法注解
|
||||||
for (funcAnnotation in funcAnnotations) {
|
for (funcAnnotation in funcAnnotations) {
|
||||||
val annotations = funcAnnotation.annotationClass.annotations
|
if (funcAnnotation is Method)
|
||||||
|
funcName = funcAnnotation.name
|
||||||
|
|
||||||
|
val annotations = funcAnnotation.annotationClass.annotations
|
||||||
//尝试寻找方法注解的处理类
|
//尝试寻找方法注解的处理类
|
||||||
val handler = annotations.find { it is Handler }
|
val handler = annotations.find { it is Handler }
|
||||||
if (handler != null && handler is Handler) {
|
if (handler != null && handler is Handler) {
|
||||||
val handleClazz = applicationContext.getBean(handler.value.java)
|
val handleClazz = applicationContext.getBean(handler.value.java)
|
||||||
handleClazz.handle(clazz, func, point.args)
|
handleClazz.handle(point.`this`, func, point.args)
|
||||||
}
|
}
|
||||||
if (funcAnnotation is MethodName)
|
|
||||||
funcName = funcAnnotation.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
point.proceed()
|
point.proceed()
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
//找到类的模块名称,否则使用类名
|
val moduleName = this.resolveModuleName(point.`this`)
|
||||||
var moduleName = clazz.name
|
var message = "$moduleName - ${funcName}异常"
|
||||||
if (IApplication::class.java.isAssignableFrom(clazz)) {
|
message = if (ex is NoticeUserException || ex is Error) {
|
||||||
moduleName = (point.`this` as IApplication).name
|
"$message: ${ex.message}"
|
||||||
} else {
|
} else {
|
||||||
val name = clazz.annotations.find { it is ModuleName }
|
"$message。"
|
||||||
if (name != null && name is ModuleName) {
|
|
||||||
moduleName = name.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var message = "$moduleName - $funcName 异常"
|
|
||||||
if (ex is NoticeUserException) {
|
|
||||||
message = "$message: ${ex.message}"
|
|
||||||
} else {
|
|
||||||
message = "$message。"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
logger.error(
|
logger.error(
|
||||||
ex,
|
ex,
|
||||||
"$message。Method args ${
|
"$message。Method args ${
|
||||||
paramDiscover.getParameterNames(func)?.contentToString()
|
parameterNameDiscoverer.getParameterNames(func)?.contentToString()
|
||||||
} values is ${
|
} values is ${
|
||||||
gson.toJson(point.args)
|
gson.toJson(point.args)
|
||||||
}"
|
}"
|
||||||
@@ -89,4 +77,24 @@ abstract class AppAspect {
|
|||||||
return HttpMessage(Status.Error, message)
|
return HttpMessage(Status.Error, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模块名
|
||||||
|
*/
|
||||||
|
private fun resolveModuleName(obj: Any): String {
|
||||||
|
val clazz = obj.javaClass
|
||||||
|
// 1.默认使用类名作为模块名
|
||||||
|
var moduleName = clazz.simpleName
|
||||||
|
// 2.找到类的模块注解解析名称
|
||||||
|
val module = clazz.annotations.find { it is Module }
|
||||||
|
if (module != null && module is Module) {
|
||||||
|
moduleName = module.name
|
||||||
|
}
|
||||||
|
// 3.尝试找类的name字段作为模块名称
|
||||||
|
val nameField = clazz.fields.find { it.name == "name" }
|
||||||
|
if (nameField != null) {
|
||||||
|
moduleName = nameField.get(obj).toString()
|
||||||
|
}
|
||||||
|
return moduleName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.synebula.gaea.app.component.aop.annotation
|
package com.synebula.gaea.spring.aop.annotation
|
||||||
|
|
||||||
import com.synebula.gaea.app.component.aop.handler.AccessLogHandler
|
import com.synebula.gaea.spring.aop.handler.AccessLogHandler
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
@Handler(AccessLogHandler::class)
|
@Handler(AccessLogHandler::class)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.synebula.gaea.spring.aop.annotation
|
||||||
|
|
||||||
|
import com.synebula.gaea.spring.aop.handler.AnnotationHandler
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标注在注解,表明注解的处理方法
|
||||||
|
*
|
||||||
|
* @param value 注解处理方法
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.ANNOTATION_CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class Handler(val value: KClass<out AnnotationHandler>)
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.synebula.gaea.app.component.aop.annotation
|
package com.synebula.gaea.spring.aop.annotation
|
||||||
|
|
||||||
import java.lang.annotation.Inherited
|
import java.lang.annotation.Inherited
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记方法名称,由AOP负责记录异常时使用该名称
|
* 标记方法名称,由AOP负责记录异常时使用该名称
|
||||||
*
|
*
|
||||||
* @param name 异常消息
|
* @param name 方法名称
|
||||||
*/
|
*/
|
||||||
@Inherited
|
@Inherited
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class MethodName(val name: String)
|
annotation class Method(val name: String)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.synebula.gaea.spring.aop.annotation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块的业务名称
|
||||||
|
* @param name 模块名称
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class Module(val name: String)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.synebula.gaea.app.component.aop.handler
|
package com.synebula.gaea.spring.aop.handler
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.synebula.gaea.log.ILogger
|
import com.synebula.gaea.log.ILogger
|
||||||
@@ -15,11 +15,11 @@ class AccessLogHandler : AnnotationHandler {
|
|||||||
@Autowired
|
@Autowired
|
||||||
lateinit var logger: ILogger
|
lateinit var logger: ILogger
|
||||||
|
|
||||||
override fun handle(clazz: Class<Any>, func: Method, args: Array<Any>, exception: Exception?) {
|
override fun handle(obj: Any, func: Method, args: Array<Any>, exception: Exception?) {
|
||||||
val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes
|
val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes
|
||||||
val request = attributes.request
|
val request = attributes.request
|
||||||
logger.info(
|
logger.info(
|
||||||
"${request.method} ${request.requestURL} from ${request.remoteAddr}, call function ${clazz.name}.${func.name}, args: ${
|
"${request.method} ${request.requestURL} from ${request.remoteAddr}, call function ${func.toGenericString()}, args: ${
|
||||||
mapper.writeValueAsString(
|
mapper.writeValueAsString(
|
||||||
args
|
args
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.synebula.gaea.spring.aop.handler
|
||||||
|
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注解对应的方法处理对象接口
|
||||||
|
*/
|
||||||
|
interface AnnotationHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理“被注解方法”的方法
|
||||||
|
* @param obj 处理的类对象
|
||||||
|
* @param func 处理的方法
|
||||||
|
* @param args 处理的方法参数信息
|
||||||
|
* @param exception 处理的异常信息
|
||||||
|
*/
|
||||||
|
fun handle(obj: Any, func: Method, args: Array<Any>, exception: Exception? = null)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.synebula.gaea.spring.autoconfig
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.FactoryBean
|
||||||
|
import org.springframework.cglib.proxy.Enhancer
|
||||||
|
import java.lang.reflect.Proxy as JdkProxy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理生成工厂
|
||||||
|
*
|
||||||
|
* @param supertype 需要被代理的父类型
|
||||||
|
* @param proxyType 代理类型:JDK 或 CGLIB
|
||||||
|
*/
|
||||||
|
abstract class Factory(
|
||||||
|
protected val supertype: Class<*>,
|
||||||
|
protected val proxyType: ProxyType = ProxyType.Cglib
|
||||||
|
) : FactoryBean<Any> {
|
||||||
|
override fun getObject(): Any {
|
||||||
|
val handler: Proxy = this.createProxy()
|
||||||
|
|
||||||
|
//JDK 方式代理代码, 暂时选用cglib
|
||||||
|
val proxy: Any = if (proxyType == ProxyType.JDK) {
|
||||||
|
JdkProxy.newProxyInstance(
|
||||||
|
this.supertype.classLoader, arrayOf(this.supertype), handler
|
||||||
|
)
|
||||||
|
} else { //cglib代理
|
||||||
|
val enhancer = Enhancer()
|
||||||
|
enhancer.setSuperclass(supertype)
|
||||||
|
enhancer.setCallback(handler)
|
||||||
|
enhancer.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getObjectType(): Class<*> {
|
||||||
|
return supertype
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSingleton(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun createProxy(): Proxy
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.synebula.gaea.spring.autoconfig
|
||||||
|
|
||||||
|
import org.springframework.cglib.proxy.MethodInterceptor
|
||||||
|
import org.springframework.cglib.proxy.MethodProxy
|
||||||
|
import java.lang.reflect.InvocationHandler
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
abstract class Proxy : MethodInterceptor, InvocationHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 方式代理代码
|
||||||
|
*/
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
override fun invoke(proxy: Any, method: Method, args: Array<Any>): Any? {
|
||||||
|
return if (Any::class.java == method.declaringClass) {
|
||||||
|
method.invoke(this, *args)
|
||||||
|
} else {
|
||||||
|
exec(proxy, method, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂时选用cglib 方式代理代码
|
||||||
|
*/
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
override fun intercept(proxy: Any, method: Method, args: Array<Any>, methodProxy: MethodProxy): Any? {
|
||||||
|
return if (Any::class.java == method.declaringClass) {
|
||||||
|
methodProxy.invoke(this, args)
|
||||||
|
} else {
|
||||||
|
exec(proxy, method, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代理方法
|
||||||
|
*
|
||||||
|
* @param proxy 代理对象
|
||||||
|
* @param method 需要执行的方法
|
||||||
|
* @param args 参数列表
|
||||||
|
* @return 方法执行结果
|
||||||
|
*/
|
||||||
|
abstract fun exec(proxy: Any, method: Method, args: Array<Any>): Any?
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.synebula.gaea.spring.autoconfig
|
||||||
|
|
||||||
|
enum class ProxyType { JDK, Cglib }
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.synebula.gaea.spring.autoconfig
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException
|
||||||
|
import org.springframework.beans.factory.BeanClassLoaderAware
|
||||||
|
import org.springframework.beans.factory.BeanFactory
|
||||||
|
import org.springframework.beans.factory.BeanFactoryAware
|
||||||
|
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||||
|
import org.springframework.context.EnvironmentAware
|
||||||
|
import org.springframework.context.ResourceLoaderAware
|
||||||
|
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
|
||||||
|
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar
|
||||||
|
import org.springframework.core.env.Environment
|
||||||
|
import org.springframework.core.io.ResourceLoader
|
||||||
|
import org.springframework.core.type.AnnotationMetadata
|
||||||
|
import org.springframework.core.type.classreading.MetadataReader
|
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory
|
||||||
|
import org.springframework.core.type.filter.TypeFilter
|
||||||
|
import org.springframework.util.ClassUtils
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
abstract class Register : ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware,
|
||||||
|
EnvironmentAware, BeanFactoryAware {
|
||||||
|
|
||||||
|
protected var _classLoader: ClassLoader? = null
|
||||||
|
protected var _environment: Environment? = null
|
||||||
|
protected var _resourceLoader: ResourceLoader? = null
|
||||||
|
protected var _beanFactory: BeanFactory? = null
|
||||||
|
|
||||||
|
override fun registerBeanDefinitions(metadata: AnnotationMetadata, registry: BeanDefinitionRegistry) {
|
||||||
|
val beanDefinitions = this.scan(metadata)
|
||||||
|
beanDefinitions.forEach { registry.registerBeanDefinition(it.key, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun scan(metadata: AnnotationMetadata): Map<String, BeanDefinition>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据过滤器扫描直接包下bean
|
||||||
|
*
|
||||||
|
* @param packages 指定的扫描包
|
||||||
|
* @param filters 过滤器
|
||||||
|
* @return 扫描后的bean定义
|
||||||
|
*/
|
||||||
|
protected fun doScan(packages: Array<String>?, filters: Array<TypeFilter>): List<BeanDefinition> {
|
||||||
|
val scanner: ClassPathScanningCandidateComponentProvider =
|
||||||
|
object : ClassPathScanningCandidateComponentProvider() {
|
||||||
|
override fun isCandidateComponent(beanDefinition: AnnotatedBeanDefinition): Boolean {
|
||||||
|
try {
|
||||||
|
val metadata = beanDefinition.metadata
|
||||||
|
val target = ClassUtils.forName(metadata.className, _classLoader)
|
||||||
|
return !target.isAnnotation
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanner.environment = _environment!!
|
||||||
|
scanner.resourceLoader = _resourceLoader!!
|
||||||
|
for (filter in filters) {
|
||||||
|
scanner.addIncludeFilter(filter)
|
||||||
|
}
|
||||||
|
val beanDefinitions: MutableList<BeanDefinition> = LinkedList()
|
||||||
|
for (basePackage in packages!!) {
|
||||||
|
beanDefinitions.addAll(scanner.findCandidateComponents(basePackage))
|
||||||
|
}
|
||||||
|
return beanDefinitions
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定接口的类型过滤器
|
||||||
|
*
|
||||||
|
* @param interfaces 需要过滤的父接口类型
|
||||||
|
* @param onlyInterface 是否只获取接口类型
|
||||||
|
* @return 类型过滤器
|
||||||
|
*/
|
||||||
|
protected fun interfaceFilter(interfaces: Array<Class<*>>, onlyInterface: Boolean = false): TypeFilter {
|
||||||
|
return TypeFilter { metadataReader: MetadataReader, _: MetadataReaderFactory? ->
|
||||||
|
// 如果只获取接口类型且当前类型非接口 直接返回
|
||||||
|
if (onlyInterface && !metadataReader.annotationMetadata.isInterface)
|
||||||
|
return@TypeFilter false
|
||||||
|
|
||||||
|
var matched = false
|
||||||
|
val interfaceNames = metadataReader.classMetadata.interfaceNames
|
||||||
|
// 如果当前类型继承接口有任一需要过滤的接口则说明复合条件
|
||||||
|
for (interfaceName in interfaceNames) {
|
||||||
|
matched = interfaces.any { clazz -> clazz.name == interfaceName }
|
||||||
|
}
|
||||||
|
matched
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setResourceLoader(resourceLoader: ResourceLoader) {
|
||||||
|
this._resourceLoader = resourceLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBeanClassLoader(classLoader: ClassLoader) {
|
||||||
|
this._classLoader = classLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setEnvironment(environment: Environment) {
|
||||||
|
this._environment = environment
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(BeansException::class)
|
||||||
|
override fun setBeanFactory(beanFactory: BeanFactory) {
|
||||||
|
this._beanFactory = beanFactory
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
publish(MavenPublication) {
|
publish(MavenPublication) {
|
||||||
group 'com.synebula'
|
group "${project.group}"
|
||||||
artifactId 'gaea'
|
artifactId "${project.name}"
|
||||||
version "$version"
|
version "$version"
|
||||||
from components.java
|
from components.java
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a message subscriber method as being thread-safe. This annotation indicates that MessageBus
|
||||||
|
* may invoke the message subscriber simultaneously from multiple threads.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This does not mark the method, and so should be used in combination with [Subscribe].
|
||||||
|
*
|
||||||
|
* @author Cliff
|
||||||
|
* @since 10.0
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
|
annotation class AllowConcurrentSubscribe
|
||||||
342
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt
Normal file
342
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches messages to listeners, and provides ways for listeners to register themselves.
|
||||||
|
*
|
||||||
|
* <h2>Avoid MessageBus</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* **We recommend against using MessageBus.** It was designed many years ago, and newer
|
||||||
|
* libraries offer better ways to decouple components and react to messages.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* To decouple components, we recommend a dependency-injection framework. For Android code, most
|
||||||
|
* apps use [Dagger](https://dagger.dev). For server code, common options include [Guice](https://github.com/google/guice/wiki/Motivation) and [Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-introduction).
|
||||||
|
* Frameworks typically offer a way to register multiple listeners independently and then request
|
||||||
|
* them together as a set ([Dagger](https://dagger.dev/dev-guide/multibindings), [Guice](https://github.com/google/guice/wiki/Multibindings), [Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation)).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* To react to messages, we recommend a reactive-streams framework like [RxJava](https://github.com/ReactiveX/RxJava/wiki) (supplemented with its [RxAndroid](https://github.com/ReactiveX/RxAndroid) extension if you are building for
|
||||||
|
* Android) or [Project Reactor](https://projectreactor.io/). (For the basics of
|
||||||
|
* translating code from using a message bus to using a reactive-streams framework, see these two
|
||||||
|
* guides: [1](https://blog.jkl.gg/implementing-an-message-bus-with-rxjava-rxbus/), [2](https://lorentzos.com/rxjava-as-message-bus-the-right-way-10a36bdd49ba).) Some usages
|
||||||
|
* of MessageBus may be better written using [Kotlin coroutines](https://kotlinlang.org/docs/coroutines-guide.html), including [Flow](https://kotlinlang.org/docs/flow.html) and [Channels](https://kotlinlang.org/docs/channels.html). Yet other usages are better served
|
||||||
|
* by individual libraries that provide specialized support for particular use cases.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Disadvantages of MessageBus include:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* * It makes the cross-references between producer and subscriber harder to find. This can
|
||||||
|
* complicate debugging, lead to unintentional reentrant calls, and force apps to eagerly
|
||||||
|
* initialize all possible subscribers at startup time.
|
||||||
|
* * It uses reflection in ways that break when code is processed by optimizers/minimizer like
|
||||||
|
* [R8 and Proguard](https://developer.android.com/studio/build/shrink-code).
|
||||||
|
* * It doesn't offer a way to wait for multiple messages before taking action. For example, it
|
||||||
|
* doesn't offer a way to wait for multiple producers to all report that they're "ready," nor
|
||||||
|
* does it offer a way to batch multiple messages from a single producer together.
|
||||||
|
* * It doesn't support backpressure and other features needed for resilience.
|
||||||
|
* * It doesn't provide much control of threading.
|
||||||
|
* * It doesn't offer much monitoring.
|
||||||
|
* * It doesn't propagate exceptions, so apps don't have a way to react to them.
|
||||||
|
* * It doesn't interoperate well with RxJava, coroutines, and other more commonly used
|
||||||
|
* alternatives.
|
||||||
|
* * It imposes requirements on the lifecycle of its subscribers. For example, if a message
|
||||||
|
* occurs between when one subscriber is removed and the next subscriber is added, the message
|
||||||
|
* is dropped.
|
||||||
|
* * Its performance is suboptimal, especially under Android.
|
||||||
|
* * With the introduction of lambdas in Java 8, MessageBus went from less verbose than listeners
|
||||||
|
* to [more verbose](https://github.com/google/guava/issues/3311).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h2>MessageBus Summary</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The MessageBus allows publish-subscribe-style communication between components without requiring
|
||||||
|
* the components to explicitly register with one another (and thus be aware of each other). It is
|
||||||
|
* designed exclusively to replace traditional Java in-process message distribution using explicit
|
||||||
|
* registration. It is *not* a general-purpose publish-subscribe system, nor is it intended
|
||||||
|
* for interprocess communication.
|
||||||
|
*
|
||||||
|
* <h2>Receiving Messages</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* To receive messages, an object should:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 1. Expose a public method, known as the *message subscriber*, which accepts a single
|
||||||
|
* argument of the type of message desired;
|
||||||
|
* 1. Mark it with a [Subscribe] annotation;
|
||||||
|
* 1. Pass itself to an MessageBus instance's [.register] method.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h2>Posting Messages</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* To post a message, simply provide the message object to the [.post] method. The
|
||||||
|
* MessageBus instance will determine the type of message and route it to all registered listeners.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Messages are routed based on their type a message will be delivered to any subscriber for
|
||||||
|
* any type to which the message is *assignable.* This includes implemented interfaces, all
|
||||||
|
* thisclasses, and all interfaces implemented by thisclasses.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h2>Subscriber Methods</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Message subscriber methods must accept only one argument: the message.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Subscribers should not, in general, throw. If they do, the MessageBus will catch and log the
|
||||||
|
* exception. This is rarely the right solution for error handling and should not be relied upon; it
|
||||||
|
* is intended solely to help find problems during development.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The MessageBus guarantees that it will not call a subscriber method from multiple threads
|
||||||
|
* simultaneously, unless the method explicitly allows it by bearing the [ ] annotation. If this annotation is not present, subscriber methods need not
|
||||||
|
* worry about being reentrant, unless also called from outside the MessageBus.
|
||||||
|
*
|
||||||
|
* <h2>Dead Messages</h2>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a message is posted, but no registered subscribers can accept it, it is considered "dead."
|
||||||
|
* To give the system a second chance to handle dead messages, they are wrapped in an instance of
|
||||||
|
* [DeadMessage] and reposted.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a subscriber for this type of all messages (such as Object) is registered, no message will
|
||||||
|
* ever be considered dead, and no DeadMessages will be generated. Accordingly, while DeadMessage
|
||||||
|
* extends [Object], a subscriber registered to receive any Object will never receive a
|
||||||
|
* DeadMessage.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This class is safe for concurrent use.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Cliff
|
||||||
|
* @since 10.0
|
||||||
|
* @param identifier a brief name for this bus, for logging purposes. Should/home/alex/privacy/project/myths/gaea be a valid Java
|
||||||
|
* @param executor the default executor this event bus uses for dispatching events to subscribers.
|
||||||
|
* @param dispatcher message dispatcher.
|
||||||
|
* @param exceptionHandler Handler for subscriber exceptions.
|
||||||
|
*/
|
||||||
|
open class Bus<T : Any>(
|
||||||
|
override val identifier: String,
|
||||||
|
override val executor: Executor,
|
||||||
|
val dispatcher: Dispatcher<T>,
|
||||||
|
val exceptionHandler: SubscriberExceptionHandler<T>,
|
||||||
|
) : IBus<T> {
|
||||||
|
|
||||||
|
val DEAD_TOPIC = "DEAD_TOPIC"
|
||||||
|
|
||||||
|
private val subscribers: SubscriberRegistry<T> = SubscriberRegistry(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MessageBus with the given `identifier`.
|
||||||
|
*
|
||||||
|
* @param identifier a brief name for this bus, for logging purposes. Should/home/alex/privacy/project/myths/gaea be a valid Java
|
||||||
|
* identifier.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(identifier: String = "default") : this(
|
||||||
|
identifier,
|
||||||
|
Executor { it.run() },
|
||||||
|
Dispatcher.perThreadDispatchQueue(),
|
||||||
|
LoggingHandler()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MessageBus with the given [SubscriberExceptionHandler].
|
||||||
|
*
|
||||||
|
* @param exceptionHandler Handler for subscriber exceptions.
|
||||||
|
* @since 16.0
|
||||||
|
*/
|
||||||
|
constructor(exceptionHandler: SubscriberExceptionHandler<T>) : this(
|
||||||
|
"default",
|
||||||
|
Executor { it.run() },
|
||||||
|
Dispatcher.perThreadDispatchQueue(),
|
||||||
|
exceptionHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AsyncMessageBus that will use `executor` to dispatch messages. Assigns `identifier` as the bus's name for logging purposes.
|
||||||
|
*
|
||||||
|
* @param identifier short name for the bus, for logging purposes.
|
||||||
|
* @param executor Executor to use to dispatch messages. It is the caller's responsibility to shut
|
||||||
|
* down the executor after the last message has been posted to this message bus.
|
||||||
|
*/
|
||||||
|
constructor(identifier: String, executor: Executor) : this(
|
||||||
|
identifier,
|
||||||
|
executor,
|
||||||
|
Dispatcher.legacyAsync(),
|
||||||
|
LoggingHandler()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AsyncMessageBus that will use `executor` to dispatch messages.
|
||||||
|
*
|
||||||
|
* @param executor Executor to use to dispatch messages. It is the caller's responsibility to shut
|
||||||
|
* down the executor after the last message has been posted to this message bus.
|
||||||
|
* @param subscriberExceptionHandler Handler used to handle exceptions thrown from subscribers.
|
||||||
|
* See [SubscriberExceptionHandler] for more information.
|
||||||
|
* @since 16.0
|
||||||
|
*/
|
||||||
|
constructor(executor: Executor, subscriberExceptionHandler: SubscriberExceptionHandler<T>) : this(
|
||||||
|
"default",
|
||||||
|
executor,
|
||||||
|
Dispatcher.legacyAsync(),
|
||||||
|
subscriberExceptionHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AsyncMessageBus that will use `executor` to dispatch messages.
|
||||||
|
*
|
||||||
|
* @param executor Executor to use to dispatch messages. It is the caller's responsibility to shut
|
||||||
|
* down the executor after the last message has been posted to this message bus.
|
||||||
|
*/
|
||||||
|
constructor(executor: Executor) : this(
|
||||||
|
"default",
|
||||||
|
executor,
|
||||||
|
Dispatcher.legacyAsync(),
|
||||||
|
LoggingHandler()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun register(topics: Array<String>, subscriber: Any) {
|
||||||
|
subscribers.register(topics, subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers subscriber method on `object` to receive messages.
|
||||||
|
*
|
||||||
|
* @param topics method subscribe topic.
|
||||||
|
* @param subscriber subscriber method declare object.
|
||||||
|
* @param method subscriber method should be registered.
|
||||||
|
*/
|
||||||
|
override fun register(topics: Array<String>, subscriber: Any, method: Method) {
|
||||||
|
subscribers.register(topics, subscriber, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers all subscriber methods on `object` to receive messages.
|
||||||
|
*
|
||||||
|
* @param subscriber object whose subscriber methods should be registered.
|
||||||
|
*/
|
||||||
|
override fun register(subscriber: Any) {
|
||||||
|
subscribers.register(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun register(subscriber: Any, method: Method) {
|
||||||
|
subscribers.register(subscriber, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all subscriber methods on a registered `object`.
|
||||||
|
*
|
||||||
|
* @param subscriber object whose subscriber methods should be unregistered.
|
||||||
|
* @throws IllegalArgumentException if the object was not previously registered.
|
||||||
|
*/
|
||||||
|
override fun unregister(subscriber: Any) {
|
||||||
|
subscribers.unregister(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unregister(topic: String, subscriber: Any) {
|
||||||
|
subscribers.unregister(topic, subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message to all registered subscribers. This method will return successfully after the
|
||||||
|
* message has been posted to all subscribers, and regardless of any exceptions thrown by
|
||||||
|
* subscribers.
|
||||||
|
*
|
||||||
|
* @param message message to post.
|
||||||
|
*/
|
||||||
|
override fun publish(message: T) {
|
||||||
|
val messageSubscribers = subscribers.getSubscribers(message::class.java.name)
|
||||||
|
if (messageSubscribers.hasNext()) {
|
||||||
|
dispatcher.dispatch(message, messageSubscribers)
|
||||||
|
} else {
|
||||||
|
// the message had no subscribers and was not itself a DeadMessage
|
||||||
|
publish(DEAD_TOPIC, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message to all registered subscribers. This method will return successfully after the
|
||||||
|
* message has been posted to all subscribers, and regardless of any exceptions thrown by
|
||||||
|
* subscribers.
|
||||||
|
*
|
||||||
|
* @param message message to post.
|
||||||
|
*/
|
||||||
|
override fun publishAsync(message: T) {
|
||||||
|
val messageSubscribers = subscribers.getSubscribers(message::class.java.name)
|
||||||
|
if (messageSubscribers.hasNext()) {
|
||||||
|
dispatcher.dispatchAsync(message, messageSubscribers)
|
||||||
|
} else {
|
||||||
|
// the message had no subscribers and was not itself a DeadMessage
|
||||||
|
publishAsync(DEAD_TOPIC, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun publish(topic: String, message: T) {
|
||||||
|
val messageSubscribers = subscribers.getSubscribers(topic)
|
||||||
|
if (messageSubscribers.hasNext()) {
|
||||||
|
dispatcher.dispatch(message, messageSubscribers)
|
||||||
|
} else if (topic != DEAD_TOPIC) {
|
||||||
|
// the message had no subscribers and was not itself a DeadMessage
|
||||||
|
publish(DEAD_TOPIC, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun publishAsync(topic: String, message: T) {
|
||||||
|
val messageSubscribers = subscribers.getSubscribers(topic)
|
||||||
|
if (messageSubscribers.hasNext()) {
|
||||||
|
dispatcher.dispatchAsync(message, messageSubscribers)
|
||||||
|
} else if (topic != DEAD_TOPIC) {
|
||||||
|
// the message had no subscribers and was not itself a DeadMessage
|
||||||
|
publishAsync(DEAD_TOPIC, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles the given exception thrown by a subscriber with the given context. */
|
||||||
|
override fun handleException(cause: Throwable?, context: SubscriberExceptionContext<T>) {
|
||||||
|
try {
|
||||||
|
exceptionHandler.handleException(cause, context)
|
||||||
|
} catch (e2: Throwable) {
|
||||||
|
// if the handler threw an exception... well, just log it
|
||||||
|
logger.log(
|
||||||
|
Level.SEVERE, String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, cause),
|
||||||
|
e2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Bus(identifier='$identifier', executor=$executor, dispatcher=$dispatcher, exceptionHandler=$exceptionHandler, subscribers=$subscribers)"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = Logger.getLogger(Bus::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a message that was posted, but which had no subscribers and thus could not be delivered.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Registering a DeadMessage subscriber is useful for debugging or logging, as it can detect
|
||||||
|
* misconfigurations in a system's message distribution.
|
||||||
|
*
|
||||||
|
* @author Cliff
|
||||||
|
* @since 10.0
|
||||||
|
*
|
||||||
|
* Creates a new DeadMessage.
|
||||||
|
*
|
||||||
|
* @param source object broadcasting the DeadMessage (generally the [Bus]).
|
||||||
|
* @param message the message that could not be delivered.
|
||||||
|
*/
|
||||||
|
class DeadMessage(val source: Any?, val message: Any?) {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "DeadMessage(source=$source, message=$message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Dispatcher.kt
Normal file
140
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Dispatcher.kt
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for dispatching messages to subscribers, providing different message ordering guarantees that
|
||||||
|
* make sense for different situations.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* **Note:** The dispatcher is orthogonal to the subscriber's `Executor`. The dispatcher
|
||||||
|
* controls the order in which messages are dispatched, while the executor controls how (i.e. on which
|
||||||
|
* thread) the subscriber is actually called when a message is dispatched to it.
|
||||||
|
*
|
||||||
|
* @author Colin Decker
|
||||||
|
*/
|
||||||
|
abstract class Dispatcher<T : Any> {
|
||||||
|
/** Dispatches the given `message` to the given `subscribers`. */
|
||||||
|
fun dispatch(message: T, subscribers: Iterator<Subscriber<T>>?) {
|
||||||
|
while (subscribers!!.hasNext()) {
|
||||||
|
subscribers.next().dispatch(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispatches the given `message` to the given `subscribers`. */
|
||||||
|
abstract fun dispatchAsync(message: T, subscribers: Iterator<Subscriber<T>>?)
|
||||||
|
|
||||||
|
/** Implementation of a [.perThreadDispatchQueue] dispatcher. */
|
||||||
|
private class PerThreadQueuedDispatcher<T : Any> : Dispatcher<T>() {
|
||||||
|
// This dispatcher matches the original dispatch behavior of MessageBus.
|
||||||
|
/** Per-thread queue of messages to dispatch. */
|
||||||
|
private val queue: ThreadLocal<Queue<Message<T>>> = object : ThreadLocal<Queue<Message<T>>>() {
|
||||||
|
override fun initialValue(): Queue<Message<T>> {
|
||||||
|
return ArrayDeque()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Per-thread dispatch state, used to avoid reentrant message dispatching. */
|
||||||
|
private val dispatching: ThreadLocal<Boolean> = object : ThreadLocal<Boolean>() {
|
||||||
|
override fun initialValue(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchAsync(message: T, subscribers: Iterator<Subscriber<T>>?) {
|
||||||
|
val queueForThread = queue.get()
|
||||||
|
queueForThread.offer(Message(message, subscribers))
|
||||||
|
if (!dispatching.get()) {
|
||||||
|
dispatching.set(true)
|
||||||
|
try {
|
||||||
|
var nextMessage: Message<T>?
|
||||||
|
while (queueForThread.poll().also { nextMessage = it } != null) {
|
||||||
|
while (nextMessage!!.subscribers!!.hasNext()) {
|
||||||
|
nextMessage!!.subscribers!!.next().dispatchAsync(nextMessage!!.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dispatching.remove()
|
||||||
|
queue.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Message<T : Any>(val message: Any, val subscribers: Iterator<Subscriber<T>>?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implementation of a [.legacyAsync] dispatcher. */
|
||||||
|
private class LegacyAsyncDispatcher<T : Any> : Dispatcher<T>() {
|
||||||
|
// This dispatcher matches the original dispatch behavior of AsyncMessageBus.
|
||||||
|
//
|
||||||
|
// We can't really make any guarantees about the overall dispatch order for this dispatcher in
|
||||||
|
// a multithreaded environment for a couple reasons:
|
||||||
|
//
|
||||||
|
// 1. Subscribers to messages posted on different threads can be interleaved with each other
|
||||||
|
// freely. (A message on one thread, B message on another could yield any of
|
||||||
|
// [a1, a2, a3, b1, b2], [a1, b2, a2, a3, b2], [a1, b2, b3, a2, a3], etc.)
|
||||||
|
// 2. It's possible for subscribers to actually be dispatched to in a different order than they
|
||||||
|
// were added to the queue. It's easily possible for one thread to take the head of the
|
||||||
|
// queue, immediately followed by another thread taking the next element in the queue. That
|
||||||
|
// second thread can then dispatch to the subscriber it took before the first thread does.
|
||||||
|
//
|
||||||
|
// All this makes me really wonder if there's any value in queueing here at all. A dispatcher
|
||||||
|
// that simply loops through the subscribers and dispatches the message to each would actually
|
||||||
|
// probably provide a stronger order guarantee, though that order would obviously be different
|
||||||
|
// in some cases.
|
||||||
|
/** Global message queue. */
|
||||||
|
private val queue = ConcurrentLinkedQueue<MessageWithSubscriber<T>>()
|
||||||
|
override fun dispatchAsync(message: T, subscribers: Iterator<Subscriber<T>>?) {
|
||||||
|
while (subscribers!!.hasNext()) {
|
||||||
|
queue.add(MessageWithSubscriber(message, subscribers.next()))
|
||||||
|
}
|
||||||
|
var e: MessageWithSubscriber<T>?
|
||||||
|
while (queue.poll().also { e = it } != null) {
|
||||||
|
e!!.subscriber!!.dispatchAsync(e!!.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageWithSubscriber<T : Any>(val message: T, val subscriber: Subscriber<T>?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Returns a dispatcher that queues messages that are posted reentrantly on a thread that is already
|
||||||
|
* dispatching a message, guaranteeing that all messages posted on a single thread are dispatched to
|
||||||
|
* all subscribers in the order they are posted.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* When all subscribers are dispatched to using a *direct* executor (which dispatches on
|
||||||
|
* the same thread that posts the message), this yields a breadth-first dispatch order on each
|
||||||
|
* thread. That is, all subscribers to a single message A will be called before any subscribers to
|
||||||
|
* any messages B and C that are posted to the message bus by the subscribers to A.
|
||||||
|
*/
|
||||||
|
fun <T : Any> perThreadDispatchQueue(): Dispatcher<T> {
|
||||||
|
return PerThreadQueuedDispatcher()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a dispatcher that queues messages that are posted in a single global queue. This behavior
|
||||||
|
* matches the original behavior of AsyncMessageBus exactly, but is otherwise not especially useful.
|
||||||
|
* For async dispatch, an [immediate][.immediate] dispatcher should generally be
|
||||||
|
* preferable.
|
||||||
|
*/
|
||||||
|
fun <T : Any> legacyAsync(): Dispatcher<T> {
|
||||||
|
return LegacyAsyncDispatcher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/gaea/src/main/kotlin/com/synebula/gaea/bus/IBus.kt
Normal file
84
src/gaea/src/main/kotlin/com/synebula/gaea/bus/IBus.kt
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
interface IBus<T : Any> {
|
||||||
|
|
||||||
|
val identifier: String
|
||||||
|
|
||||||
|
val executor: Executor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册事件Listener
|
||||||
|
* @param subscriber subscriber所在类
|
||||||
|
*/
|
||||||
|
fun register(subscriber: Any)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消注册事件Listener
|
||||||
|
* @param subscriber Listener所在类
|
||||||
|
*/
|
||||||
|
fun register(subscriber: Any, method: Method)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消注册事件Listener
|
||||||
|
* @param subscriber Listener所在类
|
||||||
|
*/
|
||||||
|
fun unregister(subscriber: Any)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步发布事件
|
||||||
|
* @param message 事件
|
||||||
|
*/
|
||||||
|
fun publish(message: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步发布事件
|
||||||
|
* @param message 事件
|
||||||
|
*/
|
||||||
|
fun publishAsync(message: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册事件Listener
|
||||||
|
* @param topics 主题
|
||||||
|
* @param subscriber subscriber对象
|
||||||
|
*/
|
||||||
|
fun register(topics: Array<String>, subscriber: Any)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册事件Listener
|
||||||
|
* @param topics 主题
|
||||||
|
* @param subscriber subscriber对象
|
||||||
|
* @param method Listener方法
|
||||||
|
*/
|
||||||
|
fun register(topics: Array<String>, subscriber: Any, method: Method)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消注册事件Listener
|
||||||
|
* @param topic 主题
|
||||||
|
* @param subscriber subscriber对象
|
||||||
|
*/
|
||||||
|
fun unregister(topic: String, subscriber: Any)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步发布事件
|
||||||
|
* @param topic 主题
|
||||||
|
* @param message 事件
|
||||||
|
*/
|
||||||
|
fun publish(topic: String, message: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步发布事件
|
||||||
|
* @param topic 主题
|
||||||
|
* @param message 事件
|
||||||
|
*/
|
||||||
|
fun publishAsync(topic: String, message: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常处理
|
||||||
|
*/
|
||||||
|
fun handleException(cause: Throwable?, context: SubscriberExceptionContext<T>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
|
/** Simple logging handler for subscriber exceptions. */
|
||||||
|
internal class LoggingHandler<T : Any> : SubscriberExceptionHandler<T> {
|
||||||
|
override fun handleException(exception: Throwable?, context: SubscriberExceptionContext<T>) {
|
||||||
|
val logger = logger(context)
|
||||||
|
if (logger.isLoggable(Level.SEVERE)) {
|
||||||
|
logger.log(Level.SEVERE, message(context), exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun <T : Any> logger(context: SubscriberExceptionContext<T>): Logger {
|
||||||
|
return Logger.getLogger(Bus::class.java.name + "." + context.bus.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Any> message(context: SubscriberExceptionContext<T>): String {
|
||||||
|
val method = context.subscriberMethod
|
||||||
|
return ("Exception thrown by subscriber method "
|
||||||
|
+ method.name
|
||||||
|
+ '('
|
||||||
|
+ method.parameterTypes[0].name
|
||||||
|
+ ')'
|
||||||
|
+ " on subscriber "
|
||||||
|
+ context.subscriber
|
||||||
|
+ " when dispatching message: "
|
||||||
|
+ context.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscribe.kt
Normal file
36
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscribe.kt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a method as a message subscriber.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The type of message will be indicated by the method's first (and only) parameter, which cannot
|
||||||
|
* be primitive. If this annotation is applied to methods with zero parameters, or more than one
|
||||||
|
* parameter, the object containing the method will not be able to register for message delivery from
|
||||||
|
* the [Bus].
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Unless also annotated with @[AllowConcurrentSubscribe], message subscriber methods will be
|
||||||
|
* invoked serially by each message bus that they are registered with.
|
||||||
|
*
|
||||||
|
* @author Cliff
|
||||||
|
* @since 10.0
|
||||||
|
*
|
||||||
|
* @param topics method subscribe topics, only for [com.synebula.gaea.bus.messagebus.MessageBus]
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
|
annotation class Subscribe(val topics: Array<String> = [])
|
||||||
134
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscriber.kt
Normal file
134
src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscriber.kt
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import com.synebula.gaea.exception.NoticeUserException
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subscriber method on a specific object, plus the executor that should be used for dispatching
|
||||||
|
* messages to it.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Two subscribers are equivalent when they refer to the same method on the same object (not
|
||||||
|
* class). This property is used to ensure that no subscriber method is registered more than once.
|
||||||
|
*
|
||||||
|
* @author Colin Decker
|
||||||
|
*
|
||||||
|
* @param bus The message bus this subscriber belongs to.
|
||||||
|
* @param target The object with the subscriber method.
|
||||||
|
* @param method Subscriber method.
|
||||||
|
*/
|
||||||
|
open class Subscriber<T : Any> private constructor(
|
||||||
|
private val bus: IBus<T>,
|
||||||
|
val target: Any,
|
||||||
|
val method: Method,
|
||||||
|
) {
|
||||||
|
/** Executor to use for dispatching messages to this subscriber. */
|
||||||
|
private val executor: Executor?
|
||||||
|
|
||||||
|
init {
|
||||||
|
method.isAccessible = true
|
||||||
|
executor = bus.executor
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispatches `message` to this subscriber . */
|
||||||
|
fun dispatch(message: Any) {
|
||||||
|
invokeSubscriberMethod(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispatches `message` to this subscriber using the proper executor. */
|
||||||
|
fun dispatchAsync(message: Any) {
|
||||||
|
executor!!.execute {
|
||||||
|
try {
|
||||||
|
invokeSubscriberMethod(message)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
bus.handleException(e.cause, context(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the subscriber method. This method can be overridden to make the invocation
|
||||||
|
* synchronized.
|
||||||
|
*/
|
||||||
|
@Throws(InvocationTargetException::class)
|
||||||
|
open fun invokeSubscriberMethod(message: Any) {
|
||||||
|
try {
|
||||||
|
method.invoke(target, message)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw Error("Method rejected target/argument: $message", e)
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
throw Error("Method became inaccessible: $message", e)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
if (e.cause is Error || e.cause is NoticeUserException) {
|
||||||
|
throw e.cause!!
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the context for the given message. */
|
||||||
|
private fun context(message: Any): SubscriberExceptionContext<T> {
|
||||||
|
return SubscriberExceptionContext(bus, message, target, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return (31 + method.hashCode()) * 31 + System.identityHashCode(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other is Subscriber<*>) {
|
||||||
|
// Use == so that different equal instances will still receive messages.
|
||||||
|
// We only guard against the case that the same object is registered
|
||||||
|
// multiple times
|
||||||
|
return target === other.target && method == other.method
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber that synchronizes invocations of a method to ensure that only one thread may enter
|
||||||
|
* the method at a time.
|
||||||
|
*/
|
||||||
|
internal class SynchronizedSubscriber<T : Any>(bus: IBus<T>, target: Any, method: Method) :
|
||||||
|
Subscriber<T>(bus, target, method) {
|
||||||
|
@Throws(InvocationTargetException::class)
|
||||||
|
override fun invokeSubscriberMethod(message: Any) {
|
||||||
|
synchronized(this) { super.invokeSubscriberMethod(message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Creates a `Subscriber` for `method` on `subscriber`. */
|
||||||
|
fun <T : Any> create(bus: IBus<T>, subscriber: Any, method: Method): Subscriber<T> {
|
||||||
|
return if (isDeclaredThreadSafe(method)) Subscriber(
|
||||||
|
bus,
|
||||||
|
subscriber,
|
||||||
|
method
|
||||||
|
) else SynchronizedSubscriber(
|
||||||
|
bus, subscriber, method
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether `method` is thread-safe, as indicated by the presence of the [ ] annotation.
|
||||||
|
*/
|
||||||
|
private fun isDeclaredThreadSafe(method: Method): Boolean {
|
||||||
|
return method.getAnnotation(AllowConcurrentSubscribe::class.java) != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for an exception thrown by a subscriber.
|
||||||
|
*
|
||||||
|
* @since 16.0
|
||||||
|
*
|
||||||
|
* @param bus The [IBus] that handled the message and the subscriber. Useful for
|
||||||
|
* broadcasting a new message based on the error.
|
||||||
|
* @param message The message object that caused the subscriber to throw.
|
||||||
|
* @param subscriber The source subscriber context.
|
||||||
|
* @param subscriberMethod the subscribed method.
|
||||||
|
*/
|
||||||
|
class SubscriberExceptionContext<T : Any> internal constructor(
|
||||||
|
val bus: IBus<T>, val message: Any, val subscriber: Any, val subscriberMethod: Method,
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for exceptions thrown by message subscribers.
|
||||||
|
*
|
||||||
|
* @since 16.0
|
||||||
|
*/
|
||||||
|
interface SubscriberExceptionHandler<T : Any> {
|
||||||
|
/** Handles exceptions thrown by subscribers. */
|
||||||
|
fun handleException(exception: Throwable?, context: SubscriberExceptionContext<T>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.synebula.gaea.bus
|
||||||
|
|
||||||
|
import com.synebula.gaea.reflect.supertypes
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of subscribers to a single message bus.
|
||||||
|
*
|
||||||
|
* @author Colin Decker
|
||||||
|
*/
|
||||||
|
open class SubscriberRegistry<T : Any>(private val bus: IBus<T>) {
|
||||||
|
/**
|
||||||
|
* All registered subscribers, indexed by message type.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The [CopyOnWriteArraySet] values make it easy and relatively lightweight to get an
|
||||||
|
* immutable snapshot of all current subscribers to a message without any locking.
|
||||||
|
*/
|
||||||
|
private val subscribers = ConcurrentHashMap<String, CopyOnWriteArraySet<Subscriber<T>>>()
|
||||||
|
|
||||||
|
open fun register(topics: Array<String>, subscriber: Any) {
|
||||||
|
val listenerMethods = findAllSubscribers(subscriber)
|
||||||
|
for (topic in topics) {
|
||||||
|
for ((_, messageMethodsInListener) in listenerMethods) {
|
||||||
|
var messageSubscribers = subscribers[topic]
|
||||||
|
if (messageSubscribers == null) {
|
||||||
|
val newSet = CopyOnWriteArraySet<Subscriber<T>>()
|
||||||
|
messageSubscribers = subscribers.putIfAbsent(topic, newSet) ?: newSet
|
||||||
|
}
|
||||||
|
messageSubscribers.addAll(messageMethodsInListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun register(topics: Array<String>, subscriber: Any, method: Method) {
|
||||||
|
for (topic in topics) {
|
||||||
|
var messageSubscribers = subscribers[topic]
|
||||||
|
if (messageSubscribers == null) {
|
||||||
|
val newSet = CopyOnWriteArraySet<Subscriber<T>>()
|
||||||
|
messageSubscribers = subscribers.putIfAbsent(topic, newSet) ?: newSet
|
||||||
|
}
|
||||||
|
messageSubscribers.add(Subscriber.create(this.bus, subscriber, method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Registers all subscriber methods on the given subscriber object. */
|
||||||
|
fun register(subscriber: Any) {
|
||||||
|
val listenerMethods = findAllSubscribers(subscriber)
|
||||||
|
for ((topic, messageMethodsInListener) in listenerMethods) {
|
||||||
|
var messageSubscribers = subscribers[topic]
|
||||||
|
if (messageSubscribers == null) {
|
||||||
|
val newSet = CopyOnWriteArraySet<Subscriber<T>>()
|
||||||
|
messageSubscribers = subscribers.putIfAbsent(topic, newSet) ?: newSet
|
||||||
|
}
|
||||||
|
messageSubscribers.addAll(messageMethodsInListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Registers subscriber method on the given subscriber object. */
|
||||||
|
fun register(subscriber: Any, method: Method) {
|
||||||
|
val parameterTypes = method.parameterTypes
|
||||||
|
check(parameterTypes.size == 1) {
|
||||||
|
"Method $method has @SubscribeTopic annotation but has ${parameterTypes.size} parameters. Subscriber methods must have exactly 1 parameter."
|
||||||
|
}
|
||||||
|
check(!parameterTypes[0].isPrimitive) {
|
||||||
|
"@SubscribeTopic method $method's parameter is ${parameterTypes[0].name}. Subscriber methods cannot accept primitives. "
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventType = parameterTypes[0]
|
||||||
|
val topic = eventType.name
|
||||||
|
var messageSubscribers = subscribers[topic]
|
||||||
|
if (messageSubscribers == null) {
|
||||||
|
val newSet = CopyOnWriteArraySet<Subscriber<T>>()
|
||||||
|
messageSubscribers = subscribers.putIfAbsent(topic, newSet) ?: newSet
|
||||||
|
}
|
||||||
|
messageSubscribers.add(Subscriber.create(bus, subscriber, method))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unregisters all subscribers on the given subscriber object. */
|
||||||
|
fun unregister(subscriber: Any) {
|
||||||
|
val listenerMethods = findAllSubscribers(subscriber)
|
||||||
|
for ((topic, listenerMethodsForType) in listenerMethods) {
|
||||||
|
val currentSubscribers = subscribers[topic]
|
||||||
|
require(!(currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType.toSet()))) {
|
||||||
|
// if removeAll returns true, all we really know is that at least one subscriber was
|
||||||
|
// removed... however, barring something very strange we can assume that if at least one
|
||||||
|
// subscriber was removed, all subscribers on subscriber for that message type were... after
|
||||||
|
// all, the definition of subscribers on a particular class is totally static
|
||||||
|
"missing message subscriber for an annotated method. Is $subscriber registered?"
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't try to remove the set if it's empty; that can't be done safely without a lock
|
||||||
|
// anyway, if the set is empty it'll just be wrapping an array of length 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unregisters all subscribers by topic on the given subscriber object. */
|
||||||
|
open fun unregister(topic: String, subscriber: Any) {
|
||||||
|
val listenerMethods = findAllSubscribers(subscriber)
|
||||||
|
if (listenerMethods[topic] == null) return //不包含topic则推出
|
||||||
|
val currentSubscribers = subscribers[topic]
|
||||||
|
require(!(currentSubscribers == null || !currentSubscribers.removeAll(listenerMethods[topic]!!.toSet()))) {
|
||||||
|
// if removeAll returns true, all we really know is that at least one subscriber was
|
||||||
|
// removed... however, barring something very strange we can assume that if at least one
|
||||||
|
// subscriber was removed, all subscribers on subscriber for that message type were... after
|
||||||
|
// all, the definition of subscribers on a particular class is totally static
|
||||||
|
"missing message subscriber for an annotated method. Is $subscriber registered?"
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't try to remove the set if it's empty; that can't be done safely without a lock
|
||||||
|
// anyway, if the set is empty it'll just be wrapping an array of length 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an iterator representing an immutable snapshot of all subscribers to the given message at
|
||||||
|
* the time this method is called.
|
||||||
|
*/
|
||||||
|
fun getSubscribers(topic: String): Iterator<Subscriber<T>> {
|
||||||
|
val topicSubscribers: CopyOnWriteArraySet<Subscriber<T>> = subscribers[topic] ?: CopyOnWriteArraySet()
|
||||||
|
return topicSubscribers.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all subscribers for the given subscriber grouped by the type of message they subscribe to.
|
||||||
|
*/
|
||||||
|
private fun findAllSubscribers(subscriber: Any): Map<String, List<Subscriber<T>>> {
|
||||||
|
val methodsInListener = mutableMapOf<String, MutableList<Subscriber<T>>>()
|
||||||
|
val clazz: Class<*> = subscriber.javaClass
|
||||||
|
for (method in getAnnotatedMethods(clazz)) {
|
||||||
|
var topics = method.getAnnotation(Subscribe::class.java).topics
|
||||||
|
|
||||||
|
//如果没有定义topic,则使用消息类名称做topic
|
||||||
|
if (topics.isEmpty()) {
|
||||||
|
val parameterTypes = method.parameterTypes
|
||||||
|
val messageType = parameterTypes[0]
|
||||||
|
topics = arrayOf(messageType.name)
|
||||||
|
}
|
||||||
|
for (topic in topics) {
|
||||||
|
val listeners = methodsInListener.getOrDefault(topic, mutableListOf())
|
||||||
|
listeners.add(Subscriber.create(bus, subscriber, method))
|
||||||
|
methodsInListener[topic] = listeners
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methodsInListener
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MethodIdentifier(method: Method) {
|
||||||
|
private val name: String = method.name
|
||||||
|
private val parameterTypes: List<Class<*>> = listOf(*method.parameterTypes)
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = name.hashCode()
|
||||||
|
for (element in parameterTypes) result = 31 * result + element.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other is MethodIdentifier) {
|
||||||
|
return name == other.name && parameterTypes == other.parameterTypes
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread-safe cache that contains the mapping from each class to all methods in that class and
|
||||||
|
* all super-classes, that are annotated with `@Subscribe`. The cache is shared across all
|
||||||
|
* instances of this class; this greatly improves performance if multiple MessageBus instances are
|
||||||
|
* created and objects of the same class are registered on all of them.
|
||||||
|
*/
|
||||||
|
protected val subscriberMethodsCache = mapOf<Class<*>, List<Method>>()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
protected fun getAnnotatedMethods(clazz: Class<*>): List<Method> {
|
||||||
|
var methods = subscriberMethodsCache[clazz]
|
||||||
|
if (methods == null)
|
||||||
|
methods = getAnnotatedMethodsNotCached(clazz, Subscribe::class.java)
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getAnnotatedMethodsNotCached(
|
||||||
|
clazz: Class<*>,
|
||||||
|
annotationClass: Class<out Annotation>,
|
||||||
|
): List<Method> {
|
||||||
|
val supertypes = flattenHierarchy(clazz)
|
||||||
|
val identifiers = mutableMapOf<MethodIdentifier, Method>()
|
||||||
|
for (supertype in supertypes) {
|
||||||
|
for (method in supertype.declaredMethods) {
|
||||||
|
if (method.isAnnotationPresent(annotationClass) && !method.isSynthetic) {
|
||||||
|
val parameterTypes = method.parameterTypes
|
||||||
|
check(parameterTypes.size == 1) {
|
||||||
|
"Method $method has @SubscribeTopic annotation but has ${parameterTypes.size} parameters. Subscriber methods must have exactly 1 parameter."
|
||||||
|
}
|
||||||
|
check(!parameterTypes[0].isPrimitive) {
|
||||||
|
"@SubscribeTopic method $method's parameter is ${parameterTypes[0].name}. Subscriber methods cannot accept primitives. "
|
||||||
|
}
|
||||||
|
|
||||||
|
val identifier = MethodIdentifier(method)
|
||||||
|
if (!identifiers.containsKey(identifier)) {
|
||||||
|
identifiers[identifier] = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identifiers.values.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/** Global cache of classes to their flattened hierarchy of supertypes. */
|
||||||
|
protected val flattenHierarchyCache = mutableMapOf<Class<*>, Set<Class<*>>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens a class's type hierarchy into a set of `Class` objects including all
|
||||||
|
* superclasses (transitively) and all interfaces implemented by these superclasses.
|
||||||
|
*/
|
||||||
|
fun flattenHierarchy(clazz: Class<*>): Set<Class<*>> {
|
||||||
|
var supertypes = flattenHierarchyCache[clazz]
|
||||||
|
if (supertypes == null) {
|
||||||
|
supertypes = clazz.supertypes()
|
||||||
|
flattenHierarchyCache[clazz] = supertypes
|
||||||
|
}
|
||||||
|
return supertypes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.synebula.gaea.data
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象转换器,支持对象之间的转换。
|
|
||||||
*
|
|
||||||
* @author alex
|
|
||||||
* @version 0.1
|
|
||||||
* @since 2020-05-15
|
|
||||||
*/
|
|
||||||
interface IObjectConverter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换源对象到目标对象。
|
|
||||||
*
|
|
||||||
* @param src 源对象。
|
|
||||||
* @param dest 目标对象。
|
|
||||||
* @param <T> 目标对象类型。
|
|
||||||
* @return 目标对象
|
|
||||||
*/
|
|
||||||
fun <T> convert(src: Any, dest: Class<T>): T
|
|
||||||
}
|
|
||||||
@@ -11,13 +11,13 @@ import java.util.*
|
|||||||
* 参数:年=y,月=M,日=d,时=H,分=m,秒=s,毫秒=S。位数最好使用默认最大长度。
|
* 参数:年=y,月=M,日=d,时=H,分=m,秒=s,毫秒=S。位数最好使用默认最大长度。
|
||||||
*/
|
*/
|
||||||
class DateCode(pattern: String = "yyyyMMdd") : ICodeGenerator<Long> {
|
class DateCode(pattern: String = "yyyyMMdd") : ICodeGenerator<Long> {
|
||||||
var formator = SimpleDateFormat()
|
var formatter = SimpleDateFormat()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
formator.applyPattern(pattern)
|
formatter.applyPattern(pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generate(): Long {
|
override fun generate(): Long {
|
||||||
return java.lang.Long.parseLong(formator.format(Date()))
|
return java.lang.Long.parseLong(formatter.format(Date()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.synebula.gaea.data.code
|
package com.synebula.gaea.data.code
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 固定长度随机编号生成。
|
* 固定长度随机编号生成。
|
||||||
@@ -9,8 +10,8 @@ import java.util.*
|
|||||||
* @since 2016年10月24日 上午10:58:05
|
* @since 2016年10月24日 上午10:58:05
|
||||||
*/
|
*/
|
||||||
class FixedRandomCode(
|
class FixedRandomCode(
|
||||||
//生成的随机编号长度。
|
//生成的随机编号长度。
|
||||||
var length: Int
|
var length: Int,
|
||||||
) : ICodeGenerator<String> {
|
) : ICodeGenerator<String> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,12 +33,12 @@ class FixedRandomCode(
|
|||||||
var format = String.format("%s%d%dd", "%", 0, calcMaxLength)
|
var format = String.format("%s%d%dd", "%", 0, calcMaxLength)
|
||||||
val count = this.length / calcMaxLength
|
val count = this.length / calcMaxLength
|
||||||
for (i in 0 until count) {
|
for (i in 0 until count) {
|
||||||
buffer.append(String.format(format, (random.nextDouble() * Math.pow(10.0, calcMaxLength.toDouble())).toInt()))
|
buffer.append(String.format(format, (random.nextDouble() * 10.0.pow(calcMaxLength.toDouble())).toInt()))
|
||||||
}
|
}
|
||||||
val last = this.length % calcMaxLength
|
val last = this.length % calcMaxLength
|
||||||
if (last != 0) {
|
if (last != 0) {
|
||||||
format = String.format("%s%d%dd", "%", 0, last)
|
format = String.format("%s%d%dd", "%", 0, last)
|
||||||
buffer.append(String.format(format, (random.nextDouble() * Math.pow(10.0, last.toDouble())).toInt()))
|
buffer.append(String.format(format, (random.nextDouble() * 10.0.pow(last.toDouble())).toInt()))
|
||||||
}
|
}
|
||||||
return buffer.toString()
|
return buffer.toString()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ package com.synebula.gaea.data.code
|
|||||||
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br></br>
|
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br></br>
|
||||||
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br></br>
|
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br></br>
|
||||||
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)<br></br>
|
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)<br></br>
|
||||||
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序Snowflake类的twepoch属性)。<br></br>
|
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序Snowflake类的origin属性)。<br></br>
|
||||||
* 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 <br></br>
|
* 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 <br></br>
|
||||||
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenter和5位worker <br></br>
|
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenter和5位worker <br></br>
|
||||||
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br></br>
|
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br></br>
|
||||||
@@ -35,7 +35,7 @@ open class SnowflakeCode(
|
|||||||
/**
|
/**
|
||||||
* 开始时间截 (2018-01-01)
|
* 开始时间截 (2018-01-01)
|
||||||
*/
|
*/
|
||||||
private val twepoch = 1514736000000L
|
private val origin = 1514736000000L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机器id所占的位数
|
* 机器id所占的位数
|
||||||
@@ -142,7 +142,7 @@ open class SnowflakeCode(
|
|||||||
lastTimestamp = current
|
lastTimestamp = current
|
||||||
|
|
||||||
//移位并通过或运算拼到一起组成64位的ID
|
//移位并通过或运算拼到一起组成64位的ID
|
||||||
return (current - twepoch shl timestampLeftShift
|
return (current - origin shl timestampLeftShift
|
||||||
or (datacenter shl datacenterShift)
|
or (datacenter shl datacenterShift)
|
||||||
or (worker shl workerShift)
|
or (worker shl workerShift)
|
||||||
or sequence)
|
or sequence)
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ class DateTime() : Comparable<DateTime> {
|
|||||||
* @return true or false
|
* @return true or false
|
||||||
*/
|
*/
|
||||||
fun isBetween(start: DateTime, end: DateTime): Boolean {
|
fun isBetween(start: DateTime, end: DateTime): Boolean {
|
||||||
//return this in start..end
|
|
||||||
return start.dateNoTime.compareTo(this.dateNoTime) * this.dateNoTime.compareTo(end.dateNoTime) >= 0
|
return start.dateNoTime.compareTo(this.dateNoTime) * this.dateNoTime.compareTo(end.dateNoTime) >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.util.*
|
|||||||
*
|
*
|
||||||
* @param T 消息数据类型
|
* @param T 消息数据类型
|
||||||
*/
|
*/
|
||||||
open class DataMessage<T>() : Message() {
|
open class DataMessage<T>() : StatusMessage() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 传递的业务数据
|
* 传递的业务数据
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.synebula.gaea.app.struct
|
package com.synebula.gaea.data.message
|
||||||
|
|
||||||
|
import com.synebula.gaea.data.serialization.json.IJsonSerializer
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.synebula.gaea.data.message.DataMessage
|
|
||||||
|
|
||||||
class HttpMessage() : DataMessage<Any>() {
|
class HttpMessage() : DataMessage<Any>() {
|
||||||
|
|
||||||
|
var serializer: IJsonSerializer? = null
|
||||||
|
|
||||||
constructor(data: Any) : this() {
|
constructor(data: Any) : this() {
|
||||||
this.data = data
|
this.data = data
|
||||||
}
|
}
|
||||||
@@ -25,6 +27,6 @@ class HttpMessage() : DataMessage<Any>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return Gson().toJson(this)
|
return serializer?.serialize(this) ?: super.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.synebula.gaea.data.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息结构
|
||||||
|
*/
|
||||||
|
interface IMessage {
|
||||||
|
/**
|
||||||
|
* 命令载荷, 实际的业务数据
|
||||||
|
*/
|
||||||
|
var message: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间戳。
|
||||||
|
*/
|
||||||
|
var timestamp: Long
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.synebula.gaea.data.message
|
package com.synebula.gaea.data.message
|
||||||
|
|
||||||
open class Message {
|
open class StatusMessage {
|
||||||
/**
|
/**
|
||||||
* 状态。200成功,400错误,500异常
|
* 状态。200成功,400错误,500异常
|
||||||
*/
|
*/
|
||||||
@@ -2,16 +2,17 @@ package com.synebula.gaea.data.serialization
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化器
|
* 序列化器
|
||||||
|
* @param S 源数据类型
|
||||||
*/
|
*/
|
||||||
interface IDeserializer {
|
interface IDeserializer<S> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反序列化
|
* 反序列化
|
||||||
*
|
*
|
||||||
* @param <S> 源数据类型
|
* @param T 目标数据类型
|
||||||
* @param <T> 目标数据类型
|
|
||||||
* @param src 源数据
|
* @param src 源数据
|
||||||
|
* @param targetClass 目标对象。
|
||||||
* @return 目标数据
|
* @return 目标数据
|
||||||
*/
|
*/
|
||||||
fun <S, T> deserialize(src: S): T
|
fun <T> deserialize(src: S, targetClass: Class<T>): T
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.synebula.gaea.data.serialization
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象映射器,支持对象之间的转换。
|
||||||
|
*
|
||||||
|
* @author alex
|
||||||
|
* @version 0.1
|
||||||
|
* @since 2020-05-15
|
||||||
|
*/
|
||||||
|
interface IObjectMapper : IDeserializer<Any>
|
||||||
@@ -2,16 +2,16 @@ package com.synebula.gaea.data.serialization
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化器
|
* 序列化器
|
||||||
|
* @param T 目标数据类型
|
||||||
*/
|
*/
|
||||||
interface ISerializer {
|
interface ISerializer<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化
|
* 序列化
|
||||||
|
* @param S 源数据类型
|
||||||
*
|
*
|
||||||
* @param <S> 源数据类型
|
|
||||||
* @param <T> 目标数据类型
|
|
||||||
* @param src 源数据
|
* @param src 源数据
|
||||||
* @return 目标数据
|
* @return 目标数据
|
||||||
*/
|
*/
|
||||||
fun <S, T> serialize(src: S): T
|
fun <S> serialize(src: S): T
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
package com.synebula.gaea.data.serialization.json
|
package com.synebula.gaea.data.serialization.json
|
||||||
|
|
||||||
|
import com.synebula.gaea.data.serialization.IDeserializer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化器
|
* 序列化器
|
||||||
*/
|
*/
|
||||||
interface IJsonDeserializer {
|
interface IJsonDeserializer : IDeserializer<String>
|
||||||
/**
|
|
||||||
* 反序列化
|
|
||||||
*
|
|
||||||
* @param <T> 目标数据类型
|
|
||||||
* @param src Json字符串数据
|
|
||||||
* @return 目标数据
|
|
||||||
*/
|
|
||||||
fun <T> deserialize(src: String): T
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
package com.synebula.gaea.data.serialization.json
|
package com.synebula.gaea.data.serialization.json
|
||||||
|
|
||||||
|
import com.synebula.gaea.data.serialization.ISerializer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化器
|
* 序列化器
|
||||||
*/
|
*/
|
||||||
interface IJsonSerializer {
|
interface IJsonSerializer : ISerializer<String>
|
||||||
/**
|
|
||||||
* 序列化
|
|
||||||
*
|
|
||||||
* @param <S> 源数据类型
|
|
||||||
* @param src 源数据
|
|
||||||
* @return Json字符串
|
|
||||||
*/
|
|
||||||
fun <S> serialize(src: S): String
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.synebula.gaea.domain.event
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
|
||||||
|
class AfterRemoveEvent<T : IAggregateRoot<I>, I>(var id: I? = null)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.synebula.gaea.domain.event
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
|
||||||
|
class BeforeRemoveEvent<T : IAggregateRoot<I>, I>(var id: I? = null)
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.model
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录聚合根
|
|
||||||
* 聚合根外添加了创建和修改的人\时间信息
|
|
||||||
*/
|
|
||||||
abstract class AggregateRecord<TKey> : AggregateRoot<TKey>() {
|
|
||||||
var creator: String? = null
|
|
||||||
var creatorName: String? = null
|
|
||||||
var created: Date = Date()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
modified = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var modifier: String? = null
|
|
||||||
var modifierName: String? = null
|
|
||||||
var modified: Date = Date()
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.synebula.gaea.domain.model
|
package com.synebula.gaea.domain.model
|
||||||
|
|
||||||
abstract class AggregateRoot<TKey> : Entity<TKey>(), IAggregateRoot<TKey> {
|
abstract class AggregateRoot<ID> : Entity<ID>(), IAggregateRoot<ID> {
|
||||||
override var alive: Boolean = true
|
override var alive: Boolean = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package com.synebula.gaea.domain.model
|
package com.synebula.gaea.domain.model
|
||||||
|
|
||||||
abstract class Entity<TKey> : IEntity<TKey>
|
abstract class Entity<ID> : IEntity<ID>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package com.synebula.gaea.domain.model
|
|||||||
*
|
*
|
||||||
* @author alex
|
* @author alex
|
||||||
**/
|
**/
|
||||||
interface IAggregateRoot<TKey> : IEntity<TKey> {
|
interface IAggregateRoot<ID> : IEntity<ID> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体对象是否有效。
|
* 实体对象是否有效。
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package com.synebula.gaea.domain.model
|
|||||||
*
|
*
|
||||||
* @author alex
|
* @author alex
|
||||||
*
|
*
|
||||||
* @param <TKey> 主键的类型。
|
* @param <ID> 主键的类型。
|
||||||
**/
|
**/
|
||||||
interface IEntity<TKey> {
|
interface IEntity<ID> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体ID
|
* 实体ID
|
||||||
*/
|
*/
|
||||||
var id: TKey?
|
var id: ID?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.model.complex
|
|
||||||
|
|
||||||
abstract class ComplexAggregateRoot<TKey, TSecond> : ComplexEntity<TKey, TSecond>(), IComplexAggregateRoot<TKey, TSecond>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.model.complex
|
|
||||||
|
|
||||||
|
|
||||||
abstract class ComplexEntity<TKey, TSecond> : IComplexEntity<TKey, TSecond> {
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.model.complex
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 继承本接口,说明对象为聚合根。
|
|
||||||
*
|
|
||||||
* @param <TKey> 主键的类型。
|
|
||||||
* @author alex
|
|
||||||
*/
|
|
||||||
interface IComplexAggregateRoot<TKey, TSecond> : IComplexEntity<TKey, TSecond>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.model.complex
|
|
||||||
|
|
||||||
import com.synebula.gaea.domain.model.IEntity
|
|
||||||
|
|
||||||
interface IComplexEntity<TKey, TSecond> : IEntity<TKey> {
|
|
||||||
var secondary: TSecond?
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.synebula.gaea.domain.record
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录信息
|
||||||
|
* 添加了创建和修改的人\时间信息
|
||||||
|
*/
|
||||||
|
abstract class Record<ID> {
|
||||||
|
|
||||||
|
//记录增加信息
|
||||||
|
var creator: String? = null
|
||||||
|
var creatorName: String? = null
|
||||||
|
var createTime: Date = Date()
|
||||||
|
|
||||||
|
//记录修改信息
|
||||||
|
var modifier: String? = null
|
||||||
|
var modifierName: String? = null
|
||||||
|
var modifyTime: Date = Date()
|
||||||
|
}
|
||||||
@@ -6,22 +6,29 @@ import com.synebula.gaea.domain.model.IAggregateRoot
|
|||||||
* 定义了提供增删改的仓储接口。
|
* 定义了提供增删改的仓储接口。
|
||||||
* 本接口泛型放置到方法上,并需要显式提供聚合根的class对象
|
* 本接口泛型放置到方法上,并需要显式提供聚合根的class对象
|
||||||
*/
|
*/
|
||||||
interface IRepository {
|
interface IRepository<TAggregateRoot : IAggregateRoot<ID>, ID> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仓储的对象类
|
||||||
|
*/
|
||||||
|
var clazz: Class<TAggregateRoot>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入单个对象。
|
* 插入单个对象。
|
||||||
*
|
*
|
||||||
* @param obj 需要插入的对象。
|
* @param obj 需要插入的对象。
|
||||||
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: TAggregateRoot, clazz: Class<TAggregateRoot>)
|
fun add(obj: TAggregateRoot)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入多个个对象。
|
* 插入多个个对象。
|
||||||
*
|
*
|
||||||
* @param obj 需要插入的对象。
|
* @param list 需要插入的对象。
|
||||||
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> add(obj: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
|
fun add(list: List<TAggregateRoot>)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新对象。
|
* 更新对象。
|
||||||
@@ -29,24 +36,30 @@ interface IRepository {
|
|||||||
* @param obj 需要更新的对象。
|
* @param obj 需要更新的对象。
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> update(obj: TAggregateRoot, clazz: Class<TAggregateRoot>)
|
fun update(obj: TAggregateRoot)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新多个个对象。
|
||||||
|
*
|
||||||
|
* @param list 需要新的对象。
|
||||||
|
*/
|
||||||
|
fun update(list: List<TAggregateRoot>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过id删除该条数据
|
* 通过id删除该条数据
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param id 对象ID。
|
||||||
* @param clazz 操作数据的类型
|
* @return
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> remove(id: TKey, clazz: Class<TAggregateRoot>)
|
fun remove(id: ID)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID获取对象。
|
* 根据ID获取对象。
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param id 对象ID。
|
||||||
* @param clazz 操作数据的类型
|
* @return
|
||||||
* @return 聚合根
|
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot : IAggregateRoot<TKey>, TKey> get(id: TKey, clazz: Class<TAggregateRoot>): TAggregateRoot?
|
fun get(id: ID): TAggregateRoot?
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,5 +68,7 @@ interface IRepository {
|
|||||||
* @param params 查询条件。
|
* @param params 查询条件。
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int
|
fun count(params: Map<String, Any>?): Int
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.synebula.gaea.domain.repository
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository 工厂接口。 定义了Repository的创建方法。
|
||||||
|
*/
|
||||||
|
interface IRepositoryFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建原始类型的IRepository接口类型
|
||||||
|
*/
|
||||||
|
fun createRawRepository(clazz: Class<*>): IRepository<*, *>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定类型的IRepository接口类型
|
||||||
|
*/
|
||||||
|
fun <T : IAggregateRoot<I>, I> createRepository(clazz: Class<T>): IRepository<T, I>
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package com.synebula.gaea.domain.repository
|
|
||||||
|
|
||||||
import com.synebula.gaea.domain.model.IAggregateRoot
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 继承本接口表示对象为仓储类。
|
|
||||||
* 定义了提供增删改的仓储接口。
|
|
||||||
* 本接口泛型定义在类上,不需要显式提供聚合根的class对象,class对象作为类的成员变量声明。
|
|
||||||
*
|
|
||||||
* @param <TAggregateRoot> this T is the parameter
|
|
||||||
* @author alex
|
|
||||||
*/
|
|
||||||
interface ISpecificRepository<TAggregateRoot : IAggregateRoot<TKey>, TKey> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仓储的对象类
|
|
||||||
*/
|
|
||||||
var clazz: Class<TAggregateRoot>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入单个对象。
|
|
||||||
*
|
|
||||||
* @param obj 需要插入的对象。
|
|
||||||
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
|
||||||
*/
|
|
||||||
fun add(obj: TAggregateRoot)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新对象。
|
|
||||||
*
|
|
||||||
* @param obj 需要更新的对象。
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
fun update(obj: TAggregateRoot)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过id删除该条数据
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
fun remove(id: TKey)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID获取对象。
|
|
||||||
*
|
|
||||||
* @param id 对象ID。
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
fun get(id: TKey): TAggregateRoot
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID获取对象。
|
|
||||||
*
|
|
||||||
* @param id id
|
|
||||||
* @param clazz 操作数据的类型
|
|
||||||
* @return 聚合根
|
|
||||||
*/
|
|
||||||
fun <T : IAggregateRoot<TKey>, TKey> get(id: TKey, clazz: Class<T>): T
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据条件查询符合条件记录的数量
|
|
||||||
*
|
|
||||||
* @param params 查询条件。
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
fun <TAggregateRoot> count(params: Map<String, Any>?): Int
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.synebula.gaea.domain.repository
|
||||||
|
|
||||||
|
import com.synebula.gaea.domain.model.IAggregateRoot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义了提供增删改的仓储接口。
|
||||||
|
* 本接口泛型放置到方法上,并需要显式提供聚合根的class对象
|
||||||
|
*/
|
||||||
|
interface IUniversalRepository {
|
||||||
|
/**
|
||||||
|
* 插入单个对象。
|
||||||
|
*
|
||||||
|
* @param root 需要插入的对象。
|
||||||
|
* @return 返回原对象,如果对象ID为自增,则补充自增ID。
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(root: TAggregateRoot, clazz: Class<TAggregateRoot>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入多个个对象。
|
||||||
|
*
|
||||||
|
* @param roots 需要插入的对象。
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> add(roots: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新对象。
|
||||||
|
*
|
||||||
|
* @param root 需要更新的对象。
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(root: TAggregateRoot, clazz: Class<TAggregateRoot>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新多个个对象。
|
||||||
|
*
|
||||||
|
* @param roots 需要更新的对象。
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> update(roots: List<TAggregateRoot>, clazz: Class<TAggregateRoot>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过id删除该条数据
|
||||||
|
*
|
||||||
|
* @param id id
|
||||||
|
* @param clazz 操作数据的类型
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> remove(id: ID, clazz: Class<TAggregateRoot>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取对象。
|
||||||
|
*
|
||||||
|
* @param id id
|
||||||
|
* @param clazz 操作数据的类型
|
||||||
|
* @return 聚合根
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot : IAggregateRoot<ID>, ID> get(id: ID, clazz: Class<TAggregateRoot>): TAggregateRoot?
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据条件查询符合条件记录的数量
|
||||||
|
*
|
||||||
|
* @param params 查询条件。
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
fun <TAggregateRoot> count(params: Map<String, Any>?, clazz: Class<TAggregateRoot>): Int
|
||||||
|
}
|
||||||
@@ -10,21 +10,21 @@ import com.synebula.gaea.domain.model.IAggregateRoot
|
|||||||
interface IContext : IUnitOfWork {
|
interface IContext : IUnitOfWork {
|
||||||
/**
|
/**
|
||||||
* 将指定的聚合根标注为“新建”状态。
|
* 将指定的聚合根标注为“新建”状态。
|
||||||
* @param obj
|
* @param obj 聚合根
|
||||||
*/
|
*/
|
||||||
fun <TType : IAggregateRoot<TKey>, TKey> add(obj: TType)
|
fun <T : IAggregateRoot<ID>, ID> add(obj: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将指定的聚合根标注为“更改”状态。
|
* 将指定的聚合根标注为“更改”状态。
|
||||||
*
|
*
|
||||||
* @param obj
|
* @param obj 聚合根
|
||||||
*/
|
*/
|
||||||
fun <TType : IAggregateRoot<TKey>, TKey> update(obj: TType)
|
fun <T : IAggregateRoot<ID>, ID> update(obj: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将指定的聚合根标注为“删除”状态。
|
* 将指定的聚合根标注为“删除”状态。
|
||||||
*
|
*
|
||||||
* @param obj
|
* @param obj 聚合根
|
||||||
*/
|
*/
|
||||||
fun <TType : IAggregateRoot<TKey>, TKey> remove(obj: TType)
|
fun <T : IAggregateRoot<ID>, ID> remove(obj: T)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,12 @@ package com.synebula.gaea.domain.repository.specifications
|
|||||||
*
|
*
|
||||||
* @author alex
|
* @author alex
|
||||||
*
|
*
|
||||||
* @param <T>
|
* @param T 规约对象的类型。
|
||||||
* 规约对象的类型。
|
* @param left 表达式左侧规约对象。
|
||||||
|
* @param right 表达式右侧规约对象。
|
||||||
*/
|
*/
|
||||||
class AndNotSpecification<T>
|
class AndNotSpecification<T>(left: ISpecification<T>, right: ISpecification<T>) :
|
||||||
/**
|
CompositeSpecification<T>(left, right) {
|
||||||
* 构造一个新的混合规约对象。
|
|
||||||
*
|
|
||||||
* @param left
|
|
||||||
* 表达式左侧规约对象。
|
|
||||||
* @param right
|
|
||||||
* 表达式右侧规约对象。
|
|
||||||
*/
|
|
||||||
(left: ISpecification<T>, right: ISpecification<T>) : CompositeSpecification<T>(left, right) {
|
|
||||||
|
|
||||||
override fun isSatisfiedBy(obj: T): Boolean {
|
override fun isSatisfiedBy(obj: T): Boolean {
|
||||||
return left.isSatisfiedBy(obj) && NotSpecification(right).isSatisfiedBy(obj)
|
return left.isSatisfiedBy(obj) && NotSpecification(right).isSatisfiedBy(obj)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user