Solon Ioc 的魔法之註解注入器(也可叫虛空注入器)

带刺的坐椅發表於2024-10-25

很多人驚歎於 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;
}

相關文章