diff --git a/.gitignore b/.gitignore index 02cfcc9..824ab1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .* gradlew* build -gradle !.gitignore \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4355845..345a5bc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,12 @@ buildscript { ext { - kotlin_version = '1.3.72' + kotlin_version = '1.6.10' + spring_version = "2.7.0" } repositories { mavenLocal() - maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } mavenCentral() } @@ -14,41 +15,33 @@ buildscript { } } -allprojects { - group 'com.synebula' - version version -} - subprojects { - ext { - version '0.13.1' - spring_version = "2.3.0.RELEASE" - } + group 'com.synebula' + version '1.3.0' buildscript { repositories { mavenLocal() - maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } mavenCentral() } } repositories { mavenLocal() - maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } mavenCentral() } apply plugin: 'idea' apply plugin: 'java' apply plugin: 'kotlin' - apply plugin: 'maven' apply plugin: 'maven-publish' dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - testCompile group: 'junit', name: 'junit', version: '4.12' + api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + testApi group: 'junit', name: 'junit', version: '4.12' } sourceCompatibility = 1.8 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..87b738c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d494b61 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle b/settings.gradle index 42a6a35..1647a40 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,8 @@ -rootProject.name = 'myths.gaea' +rootProject.name = 'gaea' include 'src:gaea' 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' + diff --git a/src/gaea.app/build.gradle b/src/gaea.app/build.gradle index 9333a61..e137186 100644 --- a/src/gaea.app/build.gradle +++ b/src/gaea.app/build.gradle @@ -7,24 +7,21 @@ buildscript { apply plugin: 'kotlin-spring' dependencies { - compile project(":src:gaea") - compile("org.springframework.boot:spring-boot-starter-web:$spring_version") - compile("org.springframework.boot:spring-boot-starter-aop:$spring_version") - compile("org.springframework.boot:spring-boot-starter-mail:$spring_version") - compile("org.springframework.boot:spring-boot-starter-security:$spring_version") - compile group: 'net.sf.dozer', name: 'dozer', version: '5.5.1' - compile group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0' - compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6' - compile group: 'com.google.guava', name: 'guava', version: '30.1.1-jre' - compile group: 'com.auth0', name: 'java-jwt', version: '3.14.0' + api project(":src:gaea") + api project(":src:gaea.spring") + + api("org.springframework.boot:spring-boot-starter-web:$spring_version") + api("org.springframework.boot:spring-boot-starter-mail:$spring_version") + api("org.springframework.boot:spring-boot-starter-security:$spring_version") + api group: 'net.sf.dozer', name: 'dozer', version: '5.5.1' + api group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0' + api group: 'com.google.code.gson', name: 'gson', version: '2.8.6' + api group: 'com.auth0', name: 'java-jwt', version: '3.14.0' } publishing { publications { publish(MavenPublication) { - group 'com.synebula' - artifactId 'gaea.app' - version "$version" from components.java } } diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt index fdcbd6d..7e0a5c7 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/Application.kt @@ -17,13 +17,12 @@ import javax.annotation.Resource * @param query 业务查询服务 * @param logger 日志组件 */ -open class Application( +open class Application( override var name: String, - override var clazz: Class, - override var service: IService, - override var query: IQuery, - override var logger: ILogger? -) : ICommandApp, IQueryApp { + override var service: IService, + override var query: IQuery, + override var logger: ILogger, +) : ICommandApp, IQueryApp { @Resource override var jsonSerializer: IJsonSerializer? = null diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt index 56bf9fa..e6429c9 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/IApplication.kt @@ -1,7 +1,7 @@ package com.synebula.gaea.app 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.log.ILogger 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() try { process(msg) - logger?.debug(this, "$name business execute success") + logger.debug(this, "$name business execute success") } catch (ex: Exception) { msg.status = Status.Error 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 } @@ -42,9 +42,9 @@ interface IApplication { val msg = HttpMessage() try { process(msg) - logger?.debug(this, "$name business execute success") + logger.debug(this, "$name business execute success") } catch (ex: Exception) { - logger?.error(this, ex, "[$name]$error。异常消息将抛出!: ${ex.message}") + logger.error(this, ex, "[$name]$error。异常消息将抛出!: ${ex.message}") throw RuntimeException(error, ex) } return msg @@ -59,12 +59,12 @@ interface IApplication { val authentication = SecurityContextHolder.getContext().authentication.principal.toString() try { val gson = Gson() - return gson.fromJson(authentication, clazz) + return gson.fromJson(authentication, clazz) } catch (ex: Exception) { - logger?.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}") + logger.error(this, ex, "[$name]解析用户信息异常!用户信息:$authentication: ${ex.message}") } } catch (ex: Exception) { - logger?.error(this, ex, "[$name]获取用户信息异常!${ex.message}") + logger.error(this, ex, "[$name]获取用户信息异常!${ex.message}") } return null } diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/SimpleApplication.kt similarity index 53% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/SimpleApplication.kt index 628a9e9..e06f139 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/LazyApplication.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/SimpleApplication.kt @@ -1,30 +1,29 @@ 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.data.serialization.json.IJsonSerializer 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.query.IQuery import javax.annotation.Resource /** - * 联合服务,同时实现了ILazyCommandApp和IQueryApp接口 + * 简单的服务, 取消了Command对象 * * @param name 业务名称 * @param service 业务domain服务 * @param query 业务查询服务 * @param logger 日志组件 */ -open class LazyApplication, TKey>( +open class SimpleApplication, ID>( override var name: String, - override var clazz: Class, //view class type - override var service: ILazyService, - override var query: IQuery, - override var logger: ILogger? -) : ILazyCommandApp, IQueryApp { + override var service: ISimpleService, + override var query: IQuery, + override var logger: ILogger, +) : ISimpleCommandApp, IQueryApp { @Resource override var jsonSerializer: IJsonSerializer? = null -} \ No newline at end of file +} diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceFactory.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceFactory.kt new file mode 100644 index 0000000..c3e33a2 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceFactory.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceProxy.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceProxy.kt new file mode 100644 index 0000000..15519fe --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceProxy.kt @@ -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>(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? { + 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 + } + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceRegister.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceRegister.kt new file mode 100644 index 0000000..0dd1f0a --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceRegister.kt @@ -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 { + val result = mutableMapOf() + + // 获取注解参数信息: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 + } +} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceScan.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceScan.kt new file mode 100644 index 0000000..a988ac2 --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/autoconfig/service/ServiceScan.kt @@ -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 = []) \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt index 78860e9..516dd1a 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/CommandApp.kt @@ -13,11 +13,11 @@ import javax.annotation.Resource * @param service 业务domain服务 * @param logger 日志组件 */ -open class CommandApp( +open class CommandApp( override var name: String, - override var service: IService, - override var logger: ILogger? -) : ICommandApp { + override var service: IService, + override var logger: ILogger, +) : ICommandApp { @Resource override var jsonSerializer: IJsonSerializer? = null } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt index 9976caf..aff4805 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ICommandApp.kt @@ -1,12 +1,12 @@ package com.synebula.gaea.app.cmd import com.synebula.gaea.app.IApplication -import com.synebula.gaea.app.component.aop.annotation.MethodName -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.serialization.json.IJsonSerializer import com.synebula.gaea.domain.service.ICommand import com.synebula.gaea.domain.service.IService +import com.synebula.gaea.spring.aop.annotation.Method import org.springframework.web.bind.annotation.* /** @@ -16,27 +16,27 @@ import org.springframework.web.bind.annotation.* * @version 0.1 * @since 2020-05-15 */ -interface ICommandApp : IApplication { +interface ICommandApp : IApplication { var jsonSerializer: IJsonSerializer? - var service: IService + var service: IService @PostMapping - @MethodName("添加") + @Method("添加") fun add(@RequestBody command: TCommand): HttpMessage { return HttpMessage(service.add(command)) } @PutMapping("/{id:.+}") - @MethodName("更新") - fun update(@PathVariable id: TKey, @RequestBody command: TCommand): HttpMessage { + @Method("更新") + fun update(@PathVariable id: ID, @RequestBody command: TCommand): HttpMessage { this.service.update(id, command) return HttpMessage() } @DeleteMapping("/{id:.+}") - @MethodName("删除") - fun remove(@PathVariable id: TKey): HttpMessage { + @Method("删除") + fun remove(@PathVariable id: ID): HttpMessage { val msg = HttpMessage() try { msg.data = this.service.remove(id) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ISimpleCommandApp.kt similarity index 65% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ISimpleCommandApp.kt index b3e1aec..98bd2ec 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ILazyCommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/ISimpleCommandApp.kt @@ -1,12 +1,12 @@ package com.synebula.gaea.app.cmd import com.synebula.gaea.app.IApplication -import com.synebula.gaea.app.component.aop.annotation.MethodName -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.serialization.json.IJsonSerializer 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.* /** @@ -16,27 +16,27 @@ import org.springframework.web.bind.annotation.* * @version 0.1 * @since 2020-05-15 */ -interface ILazyCommandApp, TKey> : IApplication { +interface ISimpleCommandApp, ID> : IApplication { var jsonSerializer: IJsonSerializer? - var service: ILazyService + var service: ISimpleService @PostMapping - @MethodName("添加") + @Method("添加") fun add(@RequestBody entity: TRoot): HttpMessage { return HttpMessage(service.add(entity)) } @PutMapping("/{id:.+}") - @MethodName("更新") - fun update(@PathVariable id: TKey, @RequestBody entity: TRoot): HttpMessage { + @Method("更新") + fun update(@PathVariable id: ID, @RequestBody entity: TRoot): HttpMessage { this.service.update(id, entity) return HttpMessage() } @DeleteMapping("/{id:.+}") - @MethodName("删除") - fun remove(@PathVariable id: TKey): HttpMessage { + @Method("删除") + fun remove(@PathVariable id: ID): HttpMessage { val msg = HttpMessage() try { msg.data = this.service.remove(id) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/SimpleCommandApp.kt similarity index 58% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/SimpleCommandApp.kt index 46abef3..84c881f 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/LazyCommandApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/cmd/SimpleCommandApp.kt @@ -2,9 +2,7 @@ package com.synebula.gaea.app.cmd import com.synebula.gaea.data.serialization.json.IJsonSerializer import com.synebula.gaea.domain.model.IAggregateRoot -import com.synebula.gaea.domain.service.ICommand -import com.synebula.gaea.domain.service.ILazyService -import com.synebula.gaea.domain.service.IService +import com.synebula.gaea.domain.service.ISimpleService import com.synebula.gaea.log.ILogger import javax.annotation.Resource @@ -15,11 +13,11 @@ import javax.annotation.Resource * @param service 业务domain服务 * @param logger 日志组件 */ -open class LazyCommandApp, TKey>( +open class SimpleCommandApp, ID>( override var name: String, - override var service: ILazyService, - override var logger: ILogger? -) : ILazyCommandApp { + override var service: ISimpleService, + override var logger: ILogger, +) : ISimpleCommandApp { @Resource override var jsonSerializer: IJsonSerializer? = null } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/DozerConverter.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/DozerConverter.kt index a2f7421..66dd9f5 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/DozerConverter.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/DozerConverter.kt @@ -1,12 +1,12 @@ 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.springframework.stereotype.Component @Component -class DozerConverter : IObjectConverter { +class DozerConverter : IObjectMapper { private val converter = DozerBeanMapper() - override fun convert(src: Any, dest: Class): T = converter.map(src, dest) + override fun deserialize(src: Any, targetClass: Class): T = converter.map(src, targetClass) } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt deleted file mode 100644 index 8448054..0000000 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBus.kt +++ /dev/null @@ -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) - } - -} - diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt index ec4150d..b86ac2e 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/Logger.kt @@ -9,7 +9,7 @@ import java.util.concurrent.ConcurrentHashMap /** * 使用 log4j進行日志记录。 * - * @author reize + * @author alex * @version 0.0.1 * @since 2016年9月18日 下午2:13:43 */ diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt deleted file mode 100644 index 73b5b47..0000000 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/Handler.kt +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt deleted file mode 100644 index 3ab6e15..0000000 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/ModuleName.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.synebula.gaea.app.component.aop.annotation - -/** - * 模块的业务名称 - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class ModuleName(val value: String) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt deleted file mode 100644 index fbd2a37..0000000 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AnnotationHandler.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.synebula.gaea.app.component.aop.handler - -import java.lang.reflect.Method - -interface AnnotationHandler { - fun handle(clazz: Class, func: Method, args: Array, exception: Exception? = null) -} \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBus.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBus.kt new file mode 100644 index 0000000..87f6c0c --- /dev/null +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBus.kt @@ -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() + diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBusSubscriberProcessor.kt similarity index 73% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt rename to src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBusSubscriberProcessor.kt index 98226c4..f87bbd6 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/EventBusSubscriberProcessor.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/bus/EventBusSubscriberProcessor.kt @@ -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.event.IEventBus +import com.synebula.gaea.bus.IBus +import com.synebula.gaea.bus.Subscribe import org.springframework.beans.BeansException import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.config.BeanPostProcessor @@ -14,7 +14,7 @@ class EventBusSubscriberProcessor : BeanPostProcessor { // 事件总线bean由Spring IoC容器负责创建,这里只需要通过@Autowired注解注入该bean即可使用事件总线 @Autowired - var eventBus: IEventBus? = null + var bus: IBus? = null @Throws(BeansException::class) override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any { @@ -28,18 +28,21 @@ class EventBusSubscriberProcessor : BeanPostProcessor { val methods: Array = bean.javaClass.methods for (method in methods) { // check the annotations on that method - val annotations: Array = method.getAnnotations() + val annotations: Array = method.annotations for (annotation in annotations) { - // if it contains the Subscribe annotation + // if it contains Subscribe annotation if (annotation.annotationClass == Subscribe::class) { // 如果这是一个Guava @Subscribe注解的事件监听器方法,说明所在bean实例 // 对应一个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 } - } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt index b8e2626..923dbd0 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/poi/Excel.kt @@ -45,7 +45,7 @@ object Excel { //声明列对象 // 第三步,在sheet中添加表头第0行,注意老版本poi对Excel的行数列数有限制 var row = sheet.createRow(0) - row.height = 25 * 20 + row.height = (25 * 20).toShort() var cell: HSSFCell //创建标题 for (col in data.columnNames.indices) { @@ -69,7 +69,7 @@ object Excel { for (i in data.data.indices) { try { row = sheet.createRow(i + 1) - row.height = 20 * 20 + row.height = (20 * 20).toShort() col = 0 while (col < data.data[i].size) { cell = row.createCell(col) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt index 1d451d4..d82f6aa 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebAuthorization.kt @@ -1,7 +1,7 @@ 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.data.message.HttpMessage import com.synebula.gaea.data.message.Status import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt index c0f6f3c..1c0ea81 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/security/WebSecurity.kt @@ -1,14 +1,14 @@ 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 org.springframework.beans.factory.annotation.Autowired 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.WebSecurity -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.SecurityFilterChain import org.springframework.stereotype.Component import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource @@ -16,38 +16,41 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource @Component -@EnableWebSecurity -class WebSecurity : WebSecurityConfigurerAdapter() { +class WebSecurity { @Autowired lateinit var tokenManager: TokenManager + @Autowired + lateinit var authenticationManager: AuthenticationManager + /** * 安全配置 */ @Throws(Exception::class) - override fun configure(http: HttpSecurity) { + fun filterChain(http: HttpSecurity): SecurityFilterChain { // 跨域共享 http.cors() - .and().csrf().disable() // 跨域伪造请求限制无效 - .authorizeRequests() - .anyRequest().authenticated()// 资源任何人都可访问 - .and() - .addFilter(WebAuthorization(authenticationManager(), tokenManager))// 添加JWT鉴权拦截器 - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext - .and() - .exceptionHandling() - .authenticationEntryPoint { _, response, _ -> - response.status = Status.Success - response.characterEncoding = "utf-8" - response.contentType = "text/javascript;charset=utf-8" - response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!")) - } + .and().csrf().disable() // 跨域伪造请求限制无效 + .authorizeRequests() + .anyRequest().authenticated()// 资源任何人都可访问 + .and() + .addFilter(WebAuthorization(authenticationManager, tokenManager))// 添加JWT鉴权拦截器 + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext + .and() + .exceptionHandling() + .authenticationEntryPoint { _, response, _ -> + response.status = Status.Success + response.characterEncoding = "utf-8" + response.contentType = "text/javascript;charset=utf-8" + response.writer.print(HttpMessage(Status.Unauthorized, "用户未登录,请重新登录后尝试!")) + } + return http.build() } @Throws(Exception::class) - override fun configure(web: WebSecurity) { - web.ignoring().antMatchers("/sign/**") + fun filterChain(): WebSecurityCustomizer { + return WebSecurityCustomizer { web -> web.ignoring().antMatchers("/sign/**") } } /** diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt index ff5ded0..b390ad8 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/IQueryApp.kt @@ -1,50 +1,45 @@ package com.synebula.gaea.app.query import com.synebula.gaea.app.IApplication -import com.synebula.gaea.app.component.aop.annotation.MethodName -import com.synebula.gaea.app.struct.HttpMessage +import com.synebula.gaea.data.message.HttpMessage import com.synebula.gaea.query.IQuery 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.PathVariable import org.springframework.web.bind.annotation.RequestParam -interface IQueryApp : IApplication { +interface IQueryApp : IApplication { /** * 查询服务 */ - var query: IQuery + var query: IQuery - /** - * 查询的View类型 - */ - var clazz: Class - - @MethodName("获取数据") + @Method("获取数据") @GetMapping("/{id:.+}") - fun get(@PathVariable id: TKey): HttpMessage { - val data = this.query.get(id, clazz) + fun get(@PathVariable id: ID): HttpMessage { + val data = this.query.get(id) val msg = HttpMessage() msg.data = data return msg } - @MethodName("获取列表数据") + @Method("获取列表数据") @GetMapping fun list(@RequestParam params: LinkedHashMap): HttpMessage { - val data = this.query.list(params, clazz) + val data = this.query.list(params) return HttpMessage(data) } - @MethodName("获取分页数据") - @GetMapping("/segments/{size}/pages/{page}") + @Method("获取分页数据") + @GetMapping("/size/{size}/pages/{page}") fun paging( @PathVariable size: Int, @PathVariable page: Int, @RequestParam parameters: LinkedHashMap ): HttpMessage { val params = Params(page, size, parameters) - val data = this.query.paging(params, clazz) + val data = this.query.paging(params) return HttpMessage(data) } } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt index 7b1a295..a301a17 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt +++ b/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/query/QueryApp.kt @@ -10,9 +10,8 @@ import com.synebula.gaea.query.IQuery * @param query 业务查询服务 * @param logger 日志组件 */ -open class QueryApp( +open class QueryApp( override var name: String, - override var clazz: Class, - override var query: IQuery, - override var logger: ILogger? -) : IQueryApp \ No newline at end of file + override var query: IQuery, + override var logger: ILogger, +) : IQueryApp \ No newline at end of file diff --git a/src/gaea.mongo/build.gradle b/src/gaea.mongo/build.gradle deleted file mode 100644 index 1ba6df0..0000000 --- a/src/gaea.mongo/build.gradle +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt b/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt deleted file mode 100644 index 75c7869..0000000 --- a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/repository/MongoRepository.kt +++ /dev/null @@ -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 , TKey> remove(id: TKey, clazz: Class) { - this.repo.remove(whereId(id), clazz) - } - - override fun , TKey> get( - id: TKey, - clazz: Class - ): TAggregateRoot? { - return this.repo.findOne(whereId(id), clazz) - } - - override fun , TKey> update( - obj: TAggregateRoot, - clazz: Class - ) { - this.repo.save(obj) - } - - override fun , TKey> add(obj: TAggregateRoot, clazz: Class) { - this.repo.save(obj) - } - - override fun , TKey> add(obj: List, clazz: Class) { - this.repo.insert(obj, clazz) - } - - override fun count(params: Map?, clazz: Class): Int { - val query = Query() - return this.repo.count(query.where(params, clazz), clazz).toInt() - } -} diff --git a/src/gaea.mongodb/build.gradle b/src/gaea.mongodb/build.gradle new file mode 100644 index 0000000..b99693a --- /dev/null +++ b/src/gaea.mongodb/build.gradle @@ -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 + } + } +} \ No newline at end of file diff --git a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt similarity index 89% rename from src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt index 1c1349f..e8f57a2 100644 --- a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/MongoExt.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/MongoExt.kt @@ -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.query.annotation.Where -import com.synebula.gaea.query.type.Operator -import com.synebula.gaea.query.type.Order +import com.synebula.gaea.query.Operator +import com.synebula.gaea.query.Order +import com.synebula.gaea.query.Where import org.springframework.data.domain.Sort import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query @@ -30,9 +30,9 @@ fun Query.select(fields: Array): Query { * @param onWhere 获取字段查询方式的方法 */ fun Query.where( - params: Map?, - onWhere: ((v: String) -> Where?) = { null }, - onFieldType: ((v: String) -> Class<*>?) = { null } + params: Map?, + onWhere: ((v: String) -> Where?) = { null }, + onFieldType: ((v: String) -> Class<*>?) = { null } ): Query { val list = arrayListOf() if (params != null) { @@ -55,7 +55,7 @@ fun Query.where( list.add(tryRangeWhere(param.key, value, onFieldType)) } else { //判断执行查询子元素还是本字段 - val field = if (where.children.isEmpty()) key else where.children + val field = where.children.ifEmpty { key } var criteria = Criteria.where(field) criteria = when (where.operator) { Operator.eq -> criteria.`is`(value) @@ -116,7 +116,7 @@ fun Query.where(params: Map?, clazz: Class<*>): Query { * * @param id 业务ID */ -fun whereId(id: TKey): Query = Query.query(Criteria.where("_id").`is`(id)) +fun whereId(id: ID): Query = Query.query(Criteria.where("_id").`is`(id)) /** * 获取排序对象 diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt new file mode 100644 index 0000000..f654626 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoFactory.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt new file mode 100644 index 0000000..5fa6459 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoProxy.kt @@ -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? { + 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}] ") + } + } + +} \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt new file mode 100644 index 0000000..85d4fee --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoRegister.kt @@ -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 { + val result = mutableMapOf() + + // 获取注解参数信息: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) { + // IRepository proxy + val builder = BeanDefinitionBuilder.genericBeanDefinition(IRepository::class.java) + builder.addConstructorArgValue(IRepository::class.java) + builder.addConstructorArgValue(this._beanFactory) + builder.addConstructorArgValue(emptyArray()) + val definition = builder.rawBeanDefinition as GenericBeanDefinition + definition.beanClass = MongodbRepoFactory::class.java + definition.autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE + result[IRepository::class.java.name] = definition + } +} \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt new file mode 100644 index 0000000..ef8aa03 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/autoconfig/MongodbRepoScan.kt @@ -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 = []) \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt new file mode 100644 index 0000000..27cabbf --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQuery.kt @@ -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(override var clazz: Class, var template: MongoTemplate) : + IQuery { + + /** + * 使用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?): List { + 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?): Int { + val query = Query() + return this.template.count(query.where(params, clazz), this.collection(clazz)).toInt() + } + + override fun paging(params: Params): Page { + val query = Query() + val fields = this.fields(clazz) + val result = Page(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): List { + return this.find(Query.query(Criteria.where(field).`in`(params)), clazz) + } + + protected fun find(query: Query, clazz: Class): List { + return this.template.find(query, clazz, this.collection(clazz)) + } + + protected fun fields(clazz: Class): Array { + val fields = mutableListOf() + 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 collection(clazz: Class): 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}]的集合") + } + } +} diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQueryFactory.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQueryFactory.kt new file mode 100644 index 0000000..ca4c351 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbQueryFactory.kt @@ -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 createQuery(clazz: Class): IQuery { + return MongodbQuery(clazz, template) + } +} \ No newline at end of file diff --git a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/query/MongoQuery.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt similarity index 67% rename from src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/query/MongoQuery.kt rename to src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt index c32a19e..823963e 100644 --- a/src/gaea.mongo/src/main/kotlin/com/synebula/gaea/mongo/query/MongoQuery.kt +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/query/MongodbUniversalQuery.kt @@ -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.firstCharLowerCase -import com.synebula.gaea.log.ILogger -import com.synebula.gaea.mongo.order -import com.synebula.gaea.mongo.select -import com.synebula.gaea.mongo.where -import com.synebula.gaea.mongo.whereId -import com.synebula.gaea.query.IQuery +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.IUniversalQuery import com.synebula.gaea.query.Page 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.query.Criteria import org.springframework.data.mongodb.core.query.Query /** - * 实现IQuery的Mongo查询类 - * @param template MongoRepo对象 + * 实现IQuery的Mongodb查询类 + * @param template MongodbRepo对象 */ - -open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null) : IQuery { +open class MongodbUniversalQuery(var template: MongoTemplate) : IUniversalQuery { /** * 使用View解析是collection时是否校验存在,默认不校验 */ var validViewCollection = false - override fun get(id: TKey, clazz: Class): TView? { + override fun get(id: ID, clazz: Class): TView? { 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() query.where(params, clazz) query.select(fields) - return this.template.find(query, clazz, this.collection(clazz)) + return this.find(query, clazz) } override fun count(params: Map?, clazz: Class): Int { @@ -58,12 +57,16 @@ open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null) query.where(params.parameters, clazz) query.with(order(params.orders)) 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 } override fun range(field: String, params: List, clazz: Class): List { - 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 find(query: Query, clazz: Class): List { + return this.template.find(query, clazz, this.collection(clazz)) } fun fields(clazz: Class): Array { @@ -81,19 +84,12 @@ open class MongoQuery(var template: MongoTemplate, var logger: ILogger? = null) * 获取collection */ fun collection(clazz: Class): String { - val table: Table? = clazz.getDeclaredAnnotation( - Table::class.java - ) - return if (table != null) - return table.name + val table = clazz.getDeclaredAnnotation(Table::class.java) + return if (table != null) table.name else { - this.logger?.info(this, "视图类没有标记[Collection]注解,无法获取Collection名称。尝试使用View<${clazz.name}>名称解析集合") val name = clazz.simpleName.removeSuffix("View").firstCharLowerCase() - if (!validViewCollection || this.template.collectionExists(name)) - name - else { - throw RuntimeException("找不到名为[$table]的集合") - } + if (!validViewCollection || this.template.collectionExists(name)) name + else throw RuntimeException("找不到名为[${clazz.name}]的集合") } } } diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt new file mode 100644 index 0000000..02ba374 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepository.kt @@ -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, ID>( + override var clazz: Class, + protected var repo: MongoTemplate +) : IRepository { + + override fun add(obj: TAggregateRoot) { + this.repo.save(obj) + } + + override fun add(list: List) { + 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) { + this.repo.save(list) + } + + override fun count(params: Map?): Int { + val query = Query() + return this.repo.count(query.where(params, clazz), clazz).toInt() + } +} diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepositoryFactory.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepositoryFactory.kt new file mode 100644 index 0000000..012db8f --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbRepositoryFactory.kt @@ -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 , I> createRepository(clazz: Class): IRepository { + return MongodbRepository(clazz, template) + } +} \ No newline at end of file diff --git a/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt new file mode 100644 index 0000000..93a3d87 --- /dev/null +++ b/src/gaea.mongodb/src/main/kotlin/com/synebula/gaea/mongodb/repository/MongodbUniversalRepository.kt @@ -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 , ID> remove(id: ID, clazz: Class) { + this.repo.remove(whereId(id), clazz) + } + + override fun , ID> get( + id: ID, + clazz: Class, + ): TAggregateRoot? { + return this.repo.findOne(whereId(id), clazz) + } + + override fun , ID> update( + root: TAggregateRoot, + clazz: Class, + ) { + this.repo.save(root) + } + + /** + * 更新多个个对象。 + * + * @param roots 需要更新的对象。 + */ + override fun , ID> update( + roots: List, + clazz: Class + ) { + this.repo.save(roots) + } + + override fun , ID> add(root: TAggregateRoot, clazz: Class) { + this.repo.save(root) + } + + override fun , ID> add( + roots: List, + clazz: Class, + ) { + this.repo.insert(roots, clazz) + } + + override fun count(params: Map?, clazz: Class): Int { + val query = Query() + return this.repo.count(query.where(params, clazz), clazz).toInt() + } +} diff --git a/src/gaea.spring/build.gradle b/src/gaea.spring/build.gradle new file mode 100644 index 0000000..d283485 --- /dev/null +++ b/src/gaea.spring/build.gradle @@ -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 + } + } +} + diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/AppAspect.kt similarity index 57% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt rename to src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/AppAspect.kt index ed07e8b..44f1029 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/AppAspect.kt +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/AppAspect.kt @@ -1,14 +1,13 @@ -package com.synebula.gaea.app.component.aop +package com.synebula.gaea.spring.aop import com.google.gson.Gson -import com.synebula.gaea.app.IApplication -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.HttpMessage import com.synebula.gaea.data.message.Status import com.synebula.gaea.exception.NoticeUserException 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.annotation.Around import org.springframework.beans.factory.annotation.Autowired @@ -16,7 +15,7 @@ import org.springframework.context.ApplicationContext import org.springframework.core.DefaultParameterNameDiscoverer abstract class AppAspect { - private var paramDiscover = DefaultParameterNameDiscoverer() + private var parameterNameDiscoverer = DefaultParameterNameDiscoverer() private val gson = Gson() @@ -37,7 +36,6 @@ abstract class AppAspect { */ @Around("func()") fun around(point: ProceedingJoinPoint): Any? { - val clazz = point.`this`.javaClass //获取实际对象的类型避免获取到父类 val func = point.signature.declaringType.methods.find { it.name == point.signature.name }!!//获取声明类型中的方法信息 @@ -46,42 +44,32 @@ abstract class AppAspect { var funcName = func.name //遍历方法注解 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 } if (handler != null && handler is Handler) { 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 { point.proceed() } catch (ex: Throwable) { - //找到类的模块名称,否则使用类名 - var moduleName = clazz.name - if (IApplication::class.java.isAssignableFrom(clazz)) { - moduleName = (point.`this` as IApplication).name + val moduleName = this.resolveModuleName(point.`this`) + var message = "$moduleName - ${funcName}异常" + message = if (ex is NoticeUserException || ex is Error) { + "$message: ${ex.message}" } else { - val name = clazz.annotations.find { it is ModuleName } - if (name != null && name is ModuleName) { - moduleName = name.value - } - } - var message = "$moduleName - $funcName 异常" - if (ex is NoticeUserException) { - message = "$message: ${ex.message}" - } else { - message = "$message。" - + "$message。" } logger.error( ex, "$message。Method args ${ - paramDiscover.getParameterNames(func)?.contentToString() + parameterNameDiscoverer.getParameterNames(func)?.contentToString() } values is ${ gson.toJson(point.args) }" @@ -89,4 +77,24 @@ abstract class AppAspect { 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 + } } \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/AccessLog.kt similarity index 52% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt rename to src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/AccessLog.kt index 28015c6..8f87125 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/AccessLog.kt +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/AccessLog.kt @@ -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) @Handler(AccessLogHandler::class) diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Handler.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Handler.kt new file mode 100644 index 0000000..ea92224 --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Handler.kt @@ -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) \ No newline at end of file diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Method.kt similarity index 61% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt rename to src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Method.kt index 3f09685..aa3cb55 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/annotation/MethodName.kt +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Method.kt @@ -1,13 +1,13 @@ -package com.synebula.gaea.app.component.aop.annotation +package com.synebula.gaea.spring.aop.annotation import java.lang.annotation.Inherited /** * 标记方法名称,由AOP负责记录异常时使用该名称 * - * @param name 异常消息 + * @param name 方法名称 */ @Inherited @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) -annotation class MethodName(val name: String) +annotation class Method(val name: String) diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Module.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Module.kt new file mode 100644 index 0000000..7202fbd --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/annotation/Module.kt @@ -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) diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AccessLogHandler.kt similarity index 79% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt rename to src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AccessLogHandler.kt index 1898eb3..a6d679c 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/component/aop/handler/AccessLogHandler.kt +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AccessLogHandler.kt @@ -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.synebula.gaea.log.ILogger @@ -15,11 +15,11 @@ class AccessLogHandler : AnnotationHandler { @Autowired lateinit var logger: ILogger - override fun handle(clazz: Class, func: Method, args: Array, exception: Exception?) { + override fun handle(obj: Any, func: Method, args: Array, exception: Exception?) { val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes val request = attributes.request 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( args ) diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AnnotationHandler.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AnnotationHandler.kt new file mode 100644 index 0000000..335a0f5 --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/aop/handler/AnnotationHandler.kt @@ -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, exception: Exception? = null) +} \ No newline at end of file diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Factory.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Factory.kt new file mode 100644 index 0000000..2c399a1 --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Factory.kt @@ -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 { + 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 +} \ No newline at end of file diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Proxy.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Proxy.kt new file mode 100644 index 0000000..201ba8a --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Proxy.kt @@ -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? { + 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, 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? +} \ No newline at end of file diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/ProxyType.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/ProxyType.kt new file mode 100644 index 0000000..7ba44cc --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/ProxyType.kt @@ -0,0 +1,3 @@ +package com.synebula.gaea.spring.autoconfig + +enum class ProxyType { JDK, Cglib } \ No newline at end of file diff --git a/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Register.kt b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Register.kt new file mode 100644 index 0000000..f9d70d5 --- /dev/null +++ b/src/gaea.spring/src/main/kotlin/com/synebula/gaea/spring/autoconfig/Register.kt @@ -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 + + /** + * 根据过滤器扫描直接包下bean + * + * @param packages 指定的扫描包 + * @param filters 过滤器 + * @return 扫描后的bean定义 + */ + protected fun doScan(packages: Array?, filters: Array): List { + 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 = LinkedList() + for (basePackage in packages!!) { + beanDefinitions.addAll(scanner.findCandidateComponents(basePackage)) + } + return beanDefinitions + } + + /** + * 获取指定接口的类型过滤器 + * + * @param interfaces 需要过滤的父接口类型 + * @param onlyInterface 是否只获取接口类型 + * @return 类型过滤器 + */ + protected fun interfaceFilter(interfaces: Array>, 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 + } +} \ No newline at end of file diff --git a/src/gaea/build.gradle b/src/gaea/build.gradle index f049c0d..69db5b0 100644 --- a/src/gaea/build.gradle +++ b/src/gaea/build.gradle @@ -1,8 +1,8 @@ publishing { publications { publish(MavenPublication) { - group 'com.synebula' - artifactId 'gaea' + group "${project.group}" + artifactId "${project.name}" version "$version" from components.java } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/AllowConcurrentSubscribe.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/AllowConcurrentSubscribe.kt new file mode 100644 index 0000000..77687ee --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/AllowConcurrentSubscribe.kt @@ -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 \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt new file mode 100644 index 0000000..e4c2952 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Bus.kt @@ -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. + * + *

Avoid MessageBus

+ * + * + * **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). + * + * + *

MessageBus Summary

+ * + * + * 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. + * + *

Receiving Messages

+ * + * + * 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. + * + * + *

Posting Messages

+ * + * + * 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. + * + * + * + *

Subscriber Methods

+ * + * + * 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. + * + *

Dead Messages

+ * + * + * 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( + override val identifier: String, + override val executor: Executor, + val dispatcher: Dispatcher, + val exceptionHandler: SubscriberExceptionHandler, +) : IBus { + + val DEAD_TOPIC = "DEAD_TOPIC" + + private val subscribers: SubscriberRegistry = 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) : 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) : 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, 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, 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) { + 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) + } +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/DeadMessage.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/DeadMessage.kt new file mode 100644 index 0000000..6255b78 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/DeadMessage.kt @@ -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)" + } +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Dispatcher.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Dispatcher.kt new file mode 100644 index 0000000..091519f --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Dispatcher.kt @@ -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 { + /** Dispatches the given `message` to the given `subscribers`. */ + fun dispatch(message: T, subscribers: Iterator>?) { + while (subscribers!!.hasNext()) { + subscribers.next().dispatch(message) + } + } + + /** Dispatches the given `message` to the given `subscribers`. */ + abstract fun dispatchAsync(message: T, subscribers: Iterator>?) + + /** Implementation of a [.perThreadDispatchQueue] dispatcher. */ + private class PerThreadQueuedDispatcher : Dispatcher() { + // This dispatcher matches the original dispatch behavior of MessageBus. + /** Per-thread queue of messages to dispatch. */ + private val queue: ThreadLocal>> = object : ThreadLocal>>() { + override fun initialValue(): Queue> { + return ArrayDeque() + } + } + + /** Per-thread dispatch state, used to avoid reentrant message dispatching. */ + private val dispatching: ThreadLocal = object : ThreadLocal() { + override fun initialValue(): Boolean { + return false + } + } + + override fun dispatchAsync(message: T, subscribers: Iterator>?) { + val queueForThread = queue.get() + queueForThread.offer(Message(message, subscribers)) + if (!dispatching.get()) { + dispatching.set(true) + try { + var nextMessage: Message? + 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(val message: Any, val subscribers: Iterator>?) + } + + /** Implementation of a [.legacyAsync] dispatcher. */ + private class LegacyAsyncDispatcher : Dispatcher() { + // 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>() + override fun dispatchAsync(message: T, subscribers: Iterator>?) { + while (subscribers!!.hasNext()) { + queue.add(MessageWithSubscriber(message, subscribers.next())) + } + var e: MessageWithSubscriber? + while (queue.poll().also { e = it } != null) { + e!!.subscriber!!.dispatchAsync(e!!.message) + } + } + + private class MessageWithSubscriber(val message: T, val subscriber: Subscriber?) + } + + 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 perThreadDispatchQueue(): Dispatcher { + 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 legacyAsync(): Dispatcher { + return LegacyAsyncDispatcher() + } + } +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/IBus.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/IBus.kt new file mode 100644 index 0000000..d0ee938 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/IBus.kt @@ -0,0 +1,84 @@ +package com.synebula.gaea.bus + +import java.lang.reflect.Method +import java.util.concurrent.Executor + +interface IBus { + + 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, subscriber: Any) + + /** + * 注册事件Listener + * @param topics 主题 + * @param subscriber subscriber对象 + * @param method Listener方法 + */ + fun register(topics: Array, 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) +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/LoggingHandler.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/LoggingHandler.kt new file mode 100644 index 0000000..0cab54a --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/LoggingHandler.kt @@ -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 : SubscriberExceptionHandler { + override fun handleException(exception: Throwable?, context: SubscriberExceptionContext) { + val logger = logger(context) + if (logger.isLoggable(Level.SEVERE)) { + logger.log(Level.SEVERE, message(context), exception) + } + } + + companion object { + private fun logger(context: SubscriberExceptionContext): Logger { + return Logger.getLogger(Bus::class.java.name + "." + context.bus.identifier) + } + + private fun message(context: SubscriberExceptionContext): 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) + } + } +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscribe.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscribe.kt new file mode 100644 index 0000000..2a7ab0d --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscribe.kt @@ -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 = []) \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscriber.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscriber.kt new file mode 100644 index 0000000..bd0484d --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/Subscriber.kt @@ -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 private constructor( + private val bus: IBus, + 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 { + 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(bus: IBus, target: Any, method: Method) : + Subscriber(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 create(bus: IBus, subscriber: Any, method: Method): Subscriber { + 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 + } + } +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionContext.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionContext.kt new file mode 100644 index 0000000..d0b9bb4 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionContext.kt @@ -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 internal constructor( + val bus: IBus, val message: Any, val subscriber: Any, val subscriberMethod: Method, +) \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionHandler.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionHandler.kt new file mode 100644 index 0000000..14c40f2 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberExceptionHandler.kt @@ -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 { + /** Handles exceptions thrown by subscribers. */ + fun handleException(exception: Throwable?, context: SubscriberExceptionContext) +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberRegistry.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberRegistry.kt new file mode 100644 index 0000000..cd713d8 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/bus/SubscriberRegistry.kt @@ -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(private val bus: IBus) { + /** + * 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>>() + + open fun register(topics: Array, subscriber: Any) { + val listenerMethods = findAllSubscribers(subscriber) + for (topic in topics) { + for ((_, messageMethodsInListener) in listenerMethods) { + var messageSubscribers = subscribers[topic] + if (messageSubscribers == null) { + val newSet = CopyOnWriteArraySet>() + messageSubscribers = subscribers.putIfAbsent(topic, newSet) ?: newSet + } + messageSubscribers.addAll(messageMethodsInListener) + } + } + } + + open fun register(topics: Array, subscriber: Any, method: Method) { + for (topic in topics) { + var messageSubscribers = subscribers[topic] + if (messageSubscribers == null) { + val newSet = CopyOnWriteArraySet>() + 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>() + 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>() + 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> { + val topicSubscribers: CopyOnWriteArraySet> = 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>> { + val methodsInListener = mutableMapOf>>() + 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> = 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, List>() + + @Synchronized + protected fun getAnnotatedMethods(clazz: Class<*>): List { + var methods = subscriberMethodsCache[clazz] + if (methods == null) + methods = getAnnotatedMethodsNotCached(clazz, Subscribe::class.java) + return methods + } + + protected fun getAnnotatedMethodsNotCached( + clazz: Class<*>, + annotationClass: Class, + ): List { + val supertypes = flattenHierarchy(clazz) + val identifiers = mutableMapOf() + 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, Set>>() + + /** + * 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> { + var supertypes = flattenHierarchyCache[clazz] + if (supertypes == null) { + supertypes = clazz.supertypes() + flattenHierarchyCache[clazz] = supertypes + } + return supertypes + } + } +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/IObjectConverter.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/IObjectConverter.kt deleted file mode 100644 index 3deb5f2..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/IObjectConverter.kt +++ /dev/null @@ -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 目标对象类型。 - * @return 目标对象 - */ - fun convert(src: Any, dest: Class): T -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt index 4afe5e4..a21d4c1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/DateCode.kt @@ -11,13 +11,13 @@ import java.util.* * 参数:年=y,月=M,日=d,时=H,分=m,秒=s,毫秒=S。位数最好使用默认最大长度。 */ class DateCode(pattern: String = "yyyyMMdd") : ICodeGenerator { - var formator = SimpleDateFormat() + var formatter = SimpleDateFormat() init { - formator.applyPattern(pattern) + formatter.applyPattern(pattern) } override fun generate(): Long { - return java.lang.Long.parseLong(formator.format(Date())) + return java.lang.Long.parseLong(formatter.format(Date())) } } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt index 001657b..4adceac 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/FixedRandomCode.kt @@ -1,6 +1,7 @@ package com.synebula.gaea.data.code import java.util.* +import kotlin.math.pow /** * 固定长度随机编号生成。 @@ -9,8 +10,8 @@ import java.util.* * @since 2016年10月24日 上午10:58:05 */ class FixedRandomCode( - //生成的随机编号长度。 - var length: Int + //生成的随机编号长度。 + var length: Int, ) : ICodeGenerator { /** @@ -32,12 +33,12 @@ class FixedRandomCode( var format = String.format("%s%d%dd", "%", 0, calcMaxLength) val count = this.length / calcMaxLength 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 if (last != 0) { 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() } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/SnowflakeCode.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/SnowflakeCode.kt index 3e1c6f1..faa5ea7 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/SnowflakeCode.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/code/SnowflakeCode.kt @@ -13,7 +13,7 @@ package com.synebula.gaea.data.code * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)

- * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序Snowflake类的twepoch属性)。

+ * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序Snowflake类的origin属性)。

* 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

* 10位的数据机器位,可以部署在1024个节点,包括5位datacenter和5位worker

* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号

@@ -35,7 +35,7 @@ open class SnowflakeCode( /** * 开始时间截 (2018-01-01) */ - private val twepoch = 1514736000000L + private val origin = 1514736000000L /** * 机器id所占的位数 @@ -142,7 +142,7 @@ open class SnowflakeCode( lastTimestamp = current //移位并通过或运算拼到一起组成64位的ID - return (current - twepoch shl timestampLeftShift + return (current - origin shl timestampLeftShift or (datacenter shl datacenterShift) or (worker shl workerShift) or sequence) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt index 053b4a9..ef6ee10 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/date/DateTime.kt @@ -275,7 +275,6 @@ class DateTime() : Comparable { * @return true or false */ fun isBetween(start: DateTime, end: DateTime): Boolean { - //return this in start..end return start.dateNoTime.compareTo(this.dateNoTime) * this.dateNoTime.compareTo(end.dateNoTime) >= 0 } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/DataMessage.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/DataMessage.kt index 96bd46e..ef129e1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/DataMessage.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/DataMessage.kt @@ -8,7 +8,7 @@ import java.util.* * * @param T 消息数据类型 */ -open class DataMessage() : Message() { +open class DataMessage() : StatusMessage() { /** * 传递的业务数据 diff --git a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/HttpMessage.kt similarity index 71% rename from src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/data/message/HttpMessage.kt index 43e8236..345acb2 100644 --- a/src/gaea.app/src/main/kotlin/com/synebula/gaea/app/struct/HttpMessage.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/HttpMessage.kt @@ -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() { + var serializer: IJsonSerializer? = null + constructor(data: Any) : this() { this.data = data } @@ -25,6 +27,6 @@ class HttpMessage() : DataMessage() { } override fun toString(): String { - return Gson().toJson(this) + return serializer?.serialize(this) ?: super.toString() } } \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/IMessage.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/IMessage.kt new file mode 100644 index 0000000..dbf332d --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/IMessage.kt @@ -0,0 +1,16 @@ +package com.synebula.gaea.data.message + +/** + * 消息结构 + */ +interface IMessage { + /** + * 命令载荷, 实际的业务数据 + */ + var message: String + + /** + * 时间戳。 + */ + var timestamp: Long +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/StatusMessage.kt similarity index 92% rename from src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/data/message/StatusMessage.kt index c003dca..a0a960b 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/Message.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/message/StatusMessage.kt @@ -1,6 +1,6 @@ package com.synebula.gaea.data.message -open class Message { +open class StatusMessage { /** * 状态。200成功,400错误,500异常 */ diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IDeserializer.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IDeserializer.kt index e0d170d..1db2a39 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IDeserializer.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IDeserializer.kt @@ -2,16 +2,17 @@ package com.synebula.gaea.data.serialization /** * 序列化器 + * @param S 源数据类型 */ -interface IDeserializer { +interface IDeserializer { /** * 反序列化 * - * @param 源数据类型 - * @param 目标数据类型 + * @param T 目标数据类型 * @param src 源数据 + * @param targetClass 目标对象。 * @return 目标数据 */ - fun deserialize(src: S): T + fun deserialize(src: S, targetClass: Class): T } \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IObjectMapper.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IObjectMapper.kt new file mode 100644 index 0000000..4ddbb65 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/IObjectMapper.kt @@ -0,0 +1,10 @@ +package com.synebula.gaea.data.serialization + +/** + * 对象映射器,支持对象之间的转换。 + * + * @author alex + * @version 0.1 + * @since 2020-05-15 + */ +interface IObjectMapper : IDeserializer diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/ISerializer.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/ISerializer.kt index 925c518..21db27c 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/ISerializer.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/ISerializer.kt @@ -2,16 +2,16 @@ package com.synebula.gaea.data.serialization /** * 序列化器 + * @param T 目标数据类型 */ -interface ISerializer { +interface ISerializer { /** * 序列化 + * @param S 源数据类型 * - * @param 源数据类型 - * @param 目标数据类型 * @param src 源数据 * @return 目标数据 */ - fun serialize(src: S): T + fun serialize(src: S): T } \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonDeserializer.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonDeserializer.kt index da7c13b..09acb01 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonDeserializer.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonDeserializer.kt @@ -1,15 +1,8 @@ package com.synebula.gaea.data.serialization.json +import com.synebula.gaea.data.serialization.IDeserializer + /** * 序列化器 */ -interface IJsonDeserializer { - /** - * 反序列化 - * - * @param 目标数据类型 - * @param src Json字符串数据 - * @return 目标数据 - */ - fun deserialize(src: String): T -} \ No newline at end of file +interface IJsonDeserializer : IDeserializer \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonSerializer.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonSerializer.kt index e154644..9130125 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonSerializer.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/data/serialization/json/IJsonSerializer.kt @@ -1,15 +1,8 @@ package com.synebula.gaea.data.serialization.json +import com.synebula.gaea.data.serialization.ISerializer + /** * 序列化器 */ -interface IJsonSerializer { - /** - * 序列化 - * - * @param 源数据类型 - * @param src 源数据 - * @return Json字符串 - */ - fun serialize(src: S): String -} \ No newline at end of file +interface IJsonSerializer : ISerializer \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/AfterRemoveEvent.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/AfterRemoveEvent.kt new file mode 100644 index 0000000..6c3b076 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/AfterRemoveEvent.kt @@ -0,0 +1,5 @@ +package com.synebula.gaea.domain.event + +import com.synebula.gaea.domain.model.IAggregateRoot + +class AfterRemoveEvent, I>(var id: I? = null) \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/BeforeRemoveEvent.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/BeforeRemoveEvent.kt new file mode 100644 index 0000000..21a9211 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/event/BeforeRemoveEvent.kt @@ -0,0 +1,5 @@ +package com.synebula.gaea.domain.event + +import com.synebula.gaea.domain.model.IAggregateRoot + +class BeforeRemoveEvent, I>(var id: I? = null) \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRecord.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRecord.kt deleted file mode 100644 index 77b1c3f..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRecord.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.synebula.gaea.domain.model - -import java.util.* - -/** - * 记录聚合根 - * 聚合根外添加了创建和修改的人\时间信息 - */ -abstract class AggregateRecord : AggregateRoot() { - 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() -} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRoot.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRoot.kt index c1bb7fb..3f7962f 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRoot.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/AggregateRoot.kt @@ -1,5 +1,5 @@ package com.synebula.gaea.domain.model -abstract class AggregateRoot : Entity(), IAggregateRoot { +abstract class AggregateRoot : Entity(), IAggregateRoot { override var alive: Boolean = true } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/Entity.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/Entity.kt index 95d3fe8..8aa77b7 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/Entity.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/Entity.kt @@ -1,3 +1,3 @@ package com.synebula.gaea.domain.model -abstract class Entity : IEntity +abstract class Entity : IEntity diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IAggregateRoot.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IAggregateRoot.kt index b442100..2222405 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IAggregateRoot.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IAggregateRoot.kt @@ -5,7 +5,7 @@ package com.synebula.gaea.domain.model * * @author alex **/ -interface IAggregateRoot : IEntity { +interface IAggregateRoot : IEntity { /** * 实体对象是否有效。 diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IEntity.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IEntity.kt index 3016f06..ea99e25 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IEntity.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/IEntity.kt @@ -5,13 +5,13 @@ package com.synebula.gaea.domain.model * * @author alex * - * @param 主键的类型。 + * @param 主键的类型。 **/ -interface IEntity { +interface IEntity { /** * 实体ID */ - var id: TKey? + var id: ID? } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexAggregateRoot.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexAggregateRoot.kt deleted file mode 100644 index 48e25fc..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexAggregateRoot.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.synebula.gaea.domain.model.complex - -abstract class ComplexAggregateRoot : ComplexEntity(), IComplexAggregateRoot diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexEntity.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexEntity.kt deleted file mode 100644 index 4d734e9..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/ComplexEntity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.synebula.gaea.domain.model.complex - - -abstract class ComplexEntity : IComplexEntity { -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexAggregateRoot.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexAggregateRoot.kt deleted file mode 100644 index 487dc15..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexAggregateRoot.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.synebula.gaea.domain.model.complex - -/** - * 继承本接口,说明对象为聚合根。 - * - * @param 主键的类型。 - * @author alex - */ -interface IComplexAggregateRoot : IComplexEntity diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexEntity.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexEntity.kt deleted file mode 100644 index 0bbca4d..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/model/complex/IComplexEntity.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.synebula.gaea.domain.model.complex - -import com.synebula.gaea.domain.model.IEntity - -interface IComplexEntity : IEntity { - var secondary: TSecond? -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/record/Record.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/record/Record.kt new file mode 100644 index 0000000..b6ae3c2 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/record/Record.kt @@ -0,0 +1,21 @@ +package com.synebula.gaea.domain.record + +import java.util.* + + +/** + * 记录信息 + * 添加了创建和修改的人\时间信息 + */ +abstract class Record { + + //记录增加信息 + var creator: String? = null + var creatorName: String? = null + var createTime: Date = Date() + + //记录修改信息 + var modifier: String? = null + var modifierName: String? = null + var modifyTime: Date = Date() +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt index 8c653ef..e8fa6cd 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepository.kt @@ -6,22 +6,29 @@ import com.synebula.gaea.domain.model.IAggregateRoot * 定义了提供增删改的仓储接口。 * 本接口泛型放置到方法上,并需要显式提供聚合根的class对象 */ -interface IRepository { +interface IRepository, ID> { + + /** + * 仓储的对象类 + */ + var clazz: Class + /** * 插入单个对象。 * * @param obj 需要插入的对象。 * @return 返回原对象,如果对象ID为自增,则补充自增ID。 */ - fun , TKey> add(obj: TAggregateRoot, clazz: Class) + fun add(obj: TAggregateRoot) /** * 插入多个个对象。 * - * @param obj 需要插入的对象。 + * @param list 需要插入的对象。 * @return 返回原对象,如果对象ID为自增,则补充自增ID。 */ - fun , TKey> add(obj: List, clazz: Class) + fun add(list: List) + /** * 更新对象。 @@ -29,24 +36,30 @@ interface IRepository { * @param obj 需要更新的对象。 * @return */ - fun , TKey> update(obj: TAggregateRoot, clazz: Class) + fun update(obj: TAggregateRoot) + + /** + * 更新多个个对象。 + * + * @param list 需要新的对象。 + */ + fun update(list: List) /** * 通过id删除该条数据 * - * @param id id - * @param clazz 操作数据的类型 + * @param id 对象ID。 + * @return */ - fun , TKey> remove(id: TKey, clazz: Class) + fun remove(id: ID) /** * 根据ID获取对象。 * - * @param id id - * @param clazz 操作数据的类型 - * @return 聚合根 + * @param id 对象ID。 + * @return */ - fun , TKey> get(id: TKey, clazz: Class): TAggregateRoot? + fun get(id: ID): TAggregateRoot? /** @@ -55,5 +68,7 @@ interface IRepository { * @param params 查询条件。 * @return int */ - fun count(params: Map?, clazz: Class): Int + fun count(params: Map?): Int + } + diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepositoryFactory.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepositoryFactory.kt new file mode 100644 index 0000000..20fb622 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IRepositoryFactory.kt @@ -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 , I> createRepository(clazz: Class): IRepository +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/ISpecificRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/ISpecificRepository.kt deleted file mode 100644 index 06e8564..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/ISpecificRepository.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.synebula.gaea.domain.repository - -import com.synebula.gaea.domain.model.IAggregateRoot - -/** - * 继承本接口表示对象为仓储类。 - * 定义了提供增删改的仓储接口。 - * 本接口泛型定义在类上,不需要显式提供聚合根的class对象,class对象作为类的成员变量声明。 - * - * @param this T is the parameter - * @author alex - */ -interface ISpecificRepository, TKey> { - - /** - * 仓储的对象类 - */ - var clazz: Class? - - /** - * 插入单个对象。 - * - * @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 , TKey> get(id: TKey, clazz: Class): T - - - /** - * 根据条件查询符合条件记录的数量 - * - * @param params 查询条件。 - * @return int - */ - fun count(params: Map?): Int - -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt new file mode 100644 index 0000000..8eaca7e --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/IUniversalRepository.kt @@ -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 , ID> add(root: TAggregateRoot, clazz: Class) + + /** + * 插入多个个对象。 + * + * @param roots 需要插入的对象。 + */ + fun , ID> add(roots: List, clazz: Class) + + /** + * 更新对象。 + * + * @param root 需要更新的对象。 + * @return + */ + fun , ID> update(root: TAggregateRoot, clazz: Class) + + /** + * 更新多个个对象。 + * + * @param roots 需要更新的对象。 + */ + fun , ID> update(roots: List, clazz: Class) + + /** + * 通过id删除该条数据 + * + * @param id id + * @param clazz 操作数据的类型 + */ + fun , ID> remove(id: ID, clazz: Class) + + /** + * 根据ID获取对象。 + * + * @param id id + * @param clazz 操作数据的类型 + * @return 聚合根 + */ + fun , ID> get(id: ID, clazz: Class): TAggregateRoot? + + + /** + * 根据条件查询符合条件记录的数量 + * + * @param params 查询条件。 + * @return int + */ + fun count(params: Map?, clazz: Class): Int +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/context/IContext.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/context/IContext.kt index 9c4cb5a..2e22c5f 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/context/IContext.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/context/IContext.kt @@ -10,21 +10,21 @@ import com.synebula.gaea.domain.model.IAggregateRoot interface IContext : IUnitOfWork { /** * 将指定的聚合根标注为“新建”状态。 - * @param obj + * @param obj 聚合根 */ - fun , TKey> add(obj: TType) + fun , ID> add(obj: T) /** * 将指定的聚合根标注为“更改”状态。 * - * @param obj + * @param obj 聚合根 */ - fun , TKey> update(obj: TType) + fun , ID> update(obj: T) /** * 将指定的聚合根标注为“删除”状态。 * - * @param obj + * @param obj 聚合根 */ - fun , TKey> remove(obj: TType) + fun , ID> remove(obj: T) } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndNotSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndNotSpecification.kt index 2ba6898..c5f3fa7 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndNotSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndNotSpecification.kt @@ -5,19 +5,12 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 + * @param left 表达式左侧规约对象。 + * @param right 表达式右侧规约对象。 */ -class AndNotSpecification -/** - * 构造一个新的混合规约对象。 - * - * @param left - * 表达式左侧规约对象。 - * @param right - * 表达式右侧规约对象。 - */ -(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { +class AndNotSpecification(left: ISpecification, right: ISpecification) : + CompositeSpecification(left, right) { override fun isSatisfiedBy(obj: T): Boolean { return left.isSatisfiedBy(obj) && NotSpecification(right).isSatisfiedBy(obj) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndSpecification.kt index bf500ce..0ed1584 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AndSpecification.kt @@ -5,19 +5,11 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 + * @param left 表达式左侧规约对象。 + * @param right 表达式右侧规约对象。 */ -class AndSpecification -/** - * 构造一个新的混合规约对象。 - * - * @param left - * 表达式左侧规约对象。 - * @param right - * 表达式右侧规约对象。 - */ -(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { +class AndSpecification(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { override fun isSatisfiedBy(obj: T): Boolean { return left.isSatisfiedBy(obj) && right.isSatisfiedBy(obj) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AnySpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AnySpecification.kt index e876883..163a956 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AnySpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/AnySpecification.kt @@ -5,8 +5,7 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 */ class AnySpecification : Specification() { diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/CompositeSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/CompositeSpecification.kt index d392344..34dde5d 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/CompositeSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/CompositeSpecification.kt @@ -4,23 +4,11 @@ package com.synebula.gaea.domain.repository.specifications * 实现了接口ICompositeSpecification,混合规约的基类。 * @author alex * - * @param - */ -abstract class CompositeSpecification -/** - * 构造一个新的混合规约对象。 - * - * @param left - * 表达式左侧规约对象。 - * @param right - * 表达式右侧规约对象。 + * @param T 规约对象的类型。 + * @param left 表达式左侧规约对象。 + * @param right 表达式右侧规约对象。 */ -( - /** - * 表达式左侧规约对象。 - */ - override val left: ISpecification, - /** - * 表达式右侧规约对象。 - */ - override val right: ISpecification) : Specification(), ICompositeSpecification +abstract class CompositeSpecification( + override val left: ISpecification, + override val right: ISpecification +) : Specification(), ICompositeSpecification diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ICompositeSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ICompositeSpecification.kt index fa715ec..92a7d06 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ICompositeSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ICompositeSpecification.kt @@ -5,8 +5,7 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 */ interface ICompositeSpecification : ISpecification { diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ISpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ISpecification.kt index 86d8006..477aef5 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ISpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/ISpecification.kt @@ -5,14 +5,13 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param + * @param T 规约对象的类型。 */ interface ISpecification { /** * 判断是否符合条件。 * - * @param obj - * 规约类型的对象。 + * @param obj 规约类型的对象。 * @return 符合条件的返回True。 */ fun isSatisfiedBy(obj: T): Boolean @@ -20,8 +19,7 @@ interface ISpecification { /** * 结合当前规约对象和另一规约对象进行与判断。 * - * @param other - * 需要进行与结合的另一个规约对象。 + * @param other 需要进行与结合的另一个规约对象。 * @return 结合后的规约对象。 */ fun and(other: ISpecification): ISpecification @@ -29,8 +27,7 @@ interface ISpecification { /** * 结合当前规约对象和另一规约对象进行或判断。 * - * @param other - * 需要进行或结合的另一个规约对象。 + * @param other 需要进行或结合的另一个规约对象。 * @return 结合后的规约对象。 */ fun or(other: ISpecification): ISpecification @@ -38,8 +35,7 @@ interface ISpecification { /** * 结合当前规约对象和另一规约对象进行与且非判断 * - * @param other - * 需要进行非结合的另一个规约对象。 + * @param other 需要进行非结合的另一个规约对象。 * @return 结合后的规约对象。 */ fun andNot(other: ISpecification): ISpecification @@ -47,8 +43,7 @@ interface ISpecification { /** * 结合当前规约对象和另一规约对象进行或非判断。 * - * @param other - * 需要进行或非结合的另一个规约对象。 + * @param other 需要进行或非结合的另一个规约对象。 * @return 结合后的规约对象。 */ fun orNot(other: ISpecification): ISpecification diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NoneSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NoneSpecification.kt index 7dac9f1..c007f71 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NoneSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NoneSpecification.kt @@ -5,8 +5,7 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 */ class NoneSpecification : Specification() { diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NotSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NotSpecification.kt index f9cc24e..fe36b32 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NotSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/NotSpecification.kt @@ -5,7 +5,7 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param 规约对象的类型。 + * @param T 规约对象的类型。 * * @param spec 需要逆反的规约对象。 * diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrNotSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrNotSpecification.kt index 504ec8d..8d35832 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrNotSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrNotSpecification.kt @@ -5,19 +5,12 @@ package com.synebula.gaea.domain.repository.specifications * * @author alex * - * @param - * 规约对象的类型。 + * @param T 规约对象的类型。 + * @param left 表达式左侧规约对象。 + * @param right 表达式右侧规约对象。 */ -class OrNotSpecification -/** - * 构造一个新的混合规约对象。 - * - * @param left - * 表达式左侧规约对象。 - * @param right - * 表达式右侧规约对象。 - */ -(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { +class OrNotSpecification(left: ISpecification, right: ISpecification) : + CompositeSpecification(left, right) { override fun isSatisfiedBy(obj: T): Boolean { return left.isSatisfiedBy(obj) || NotSpecification(right).isSatisfiedBy(obj) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrSpecification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrSpecification.kt index e9297b2..5aeb8f8 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrSpecification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/OrSpecification.kt @@ -1,16 +1,14 @@ package com.synebula.gaea.domain.repository.specifications -class OrSpecification /** * 构造一个新的混合规约对象。 * - * @param left - * 表达式左侧规约对象。 - * @param right - * 表达式右侧规约对象。 + * @param T 规约对象的类型。 + * + * @param left 表达式左侧规约对象。 + * @param right 表达式右侧规约对象。 */ -(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { - +class OrSpecification(left: ISpecification, right: ISpecification) : CompositeSpecification(left, right) { override fun isSatisfiedBy(obj: T): Boolean { return left.isSatisfiedBy(obj) || right.isSatisfiedBy(obj) } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/Specification.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/Specification.kt index 0a0fdd8..c688a12 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/Specification.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/repository/specifications/Specification.kt @@ -1,5 +1,11 @@ package com.synebula.gaea.domain.repository.specifications +/** + * 抽象的的规约对象。 + * + * @param T 规约对象的类型。 + * + */ abstract class Specification : ISpecification { abstract override fun isSatisfiedBy(obj: T): Boolean diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt index 2c07161..6ac0eb1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Command.kt @@ -8,5 +8,6 @@ package com.synebula.gaea.domain.service * @since 2020-05-15 */ open class Command : ICommand { + override var payload = "" override var timestamp = 0L } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Domain.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Domain.kt new file mode 100644 index 0000000..16f787c --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Domain.kt @@ -0,0 +1,13 @@ +package com.synebula.gaea.domain.service + +import com.synebula.gaea.domain.model.IAggregateRoot +import kotlin.reflect.KClass + +/** + * 声明服务依赖的聚合根,若服务没有实现类则可以根据依赖项自动组装服务。 + * + * @param clazz 依赖的聚合根类型 + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Domain(val clazz: KClass>) diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ICommand.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ICommand.kt index 41e4512..fe3543b 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ICommand.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ICommand.kt @@ -8,6 +8,12 @@ package com.synebula.gaea.domain.service * @since 2020-05-15 */ interface ICommand { + + /** + * 命令载荷, 实际的业务数据 + */ + var payload: String + /** * 时间戳。 */ diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt deleted file mode 100644 index 2d2efec..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ILazyService.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.synebula.gaea.domain.service - -import com.synebula.gaea.data.message.DataMessage -import com.synebula.gaea.data.message.Message -import com.synebula.gaea.domain.model.IAggregateRoot -import com.synebula.gaea.log.ILogger - - -/** - * 继承该接口,表明对象为领域服务。 - * @author alex - * @version 0.0.1 - * @since 2016年9月18日 下午2:23:15 - */ -interface ILazyService, TKey> { - /** - * 日志组件。 - */ - var logger: ILogger - - fun add(root: TAggregateRoot): DataMessage - - fun update(id: TKey, root: TAggregateRoot) - - fun remove(id: TKey) - - /** - * 添加一个删除对象前执行监听器。 - * @param key 监听器标志。 - * @param func 监听方法。 - */ - fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) - - /** - * 移除一个删除对象前执行监听器。 - * @param key 监听器标志。 - */ - fun removeBeforeRemoveListener(key: String) -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/IService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/IService.kt index 46d39a1..82f26d6 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/IService.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/IService.kt @@ -1,8 +1,6 @@ package com.synebula.gaea.domain.service import com.synebula.gaea.data.message.DataMessage -import com.synebula.gaea.data.message.Message -import com.synebula.gaea.log.ILogger /** @@ -11,28 +9,40 @@ import com.synebula.gaea.log.ILogger * @version 0.0.1 * @since 2016年9月18日 下午2:23:15 */ -interface IService { - /** - * 日志组件。 - */ - var logger: ILogger - - fun add(command: ICommand): DataMessage - - fun update(id: TKey, command: ICommand) - - fun remove(id: TKey) +interface IService { /** - * 添加一个删除对象前执行监听器。 - * @param key 监听器标志。 - * @param func 监听方法。 + * 增加对象 + * + * @param command 增加对象命令 */ - fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) + fun add(command: ICommand): DataMessage /** - * 移除一个删除对象前执行监听器。 - * @param key 监听器标志。 + * 增加对象 + * + * @param commands 增加对象命令列表 */ - fun removeBeforeRemoveListener(key: String) + fun add(commands: List) + + /** + * 更新对象 + * + * @param id 对象ID + * @param command 更新对象命令 + */ + fun update(id: ID, command: ICommand) + + /** + * 批量更新对象 + * + * @param commands 更新对象命令列表 + */ + fun update(commands: List) + + /** + * 增加对象 + * @param id 对象ID + */ + fun remove(id: ID) } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ISimpleService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ISimpleService.kt new file mode 100644 index 0000000..ad7b5bc --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/ISimpleService.kt @@ -0,0 +1,54 @@ +package com.synebula.gaea.domain.service + +import com.synebula.gaea.data.message.DataMessage +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.log.ILogger + + +/** + * 继承该接口,表明对象为领域服务。 + * @author alex + * @version 0.0.1 + * @since 2016年9月18日 下午2:23:15 + */ +interface ISimpleService, ID> { + /** + * 日志组件。 + */ + var logger: ILogger + + /** + * 增加对象 + * + * @param root 增加对象命令 + */ + fun add(root: TAggregateRoot): DataMessage + + /** + * 增加对象 + * + * @param roots 增加对象命令列表 + */ + fun add(roots: List) + + /** + * 更新对象 + * + * @param id 对象ID + * @param root 更新对象命令 + */ + fun update(id: ID, root: TAggregateRoot) + + /** + * 批量更新对象 + * + * @param roots 更新对象命令列表 + */ + fun update(roots: List) + + /** + * 增加对象 + * @param id 对象ID + */ + fun remove(id: ID) +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt deleted file mode 100644 index a0f6f4b..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/LazyService.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.synebula.gaea.domain.service - -import com.synebula.gaea.data.IObjectConverter -import com.synebula.gaea.data.message.DataMessage -import com.synebula.gaea.data.message.Message -import com.synebula.gaea.domain.model.IAggregateRoot -import com.synebula.gaea.domain.repository.IRepository -import com.synebula.gaea.log.ILogger - - -/** - * 依赖了IRepository仓储借口的服务实现类 Service - * 该类依赖仓储接口 @see IRepository, 需要显式提供聚合根的class对象 - * - * @param repository 仓储对象 - * @param clazz 聚合根类对象 - * @param logger 日志组件 - * @author alex - * @version 0.1 - * @since 2020-05-17 - */ -open class LazyService, TKey>( - protected open var clazz: Class, - protected open var repository: IRepository, - override var logger: ILogger -) : ILazyService { - - /** - * 删除对象前执行监听器。 - */ - protected val beforeRemoveListeners = mutableMapOf Message>() - - /** - * 添加一个删除对象前执行监听器。 - * @param key 监听器标志。 - * @param func 监听方法。 - */ - override fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) { - this.beforeRemoveListeners[key] = func - } - - /** - * 移除一个删除对象前执行监听器。 - * @param key 监听器标志。 - */ - override fun removeBeforeRemoveListener(key: String) { - this.beforeRemoveListeners.remove(key) - } - - override fun add(root: TAggregateRoot): DataMessage { - val msg = DataMessage() - this.repository.add(root, this.clazz) - msg.data = root.id - return msg - } - - override fun update(id: TKey, root: TAggregateRoot) { - root.id = id - this.repository.update(root, this.clazz) - } - - override fun remove(id: TKey) { - val functions = this.beforeRemoveListeners.values - var msg: Message - for (func in functions) { - msg = func(id) - if (!msg.success()) { - throw IllegalStateException(msg.message) - } - } - this.repository.remove(id, this.clazz) - } -} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt index c68a1f1..27b8021 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/Service.kt @@ -1,78 +1,96 @@ package com.synebula.gaea.domain.service -import com.synebula.gaea.data.IObjectConverter +import com.synebula.gaea.bus.IBus import com.synebula.gaea.data.message.DataMessage -import com.synebula.gaea.data.message.Message +import com.synebula.gaea.data.serialization.IObjectMapper +import com.synebula.gaea.domain.event.AfterRemoveEvent +import com.synebula.gaea.domain.event.BeforeRemoveEvent import com.synebula.gaea.domain.model.IAggregateRoot import com.synebula.gaea.domain.repository.IRepository -import com.synebula.gaea.log.ILogger +import com.synebula.gaea.ext.firstCharLowerCase +import javax.annotation.Resource /** - * 依赖了IRepository仓储借口的服务实现类 Service - * 该类依赖仓储接口 @see IRepository, 需要显式提供聚合根的class对象 + * 依赖了IRepository仓储借口的服务实现类 GenericsService + * 该类依赖仓储接口 @see IGenericsRepository, 需要显式提供聚合根的class对象 * - * @param repository 仓储对象 * @param clazz 聚合根类对象 - * @param converter 对象转换组件 - * @param logger 日志组件 + * @param repository 仓储对象 + * @param mapper 对象转换组件 * @author alex * @version 0.1 * @since 2020-05-17 */ -open class Service, TKey>( - protected open var clazz: Class, - protected open var repository: IRepository, - protected open var converter: IObjectConverter, - override var logger: ILogger -) : IService { +open class Service, ID>( + protected open var clazz: Class, + protected open var repository: IRepository, + protected open var mapper: IObjectMapper, +) : IService { + @Resource + protected open var bus: IBus? = null /** - * 删除对象前执行监听器。 + * 增加对象 + * + * @param command 增加对象命令 */ - protected val beforeRemoveListeners = mutableMapOf Message>() - - /** - * 添加一个删除对象前执行监听器。 - * @param key 监听器标志。 - * @param func 监听方法。 - */ - override fun addBeforeRemoveListener(key: String, func: (id: TKey) -> Message) { - this.beforeRemoveListeners[key] = func - } - - /** - * 移除一个删除对象前执行监听器。 - * @param key 监听器标志。 - */ - override fun removeBeforeRemoveListener(key: String) { - this.beforeRemoveListeners.remove(key) - } - - override fun add(command: ICommand): DataMessage { - val msg = DataMessage() - val root = this.convert(command) - this.repository.add(root, this.clazz) + override fun add(command: ICommand): DataMessage { + val msg = DataMessage() + val root = this.map(command) + this.repository.add(root) msg.data = root.id return msg } - override fun update(id: TKey, command: ICommand) { - val root = this.convert(command) - root.id = id - this.repository.update(root, this.clazz) + /** + * 增加对象 + * + * @param commands 增加对象命令列表 + */ + override fun add(commands: List) { + val roots = commands.map { this.map(it) } + this.repository.add(roots) } - override fun remove(id: TKey) { - val functions = this.beforeRemoveListeners.values - var msg: Message - for (func in functions) { - msg = func(id) - if (!msg.success()) { - throw IllegalStateException(msg.message) - } - } - this.repository.remove(id, this.clazz) + /** + * 更新对象 + * + * @param id 对象ID + * @param command 更新对象命令 + */ + override fun update(id: ID, command: ICommand) { + val root = this.map(command) + root.id = id + this.repository.update(root) + } + + /** + * 批量更新对象 + * + * @param commands 更新对象命令列表 + */ + override fun update(commands: List) { + val roots = commands.map { this.map(it) } + this.repository.update(roots) + } + + /** + * 增加对象 + * @param id 对象ID + */ + override fun remove(id: ID) { + val beforeRemoveEvent = BeforeRemoveEvent(id) + this.bus?.publish( + "${this.clazz.simpleName.firstCharLowerCase()}${BeforeRemoveEvent::class.java.simpleName}", + beforeRemoveEvent + ) + this.repository.remove(id) + val afterRemoveEvent = AfterRemoveEvent(id) + this.bus?.publish( + "${this.clazz.simpleName.firstCharLowerCase()}${AfterRemoveEvent::class.java.simpleName}", + afterRemoveEvent + ) } /** @@ -81,9 +99,9 @@ open class Service, TKey>( * @param command 需要转换的命令 * @return 聚合根 */ - protected open fun convert(command: ICommand): TAggregateRoot { + protected open fun map(command: ICommand): TRoot { try { - return converter.convert(command, this.clazz) + return mapper.deserialize(command, this.clazz) } catch (ex: Exception) { throw RuntimeException("command not match aggregate root", ex) } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/SimpleService.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/SimpleService.kt new file mode 100644 index 0000000..3c31c8e --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/domain/service/SimpleService.kt @@ -0,0 +1,59 @@ +package com.synebula.gaea.domain.service + +import com.synebula.gaea.data.message.DataMessage +import com.synebula.gaea.domain.model.IAggregateRoot +import com.synebula.gaea.domain.repository.IRepository +import com.synebula.gaea.log.ILogger + + +/** + * 依赖了IRepository仓储借口的服务实现类 GenericsService + * 该类依赖仓储接口 @see IGenericsRepository, 需要显式提供聚合根的class对象 + * + * @param repository 仓储对象 + * @param clazz 聚合根类对象 + * @param logger 日志组件 + * @author alex + * @version 0.1 + * @since 2020-05-17 + */ +open class SimpleService, ID>( + protected open var clazz: Class, + protected open var repository: IRepository, + override var logger: ILogger, +) : ISimpleService { + + override fun add(root: TAggregateRoot): DataMessage { + val msg = DataMessage() + this.repository.add(root) + msg.data = root.id + return msg + } + + override fun update(id: ID, root: TAggregateRoot) { + root.id = id + this.repository.update(root) + } + + override fun remove(id: ID) { + this.repository.remove(id) + } + + /** + * 增加对象 + * + * @param roots 增加对象命令列表 + */ + override fun add(roots: List) { + this.repository.add(roots) + } + + /** + * 批量更新对象 + * + * @param roots 更新对象命令列表 + */ + override fun update(roots: List) { + this.repository.update(roots) + } +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt deleted file mode 100644 index 59132cb..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEvent.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.synebula.gaea.event - -interface IEvent { -} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt deleted file mode 100644 index c0b6850..0000000 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/event/IEventBus.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.synebula.gaea.event - -interface IEventBus { - - /** - * 注册事件Listener - * @param obj Listener所在类 - */ - fun register(obj: Any) - - /** - * 取消注册事件Listener - * @param obj Listener所在类 - */ - fun unregister(obj: Any) - - /** - * 同步发布事件 - * @param event 事件 - */ - fun publish(event: IEvent) - - /** - * 异步发布事件 - * @param event 事件 - */ - fun publishAsync(event: IEvent) -} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt index 7aa60b5..90cbd6f 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassPath.kt @@ -34,10 +34,11 @@ object ClassPath { // 判断路径最后一个字符是不是"/",如果不是加上 path = if (path.lastIndexOf("/") != path.length - 1) "$path/" else path - var resources: Enumeration? = null + val resources: Enumeration? try { resources = ClassLoaderContext.get().getResources(path) - } catch (e: IOException) { + } catch (ex: IOException) { + throw RuntimeException(ex) } while (resources!!.hasMoreElements()) { diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt index 3b03ef0..2a6b2ed 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/io/scan/ClassScanner.kt @@ -30,14 +30,15 @@ class ClassScanner(private var packageName: String) { /** * 文件过滤器 */ - private val fileFilter = FileFilter { file -> file.isDirectory || file.name.endsWith(".class") || file.name.endsWith(".jar") } + private val fileFilter = + FileFilter { file -> file.isDirectory || file.name.endsWith(".class") || file.name.endsWith(".jar") } /** * 构造方法。 * * @param packageName 需要扫描的包名。 - * @param classFilter + * @param classFilter 过滤器 */ constructor(packageName: String, vararg classFilter: IClassFilter) : this(packageName) { this.classFilters = classFilter @@ -81,12 +82,15 @@ class ClassScanner(private var packageName: String) { val files = scanDirectory(realPath) for (file in files) { val fileName = file.toString() - if (fileName.contains(".jar") && !fileName.endsWith(".class")) + if (fileName.contains(".jar") && !fileName.endsWith(".class")) { scanJar(file) - else if (fileName.endsWith(".class")) - scanClass(realPath, file) + } +// else if (fileName.endsWith(".class")) { +// } + scanClass(realPath, file) } - } catch (e: UnsupportedEncodingException) { + } catch (ex: UnsupportedEncodingException) { + throw ex } } } @@ -145,6 +149,7 @@ class ClassScanner(private var packageName: String) { } jar.close() } catch (ex: Throwable) { + throw ex } } @@ -191,7 +196,7 @@ class ClassScanner(private var packageName: String) { } } } catch (ex: Throwable) { - + throw ex } } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/Level.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/Level.kt index 429ad23..e636cf5 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/Level.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/Level.kt @@ -2,25 +2,28 @@ package com.synebula.gaea.log /** * 日志等级 - * @author Looly */ enum class Level { /** * 'TRACE' log level. */ TRACE, + /** * 'DEBUG' log level. */ DEBUG, + /** * 'INFO' log level. */ INFO, + /** * 'WARN' log level. */ WARN, + /** * 'ERROR' log level. */ diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IDebugLogger.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IDebugLogger.kt index 5a1cbb1..ba80a59 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IDebugLogger.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IDebugLogger.kt @@ -3,7 +3,6 @@ package com.synebula.gaea.log.logger /** * DEBUG级别日志接口 * - * @author Looly */ interface IDebugLogger { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IErrorLogger.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IErrorLogger.kt index c561a55..5fb200c 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IErrorLogger.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IErrorLogger.kt @@ -2,7 +2,6 @@ package com.synebula.gaea.log.logger /** * ERROR级别日志接口 - * @author Looly */ interface IErrorLogger { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IInfoLogger.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IInfoLogger.kt index fed35bd..453ade2 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IInfoLogger.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IInfoLogger.kt @@ -2,7 +2,6 @@ package com.synebula.gaea.log.logger /** * INFO级别日志接口 - * @author Looly */ interface IInfoLogger { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/ITraceLogger.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/ITraceLogger.kt index ed29589..c4b89c8 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/ITraceLogger.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/ITraceLogger.kt @@ -3,7 +3,6 @@ package com.synebula.gaea.log.logger /** * TRACE级别日志接口 * - * @author Looly */ interface ITraceLogger { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IWarnLogger.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IWarnLogger.kt index 803aded..731e21a 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IWarnLogger.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/log/logger/IWarnLogger.kt @@ -2,7 +2,6 @@ package com.synebula.gaea.log.logger /** * WARN级别日志接口 - * @author Looly */ interface IWarnLogger { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt index debcd0a..1f2aa50 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQuery.kt @@ -5,22 +5,27 @@ package com.synebula.gaea.query * * @author alex */ -interface IQuery { +interface IQuery { + /** + * 仓储的视图类 + */ + var clazz: Class + /** * 根据Key获取对象。 * * @param id 对象Key。 * @return 视图结果 */ - fun get(id: TKey, clazz: Class): TView? + fun get(id: ID): TView? /** * 根据实体类条件查询所有符合条件记录 - * + *` * @param params 查询条件。 * @return 视图列表 */ - fun list(params: Map?, clazz: Class): List + fun list(params: Map?): List /** * 根据条件查询符合条件记录的数量 @@ -28,7 +33,7 @@ interface IQuery { * @param params 查询条件。 * @return 数量 */ - fun count(params: Map?, clazz: Class): Int + fun count(params: Map?): Int /** * 根据实体类条件查询所有符合条件记录(分页查询) @@ -36,7 +41,7 @@ interface IQuery { * @param params 分页条件 * @return 分页数据 */ - fun paging(params: Params, clazz: Class): Page + fun paging(params: Params): Page /** * 查询条件范围内数据。 @@ -45,5 +50,5 @@ interface IQuery { * * @return 视图列表 */ - fun range(field: String, params: List, clazz: Class): List + fun range(field: String, params: List): List } diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQueryFactory.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQueryFactory.kt new file mode 100644 index 0000000..f89d856 --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IQueryFactory.kt @@ -0,0 +1,18 @@ +package com.synebula.gaea.query + + +/** + * Query 工厂接口。 定义了Query的创建方法。 + */ +interface IQueryFactory { + + /** + * 创建原始类型的IQuery接口类型 + */ + fun createRawQuery(clazz: Class<*>): IQuery<*, *> + + /** + * 创建指定类型的IQuery接口类型 + */ + fun createQuery(clazz: Class): IQuery +} \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt new file mode 100644 index 0000000..05929ab --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/IUniversalQuery.kt @@ -0,0 +1,49 @@ +package com.synebula.gaea.query + +/** + * 查询基接口, 其中方法都指定了查询的视图类型。 + * + * @author alex + */ +interface IUniversalQuery { + /** + * 根据Key获取对象。 + * + * @param id 对象Key。 + * @return 视图结果 + */ + fun get(id: ID, clazz: Class): TView? + + /** + * 根据实体类条件查询所有符合条件记录 + * + * @param params 查询条件。 + * @return 视图列表 + */ + fun list(params: Map?, clazz: Class): List + + /** + * 根据条件查询符合条件记录的数量 + * + * @param params 查询条件。 + * @return 数量 + */ + fun count(params: Map?, clazz: Class): Int + + /** + * 根据实体类条件查询所有符合条件记录(分页查询) + * + * @param params 分页条件 + * @return 分页数据 + */ + fun paging(params: Params, clazz: Class): Page + + /** + * 查询条件范围内数据。 + * @param field 查询字段 + * @param params 查询条件 + * + * @return 视图列表 + */ + fun range(field: String, params: List, clazz: Class): List +} diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Operator.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt similarity index 92% rename from src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Operator.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt index e3ece94..523cf30 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Operator.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Operator.kt @@ -1,4 +1,4 @@ -package com.synebula.gaea.query.type +package com.synebula.gaea.query enum class Operator { /** diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Order.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Order.kt similarity index 84% rename from src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Order.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/query/Order.kt index 35cd05f..8fdeba1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/type/Order.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Order.kt @@ -1,4 +1,4 @@ -package com.synebula.gaea.query.type +package com.synebula.gaea.query /** * class OrderType diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt index 0362126..ed6ed3b 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Params.kt @@ -1,7 +1,5 @@ package com.synebula.gaea.query -import com.synebula.gaea.query.type.Order - /** * class 分页参数信息 * diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Table.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Table.kt similarity index 51% rename from src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Table.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/query/Table.kt index 00d7922..59431c1 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Table.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Table.kt @@ -1,3 +1,3 @@ -package com.synebula.gaea.query.annotation +package com.synebula.gaea.query annotation class Table(val name: String = "") \ No newline at end of file diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Where.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Where.kt similarity index 83% rename from src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Where.kt rename to src/gaea/src/main/kotlin/com/synebula/gaea/query/Where.kt index 8170cf3..1e06df3 100644 --- a/src/gaea/src/main/kotlin/com/synebula/gaea/query/annotation/Where.kt +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/query/Where.kt @@ -1,6 +1,4 @@ -package com.synebula.gaea.query.annotation - -import com.synebula.gaea.query.type.Operator +package com.synebula.gaea.query /** * 字段注解,规定字段的查询方式 diff --git a/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt b/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt new file mode 100644 index 0000000..95e1dce --- /dev/null +++ b/src/gaea/src/main/kotlin/com/synebula/gaea/reflect/Types.kt @@ -0,0 +1,28 @@ +package com.synebula.gaea.reflect + +import java.lang.reflect.ParameterizedType + +/** + * 获取类的所有父类型。 + */ +fun Class<*>.supertypes(): Set> { + val supertypes = mutableSetOf>() + supertypes.add(this) + if (this.interfaces.isNotEmpty()) { + supertypes.addAll(this.interfaces.map { it.supertypes() }.reduce { r, c -> + val all = r.toMutableSet() + all.addAll(c) + all + }) + } + if (this.superclass != null) + supertypes.addAll(this.superclass.supertypes()) + return supertypes +} + +fun Class<*>.getGenericInterface(interfaceClazz: Class<*>): ParameterizedType? { + val type = this.genericInterfaces.find { it.typeName.startsWith(interfaceClazz.typeName) } + return if (type == null) null + else type as ParameterizedType + +} diff --git a/src/gaea/src/test/kotlin/com/synebula/gaea/bus/BusTest.kt b/src/gaea/src/test/kotlin/com/synebula/gaea/bus/BusTest.kt new file mode 100644 index 0000000..da0a145 --- /dev/null +++ b/src/gaea/src/test/kotlin/com/synebula/gaea/bus/BusTest.kt @@ -0,0 +1,91 @@ +package com.synebula.gaea.bus + +import org.junit.Test +import java.util.concurrent.Executors + + +class BusTest { + + @Test + fun testBus() { + val bus: IBus = Bus() + val subscriber = TestSubscriber() + bus.register(subscriber) + bus.publish("Hello world") + bus.publish(subscriber) + bus.publish(1) + } + + @Test + fun testAsyncBus() { + val bus: IBus = Bus(Executors.newFixedThreadPool(10)) + val subscriber = TestSubscriber() + bus.register(subscriber, subscriber.javaClass.declaredMethods[0]) + bus.register(subscriber, subscriber.javaClass.declaredMethods[1]) + bus.publishAsync("Hello world") + bus.publishAsync(subscriber) + } + + @Test + fun testTopicBus() { + val bus: IBus = Bus() + val subscriber = TestTopicSubscriber() + bus.register(subscriber) + bus.publish("hello", "Hello world") + bus.publishAsync("whoami", subscriber) + } + + @Test + fun testAsyncTopicBus() { + val bus: IBus = Bus(Executors.newFixedThreadPool(10)) + val subscriber = TestTopicSubscriber() + val subscriber2 = TestTopicSubscriber2() + bus.register(subscriber) + bus.register(subscriber2) + bus.publishAsync("hello", "Hello world") + bus.publishAsync("whoami", subscriber) + + bus.unregister(subscriber2) + bus.publishAsync("hello", "Hello world") + bus.publishAsync("whoami", subscriber) + } + + internal class TestSubscriber { + @Subscribe + fun echo(name: String) { + println(name) + } + + @Subscribe + fun whoAmI(testSubscriber: TestSubscriber) { + println(testSubscriber.javaClass.name) + } + + } + + internal class TestTopicSubscriber { + @Subscribe(topics = ["hello"]) + fun echo(name: String) { + println(name) + } + + @Subscribe(topics = ["whoami"]) + fun whoAmI(testSubscriber: TestTopicSubscriber) { + println(testSubscriber.javaClass.name) + } + + } + + internal class TestTopicSubscriber2 { + @Subscribe(topics = ["hello"]) + fun echo(name: String) { + println("2 $name") + } + + @Subscribe(topics = ["whoami"]) + fun whoAmI(testSubscriber: TestTopicSubscriber) { + println("2 ${testSubscriber.javaClass.name}") + } + + } +} \ No newline at end of file