ByxContainerAnnotation——基于注解的輕量級IOC容器

ByxContainerAnnotation是一個模仿Spring IOC容器基于注解的輕量級IOC容器,支持構(gòu)造函數(shù)注入和字段注入傻盟,支持循環(huán)依賴處理和檢測娘赴,具有高可擴展的插件系統(tǒng)诽表。

項目地址:https://github.com/byx2000/byx-container-annotation

Maven引入

<repositories>
    <repository>
        <id>byx-maven-repo</id>
        <name>byx-maven-repo</name>
        <url>https://gitee.com/byx2000/maven-repo/raw/master/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>byx.ioc</groupId>
        <artifactId>byx-container-annotation</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

使用示例

通過一個簡單的例子來快速了解ByxContainerAnnotation的使用。

A.java:

package byx.test;

import byx.ioc.annotation.Autowired;
import byx.ioc.annotation.Autowired;
import byx.ioc.annotation.Component;

@Component
public class A {
    @Autowired
    private B b;

    public void f() {
        b.f();
    }
}

B.java:

package byx.test;

import byx.ioc.annotation.Component;

@Component
public class B {
    public void f() {
        System.out.println("hello!");
    }

    @Component
    public String info() {
        return "hi";
    }
}

main函數(shù):

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    A a = container.getObject(A.class);
    a.f();

    String info = container.getObject("info");
    System.out.println(info);
}

執(zhí)行main函數(shù)后糊秆,控制臺輸出結(jié)果:

hello!
hi

快速入門

如無特殊說明痘番,以下示例中的類均定義在byx.test包下汞舱。

AnnotationContainerFactory

該類是ContainerFactory接口的實現(xiàn)類昂芜,ContainerFactory是容器工廠泌神,用于從指定的地方創(chuàng)建IOC容器欢际。

AnnotationContainerFactory通過包掃描的方式創(chuàng)建IOC容器损趋,用法如下:

Container container = new AnnotationContainerFactory(/*包名或某個類的Class對象*/).create();

在構(gòu)造AnnotationContainerFactory時浑槽,需要傳入一個包名或者某個類的Class對象返帕。調(diào)用create方法時溉旋,該包及其子包下所有標(biāo)注了@Component的類都會被掃描观腊,掃描完成后返回一個Container實例。

Container

該接口是IOC容器的根接口苫耸,可以用該接口接收ContainerFactorycreate方法的返回值褪子,內(nèi)含方法如下:

方法 描述
void registerObject(String id, ObjectFactory factory) 向IOC容器注冊對象,如果id已存在呀枢,則拋出IdDuplicatedException
<T> T getObject(String id) 獲取容器中指定id的對象裙秋,如果id不存在則拋出IdNotFoundException
<T> T getObject(Class<T> type) 獲取容器中指定類型的對象摘刑,如果類型不存在則拋出TypeNotFoundException枷恕,如果存在多于一個指定類型的對象則拋出MultiTypeMatchException
Set<String> getObjectIds() 獲取容器中所有對象的id集合

用法如下:

Container container = new AnnotationContainerFactory(...).create();

// 獲取容器中類型為A的對象
A a = container.getObject(A.class);

// 獲取容器中id為msg的對象
String msg = container.getObject("msg");

@Component注解

@Component注解可以加在類上徐块,用于向IOC容器注冊對象蛹锰。在包掃描的過程中绰疤,只有標(biāo)注了@Component注解的類才會被掃描轻庆。

例子:

@Component
public class A {}

public class B {}


// 可以獲取到A對象
A a = container.getObject(A.class);

// 這條語句執(zhí)行會拋出TypeNotFoundException
// 因為class B沒有標(biāo)注@Component注解余爆,所以沒有被注冊到IOC容器中
B b = container.getObject(B.class);

@Component注解還可以加在方法上蛾方,用于向IOC容器注冊一個實例方法創(chuàng)建的對象上陕,注冊的id為方法名释簿。

例子:

@Component
public class A {
    // 注冊了一個id為msg的String
    @Component
    public String msg() {
        return "hello";
    }
}

// msg的值為hello
String msg = container.getObject("msg");

注意庶溶,如果某個方法被標(biāo)注了@Component,則該方法所屬的類也必須標(biāo)注@Component行疏,否則該方法不會被包掃描器掃描酿联。

