在學習Spring的時候,常聽到的兩個東西:IOC和DI。
IOC:控制反轉,將對象的生命週期交給Spring去維護,我們需要對象時從容器中拿。
DI:依賴注入,類與類之間的依賴關係也交給Spring去維護。例如:A依賴B,程序運行時Spring會幫我們自動注入B實例。
Spring的容器有兩大類:BeanFactory和ApplicationContext。
具體的內容可以參考筆者以前的博客,今天主要記錄如何自己實現簡單的ApplicationContext。
查看Spring的源碼會非常頭疼,Spring封裝了太多的類,設計固然優秀,但是小白看起來確實很喫力啊…
容器
Spring初始化容器有兩種方式:通過註解和xml配置文件。
即AnnotationConfigApplicationContext和ClassPathXmlApplicationContext。
有興趣的同學可以去看下源碼。
原理都差不多,無非就是解析註解和xml文件的區別。
今天通過註解的方式來實現。
分析
實現前先簡單分析一下過程,沒有思路是很難寫代碼的。
先總結一下需要用到的註解。
基本註解
- @ComponentScan
- @Component
- @Scope
- @Autowired
學過Spring肯定對這些註解很熟悉,現在我們要自己來實現這些註解。
爲了區別於Spring的註解,自定義註解都會加“My”前綴。
實現過程
- 編寫自己的註解
- 定義需要掃描的包路徑
- 將掃描的類進行實例化,放到容器中
- 將實例化的類進行加工(依賴注入)
- 客戶端獲取Bean時從容器中返回
自己實現
項目結構
自定義註解
首先定義需要用到的4個基本註解,然後再來實現功能。
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//組件類的掃描
public @interface MyCommentScan {
String value();
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//組件 掃描到會實例化到容器中
public @interface MyComment {
}
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//作用域 單例/多例
public @interface MyScope {
String value();
}
@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//依賴注入
public @interface MyAutoWrite {
}
指定掃描的包
/**
* @Description: 掃描com.ch.entity包下的類
*/
@MyCommentScan("com.ch.entity")
public class BeanConfig {
}
實體類
創建兩個實體類Person和Wife,且Person依賴Wife。
將其註冊爲組件,註冊到容器中,容器會幫我們自動做 依賴注入。
/**
* @Description:
*/
@MyComment
//默認單例,可配置
//@MyScope("prototype")
@Getter
@Setter
public class Person {
private String name;
private int age;
//自動注入Wife
@MyAutoWrite
Wife wife;
public void say() {
System.out.println("person...");
}
}
@MyComment
@Getter
@Setter
public class Wife {
public void say() {
System.out.println("wife...");
}
}
重頭戲
現在開始編寫核心實現。
BeanDefinition
編寫一個類,作用是對Bean進行定義描述。
這裏簡單點,只描述 作用域。
/**
* @Description: Bean的定義描述類
*/
@Data
public class BeanDefinition {
//默認 單例
private boolean single = true;
//是否多例
private boolean prototype = false;
public boolean isSingle(){
return !prototype;
}
}
AppContext
這裏實現了所有的邏輯,包括對類進行掃描,註冊到容器,對類進行依賴注入等…。
/**
* @Description: 基於註解的 應用上下文
*/
public class AppContext {
//Bean的配置類,基於該類初始化容器
private Class configClass;
//掃描到的Bean容器
private Map<Class, Object> beanContainer;
//掃描的Bean的定義描述
private Map<Class, BeanDefinition> beanDefinitionMap;
public AppContext(Class configClass) {
this.configClass = configClass;
//初始化容器
init();
}
private void init() {
MyCommentScan configClassAnnotation = (MyCommentScan) configClass.getAnnotation(MyCommentScan.class);
if (configClassAnnotation == null) {
return;
}
String basePackage = configClassAnnotation.value();
if (StrUtil.isBlank(basePackage)) {
return;
}
Set<Class<?>> scanClass = ClassScaner.scanPackage(basePackage);
beanContainer = new HashMap<>(16);
beanDefinitionMap = new HashMap<>(16);
for (Class<?> c : scanClass) {
if (c.getAnnotation(MyComment.class) == null) {
continue;
}
try {
resolveBeanDefinition(c);
if (beanDefinitionMap.get(c).isSingle()) {
//單例的才直接實例化,多例的getBean時才實例化
beanContainer.put(c, c.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
//對bean進行加工
for (Map.Entry<Class, Object> entry : beanContainer.entrySet()) {
beanProcess(entry.getKey(), entry.getValue());
}
}
/**
* 解析Bean的定義
* @param c
*/
private void resolveBeanDefinition(Class c){
BeanDefinition definition = new BeanDefinition();
MyScope scope = (MyScope) c.getAnnotation(MyScope.class);
definition.setPrototype(scope != null && "prototype".equalsIgnoreCase(scope.value()));
beanDefinitionMap.put(c, definition);
}
/**
* 對bean進行加工 依賴注入
* @param c
* @return
*/
private void beanProcess(Class c,Object o){
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(MyAutoWrite.class) == null) {
continue;
}
//去容器中找 是否有其依賴的對象
if (!beanContainer.containsKey(field.getType())) {
continue;
}
Object depend = beanContainer.get(field.getType());
field.setAccessible(true);
try {
field.set(o, depend);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 從容器中獲取對象
* @param c
* @param <T>
* @return
*/
public <T> T getBean(Class<T> c) {
BeanDefinition beanDefinition = beanDefinitionMap.get(c);
if (beanDefinition == null) {
return null;
}
if (beanDefinition.isSingle()) {
return (T) beanContainer.get(c);
}
if (beanDefinition.isPrototype()) {
try {
T t = c.newInstance();
beanProcess(c, t);
return t;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
客戶端測試
單例
容器初始化時直接實例化對象並註冊到容器中,每次獲取時,直接從容器中返回,單實例。
public class Client {
public static void main(String[] args) {
AppContext context = new AppContext(BeanConfig.class);
Person bean = context.getBean(Person.class);
System.out.println(bean);
bean = context.getBean(Person.class);
System.out.println(bean);
/*bean.say();
bean.getWife().say();*/
}
}
輸出:
com.ch.entity.Person@87aac27
com.ch.entity.Person@87aac27
可以看到,獲取的都是同一個實例。
多例
默認是單例,給Person類加上註解:@MyScope(“prototype”) 即可。
給Person類加上@MyScope(“prototype”)註解後重新測試,
輸出如下:
com.ch.entity.Person@3e3abc88
com.ch.entity.Person@6ce253f1
每次getBean()獲取的都是新的實例。
依賴注入
測試容器是否會幫我們自動注入依賴。
public class Client {
public static void main(String[] args) {
AppContext context = new AppContext(BeanConfig.class);
Person bean = context.getBean(Person.class);
System.out.println(bean);
bean.say();
System.out.println(bean.getWife());
bean.getWife().say();
}
}
輸出:
com.ch.entity.Person@3e3abc88
person...
com.ch.entity.Wife@6ce253f1
wife...
可以看到,容器自動幫我們給Person實例自動注入了Wife實例。
尾巴
這應該是一個最簡單的容器了,只實現了最基礎的容器功能,但重要的是明白它的原理不是嘛。
畢竟,人生就是一個不斷追求“不惑”的過程。