上一篇博客介紹瞭如何基於xml配置文件在運行時建立實例對象,這篇博客將介紹基於註解方式怎樣實現對象的建立。java
廢話很少說,直接上代碼。spring
首先仍是建立項目,因爲此次不須要使用第三方的API,建立一個簡單的Java項目便可,依然仍是JDK7的環境下。app
第二步是建立屬於本身的註解。框架
MyComponent註解內容以下:ide
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(); }
MyValue註解內容以下:測試
package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyValue { /**定義value屬性*/ public String value(); }
第三步是建立entity對象類型,用於在運行時建立其實例對象。url
方便測試,該User類型分別建立兩個單例singleton和多例prototype的User類型。spa
SingletonUser類的內容以下:.net
package entity; import annotation.MyComponent; import annotation.MyValue; @MyComponent(scope="singleton") public class SingletonUser { @MyValue("1") private Integer id; @MyValue("zhangsan") private String name; @MyValue("zhangsan") private String password; public SingletonUser() { System.out.println("無參構造方法執行"); } //setters和getters... }
PrototypeUser類的內容以下:prototype
package entity; import annotation.MyComponent; import annotation.MyValue; @MyComponent(scope="prototype") public class PrototypeUser { @MyValue("2") private Integer id; @MyValue("lisi") private String name; @MyValue("lisi") private String password; public PrototypeUser() { System.out.println("無參構造方法執行"); } //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(); setFieldValues(cls,instance); singletonbeanFactory.put(beanId,instance); } //根據beanId獲取對象並返回 return singletonbeanFactory.get(beanId); } //若是scope等於prototype,則建立並返回多例對象 if("prototype".equals(scope)){ Object instance = cls.newInstance(); setFieldValues(cls,instance); return instance; } //目前僅支持單例和多例兩種建立對象的方式 } 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); }
在獲取對象時須要爲其成員屬性賦值,調用了setFieldValue方法,所以定義該方法:
/** * 此方法用於爲對象的屬性賦值 * 內部是經過獲取成員屬性上註解的值,在轉換爲類型以後,經過反射爲對象賦值 * @param cls 類定義對象 * @param obj 要爲其賦值的實例對象 */ public void setFieldValues(Class<?> cls,Object obj){ //獲取類中全部的成員屬性 Field[] fields = cls.getDeclaredFields(); //遍歷全部屬性 for (Field field : fields) { //若是此屬性有MyValue註解修飾,對其進行操做 if(field.isAnnotationPresent(MyValue.class)){ //獲取屬性名 String fieldName = field.getName(); //獲取註解中的值 String value = field.getAnnotation(MyValue.class).value(); //獲取屬性所定義的類型 String type = field.getType().getName(); //將屬性名改成以大寫字母開頭,如:id改成ID,name改成Name fieldName = String.valueOf(fieldName.charAt(0)).toUpperCase()+fieldName.substring(1); //set方法名稱,如:setId,setName... String setterName = "set" + fieldName; try { //根據方法名稱和參數類型獲取對應的set方法對象 Method method = cls.getDeclaredMethod(setterName, field.getType()); //判斷屬性類型,如類型不一致,則轉換類型後調用set方法爲屬性賦值 if("java.lang.Integer".equals(type) || "int".equals(type)){ int intValue = Integer.valueOf(value); method.invoke(obj, intValue); } else if("java.lang.String".equals(type)){ method.invoke(obj, value); } //做爲測試,僅判斷Integer和String類型,其它類型同理 } catch (NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } }
定義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所獲取的對象是不一樣的。
若是須要看對象的屬性是否注入成功,能夠在兩個User類中增長toSrting方法
@Override public String toString() { return "SingletonUser [id=" + id + ", name=" + name + ", password=" + password + "]"; }
再次執行程序,結果以下:
從控制檯的輸處結果中能夠看到,所獲取的每一個對象都已經有值。注意:這種爲對象注入屬性值的方式耦合度較高,可根據狀況使用。
寫在最後:因爲手寫SpringIOC只是出於對Spring框架的理解,並不是要寫一個可用的框架。所以在代碼中省略了大量的對於參數的判斷和異常處理,免去代碼的冗餘,方便看官理解。高手請勿吐槽啊,如有問題或建議,歡迎留言指正。