1. 程式人生 > >自己動手寫一個ioc容器

自己動手寫一個ioc容器

       控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中,我們常用的spring框架核心就是一個ioc容器。

1.首先定義一個ioc的介面

package pers.dwl.sardine.ioc;

/**
 * ioc 容器介面
 * @author dwl
 *
 */
public interface Ioc {

    /**
     * 獲取例項物件
     * @param clazz
     * @return 例項物件
     */
    <T> T getEntity(Class<T> clazz);
	
    /**
     * ioc中該是否有例項物件
     * @param clazz
     * @return boolean 
     */
    boolean hasEntity(Class<?> clazz);
    
    /**
     * 將容器登出
     */
    void depose();

}

2.傳統的bean都是配置在xml中,不過現在也流行通過註解來注入,所以我們定義兩個註解。

@Inject 表示屬性需要由ioc來注入

package pers.dwl.sardine.ioc.annotation;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * 標識需要注入的屬性
 * @author dwl
 *
 */
@Documented
@Retention(RUNTIME)
@Target(FIELD)
public @interface Inject {
	
	Class<?> value() default String.class;

}

@IocBean表示該類交給ioc管理

package pers.dwl.sardine.ioc.annotation;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * 定義bean類 -標識該類交給IOC管理
 * @author dwl
 *
 */
@Documented
@Retention(RUNTIME)
@Target(TYPE)
public @interface IocBean {

}

 3.實現ioc介面,完成依賴注入的功能

package pers.dwl.sardine.ioc.impl;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import pers.dwl.sardine.ioc.Ioc;
import pers.dwl.sardine.ioc.annotation.Inject;
import pers.dwl.sardine.ioc.annotation.IocBean;
import pers.dwl.sardine.util.ArrayUtil;
import pers.dwl.sardine.util.ClassHelper;

/**
 * ioc實現類
 * @author dwl
 *
 */
public class SineIoc implements Ioc {
	
	private static final Object lock = new Object();
	
	/**
	 * 例項物件集合-未完成注入
	 */
	private static Map<Class<?>, Object> beanMap = new HashMap<Class<?>, Object>();
	
	/**
	 * 例項物件集合-已完成注入(第一次呼叫時注入)
	 */
	private static Map<Class<?>, Object> injectMap = new HashMap<Class<?>, Object>();
	
	//初始化
	static{
		//獲取基礎包下所有class
		List<Class<?>> classes = ClassHelper.getClassList();
		classes.forEach(clazz->{
			if(clazz.isAnnotationPresent(IocBean.class)){
				try {
					//加入例項物件集合
					beanMap.put(clazz, clazz.newInstance());
				} catch (InstantiationException | IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			
		});
	}
	

	@SuppressWarnings("unchecked")
	public <T> T getEntity(Class<T> clazz) {
		if(!injectMap.containsKey(clazz)){
			if(!beanMap.containsKey(clazz)){
				throw new RuntimeException("沒有該示例物件 - "+clazz);
			}else{
				synchronized (lock) {
					if(!injectMap.containsKey(clazz))
						setFiled(clazz);//依賴注入,加入快取	
				}
			}
		}
		return (T)injectMap.get(clazz);
	}

	public boolean hasEntity(Class<?> clazz) {
		return injectMap.containsKey(clazz);
	}
	
	public void depose() {
		beanMap.clear();
		injectMap.clear();
	}
	
	/**
	 * 給屬性賦值-依賴注入
	 * @param clazz
	 */
	public void setFiled(Class<?> clazz){
		 //獲取所有屬性
		 Field[] fields = clazz.getDeclaredFields();
		 //獲取例項物件
		 Object instance = beanMap.get(clazz);
		 //有屬性
		 if(!ArrayUtil.isEmpty(fields)){
			 for(Field field:fields){
				 //有待注入的屬性
				 if(field.isAnnotationPresent(Inject.class)){
					 //獲取屬性上的inject資訊
					 Inject inject = field.getAnnotation(Inject.class);
					 Class<?> type = inject.value();
					 //預設String.class,表示該屬性型別是類不是介面
					 if(type.isAssignableFrom(String.class)){
						 Class<?> implClass = field.getType();
						//為屬性物件例項進行依賴注入,不考慮迴圈依賴
						 setFiled(implClass);
						 //從injectMap獲取已經注入好的屬性物件例項
						 Object implBean =injectMap.get(implClass);
						 field.setAccessible(true);
						 try {
							 //為屬性賦值
							field.set(instance, implBean);
						} catch (IllegalArgumentException | IllegalAccessException e) {
							e.printStackTrace();
						}
					 }else{//為介面時value要宣告具體實現類
						 setFiled(type);//為屬性物件例項進行依賴注入,不考慮迴圈依賴
						//從injectMap獲取已經注入好的屬性物件例項
						 Object implBean =injectMap.get(type);
						 field.setAccessible(true);
						 try {
							 //為屬性賦值
							field.set(instance, implBean);
						} catch (IllegalArgumentException | IllegalAccessException e) {
							e.printStackTrace();
						}
					 }
				 }
			 }
			
		 }
		 injectMap.put(clazz, instance);//加入快取
		 
	}

}

     在獲取物件例項的時有需要注入的屬性會先例項該屬性物件,使用遞迴從下至上依次例項化,但是沒有考慮迴圈依賴的情況,如果程式碼中不注意寫出了迴圈依賴的話會產生死迴圈。

4.測試 我們寫個兩層依賴試一試效果。 MyService依賴MyService2   ,MyAction依賴MyService。

MyService2只有一個方法

package pers.dwl.sardine.ioc.test;

import pers.dwl.sardine.ioc.annotation.IocBean;

@IocBean
public class Myservice2 {
	
	public void doSome(){
		System.out.println("----------注入啦------------");
	}

}

 MyService依賴MyService2

package pers.dwl.sardine.ioc.test;

import pers.dwl.sardine.ioc.annotation.Inject;
import pers.dwl.sardine.ioc.annotation.IocBean;

@IocBean
public class MyService {
	
	@Inject
	private Myservice2 myservice2;
	
	public void doSome(){
		myservice2.doSome();
	}

}

MyAction依賴MyService

package pers.dwl.sardine.ioc.test;

import pers.dwl.sardine.ioc.annotation.Inject;
import pers.dwl.sardine.ioc.annotation.IocBean;

@IocBean
public class MyAction {
	
	@Inject
	private MyService myService;
	
	public void doSome(){
		myService.doSome();
	}

}

寫個main方法測試效果

package pers.dwl.sardine.ioc.test;

import pers.dwl.sardine.ioc.Ioc;
import pers.dwl.sardine.ioc.impl.SineIoc;

public class test {

	public static void main(String[] args) {
		Ioc ioc= new SineIoc();
		MyAction myBean = ioc.getEntity(MyAction.class);
		myBean.doSome();
	}

}

控制檯效果