1. 程式人生 > >IoC容器通過註解進行注入的簡單實現

IoC容器通過註解進行注入的簡單實現

控制反轉和和依賴注入是Spring中的重要概念。控制反轉是將當前bean所依賴的其他bean元件的例項化過程交由IoC容器來實現並由IoC容器注入到當前bean當中。之前對其實現有一個簡單的認識,即根據類名通過反射的方式載入所需要的bean元件。現在對其做一個通過註解方式進行注入簡單的實現。 首先定義Container介面,容器的主要功能即實現對bean的儲存,即實現bean的增刪查。所以定義其獲取、註冊、刪除、初始化功能。

/**
 * IoC容器介面
 * @author OrangeXXL
 *
 */
public interface Container {
	//根據class獲取Bean
    public <T> T getBean(Class<T> clazz);

    //使用name獲取Bean
    public <T> T getBeanByName(String name);

    //將Bean註冊到容器
    public Object registerBean(Object bean);

    //將class註冊到容器
    public Object registerBean(Class<?> clazz);

    //註冊帶名稱的bean到容器
    public Object registerBean(String name, Object bean);

    //刪除一個Bean
    public void remove(Class<?> clazz);

    //根據名稱刪除Bean
    public void removeByName(String name);

    //返回所有bean物件名稱
    public Set<String> getBeanNames();

    //初始化裝配
    public void initWired();
}

接下來是對容器的具體實現。容器實現的是在載入bean的過程中,對其進行掃描,找到有註解的欄位後對其所依賴的bean元件進行注入。首先我們需要儲存已載入的bean。所以通過map以<class name,bean object>的形式儲存bean。同時使用者會對bean進行命名,因此再建立一個map以<custom name, class name>的形式儲存bean名稱和類名稱的對應關係。再初始化當中載入當前bean。因此容器的實現如下。

public class SampleContainer implements Container {

	/**
	 * 建立map用來儲存所有beans
	 * key是className value為Bean物件
	 */
	private Map<String, Object> beans;
	
	/**
	 * 儲存Bean和className的關係
	 * 其中key是customName value是className
	 */
	private Map<String, String> beanKeys;

    public SampleContainer() {
    	this.beans = new ConcurrentHashMap<String, Object>();
    	this.beanKeys = new ConcurrentHashMap<String, String>();
    }

    @Override
    //根據class獲取Bean
    public <T> T getBean(Class<T> clazz) {
		String name = clazz.getName();
		Object object = beans.get(name);
		if(null != object){
			return (T) object;
		}
		return null;
    }
    
    @Override
    //根據name獲取bean
	public <T> T getBeanByName(String customName) {
		String className = beanKeys.get(customName);
		Object object = beans.get(className);
		if(null != object){
			return (T) object;
		}
		return null;
    }
    
    @Override
    //將Bean註冊到容器
	public Object registerBean(Object bean) {
		String customName = bean.getClass().getName();
		beanKeys.put(customName, customName);
		beans.put(customName, bean);
		return bean;
	}
	
    @Override
    //將class註冊到容器
	public Object registerBean(Class<?> clazz) {
		String className = clazz.getName();
		beanKeys.put(className, className);
		Object bean = ReflectUtil.newInstance(clazz);
		beans.put(className, bean);
		return bean;
	}

    @Override
    //註冊帶名稱的bean到容器
	public Object registerBean(String customName, Object bean) {
		String className = bean.getClass().getName();
		beanKeys.put(customName, className);
		beans.put(className, bean);
		return bean;
    }

    @Override
    //刪除一個Bean
	public void remove(Class<?> clazz) {
		String className = clazz.getName();
		if(null != className && !className.equals("")){
			beanKeys.remove(className);
			beans.remove(className);
		}
	}

    @Override
    //根據名稱刪除Bean
	public void removeByName(String customName) {
		String className = beanKeys.get(customName);
		if(null != className && !className.equals("")){
			beanKeys.remove(customName);
			beans.remove(className);
		}
    }
    
    @Override
    //返回所有bean物件名稱
	public Set<String> getBeanNames() {
		return beanKeys.keySet();
	}

    @Override
    //初始化裝配
	public void initWired() {
		Iterator<Entry<String, Object>> it = beans.entrySet().iterator();
        while (it.hasNext()) {
			Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
			System.out.println("[DEBUG] className=>"+entry.getKey()+"\n[DEBUG] beanName=>"+entry.getValue().getClass().getName());
			Object object = entry.getValue();
			injection(object);
		}
    }
    
