很多人驚歎於 Solon 的注入能力,一個註解怎可注萬物???
一、註解注入器
Solon Ioc 的四大魔法之一:註解注入器(BeanInjector<T extends Annotation>
)。在掃描時,Solon 會檢查相關元件的欄位或者引數,上面有沒有註解?如果有註解,有沒有對應的注入器註冊過?如果有,則執行注入器。
1、什麼是註解?
註解一般也叫後設資料,是一種程式碼級別的說明性內容。編譯器在編譯時,可以藉助註解產生很多魔法效果;Solon Ioc 在執行時,也藉助註解產生了很多魔法效果。
其中,註解注入器便是 Solon Ioc 的四大魔法之一。
2、注入器介面是怎麼樣的?
@FunctionalInterface
public interface BeanInjector<T extends Annotation> {
void doInject(VarHolder vh, T anno);
}
其中:
- vh,用於接收變數資料
- anno,則是申明的註解
3、Solon Ioc 的注入器註冊介面
void beanInjectorAdd(Class<T> annoClz, BeanInjector<T> injector);
void beanInjectorAdd(Class<T> annoClz, Class<?> targetClz, BeanInjector<T> injector);
二、為什麼也可叫“虛空”注入器?
這個是因為,Solon 的注入是執行一個介面,而不是即定的內容。內容,可以是現成的,也可以是動態構建的。所以很“虛空”。
1、分解內建的的 @Inject
註解實現
@Inject
的簡單使用示例
//主入配置
@Component
public class DemoService{
@Inject("${track.url}")
String trackUrl;
@Inject("${track.db1}")
HikariDataSource trackDs;
}
//注入 bean
@Component
public class DemoService{
@Inject
private static TrackService trackService;
@Inject("userService")
private UserService userService;
}
注入器的能力實現剖析(簡單的示意實現,框架的實現比這個複雜)
context.beanInjectorAdd(Inject.class, (vh, anno) -> {
//申明:是否必須要注入?
vh.required(anno.required());
if (Utils.isEmpty(anno.value)) {
//沒有值,說明是 bean type 注入
vh.content().getBeanAsync(vh.type(), bean->{ //vh.content() 即 context。在“熱插撥”時可能會不同
vh.setValue(bean);
});
} else {
if(anno.value().startsWith("${")) {
//說明是配置注入
String val = vh.content().cfg().getByExpr(anno.value());
vh.setValue(val);
} else {
//說明是 bean name 注入
vh.content().getBeanAsync(anno.value(), bean->{
vh.setValue(bean);
});
}
}
});
2、“型別增強”注入器。魔法的升級!
Solon 內建的注入器,你不喜歡?
想換掉實現行不行?行!完全換掉程式碼太多,想為特定的型別增加註入行不行?也行!比如,我們設計了一個 EsMapper<T>
用於操作 Elasticsearch。然後可以自由的擴充套件:
public interface AuthorMapper extends EsMapper<Author> {
}
public interface CommentMapper extends EsMapper<Comment> {
}
public interface ContactMapper extends EsMapper<Contact> {
}
public interface DocumentMapper extends EsMapper<Document> {
}
估計還會想擴充套件更多的子類?“型別增強” 注入器在手,一切我有。
EsMapperFactory esMapperFactory;
context.beanInjectorAdd(Inject.class, EsMapper.class, (vh, anno) -> {
EsMapper mapper = esMapperFactory.create(vh.getType());
vh.setValue(mapper);
});
可以再借用容器的“快取”特性,同型別的注入效能就提高了:
EsMapperFactory esMapperFactory;
context.beanInjectorAdd(Inject.class, EsMapper.class, (vh, anno) -> {
EsMapper mapper = vh.context().getBean(vh.getType());
if (mapper == null) {
mapper = esMapperFactory.create(vh.type());
vh.context().wrapAndPut(mapper.getClass(), bean); //有可能被代理了,型別與 vh.getType() 不同
vh.context().wrapAndPut(vh.getType(), bean);
}
vh.setValue(mapper);
});
如果有“多源”的概念,我們還可以支援 @Inject("name")
:
EsMapperFactory esMapperFactory;
context.beanInjectorAdd(Inject.class, EsMapper.class, (vh, anno) -> {
EsMapper mapper = null;
if (Utils.isEmpty(anno.value)) {
//按型別取
mapper = vh.context().getBean(vh.getType());
} else {
//按名字取
mapper = vh.context().getBean(anno.value());
}
if (mapper == null) {
mapper = esMapperFactory.create(anno.value(), vh.type());
if (Utils.isEmpty(anno.value)) {
//按類注型注入;就按型別快取
vh.context().wrapAndPut(mapper.getClass(), bean); //有可能被代理了,型別與 vh.getType() 不同
vh.context().wrapAndPut(vh.getType(), bean);
} else {
//按類名字注入;就按名字快取
vh.context().wrapAndPut(anno.value(), bean);
}
}
vh.setValue(mapper);
});
現在我們可以用了(吃飯嘍,下班嘍!):
//主入配置
@Component
public class DemoService{
@Inject
DocumentMapper documentMapper;
@Inject("es2")
DocumentMapper documentMapper2;
}