Mirage Framework
介绍
Mirage 的核心框架,其中包含了以下基础功能
应用对象工厂
相关信息
mirage 的对象工厂,完全兼容 JSR 303的依赖注入规范
应用对象工厂,即 IOC(控制反转)+ DI(依赖注入)
即对象的创建,依赖注入的过程都将由对象工厂来完成,那么开发需要做的是在需要对象工厂管理的对象上使用指定注解来标识,这样在应用启动时,会自动扫描这些类并且加载到工厂中进行管理
那么如果定义一个工厂对象呢?,请继续往下看:
定义工厂对象
使用 @Component
注解标识在对象上,即可标识为一个工厂对象。
如果未显式的定义对象的名称,则默认为类名首字母小写驼峰命名法,如果需要显示的定义使用 @Component(value="对象名称")
示例:
@Slf4j
@Component(lazy = false)
class TestComponent : InitializingBean {
override fun init() {
log.info("testComponent 初始化成功:${this}")
}
}
@Slf4j
@Component(lazy = false)
public class TestComponent implements InitializingBean {
@Override
public void init() throws Exception {
log.info("testComponent 初始化成功:{}", this);
}
}
使用 @Configuration
注解标识在对象上,那么该对象将被标识为一个配置工厂对象,在该对象中使用 @Bean
标识方法来表示这是一个工厂对象方法。
如果未显示的定义对象的名称则使用方法名作为对象名称,如果需要显示的定义使用 @Bean(value="对象名称")
示例:
@Configuration
class TestConfiguration {
@Slf4j
class TestBean : InitializingBean {
override fun init() {
log.info("testBean 初始化成功:${this}")
}
}
@Bean(lazy = false)
fun testBean(): TestBean {
return TestBean()
}
}
@Configuration
public class TestConfiguration {
@Slf4j
public static class TestBean implements InitializingBean {
@Override
public void init() throws Exception {
log.info("testBean 初始化成功:{}", this);
}
}
@Bean(lazy = false)
public TestBean testBean() {
return new TestBean();
}
}
懒加载
默认情况下工厂对象都是懒加载的,如果需要定义非懒加载对象可以通过 @Component(lazy = false)
或者 @Bean(lazy = false)
来定义,非懒加载对象将在应用工厂所有的类型扫描完成后,进行实例化。
工厂对象扫描
默认情况下,优先扫描启动类的包路径及其子路径中所有定义的工厂对象,接着将加载 SPI 机制 中的自动装配对象,如果扫描到对象标识着 @ComponentScan
注解,则基于该注解定义的路径继续扫描。
@Import
在工厂的配置对象(即:@Configuration
注解标识的对象)上可以使用 @Import
注解来导入指定类,结合 ImportBeanDescriptionRegistrar
接口可以在运行时添加自定义的 BeanDescription
到对象工厂中,以下是一个示例:
导入普通组件示例:
@Component
class MirageComponent
@Configuration
@Import(MirageComponent::class)
class MirageConfiguration
@Component
public class MirageComponent{
}
@Import(MirageComponent.class)
@Configuration
public class MirageConfiguration {
}
导入BeanDescription的示例:
class MirageBeanDefinitionRegistrar : ImportBeanDescriptionRegistrar {
override fun registerBeanDescriptions(
annotatedMetadata: AnnotatedElementMetadata,
registry: BeanDescriptionRegistry,
beanKeyLoader: BeanKeyLoader) {
// @Import 定义在 @EnableDemo 注解上,目的是可以使用 annotatedMetadata 获取该注解的元数据,以达到作为配置注解的目的
// 可以使用 beanKeyLoader 来扫描指定目录或指定类的 BeanKey 作为 BeanDescription 的唯一键
// 使用 registry 注入 BeanDescription
}
}
@Target(CLASS)
@Retention(RUNTIME)
@Import(MirageBeanDefinitionRegistrar::class)
annotation class EnableDemo
@EnableDemo
@Configuration
public class MirageConfig
public class MirageBeanDefinitionRegistrar implements ImportBeanDescriptionRegistrar {
public MirageBeanDefinitionRegistrar(){}
public void registerBeanDescriptions(AnnotatedElementMetadata annotatedMetadata,
BeanDescriptionRegistry registry,
BeanKeyLoader beanKeyLoader) {
// @Import 定义在 @EnableDemo 注解上,目的是可以使用 annotatedMetadata 获取该注解的元数据,以达到作为配置注解的目的
// 可以使用 beanKeyLoader 来扫描指定目录或指定类的 BeanKey 作为 BeanDescription 的唯一键
// 使用 registry 注入 BeanDescription
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MirageBeanDefinitionRegistrar.class)
public @interface EnableDemo {
}
@EnableDemo
@Configuration
public class MirageConfig {
}
注意
需要注意的是:ImportBeanDescriptionRegistrar
接口的实现必须存在无参构造函数,将用来初始化该对象。
ImportBeanDescriptionRegistrar
接口的实现不会添加到对象工厂中,但是支持 Aware
接口的注入
对象生命周期
作用域
这里值得一提的是,被对象工厂管理的实例,它的作用域一定是单例的,对于 原型对象对象,即:工厂每次都会创建一个对象,创建好后将不会继续管理后续的生命周期,交由使用者进行管理。
如果需要定义原型对象则需要额外使用 @Prototype
注解标识
通过以上的生命周期图中可以看出在对象创建的过程中定义了一些回调方法,用于参与对象的创建。
实例化
对象的实例化是通过构造函数完成的,那么如果该对象只存在一个构造函数(无论是否私有),则使用它,如果该对象存在多个构造函数,那么存在一个判断逻辑,构造函数必须的public
的且必须标识 @Inject
或者 @Autowired
注解,如果匹配到多个那么将抛出例外!
循环依赖
请注意,当前版本不尝试解决任何形式的循环注入问题,即不可以 A实例是B实例的依赖性,同时B实例又是A实例的依赖项。这样的情况将导致获取该实例对象时抛出BeanException
异常!
依赖注入
依赖注入分为两种,对象属性注入和对象方法注入。
对象属性注入条件:
- 非静态属性
- 非 final 属性
- 属性上使用
@Inject
或@Autowired
注解
对象方法注入条件:
- 非静态方法
- public 标识的方法
- 方法是上使用
@Inject
或@Autowired
注解 - 非被重写的方法
依赖名称注入
默认情况下依赖注入使用类型注入,如果出现多个匹配的类型将抛出例外!
如果需要通过名称进行注入,则需要使用 @Named
注解定义注入对象的名称。
如果你想为指定对象标识一个类型,通过它来完成依赖注入,可以参考 jakarta.inject.Qualifier
,通过自定义注解的方式匹配工厂对象
初始化
如果对象实现了接口 InitializingBean
,那么在初始化这个生命周期时,将回调 InitializingBean#init
方法,可以在该方法中进行一些对象初始化逻辑
Aware
Aware
即感知,在对象创建的过程中,可以实现 Aware
定义一些子接口,用于获取特定的对象实例。
- BeanFactoryAware 对象工厂感知接口,可以获取当前应用的工厂对象
BeanFactory
- BeanNameAware 对象名称感知接口,可以获取当前对象实例在对象工厂中的名称
- EnvironmentAware 环境对象感知接口,可以获取当前应用的环境配置对象
Environment
- ApplicationContextAware 应用上下文感知接口,可以获取当前应用的上下文对象
ApplicationContext
BeanPostProcessor
BeanPostProcessor
即对象的后置处理器,在工厂对象实例初始化前和初始化后可以做一些特定的操作。
比如:环境配置注解 ConfigurationProperties
的实现就是依赖 BeanPostProcessor
完成的,在实例初始化前判断该对象类是否标识 ConfigurationProperties
注解,如果标识则进行配置属性绑定。
您也可以基于 BeanPostProcessor
的机制做一些特定的操作,比如生成代理对象?
AOP
需要注意的是:mirage 目前不支持aop
相关信息
BeanPostProcessor
的对象配置基于 SPI 机制 实现,也被视为对象工厂中的对象,在此我们建议 BeanPostProcessor
不要存在对象依赖,因为这将导致这些依赖对象的创建时机被提到 BeanPostProcessor
之前
工厂对象覆盖
mirage 使用约定大于配置的方式构建应用,那么在一些场景上,可能不太满足,这个时候需要将原有对象排除使用自定义的注入对象,为此我们提供了以下两种方式
条件覆盖
条件覆盖通过在定义工厂对象上标识 @Conditional
注解的方式来完成,@Conditional#value
属性为 Condition
接口类型,实现该接口定义工厂对象条件覆盖的具体逻辑即可。
需要注意的是 Condition
的实现对象,必须提供一个无参构造函数,否则将无法初始化该对象。
mirage 内部提供了一些 @Conditional
的实现,可以使用以下注解
@ConditionalOnClass
:当存在指定类时加载@ConditionalOnMissingBean
:当不存在指定的对象,那么将当前对象添加到对象工厂中
对象排除
对象排除一般来说不建议使用,我们更加建议通过条件覆盖的方式。
如果必须要使用对象排除,则需要使用 @ExcludeComponent
注解定义排除的对象类型或者对象名称,也可以通过 SPI 机制 排除指定类型
排除对象
需要注意的是:如果该对象已经完成了初始化,那么该对象就无法被排除
SPI 机制
mirage 的 SPI 机制通过在资源目录下定义 META-INF\mirageFactories.properties
配置文件的方式,以下是一份参考
# 自动装配的类
factories.autoConfiguration=\
cc.shacocloud.mirage.context.MirageVertxConfiguration,\
cc.shacocloud.mirage.context.MirageVertxProperties
# 排除的组件
factories.excludeComponent=
如上所示,通过全类名的方式定义,多个英文半角逗号分割
- factories.autoConfiguration:为自动装配的一些类
- factories.excludeComponent:为需要排除的对象类型
Vertx Verticle组件
mirage 自定义了 VerticleFactory
,所以 Vertx
的 Verticle
都可以使用组件的方式存在于 mirage 中,因此我们提供了 @DeployVerticle
注解用于定义 Verticle
组件,以下是一份示例:
@Slf4j
@DeployVerticle(instances = 2)
class TestVerticle : CoroutineVerticle() {
companion object {
val i = AtomicInteger(0)
}
private var num: Int = -1
override suspend fun start() {
num = i.incrementAndGet()
log.info("TestVerticle start $num")
}
override suspend fun stop() {
log.info("TestVerticle stop $num")
}
}
@Slf4j
@DeployVerticle(instances = 2)
public class TestVerticle extends AbstractVerticle {
private static final AtomicInteger i = new AtomicInteger(0);
private Integer num;
@Override
public void start() throws Exception {
num = i.incrementAndGet();
log.info("TestVerticle start {}", num);
}
@Override
public void stop() throws Exception {
log.info("TestVerticle stop {}", num);
}
}
以上的示例可以得到2个 TestVerticle
实例,其启动/关闭时打印的日志中都有对应的序号。
相关信息
@DeployVerticle
标识类都是原型对象,即每次获取都将创建一个新的对象
应用环境配置
系统的环境配置提供了2个内置的配置文件,路径都是相对于资源目录,文件不存在则不加载
- 环境配置文件:
environment.yaml
用于定义环境配置信息,可以在其中配置不同环境读取的配置文件 - 应用配置文件:
application.yaml
用于定义应用配置信息,内置配置文件。即:无论环境配置中是否定义,该文件都会加载
相关信息
系统支持3种文件格式,分别是 yaml,json,properties,所以上面的2个配置文件使用 environment.json
,environment.properties
,application.json
,application.properties
也可以
环境配置文件
在环境配置文件中可以定义不同环境下加载的配置文件,示例如下:
mirage:
environment:
# 激活的环境
active: ${mirage_environment_active:dev}
# 配置文件刷新间隔,默认是5秒
refresh: 5000
# 环境配置集
profiles:
# 环境名称和 mirage.environment.active 对应
- id: dev
# 环境存储信息
stores:
# classpath:// 表示类路径加载
- path: classpath://application-dev.yaml
# https:// 表示远端加载
- path: https://gitee.com/lulihu/mirage-demo/raw/master/mirage-kotlin-demo/src/main/resources/application-dev1.yaml
headers:
client: mirage demo
# file:// 表示文件系统加载,绝对路径
- path: file:///opt/app/application-dev2.yaml
# optional 如果为 true 表示为可选的,即:文件不存在则忽略,默认为 false 不存在将抛出例外
optional: true
- id: uat
stores:
- path: classpath://application-uat.yaml
- path: https://gitee.com/lulihu/mirage-demo/raw/master/mirage-kotlin-demo/src/main/resources/application-uat1.yaml
headers:
client: mirage demo
- path: file:///opt/app/application-uat2.yaml
optional: true
从以上配置信息示例可以看出,目前系统支持3种配置文件加载方式,分别是
- 类路径加载:使用
classpath://
作为路径前缀 - 文件系统加载:使用
file://
作为路径前缀 - 远端请求加载:使用
http://
或者https://
作为路径前缀,如果使用该方式可以通过headers
属性配置请求时携带的自定义头部信息
如果指定配置路径文件有可能不存在,那么可以将 optional
属性定义 true,表示为文件不存在则忽略,默认为 false 不存在将抛出例外
相关信息
在所有的配置文件信息中,字符串的内容都会被当做表达式内容进行表达式替换,比如:${mirage_environment_active:dev}
表示为 如果 mirage_environment_active 配置键的值存在则使用,否则使用 dev 作为值
配置的键可以使用中划线分割字符,例如 mirage.vertx.event-loop-pool-size
等效于 mirage.vertx.eventLoopPoolSize
配置文件加载顺序
配置的顺序非常重要, 因为它定义了覆盖顺序。对于冲突的key, 后声明的配置中心会覆盖之前的。我们举个例子。 我们有两个配置:
A
提供{a:value, b:1}
配置B
提供{a:value2, c:2}
配置
以 A,B 的顺序声明配置,最终配置应该为: {a:value2, b:1, c:2}
如果您将声明的顺序反过来(B,A),那么您会得到 {a:value, b:1, c:2}
配置注入
系统提供了一下注解,用于注入配置信息到指定的对象中
相关信息
环境配置每隔 mirage.environment.refresh
重新加载一次配置信息,如果配置发生了变更将发布应用事件 EnvironmentChangeEvent
,且所有的配置对象都将被重新注入新的值
@ConfigurationProperties
配置属性注解,在对象类上使用了该注解后,该对象的所有Set方法都将被视为配置属性的注入点。
示例:
@ConfigurationProperties(prefix = "mirage.demo")
class MirageDemoProperties {
var dev: String = ""
var dev1: String = ""
}
@Setter
@Getter
@NoArgsConstructor
@ConfigurationProperties(prefix = "mirage.demo")
public class MirageDemoProperties {
private String dev;
private String dev1;
}
@EnvValue
环境值注解,使用其定义在工厂对象的属性或者Set方法上,即可为这个工厂对象注入环境配置信息。
示例:
属性注入
@Component
class MirageDemoBean {
@EnvValue("\${mirage.demo.dev1}")
private var dev1: String = ""
}
@Component
public class MirageDemoBean {
@EnvValue("${mirage.demo.dev1}")
private String dev1;
}
方法注入
@Component
class MirageDemoBean {
private lateinit var dev1: String
@EnvValue("\${mirage.demo.dev1}")
fun setDev1(dev1: String) {
this.dev1 = dev1;
}
}
@Component
public class MirageDemoBean {
private String dev1;
@EnvValue("${mirage.demo.dev1}")
public void setDev1(String dev1){
this.dev1 = dev1;
}
}
注意
注意:set 方法必须满足以下几个条件
方法名称必须以 set 为前缀,且 set 的下一个字符大写
方法必须只有一个入参
Vertx 配置
通过应用环境配置机制可以基于MirageVertxProperties
对象配置 Vertx
对象的一些属性,以下是一部分配置示例:
mirage:
vertx:
eventLoopPoolSize: 2
workerPoolSize: 20
internalBlockingPoolSize: 20
blockedThreadCheckInterval: 1000
maxEventLoopExecuteTime: 2000
maxWorkerExecuteTime: 60000
应用事件系统
当应用运行到某个阶段事件,将会发出应用事件,工厂对象通过实现 ApplicationListener
来定义监听的事件,当该事件发生时将会触发 ApplicationListener#onApplicationEvent
方法。
示例:
@Slf4j
@Component
class MirageDemoEventBean : CoroutineApplicationListener<EnvironmentChangeEvent> {
override suspend fun doApplicationEvent(event: EnvironmentChangeEvent) {
log.info("配置发生变更...")
}
}
@Slf4j
@Component
public class MirageDemoEventBean implements ApplicationListener<EnvironmentChangeEvent> {
@Override
public Future<Void> onApplicationEvent(EnvironmentChangeEvent event) {
log.info("配置发生变更...");
return Future.succeededFuture();
}
}
自定义事件
示例:
// 定义自定义事件对象
class CustomEvent : ApplicationEvent {
}
// 发布事件
MirageHolder.publishEvent(CustomEvent())
// 定义自定义事件对象
public class CustomEvent implements ApplicationEvent {
}
// 发布事件
MirageHolder.publishEvent(new CustomEvent())
所有的事件对象必须继承 ApplicationEvent
接口,以表示自己是一个事件对象