天天看點

手寫Spring之IOC基于注解動态建立對象

 上一篇部落格介紹了如何基于xml配置檔案在運作時建立執行個體對象,這篇部落格将介紹基于注解方式怎樣實作對象的建立。

 廢話不多說,直接上代碼。

 首先還是建立項目,由于這次不需要使用第三方的API,建立一個簡單的Java項目即可,依然還是JDK 7的環境下。

手寫Spring之IOC基于注解動态建立對象

第二步是建立屬于自己的注解。

手寫Spring之IOC基于注解動态建立對象

注解内容如下:

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**@Target 屬性用于注明此注解用在什麼位置,
 * ElementType.TYPE表示可用在類、接口、枚舉上等*/
@Target(ElementType.TYPE)
/**@Retention 屬性表示所定義的注解何時有效,
 * RetentionPolicy.RUNTIME表示在運作時有效*/
@Retention(RetentionPolicy.RUNTIME)
/**@interface 表示注解類型*/
public @interface MyComponent {
	/**為此注解定義scope屬性*/
	public String scope();
}
           

第三步是建立entity對象類型,用于在運作時建立其執行個體對象。

友善測試,該User類型分别建立兩個單例singleton和多例prototype的User類型。

手寫Spring之IOC基于注解動态建立對象

User類的内容如下:

注:PrototypeUser和SingletonUser的内容基本相同,僅類名和注解的scope屬性不同。

PrototypeUser的注解為:@MyComponent(scope="prototype")

SingletonUser的注解為:@MyComponent(scope="singleton")

package entity;

import annotation.MyComponent;

@MyComponent(scope="prototype")
public class PrototypeUser {
	private Integer id;
	private String name;
	private String password;
	public PrototypeUser() {
		System.out.println("無參構造方法執行");
	}
	public PrototypeUser(int id, String name, String password) {
		System.out.println("有參構造方法執行");
		this.id = id;
		this.name = name;
		this.password = password;
	}
	//setters和getters...
}
           

 主角登場,建立AnnotationConfigApplicationContext工廠類。

手寫Spring之IOC基于注解動态建立對象

該類的内容如下:

首先定義兩個Map容器用于存儲對象。

package applicationContext;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import annotation.MyComponent;

public class AnnotationConfigApplicationContext {
	/**此Map容器用于存儲類定義對象*/
	private Map<String, Class<?>> beanDefinationFacotry=new ConcurrentHashMap<>();
	/**此Map容器用于存儲單例對象*/
	private Map<String,Object> singletonbeanFactory=new ConcurrentHashMap<>();
}
           

定義有參構造方法:

/**有參構造方法,參數類型為指定要掃描加載的包名*/
public AnnotationConfigApplicationContext(String packageName) {
	/**掃描指定的包路徑*/
	scanPkg(packageName);
}
           

定義scanPkg方法:

/**
 * 掃描指定包,找到包中的類檔案。
 * 對于标準(類上有定義注解的)類檔案反射加載建立類定義對象并放入容器中
 */
private void scanPkg(final String pkg){
	//替換包名中的".",将包結構轉換為目錄結構
	String pkgDir=pkg.replaceAll("\\.", "/");
	//擷取目錄結構在類路徑中的位置(其中url中封裝了具體資源的路徑)
	URL url=getClass().getClassLoader().getResource(pkgDir);
	//基于這個路徑資源(url),建構一個檔案對象
	File file=new File(url.getFile());
	//擷取此目錄中指定标準(以".class"結尾)的檔案
	File[] fs=file.listFiles(new FileFilter() {
		@Override
		public boolean accept(File file) {
			//擷取檔案名
			String fName=file.getName();
			//判斷該檔案是否為目錄,如為目錄,遞歸進一步掃描其内部所有檔案
			if(file.isDirectory()){
				scanPkg(pkg+"."+fName);
			}else{
				//判定檔案的字尾是否為.class
				if(fName.endsWith(".class")){
					return true;
				}
			}
			return false;
		}
	});	
	//周遊所有符合标準的File檔案
	for(File f:fs){
		//擷取檔案名
		String fName=f.getName();
		//擷取去除.class之後的檔案名
		fName=fName.substring(0,fName.lastIndexOf("."));
		//将名字(類名,通常為大寫開頭)的第一個字母轉換小寫(用它作為key存儲工廠中)
		String key=String.valueOf(fName.charAt(0)).toLowerCase()+fName.substring(1);
		//建構一個類全名(包名.類名)
		String pkgCls=pkg+"."+fName;
		try{
			//通過反射建構類對象
			Class<?> c=Class.forName(pkgCls);
			//判定這個類上是否有MyComponent注解
			if(c.isAnnotationPresent(MyComponent.class)){
				//将類對象存儲到map容器中
				beanDefinationFacotry.put(key, c);
			}
		}catch(Exception e){
			throw new RuntimeException(e); 
		}
	}
}
           