@Id注解

@Id注解可以加在類上,與@Component配合使用采幌,用于指定注冊對象時所用的id休傍。

例子:

@Component @Id("a")
public class A {}

// 使用id獲取A對象
A a = container.getObject("a");

注意磨取,如果類上沒有標(biāo)注@Id忙厌,則該類注冊時的id為該類的全限定類名江咳。

@Id注解也可以加在方法上歼指,用于指定實例方法創(chuàng)建的對象的id踩身。

例子:

@Component
public class A {
    // 注冊了一個id為msg的String
    @Component @Id("msg")
    public String f() {
        return "hello";
    }
}

// hello
String msg = container.getObject("msg");

@Id注解還可以加在方法參數(shù)和字段上挟阻,請看構(gòu)造函數(shù)注入附鸽、方法參數(shù)注入@Autowire自動裝配

構(gòu)造函數(shù)注入

如果某類只有一個構(gòu)造函數(shù)(無參或有參)挪拟,則IOC容器在實例化該類的時候會調(diào)用該構(gòu)造函數(shù)玉组,并自動從容器中注入構(gòu)造函數(shù)的參數(shù)。

例子:

@Component
public class A {
    private final B b;

    // 通過構(gòu)造函數(shù)注入字段b
    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {}

// a被正確地構(gòu)造朝巫,其字段b也被正確地注入
A a = container.getObject(A.class);

在構(gòu)造函數(shù)的參數(shù)上可以使用@Id注解來指定注入的對象id劈猿。如果沒有標(biāo)注@Id注解揪荣,則默認是按照類型注入仗颈。

例子:

@Component
public class A {
    private final B b;

    // 通過構(gòu)造函數(shù)注入id為b1的對象
    public A(@Id("b1") B b) {
        this.b = b;
    }
}

public class B {}

@Component @Id("b1")
public class B1 extends B {}

@Component @Id("b2")
public class B2 extends B {}

// 此時a中的b注入的是B1的實例
A a = container.getObject(A.class);

對于有多個構(gòu)造函數(shù)的類挨决,需要使用@Autowire注解標(biāo)記實例化所用的構(gòu)造函數(shù)脖祈。

例子:

@Component
public class A {
    private Integer i;
    private String s;

    public A(Integer i) {
        this.i = i;
    }

    // 使用這個構(gòu)造函數(shù)來創(chuàng)建A對象
    @Autowire
    public A(String s) {
        this.s = s;
    }
}

@Component
public class B {
    @Component
    public Integer f() {
        return 123;
    }

    @Component
    public String g() {
        return "hello";
    }
}

// 使用帶String參數(shù)的構(gòu)造函數(shù)實例化的a
A a = container.getObject(A.class);

注意盖高,不允許同時在多個構(gòu)造函數(shù)上標(biāo)注@Autowire注解掏秩。

@Autowired自動裝配

@Autowired注解標(biāo)注在對象中的字段上蒙幻,用于直接注入對象的字段邮破。

例子:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {}

// a中的字段b被成功注入
A a = container.getObject(A.class);

默認情況下抒和,@Autowired按照類型注入摧莽。@Autowired也可以配合@Id一起使用镊辕,實現(xiàn)按照id注入。

例子:

@Component
public class A {
    // 注入id為b1的對象
    @Autowired @Id("b1")
    private B b;
}

public class B {}

@Component @Id("b1")
public class B1 extends B {}

@Component @Id("b2")
public class B2 extends B {}

// a中的字段b注入的是B1的對象
A a = container.getObject(A.class);

@Autowired還可標(biāo)注在構(gòu)造函數(shù)上石咬,請看構(gòu)造函數(shù)注入鬼悠。

方法參數(shù)注入

如果標(biāo)注了@Component的實例方法帶有參數(shù)列表亏娜,則這些參數(shù)也會從容器自動注入维贺,注入規(guī)則與構(gòu)造函數(shù)的參數(shù)注入相同。

例子:

@Component
public class A {
    // 該方法的所有參數(shù)都從容器中獲得
    @Component
    public String s(@Id("s1") String s1, @Id("s2") String s2) {
        return s1 + " " + s2;
    }
}

@Component
public class B {
    @Component
    public String s1() {
        return "hello";
    }

