上一篇部落格介紹了如何基于xml配置檔案在運作時建立執行個體對象,這篇部落格将介紹基于注解方式怎樣實作對象的建立。
廢話不多說,直接上代碼。
首先還是建立項目,由于這次不需要使用第三方的API,建立一個簡單的Java項目即可,依然還是JDK 7的環境下。
第二步是建立屬于自己的注解。
注解内容如下:
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類型。
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工廠類。
該類的内容如下:
首先定義兩個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;
}
至此,該工廠類的内容全部完成。接下來寫測試類:
該測試類的内容如下:
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();
}
}
運作此測試類,控制台輸出結果如下:
可以看到,在建立單例對象時,無參的構造方法隻調用了一次,并且兩次調用getBean方法擷取的對象是一緻的。
而在建立多例對象時,無參的構造方法被調用了兩次,兩次調用getBean所擷取的對象是不同的。
注:由于手寫SpringIOC隻是出于對Spring架構的了解,并非要寫一個可用的架構。
是以在代碼中省略了大量的對于參數的判斷和異常處理,免去代碼的備援,友善看官了解。