以上是初始化方法,在建立AnnotationConfigApplicationContext對象時即會掃描指定的包路徑,并加載類定義對象到容器中。

接下來定義getBean方法,用于擷取工廠所建立的對象:

/**
 * 根據傳入的bean的id值擷取容器中的對象,類型為Object
 */
public Object getBean(String beanId){
	//根據傳入beanId擷取類對象
	Class<?> cls = beanDefinationFacotry.get(beanId);
	//根據類對象擷取其定義的注解
	MyComponent annotation = cls.getAnnotation(MyComponent.class);
	//擷取注解的scope屬性值
	String scope = annotation.scope();
	try {
		//如果scope等于singleton,建立單例對象
		if("singleton".equals(scope)){
			//判斷容器中是否已有該對象的執行個體,如果沒有,建立一個執行個體對象放到容器中
			if(singletonbeanFactory.get(beanId)==null){
				Object instance = cls.newInstance();
				singletonbeanFactory.put(beanId,instance);
			}
			//根據beanId擷取對象并傳回
			return singletonbeanFactory.get(beanId);
		}
		//如果scope等于prototype,則建立并傳回多例對象
		if("prototype".equals(scope)){
			return cls.newInstance();
		}
	} catch (InstantiationException e) {
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	}
	//如果遭遇異常,傳回null
	return null;
}
/**
 * 此為重載方法,根據傳入的class對象在内部進行強轉,
 * 傳回傳入的class對象的類型
 */
public <T>T getBean(String beanId, Class<T> c){
	return (T)getBean(beanId);
}
           

 定義close銷毀方法:  

/**
 * 銷毀方法,用于釋放資源
 */
public void close(){
	beanDefinationFacotry.clear();
	beanDefinationFacotry=null;
	singletonbeanFactory.clear();
	singletonbeanFactory=null;
}
           

 至此,該工廠類的内容全部完成。接下來寫測試類:

手寫Spring之IOC基于注解動态建立對象

該測試類的内容如下:

package springTest;

import applicationContext.AnnotationConfigApplicationContext;
import entity.PrototypeUser;
import entity.SingletonUser;

public class springIocTest {
	public static void main(String[] args) {
		//建立AnnotationConfigApplicationContext對象
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("entity");
		//僅使用key作為參數擷取對象,需要強轉
		SingletonUser singletonUser1=(SingletonUser) ctx.getBean("singletonUser");
		System.out.println("單例User對象:"+singletonUser1);
		//使用key和類對象作為參數擷取對象,無需強轉
		SingletonUser singletonUser2=ctx.getBean("singletonUser",SingletonUser.class);
		System.out.println("單例User對象:"+singletonUser2);
		//僅使用key作為參數擷取對象,需要強轉
		PrototypeUser prototypeUser1=(PrototypeUser) ctx.getBean("prototypeUser");
		System.out.println("多例User對象:"+prototypeUser1);
		//使用key和類對象作為參數擷取對象,無需強轉
		PrototypeUser prototypeUser2=ctx.getBean("prototypeUser",PrototypeUser.class);
		System.out.println("多例User對象:"+prototypeUser2);
		//銷毀資源
		ctx.close();
	}
}
           

 運作此測試類,控制台輸出結果如下:

手寫Spring之IOC基于注解動态建立對象

可以看到,在建立單例對象時,無參的構造方法隻調用了一次,并且兩次調用getBean方法擷取的對象是一緻的。

而在建立多例對象時,無參的構造方法被調用了兩次,兩次調用getBean所擷取的對象是不同的。

注:由于手寫SpringIOC隻是出于對Spring架構的了解,并非要寫一個可用的架構。

是以在代碼中省略了大量的對于參數的判斷和異常處理,免去代碼的備援,友善看官了解。