    @Component
    public String s2() {
        return "hi";
    }
}

// s的值為:hello hi
String s = container.getObject("s");

@Init注解

@Init注解用于指定對象的初始化方法群发,該方法在對象屬性填充后熟妓、創(chuàng)建代理對象前創(chuàng)建起愈。

例子:

@Component
public class A {
    public A() {
        System.out.println("constructor");
        State.state += "c";
    }

    @Autowired
    public void set1(String s) {
        System.out.println("setter 1");
        State.state += "s";
    }

    @Init
    public void init() {
        System.out.println("init");
        State.state += "i";
    }

    @Autowired
    public void set2(Integer i) {
        System.out.println("setter 2");
        State.state += "s";
    }
}

// 獲取a對象
A a = container.getObject(A.class);

輸出如下:

constructor
setter 1
setter 2
init

@Value注解

@Value注解用于向容器中注冊常量值抬虽。該注解標(biāo)注在某個被@Component標(biāo)注的類上阐污,可重復(fù)標(biāo)注笛辟。

@Component
// 注冊一個id為strVal手幢、值為hello的String類型的對象
@Value(id = "strVal", value = "hello")
// 注冊一個id為intVal忱详、值為123的int類型的對象
@Value(type = int.class, id = "intVal", value = "123")
// 注冊一個id和值都為hi的String類型的對象
@Value(value = "hi")
// 注冊一個id和值都為6.28的double類型的對象
@Value(type = double.class, value = "6.28")
public class A {
}

用戶可通過實現(xiàn)一個ValueConverter來注冊自定義類型:

public class User {
    private final Integer id;
    private final String username;
    private final String password;

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    // 省略getter和setter ...
}

@Component // 注意监透,該轉(zhuǎn)換器要在容器中注冊
public class UserConverter implements ValueConverter {
    @Override
    public Class<?> getType() {
        return User.class;
    }

    @Override
    public Object convert(String s) {
        // 將字符串轉(zhuǎn)換為User對象
        s = s.substring(5, s.length() - 1);
        System.out.println(s);
        String[] ps = s.split(",");
        System.out.println(Arrays.toString(ps));
        return new User(Integer.valueOf(ps[0]), ps[1].substring(1, ps[1].length() - 1), ps[2].substring(1, ps[2].length() - 1));
    }
}

// 注冊一個User對象
@Value(id = "user", type = User.class, value = "User(1001,'byx','123')")
public class A {
}

循環(huán)依賴

ByxContainerAnnotation支持各種循環(huán)依賴的處理和檢測才漆,以下是一些例子。

一個對象的循環(huán)依賴:

@Component
public class A {
    @Autowired
    private A a;
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // a被成功創(chuàng)建并初始化
    A a = container.getObject(A.class);
}

兩個對象的循環(huán)依賴:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // a和b都被成功創(chuàng)建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

構(gòu)造函數(shù)注入與字段注入混合的循環(huán)依賴:

@Component
public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    @Autowired
    private A a;
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // a和b都被成功創(chuàng)建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

三個對象的循環(huán)依賴:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private C c;
}

@Component
public class C {
    @Autowired
    private A a;
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // a、b阅虫、c都被成功創(chuàng)建并初始化
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
    C c = container.getObject(C.class);
}

無法解決的循環(huán)依賴:

@Component
public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private final A a;

    public B(A a) {
        this.a = a;
    }
}

public static void main(String[] args) {
    Container container = new AnnotationContainerFactory("byx.test").create();

    // 拋出CircularDependencyException異常
    A a = container.getObject(A.class);
    B b = container.getObject(B.class);
}

擴展

ByxContainer提供了一個靈活的插件系統(tǒng)不跟,你可以通過引入一些名稱為byx-container-extension-*的依賴來擴展ByxContainer的功能。當(dāng)然窝革,你也可以編寫自己的擴展虐译。

當(dāng)前已有的擴展

擴展 說明
byx-container-extension-aop 提供面向切面編程(AOP)的支持漆诽,包括前置增強(@Before)、后置增強(@After)兰英、環(huán)繞增強(@Around)供鸠、異常增強(@AfterThrowing)四種增強類型
byx-container-extension-transaction 提供聲明式事務(wù)的支持,包括對JdbcUtils@Transactional注解的支持

自己編寫擴展