    /**
     * 注入物件
     * @param obj
     * @return 
     */
    public void injection(Object obj){
    	try{
    		Field[] fields = obj.getClass().getDeclaredFields();
    		for(Field field : fields){
    			/**
    			 * 獲取Autowired註解標註的欄位
    			 * 即獲取需要注入的欄位
    			 */
    			
    			/**
    			 * getAnnotation方法是AccessibleObject類的方法。
    			 * 如果存在註解,則返回該元素的指定型別的註解,否則返回null
    			 */
    			Autowired autowired = field.getAnnotation(Autowired.class);
    			System.out.println("[DEBUG] print autowired field name"+autowired+"end");
    			if(null != autowired){
    				//進入判斷條件的即為存在Autowired型別註解的欄位
    				Object autowiredField = null;
    				String name = autowired.name();
    				System.out.println("[DEBUG] autowired field name"+"["+name+"]");
    				if(!name.equals("")){
    					//name不為空,則需要注入的就是bean,所以首先通過beanName拿到className
    					String className = beanKeys.get(name);
    					if(null != className && className.equals("")){
    						//className存在說明曾經注入過。則直接取出相應的例項即可
    						autowiredField = beans.get(className);
    					}
    					if(null == autowiredField){
    						throw new RuntimeException("Unable to load " + name);
    					}
    					    					
    				}else{
    					//name為空則需要注入的是class,
    					System.out.println("[DEBUG] name == null in this");
    					if(autowired.value() == Class.class){
    						autowiredField = recursiveAssembly(field.getType());
    					}else{
    						autowiredField = this.getBean(autowired.value());
    						if(autowiredField == null){
    							autowiredField = recursiveAssembly(autowired.value());
    						}
    					}
    				}
    				 if (null == autowiredField) {
 			            throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
 			        }
 			        //將獲取到的例項注入到相應欄位(autowiredField)中
 			        boolean accessible = field.isAccessible();
 			        field.setAccessible(true);
 			        field.set(obj, autowiredField);
 			        field.setAccessible(accessible);
    			}
    		}
    	}catch(SecurityException e) {
        	e.printStackTrace();
        } catch (IllegalArgumentException e) {
        	e.printStackTrace();
        }catch (IllegalAccessException e) {
        	e.printStackTrace();
        }
    }
    
    /**
     * 裝配class
     * @param clazz
     * @return
     */
    private Object recursiveAssembly(Class<?> clazz){
    	if(null != clazz){
    		return this.registerBean(clazz);
    	}
    	return null;
    }

}

這裡對initWired函式做一個說明。當我們需要對某個bean進行載入時,首先我們會先將該類註冊到容器當中。之後初始化時,我們迭代容器中所有的bean對其進行檢檢視是否存在需要注入的欄位。並進行注入操作。這裡迭代容器中的bean有一個很有意思的地方。舉個例子,我們需要載入的bean中有若干欄位需要被注入。第一次,我們將該bean註冊時候去檢查其欄位並載入它的依賴。當載入完成後,初始化操作還會繼續迭代(此時map中有了新的元素)。此時倘若被依賴的bean中同樣存在對別的bean的依賴則同樣會被載入進來。 但這一處我也有個疑問,在載入當前bean的時候map通過迭代器讀取當前Entry,之後我們對當前Value中儲存的bean進行注入。而注入時又會向map中新增元素。雖然concurrentHashMap支援迭代器的邊訪問,便修改的操作。concurrentHashMap通過segements細化粒度來提高其併發度,但是也存在機率新新增的元素和正在訪問的元素在一個segement當中。我認為同一個segement中是會阻塞的吧。但是看到有文章說支援邊訪問邊修改。我目前也不太清楚怎麼證實。如果有了解的老哥可以評論一下。 另外容器中註冊bean中有一個ReflectUtil其中主要有一個newInstance的過載方法。可以根據類名(全限定類名)和類物件建立bean。

/**
	 * 根據類名建立物件
	 * @param className
	 * @return
	 */
	public static Object newInstance(String className){
		Object obj = null;
		try{
			Class<?> clazz = Class.forName(className);
			obj = clazz.newInstance();
		}catch(Exception e){
			e.printStackTrace();
		}
		return obj;
	}
	
	/**
	 * 建立類的例項
	 * @param clazz
	 * @return
	 */
	public static Object newInstance(Class<?> clazz){
		try{
			return clazz.newInstance();
		}catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
    	return null;
	}

以上就是容器的基本實現。另外我們需要通過註解對依賴的bean進行標註。因此需要實現一個註解。註解有兩個預設引數,一個是類物件,另一個是類名。程式碼如下。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
	
	/**
	 * @return	要注入的類型別
	 */
    Class<?> value() default Class.class;
	
    /**
     * @return	bean的名稱
     */
    String name() default "";
    
}

最後我們建立兩個類Stu和OutputStu來進行驗證。

  • Stu是一個被依賴的bean
public class Stu {
	private String name;
	private int age;
	
	public void setName(String name){
		this.name = name;
	}
	public void setAge(int age){
		this.age = age;
	}
	public String getName(){
		return name;
	}
	public int getAge(){
		return age;
	}
	
	public void show(){
		System.out.println(name+"的年齡為:"+age);
	}
}

OutputStu是我們希望載入的bean

public class OutputStu {

	@Autowired
	private Stu s;
	
	public void setValue(String name, int age){
		s.setName(name);
		s.setAge(age);
	}
	public void printAns(){
		s.show();
	}
}

他們之間的關係可以將OutoutStu對應為開發中的Controller將Stu對應為Service 以下是測試類:

public class IoCTest {

	public static Container container = new SampleContainer();
	public static void testAutowired(){
		container.registerBean(OutputStu.class);
		container.initWired();
		
		OutputStu op = container.getBeanByName("com.wen.ioc.test.OutputStu");
		op.setValue("hezhiheng", 23);
		op.printAns();
		
	}
	
	public static void main(String[] args){
		testAutowired();
	}
}

首先我們載入OutputStu類,之後通過初始化裝配,載入依賴beanStu。之後我們獲取OutputStu物件進行相應操作。 至此一個通過註解進行注入的簡單IoC容器就實現了。 參考來自biezhi的github。如有紕漏歡迎指正交流。