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容器的根接口苫耸,可以用該接口接收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
注解揪荣,則默認是按照類型注入仗颈。
例子:
@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擴展的步驟:
定義一個或多個
ContainerCallback
和ObjectCallback
的實現(xiàn)類症脂,這些實現(xiàn)類需要有可訪問的默認構(gòu)造函數(shù)-
在
resources
目錄下創(chuàng)建一個名為byx-container-extension.properties
的文件诱篷,該文件聲明了需要導(dǎo)出的組件棕所,包含的鍵值如下:鍵值 含義 containerCallback
所有 ContainerCallback
的全限定類名,用,
分隔objectCallback
所有 ObjectCallback
的全限定類名迎吵,用,
分隔 將該項目打包成Jar包或Maven依賴击费,在主項目(即引入了byx-container-annotation的項目)中引入蔫巩,即可啟用自定義的回調(diào)組件