AnnotationContainerFactory對外提供兩個擴展點:

  • ContainerCallback接口

    該接口定義如下:

    public interface ContainerCallback {
        void afterContainerInit(Container container);
    
        default int getOrder() {
            return 1;
        }
    }
    

    ContainerCallback類似于Spring的BeanFactoryPostProcessor家制。afterContainerInit方法會在包掃描結(jié)束后回調(diào),用戶可通過創(chuàng)建該接口的實現(xiàn)類來動態(tài)地向容器中注冊額外的組件鼻忠。

    當(dāng)存在多個ContainerCallback時帖蔓,它們調(diào)用的先后順序取決于getOrder返回的順序值,數(shù)字小的先執(zhí)行澈侠。

  • ObjectCallback接口

    該接口定義如下:

    public interface ObjectCallback{
        default void afterObjectInit(ObjectCallbackContext ctx) {
    
        }
    
        default Object afterObjectWrap(ObjectCallbackContext ctx) {
            return ctx.getObject();
        }
    
        default int getOrder() {
            return 1;
        }
    }
    

    ObjectCallback類似于Spring的BeanPostProcessor哨啃。afterObjectInit方法會在對象初始化后(即屬性填充后)回調(diào)写妥,afterObjectWrap方法會在代理對象創(chuàng)建后回調(diào)。

    當(dāng)存在多個ObjectCallback時祝峻,它們調(diào)用的先后順序取決于getOrder返回的順序值莱找,數(shù)字小的先執(zhí)行奥溺。

編寫B(tài)yxContainer擴展的步驟:

  1. 定義一個或多個ContainerCallbackObjectCallback的實現(xiàn)類症脂,這些實現(xiàn)類需要有可訪問的默認構(gòu)造函數(shù)

  2. resources目錄下創(chuàng)建一個名為byx-container-extension.properties的文件诱篷,該文件聲明了需要導(dǎo)出的組件棕所,包含的鍵值如下:

    鍵值 含義
    containerCallback 所有ContainerCallback的全限定類名,用,分隔
    objectCallback 所有ObjectCallback的全限定類名迎吵,用,分隔
  3. 將該項目打包成Jar包或Maven依賴击费,在主項目(即引入了byx-container-annotation的項目)中引入蔫巩,即可啟用自定義的回調(diào)組件

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市垃瞧,隨后出現(xiàn)的幾起案子个从,更是在濱河造成了極大的恐慌歪沃,老刑警劉巖绸罗,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珊蟀,死亡現(xiàn)場離奇詭異,居然都是意外死亡育灸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門磅崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸喻,“玉大人,你說我怎么就攤上這事愉适∥蹋” “怎么了惠爽?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵婚肆,是天一觀的道長较性。 經(jīng)常有香客問我讨越,道長,這世上最難降的妖魔是什么人弓? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任分别,我火速辦了婚禮徘层,結(jié)果婚禮上伺绽,老公的妹妹穿的比我還像新娘慈迈。我一直安慰自己痒留,他們只是感情好伸头,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布恤磷。 她就那樣靜靜地躺著,像睡著了一般魔策。 火紅的嫁衣襯著肌膚如雪代乃。 梳的紋絲不亂的頭發(fā)上仿粹,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天吭历,我揣著相機與錄音晌区,去河邊找鬼。 笑死昌罩,一個胖子當(dāng)著我的面吹牛灾馒,可吹牛的內(nèi)容都是我干的睬罗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羡滑!你這毒婦竟也來了啄栓?” 一聲冷哼從身側(cè)響起也祠,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤堪旧,失蹤者是張志新(化名)和其女友劉穎淳梦,沒想到半個月后爆袍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陨囊,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蜘醋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年压语,在試婚紗的時候發(fā)現(xiàn)自己被綠了胎食。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡训桶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躁锡,到底是詐尸還是另有隱情映之,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布蜡坊,位于F島的核電站杠输,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秕衙。R本人自食惡果不足惜蠢甲,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望据忘。 院中可真熱鬧鹦牛,春花似錦、人聲如沸勇吊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汉规。三九已至晶伦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間近忙,已是汗流浹背未辆。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工拙友, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遗契,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓逼蒙,卻偏偏與公主長得像顶考,于是被迫代替她去往敵國和親蹈胡。 傳聞我的和親對象是個殘疾皇子却汉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容