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容器的根接口,可以用該接口接收ContainerFactory的create方法的返回值,內(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的BeanPostProcessor。afterObjectInit方法會在對象初始化后(即屬性填充后)回調(diào),afterObjectWrap方法會在代理對象創(chuàng)建后回調(diào)。當(dāng)存在多個
ObjectCallback時,它們調(diào)用的先后順序取決于getOrder返回的順序值,數(shù)字小的先執(zhí)行。
編寫B(tài)yxContainer擴(kuò)展的步驟:
定義一個或多個
ContainerCallback和ObjectCallback的實現(xiàn)類,這些實現(xiàn)類需要有可訪問的默認(rèn)構(gòu)造函數(shù)-
在
resources目錄下創(chuàng)建一個名為byx-container-extension.properties的文件,該文件聲明了需要導(dǎo)出的組件,包含的鍵值如下:鍵值 含義 containerCallback所有 ContainerCallback的全限定類名,用,分隔objectCallback所有 ObjectCallback的全限定類名,用,分隔 將該項目打包成Jar包或Maven依賴,在主項目(即引入了byx-container-annotation的項目)中引入,即可啟用自定義的回調(diào)組件