Merge branch 'dev' of github.com:synebula-myths/gaea into dev

This commit is contained in:
2022-08-23 20:21:31 +08:00
139 changed files with 2904 additions and 883 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.* .*
gradlew* gradlew*
build build
gradle
!.gitignore !.gitignore

View File

@@ -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

Binary file not shown.

View 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

View File

@@ -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'

View File

@@ -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
} }
} }

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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
} }

View File

@@ -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)
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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> = [])

View File

@@ -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
} }

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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)
} }

View File

@@ -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)
}
}

View File

@@ -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
*/ */

View File

@@ -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>)

View File

@@ -1,8 +0,0 @@
package com.synebula.gaea.app.component.aop.annotation
/**
* 模块的业务名称
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ModuleName(val value: String)

View File

@@ -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)
}

View File

@@ -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>()

View File

@@ -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
} }
} }

View File

@@ -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)

View File

@@ -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

View File

@@ -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/**") }
} }
/** /**

View File

@@ -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)
} }
} }

View File

@@ -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>

View File

@@ -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
}
}
}

View File

@@ -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()
}
}

View 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
}
}
}

View File

@@ -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))
/** /**
* 获取排序对象 * 获取排序对象

View File

@@ -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)
}
}

View File

@@ -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}] ")
}
}
}

View File

@@ -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
}
}

View File

@@ -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> = [])

View File

@@ -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}]的集合")
}
}
}

View File

@@ -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)
}
}

View File

@@ -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]的集合")
}
} }
} }
} }

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View 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
}
}
}

View File

@@ -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
}
} }

View File

@@ -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)

View File

@@ -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>)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
) )

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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?
}

View File

@@ -0,0 +1,3 @@
package com.synebula.gaea.spring.autoconfig
enum class ProxyType { JDK, Cglib }

View File

@@ -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
}
}

View File

@@ -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
} }

View File

@@ -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

View 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)
}
}

View 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
/**
* 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)"
}
}

View 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()
}
}
}

View 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>)
}

View File

@@ -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)
}
}
}

View 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> = [])

View 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
}
}
}

View File

@@ -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,
)

View File

@@ -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>)
}

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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()))
} }
} }

View File

@@ -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()
} }

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -8,7 +8,7 @@ import java.util.*
* *
* @param T 消息数据类型 * @param T 消息数据类型
*/ */
open class DataMessage<T>() : Message() { open class DataMessage<T>() : StatusMessage() {
/** /**
* 传递的业务数据 * 传递的业务数据

View File

@@ -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()
} }
} }

View File

@@ -0,0 +1,16 @@
package com.synebula.gaea.data.message
/**
* 消息结构
*/
interface IMessage {
/**
* 命令载荷, 实际的业务数据
*/
var message: String
/**
* 时间戳。
*/
var timestamp: Long
}

View File

@@ -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异常
*/ */

View File

@@ -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
} }

View File

@@ -0,0 +1,10 @@
package com.synebula.gaea.data.serialization
/**
* 对象映射器,支持对象之间的转换。
*
* @author alex
* @version 0.1
* @since 2020-05-15
*/
interface IObjectMapper : IDeserializer<Any>

View File

@@ -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
} }

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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
} }

View File

@@ -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>

View File

@@ -5,7 +5,7 @@ package com.synebula.gaea.domain.model
* *
* @author alex * @author alex
**/ **/
interface IAggregateRoot<TKey> : IEntity<TKey> { interface IAggregateRoot<ID> : IEntity<ID> {
/** /**
* 实体对象是否有效。 * 实体对象是否有效。

View File

@@ -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?
} }

View File

@@ -1,3 +0,0 @@
package com.synebula.gaea.domain.model.complex
abstract class ComplexAggregateRoot<TKey, TSecond> : ComplexEntity<TKey, TSecond>(), IComplexAggregateRoot<TKey, TSecond>

View File

@@ -1,5 +0,0 @@
package com.synebula.gaea.domain.model.complex
abstract class ComplexEntity<TKey, TSecond> : IComplexEntity<TKey, TSecond> {
}

View File

@@ -1,9 +0,0 @@
package com.synebula.gaea.domain.model.complex
/**
* 继承本接口,说明对象为聚合根。
*
* @param <TKey> 主键的类型。
* @author alex
*/
interface IComplexAggregateRoot<TKey, TSecond> : IComplexEntity<TKey, TSecond>

View File

@@ -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?
}

View File

@@ -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()
}

View File

@@ -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
} }

View File

@@ -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>
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
} }

View File

@@ -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