實現簡單的Spring容器

在學習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”前綴。

實現過程

  1. 編寫自己的註解
  2. 定義需要掃描的包路徑
  3. 將掃描的類進行實例化,放到容器中
  4. 將實例化的類進行加工(依賴注入)
  5. 客戶端獲取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實例。

尾巴

這應該是一個最簡單的容器了,只實現了最基礎的容器功能,但重要的是明白它的原理不是嘛。

畢竟,人生就是一個不斷追求“不惑”的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章