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

ByxContainerAnnotation是一個模仿Spring IOC容器基于注解的輕量級IOC容器,支持構(gòu)造函數(shù)注入和字段注入,支持循環(huán)依賴處理和檢測,具有高可擴(kuò)展的插件系統(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注解,則默認(rèn)是按照類型注入。

例子:

@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);

默認(rèn)情況下,@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);
}

擴(kuò)展

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

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

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

自己編寫擴(kuò)展

AnnotationContainerFactory對外提供兩個擴(kuò)展點:

  • 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的BeanPostProcessorafterObjectInit方法會在對象初始化后(即屬性填充后)回調(diào),afterObjectWrap方法會在代理對象創(chuàng)建后回調(diào)。

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

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

  1. 定義一個或多個ContainerCallbackObjectCallback的實現(xiàn)類,這些實現(xiàn)類需要有可訪問的默認(rè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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容