javascript
利用递归,反射,注解等,手写Spring Ioc和Di 底层(喷倒面试官
在我們現(xiàn)在項目中Spring框架是目前各大公司必不可少的技術(shù),而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底層是如何工作的,而一個開發(fā)人員知道他的源碼,底層工作原理,對于我們對項目的理解是有非常大的幫助的,有可能工作了兩三年的中級工程師,乃至四五年的,只知其然,卻不知其所以然。我的一個盆友,今年年初以實習生的身份去北京面試 ,面試官讓我的朋友說Spring源碼,作為一個實習生,就要去知道Spring的源碼。雖然我們可以不用知道,也可以做項目,但他會成為我們面試結(jié)果的絆腳石,
而各個公司面試喜歡提問都是Spring的原理,底層,源碼。而看了今天的文章,再去面試我們就不用怕了。
當面試官問我們,了解Spring嗎,我們回答,了解而且對Ioc還很深入,這時候,面試官的興趣一下子就被你勾引了,心想,一個小菜鳥居然敢說聽深入,他必會讓你講,殊不知他已經(jīng)上當了,這時候我們給他來一手手寫SpringIoc和Di的工作原理,而把這些都寫完,解釋完,怎么的也得半個多小時過去了,而面試官不會去花太多時間去面試一個人,甚至有可能你把這個問題說完,隨便問幾句,直接就錄用你了,(因為面試官都有可能都知道他底層的工作原理)這樣即使面試官百分之八十就會高看我們一眼,起始的薪資呢,也不會低。
進入正題:一張圖就可以看出我們的整體結(jié)構(gòu)。是用MVC模式,去引出我們的底層代碼。
Spring Ioc
首先創(chuàng)建pojo,dao層,service層和controller層
pojo:定義兩個屬性,加上getset和toString方法
package cn.com.wx.pojo.User; public class User {private Integer id;private String name;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User [id=" + id + ", name=" + name + "]";}}dao層:這里我們要模仿數(shù)據(jù)庫的層:寫一個selectone的方法,用以調(diào)用
package cn.com.wx.Dao; import cn.com.wx.pojo.User.User; public class UserDao {public User selectone(Integer id,String name) {User user =new User();user.setId(id);user.setName(name);return user;} }最后加上Service層和Controller層
package cn.com.wx.Service; import cn.com.wx.Dao.UserDao; import cn.com.wx.pojo.User.User; public class UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao = userDao;} public User get(Integer id,String name) {User user=userDao.selectone(id, name);return user;} } package cn.com.wx.Controller; import cn.com.wx.Service.UserService; import cn.com.wx.pojo.User.User; public class UserController {private UserService userService ;public void setUserService(UserService userService) {this.userService = userService;} public User get(Integer id, String name) {User user =userService.get(id, name);return user;} }接下里我們寫容器的創(chuàng)建過程,遍歷目錄,獲取所有的class文件,這里我們使用IO和文件Api,利用遞歸的方式把文件拿出來,
package cn.com.wx.Files; import java.io.File; import java.util.List; public class JUnitFile {public static List<String> getFileName(String Dir, List<String> list) {File file = new File(Dir);//拿到目錄File[] f = file.listFiles();//封裝在一個數(shù)組里面for (File name : f) {if(name.isDirectory()) {//API isDirectory:判斷是否為文件,如果是文件夾,證明文件夾里邊還有文件,利用遞歸的方式進入文件夾getFileName(name.getAbsolutePath(), list);}else {//不是文件夾就是文件,把文件的路徑放到集合中l(wèi)ist.add(name.getAbsolutePath());}}return list;} }這時我們創(chuàng)建一個測試類來驗證下我們的文件是否拿到
package cn.com.wx.test; import java.util.ArrayList; import java.util.List; import cn.com.wx.Files.JUnitFile; public class Test {public static void main(String[] args) {List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin";JUnitFile.getFileName(Dir, list);for (String string : list) {System.out.println(string);}} }輸出結(jié)果:這樣我們bin目錄下的所有的class文件就都拿到了
C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Controller\UserController.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Dao\UserDao.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Files\JUnitFile.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\pojo\User\User.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Service\UserService.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\test\Test.class這時候拿一個路徑過來分析:
C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\test\Test.class 我們整整需要的是包名加上文件名: cn\com\wx\test\Test 這樣就需要我們干掉bin目錄之前的目錄和類的后綴.class 這個就非常簡單了,我們可以使用基礎(chǔ)的Api去截取字符串: package cn.com.wx.Files; import java.util.List; public class Covers {// C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\test\Test.class// C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\// 看著上邊的目錄我們來分析問題:首選我們已經(jīng)把標準目錄拿到了,放到一個list集合中,集合作為參數(shù)傳到我的getScanName方法中// 這樣我只需要一個需要截取的部分的字符串,這里我用scanDir來表示public static List<String> getScanName(String scanDir, List<String> list) {// scanDir=C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\// 下一步分析:假定我們截取完字符串,那我們是不是還要把它放回去,放回去就要知道我們的角標,而普通的foreach循環(huán)滿足不了,我們用普通的for循環(huán)來替代for (int i = 0; i < list.size(); i++) {String strname = list.get(i);// 逐個去拿他的目錄// 先替換雖有的斜杠strname = strname.replace("\\", "/");scanDir = scanDir.replace("\\", "/");// 干掉scanDir部分strname = strname.replace(scanDir, "");// 從后邊拿到點出現(xiàn)的位置int pos = strname.lastIndexOf(".");strname = strname.substring(0, pos);// 這樣我們就拿到了這樣的格式// cn/com/wx/test/Test// 最后再把斜杠替換成點strname = strname.replace("/", ".");// 最后再放回集合中l(wèi)ist.set(i, strname);}return list;} }然后我們在來測試:還是用剛才的測試類:
package cn.com.wx.test; import java.util.ArrayList; import java.util.List; import cn.com.wx.Files.Covers; import cn.com.wx.Files.JUnitFile; public class Test {public static void main(String[] args) {List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin\\";JUnitFile.getFileName(Dir, list);for (String string : list) {System.out.println(string);}Covers.getScanName(Dir, list);for (String string : list) {System.out.println(string);}} }運行結(jié)果:
C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Controller\UserController.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Dao\UserDao.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Files\Covers.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Files\JUnitFile.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\pojo\User\User.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\Service\UserService.class C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\test\Test.class cn.com.wx.Controller.UserController cn.com.wx.Dao.UserDao cn.com.wx.Files.Covers cn.com.wx.Files.JUnitFile cn.com.wx.pojo.User.User cn.com.wx.Service.UserService cn.com.wx.test.Test這里我們有一處出現(xiàn)了硬編碼拿就是測試類:我門把路徑寫死了,而我們的Spring底層肯定不會出現(xiàn)這種情況,這樣就用我們的Api來替代他,讓他動態(tài)的獲取路徑
package cn.com.wx; public class RunApp {public static void main(String[] args) {//獲取主路徑(bin之前)// String Dir=RunApp.class.getResource("/").getPath()// /C:/Users/wangxiao/eclipse-workspace/BokeSpringIoc/bin///上面是正常的獲取主路徑但是獲取的路徑前面會有一個斜杠,所以我們要加上一個subString的API去掉斜杠//主路徑String Dir=RunApp.class.getResource("/").getPath().substring(1);System.out.println(Dir);//包路徑String str=RunApp.class.getPackage().getName();System.out.println(str);//cn.com.wx 輸出的結(jié)果,分割是用點分割的,所有要替換成斜杠//最后在于我們的主路徑拼接String scanDir=Dir+str.replace(".", "/");System.out.println(scanDir);} }運行結(jié)果:
C:/Users/wangxiao/eclipse-workspace/BokeSpringIoc/bin/ cn.com.wx C:/Users/wangxiao/eclipse-workspace/BokeSpringIoc/bin/cn/com/wx這樣我們的動態(tài)獲取路徑的方法就實現(xiàn)了,正常我們開發(fā)一般都是面向接口的開發(fā):所有要把我們得Test方法改成面向接口的格式:創(chuàng)建接口:BeanFactory,寫兩個方法,讓Test類去實現(xiàn)接口,RunApp方法去調(diào)用接口
package cn.com.wx.test; import java.util.List; public interface BeanFactory {public List<String> Fild(String Dir, List<String> list) ;public void Cover(String scanDir, List<String> list); }修改Test類,實現(xiàn)BeanFactory接口
package cn.com.wx.test; import java.util.ArrayList; import java.util.List; import cn.com.wx.Files.Covers; import cn.com.wx.Files.JUnitFile; public class Test implements BeanFactory {List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄// String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin\\";public List<String> Fild(String Dir, List<String> list) {List<String> DirList = JUnitFile.getFileName(Dir, list);return DirList;}public void Cover(String scanDir, List<String> list) {List<String> coverlist = Covers.getScanName(scanDir, list);} }RunApp調(diào)用:
package cn.com.wx; import java.util.ArrayList; import java.util.List; import cn.com.wx.test.BeanFactory; import cn.com.wx.test.Test; public class RunApp {public static void main(String[] args) {//獲取主路徑(bin之前)// String Dir=RunApp.class.getResource("/").getPath()// /C:/Users/wangxiao/eclipse-workspace/BokeSpringIoc/bin///上面是正常的獲取主路徑但是獲取的路徑前面會有一個斜杠,所以我們要加上一個subString的API去掉斜杠//主路徑String Dir=RunApp.class.getResource("/").getPath().substring(1);//System.out.println(Dir);//包路徑String str=RunApp.class.getPackage().getName();// System.out.println(str);//cn.com.wx 輸出的結(jié)果,分割是用點分割的,所有要替換成斜杠//最后在于我們的主路徑拼接String scanDir=Dir+str.replace(".", "/");// System.out.println(scanDir);List<String> list =new ArrayList<String>();BeanFactory context =new Test();context.Fild(scanDir, list);context.Cover(Dir, list);for (String string : list) {System.out.println(string);}} }輸出結(jié)果:起始正題的思想并沒有變,只是我們把開發(fā)的過程變成了面向接口的開發(fā):并且將來我們要在在Test方法中建立反射,而獲取的結(jié)果包名點類名:就是我們所說的全局限定名
cn.com.wx.Controller.UserController cn.com.wx.Dao.UserDao cn.com.wx.Files.Covers cn.com.wx.Files.JUnitFile cn.com.wx.pojo.User.User cn.com.wx.RunApp cn.com.wx.Service.UserService cn.com.wx.test.BeanFactory cn.com.wx.test.Test在我們Spring使用的過程中是使用注解的開發(fā)方式,而我們的底層也是注解的方式,用注解去標識三個層:控制層業(yè)務(wù)層持久層,所以就要建立三個注解去標識他們:
Controller注解
package cn.com.wx.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE)//注解在類上使用 @Retention(RetentionPolicy.RUNTIME)//運行時使用 public @interface Service { } package cn.com.wx.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE)//注解在類上使用 @Retention(RetentionPolicy.RUNTIME)//運行時使用 public @interface Controller { } package cn.com.wx.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE)//注解在類上使用 @Retention(RetentionPolicy.RUNTIME)//運行時使用 public @interface Repository { }并在每一層的類上面去引用注解:
package cn.com.wx.Service; import cn.com.wx.Annotation.Service; import cn.com.wx.Dao.UserDao; import cn.com.wx.pojo.User.User; @Service//加入注解 public class UserService {private UserDao userDao;public User get(Integer id,String name) {User user=userDao.selectone(id, name);return user;} } package cn.com.wx.Controller; import cn.com.wx.Annotation.Controller; import cn.com.wx.Service.UserService; import cn.com.wx.pojo.User.User; @Controller//加入注解 public class UserController {private UserService userService ;public User get(Integer id, String name) {User user =userService.get(id, name);return user;} } package cn.com.wx.Dao; import cn.com.wx.Annotation.Repository; import cn.com.wx.pojo.User.User; @Repository//加入注解 public class UserDao {public User selectone(Integer id,String name) {User user =new User();user.setId(id);user.setName(name);return user;} }然后通過全局限定名,利用反射 Class.forName 方法創(chuàng)建類。遍歷這些文件,判斷其上面有無 @Controller 注解或者 @Service 注解,如果沒有繼續(xù)循環(huán),如果有其一,或者 @Controller 或者 @Service 就去根據(jù)反射獲取它的類上注解來判斷。對他創(chuàng)建對象,利用反射 clazz.newInstance 的方法創(chuàng)建對象實例,把它暫存到一個容器中,而容器容器 實際是一個 Map 集合, Map 集合有 key ,有 value , key 就是類的全路徑, value 就是對象實例。
這樣做有什么好處,對象創(chuàng)建直接就可以通過包掃描機制在類調(diào)用之前(初始化階段)全都放入容器,創(chuàng)建好。
在需要用的時候,直接從容器中獲取。對象都創(chuàng)建好了,性能高,代碼少。
下面我們在Cover方法中建立反射:
package cn.com.wx.test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import cn.com.wx.Annotation.Controller; import cn.com.wx.Annotation.Repository; import cn.com.wx.Annotation.Service; import cn.com.wx.Files.Covers; import cn.com.wx.Files.JUnitFile; public class Test implements BeanFactory {//創(chuàng)建容器private static final Map<String, Object> beans =new HashMap<String, Object>();List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄// String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin\\";public List<String> Fild(String Dir, List<String> list) {List<String> DirList = JUnitFile.getFileName(Dir, list);return DirList;}public void Cover(String scanDir, List<String> list) throws Exception {List<String> coverlist = Covers.getScanName(scanDir, list);for (String classname : coverlist) {Class<?> clazz = Class.forName(classname);// 需要拋異常,記得把接口和實現(xiàn)的方法都拋異常// 通過反射拿到注解標識的類名Controller controller = clazz.getAnnotation(Controller.class);Service service = clazz.getAnnotation(Service.class);Repository repository = clazz.getAnnotation(Repository.class);// 判斷如果拿到了注解類,就不會是空值,所以這里加上判斷if (controller != null || service != null || repository != null) {//一旦我們拿到注解類,就要創(chuàng)建實例對象Object obj = clazz.newInstance();beans.put(classname, obj);}}for (String key : beans.keySet()) {System.out.println(beans.get(key));}} }執(zhí)行RunApp:輸出結(jié)果為:可以看見前三個輸出就是我們獲取的對象
cn.com.wx.Service.UserService@45ee12a7 cn.com.wx.Controller.UserController@330bedb4 cn.com.wx.Dao.UserDao@2503dbd3 cn.com.wx.Annotation.Controller cn.com.wx.Annotation.Repository cn.com.wx.Annotation.Service cn.com.wx.Controller.UserController cn.com.wx.Dao.UserDao cn.com.wx.Files.Covers cn.com.wx.Files.JUnitFile cn.com.wx.pojo.User.User cn.com.wx.RunApp cn.com.wx.Service.UserService cn.com.wx.test.BeanFactory cn.com.wx.test.Test這時候我們放進容器的是class的路徑對象,我們定義的時候是定義成 private UserService userService的模式,而我們當控制層去調(diào)用業(yè)務(wù)成和業(yè)務(wù)層調(diào)用持久層的時候是拿的userSevice實例對象,這時候需求來了,我們要吧包名去掉,把首字母變成小寫;這時候我們就需要寫一個方法,把
反射拿到的類名轉(zhuǎn)換成容器正真需要的格式:在Covers類寫一個方法
package cn.com.wx.Files; import java.util.List; public class Covers {// C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\cn\com\wx\test\Test.class// C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\// 看著上邊的目錄我們來分析問題:首選我們已經(jīng)把標準目錄拿到了,放到一個list集合中,集合作為參數(shù)傳到我的getScanName方法中// 這樣我只需要一個需要截取的部分的字符串,這里我用scanDir來表示public static List<String> getScanName(String scanDir, List<String> list) {// scanDir=C:\Users\wangxiao\eclipse-workspace\BokeSpringIoc\bin\// 下一步分析:假定我們截取完字符串,那我們是不是還要把它放回去,放回去就要知道我們的角標,而普通的foreach循環(huán)滿足不了,我們用普通的for循環(huán)來替代for (int i = 0; i < list.size(); i++) {String strname = list.get(i);// 逐個去拿他的目錄// 先替換雖有的斜杠strname = strname.replace("\\", "/");scanDir = scanDir.replace("\\", "/");// 干掉scanDir部分strname = strname.replace(scanDir, "");// 從后邊拿到點出現(xiàn)的位置int pos = strname.lastIndexOf(".");strname = strname.substring(0, pos);// 這樣我們就拿到了這樣的格式// cn/com/wx/test/Test// 最后再把斜杠替換成點strname = strname.replace("/", ".");// 最后再放回集合中l(wèi)ist.set(i, strname);}return list;}public static String getBeanName(String classname) {// 拿去點的最后邊的位置加上一int pos = classname.lastIndexOf(".") + 1;classname = classname.substring(pos);//把第一個字母變成小寫,并且拼接字符串classname =classname.toLowerCase().charAt(0)+classname.substring(1);return classname;} }之后我們就需要修改反射的內(nèi)容,把之前放入容器得classname轉(zhuǎn)成beanname格式:
下面是Test類的反射變化
public void Cover(String scanDir, List<String> list) throws Exception {List<String> coverlist = Covers.getScanName(scanDir, list);for (String classname : coverlist) {Class<?> clazz = Class.forName(classname);// 需要拋異常,記得把接口和實現(xiàn)的方法都拋異常// 通過反射拿到注解標識的類名Controller controller = clazz.getAnnotation(Controller.class);Service service = clazz.getAnnotation(Service.class);Repository repository = clazz.getAnnotation(Repository.class);// 判斷如果拿到了注解類,就不會是空值,所以這里加上判斷String beanname="";//這里我是方便看,把classname轉(zhuǎn)成beannameif (controller != null || service != null || repository != null) {//一旦我們拿到注解類,就要創(chuàng)建實例對象Object obj = clazz.newInstance();beanname=Covers.getBeanName(classname);//把轉(zhuǎn)換好的beanname放進容器beans.put(beanname, obj);}}for (String key : beans.keySet()) {System.out.println(beans.get(key));}}這時候只要我們需要容器中key值就可以去取容器中查找,看有沒有我需要的實例名稱,而容器中value值就是實例名稱對應(yīng)的對象實例,這樣,以后我們在使用的時候就不要new了,我們直接從容器中獲取就可以了:
首先在我們的接口方法中加上我們的方法:
package cn.com.wx.test; import java.util.List; public interface BeanFactory {public List<String> Fild(String Dir, List<String> list) ;public void Cover(String scanDir, List<String> list) throws Exception;//從容器中調(diào)用實例對象public<T> T getBean(String beanname); } package cn.com.wx.test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import cn.com.wx.Annotation.Controller; import cn.com.wx.Annotation.Repository; import cn.com.wx.Annotation.Service; import cn.com.wx.Files.Covers; import cn.com.wx.Files.JUnitFile; public class Test implements BeanFactory {//創(chuàng)建容器private static final Map<String, Object> beans =new HashMap<String, Object>();List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄// String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin\\";public List<String> Fild(String Dir, List<String> list) {List<String> DirList = JUnitFile.getFileName(Dir, list);return DirList;}public void Cover(String scanDir, List<String> list) throws Exception {List<String> coverlist = Covers.getScanName(scanDir, list);for (String classname : coverlist) {Class<?> clazz = Class.forName(classname);// 需要拋異常,記得把接口和實現(xiàn)的方法都拋異常// 通過反射拿到注解標識的類名Controller controller = clazz.getAnnotation(Controller.class);Service service = clazz.getAnnotation(Service.class);Repository repository = clazz.getAnnotation(Repository.class);// 判斷如果拿到了注解類,就不會是空值,所以這里加上判斷String beanname="";//這里我是方便看,把classname轉(zhuǎn)成beannameif (controller != null || service != null || repository != null) {//一旦我們拿到注解類,就要創(chuàng)建實例對象Object obj = clazz.newInstance();beanname=Covers.getBeanName(classname);//把轉(zhuǎn)換好的beanname放進容器beans.put(beanname, obj);}}for (String key : beans.keySet()) {System.out.println(beans.get(key));}}//<T>代表后面出現(xiàn)T就是泛型,T代表返回值//也稱為泛型方法public<T> T getBean(String beanname) {return (T) beans.get(beanname);} }這樣我們就可以在RunApp方法中去測試,看我們能不能找到實例對象:在最下面測試我們的方法
UserService us =context.getBean("userService");System.out.println("這是從容器中拿到的"+us);輸出結(jié)果:可以看見我們的實例對象已經(jīng)放到容器中了:這樣我們Ioc的底層就實現(xiàn)了。
cn.com.wx.Controller.UserController@45ee12a7 cn.com.wx.Dao.UserDao@330bedb4 cn.com.wx.Service.UserService@2503dbd3 這是從容器中拿到的cn.com.wx.Service.UserService@2503dbd3這樣我們就可以通過三個層調(diào)用方法了:在RunApp下面加上測試
UserController uc = context.getBean("userController");UserService us = context.getBean("userService");UserDao ud = context.getBean("userDao");uc.setUserService(us);us.setUserDao(ud);User user = uc.get(5,"馬云");System.out.println(user);我們看下輸出結(jié)果:
cn.com.wx.Controller.UserController@45ee12a7 cn.com.wx.Dao.UserDao@330bedb4 cn.com.wx.Service.UserService@2503dbd3 User [id=5, name=馬云]Di(依賴注入)
uc.setUserService(us);us.setUserDao(ud);注意我上邊寫的代碼,我是在UserController和UserService類中定義了set方法,然后這里才可以調(diào)用,繼而把參數(shù)傳過去,其一:這是沒有太多的屬性,其二:開發(fā)過程麻煩, 一不小心忘記寫了哪一步,就會報空指針異常。那么近引進我們得Di(依賴注入),也叫自動駐入,我認為自動注入這個名字更能體現(xiàn)他,讓我們的Di去幫我們實現(xiàn)注入(也就是去set) 他,那么我們的UserController和UsereService類就不用寫set方法了,直接一個注解就搞定,實現(xiàn)的時候,我們也不用去調(diào)用了set了。網(wǎng)上大部分人說Ioc是概念而Di才是真正的去實現(xiàn)Ioc,但是我認為這種說話是不正確的,我認為Di是Ioc的重要實現(xiàn)功能,是在有Ioc的前提下,有容器,有屬性
之后Di再去實現(xiàn),Di它實現(xiàn)了對象之間關(guān)系的綁定,判斷什么類需要進行諸如,通過我們的@Autowired注解識別,然后從容器中拿到對象。
首先我們來定義注解@Autowired來定義Di:
package cn.com.wx.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.Authenticator.RequestorType; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }這時候我們就可以把UserController和UserService:中的Set方法直接去掉,然后在私有屬性上定義注解:
package cn.com.wx.Controller; import cn.com.wx.Annotation.Autowired; import cn.com.wx.Annotation.Controller; import cn.com.wx.Service.UserService; import cn.com.wx.pojo.User.User; @Controller //加入注解 public class UserController {@Autowiredprivate UserService userService; // public void setUserService(UserService userService) { // this.userService = userService; // }public User get(Integer id ,String name) {User user = userService.get(id,name);return user;} } package cn.com.wx.Service; import cn.com.wx.Annotation.Autowired; import cn.com.wx.Annotation.Service; import cn.com.wx.Dao.UserDao; import cn.com.wx.pojo.User.User; @Service public class UserService {@Autowiredprivate UserDao userDao;// public void setUserDao(UserDao userDao) { // this.userDao = userDao; // }public User get(Integer id,String name) {User user=userDao.selectone(id,name);return user;} }這時候我滿看見RunApp類的set的兩個方法報錯,我們直接去掉就可以,因為我們已經(jīng)不需要set去注入了
在我們的接口中加入我們的inject方法:因為這里我們要用倒反射,所以后面呢肯定會拋異常,這里我直接提前拋出異常
package cn.com.wx.test; import java.util.List; public interface BeanFactory {public List<String> Fild(String Dir, List<String> list) ;public void Cover(String scanDir, List<String> list) throws Exception;//從容器中調(diào)用實例對象public<T> T getBean(String beanname);//自動注入public void inject() throws Exception; }先分析放射的步驟:先獲取到當前對象,通過當前對象 getClass 方法就獲得 Class 類,通過 Class 類的 getDeclaredField() 獲取這個類上的所有的屬性, Declea 。。這個方法可以獲取類的成員變量,而且可以獲取私有的。但是私有屬性在操作時,
必須先設(shè)置其訪問權(quán)限可用。然后利用反射屬性 set 方法,設(shè)置它的 值
在Test類中加上我們的inject方法:里邊的每一步我都有注釋,解釋當前這一步的目的
package cn.com.wx.test; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import cn.com.wx.Annotation.Autowired; import cn.com.wx.Annotation.Controller; import cn.com.wx.Annotation.Repository; import cn.com.wx.Annotation.Service; import cn.com.wx.Files.Covers; import cn.com.wx.Files.JUnitFile; public class Test implements BeanFactory {//創(chuàng)建容器private static final Map<String, Object> beans =new HashMap<String, Object>();List<String> list = new ArrayList<String>();// 創(chuàng)建集合用于接受JUnnitFile的返回值// 這個路徑是我們項目的路徑,一定要根據(jù)自己工作空間建的項目的目錄// String Dir = "C:\\Users\\wangxiao\\eclipse-workspace\\BokeSpringIoc\\bin\\";public List<String> Fild(String Dir, List<String> list) {List<String> DirList = JUnitFile.getFileName(Dir, list);return DirList;}public void Cover(String scanDir, List<String> list) throws Exception {List<String> coverlist = Covers.getScanName(scanDir, list);for (String classname : coverlist) {Class<?> clazz = Class.forName(classname);// 需要拋異常,記得把接口和實現(xiàn)的方法都拋異常// 通過反射拿到注解標識的類名Controller controller = clazz.getAnnotation(Controller.class);Service service = clazz.getAnnotation(Service.class);Repository repository = clazz.getAnnotation(Repository.class);// 判斷如果拿到了注解類,就不會是空值,所以這里加上判斷String beanname="";//這里我是方便看,把classname轉(zhuǎn)成beannameif (controller != null || service != null || repository != null) {//一旦我們拿到注解類,就要創(chuàng)建實例對象Object obj = clazz.newInstance();beanname=Covers.getBeanName(classname);//把轉(zhuǎn)換好的beanname放進容器beans.put(beanname, obj);System.out.println(beanname);}}for (String key : beans.keySet()) {System.out.println(beans.get(key));}}//<T>代表后面出現(xiàn)T就是泛型,T代表返回值//也稱為泛型方法public<T> T getBean(String beanname) {return (T) beans.get(beanname);}public void inject() throws Exception {for (String beanname : beans.keySet()) {//先從容器中獲取對象Object obj= getBean(beanname);//創(chuàng)建反射Class<?> clazz =obj.getClass();//遍歷所有屬性:找到私有屬性,然后判斷他是否有Autowired注解//這里邊我只寫了一個私有的,但是底層不可能就是只能去獲取一個,//可能一個Controller層有多個私有的屬性,但是不一定每一個屬性上都有Autowired注解,這時候我們就需要用一個數(shù)組去接受//反射的基本ApiDeclared獲取私有的 Fields獲取屬性,最后我們用一個數(shù)組作為返回值;Field[] field = clazz.getDeclaredFields();//但是不一定每一個屬性上都有Autowired注解,所以我們需要去判斷一下for (Field f : field) {//屬性是私有的,訪問需要開通權(quán)限f.setAccessible(true);Autowired autowired =f.getAnnotation(Autowired.class);//判斷私有屬性上是否有注解,有注解就不為空if(autowired!=null) {//拿到注解,就證明我們這個私有屬性需要去注入,下面就是注入//拿到屬性名,這個getName不是我寫的方法,他是反射源碼自己定義的一個方法,鼠標放上去,看到返回值是String類型,定義一個變量去接收。String claname=f.getName();//注入f.set(obj, getBean(claname));}}}}}然后在我們的RunApp上調(diào)用我們的inject方法(一定不要忘記這一步,不寫是不會調(diào)用自動注入,自然運行就會空指針異常)
package cn.com.wx; import java.util.ArrayList; import java.util.List; import cn.com.wx.Controller.UserController; import cn.com.wx.Dao.UserDao; import cn.com.wx.Service.UserService; import cn.com.wx.pojo.User.User; import cn.com.wx.test.BeanFactory; import cn.com.wx.test.Test; public class RunApp {public static void main(String[] args) throws Exception {// 獲取主路徑(bin之前)// String Dir=RunApp.class.getResource("/").getPath()// /C:/Users/wangxiao/eclipse-workspace/BokeSpringIoc/bin/// 上面是正常的獲取主路徑但是獲取的路徑前面會有一個斜杠,所以我們要加上一個subString的API去掉斜杠// 主路徑String Dir = RunApp.class.getResource("/").getPath().substring(1);// System.out.println(Dir);// 包路徑String str = RunApp.class.getPackage().getName();// System.out.println(str);//cn.com.wx 輸出的結(jié)果,分割是用點分割的,所有要替換成斜杠// 最后在于我們的主路徑拼接String scanDir = Dir + str.replace(".", "/");// System.out.println(scanDir);List<String> list = new ArrayList<String>();BeanFactory context = new Test();context.Fild(scanDir, list);context.Cover(Dir, list); // for (String string : list) { // System.out.println(string); // } // UserService us =context.getBean("userService"); // System.out.println("這是從容器中拿到的"+us);context.inject();UserController uc = context.getBean("userController");UserService us = context.getBean("userService");UserDao ud = context.getBean("userDao"); // uc.setUserService(us); // us.setUserDao(ud);User user = uc.get(5,"馬云");System.out.println(user);} }運行結(jié)果:
cn.com.wx.Controller.UserController@45ee12a7 cn.com.wx.Dao.UserDao@330bedb4 cn.com.wx.Service.UserService@2503dbd3 User [id=5, name=馬云]這樣一來注入就成功,看到這個底層我們就能明白我上邊所說的,Di是在ioc的前提下完成的。
最后說一下,我還是希望大家能耐下心來花幾天的時間吧這些代碼搞一下,收獲還有蠻大的,前幾次寫可能某一步不能理解,但是多敲幾遍,其義自見。
?
總結(jié)
以上是生活随笔為你收集整理的利用递归,反射,注解等,手写Spring Ioc和Di 底层(喷倒面试官的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 各种杂志投稿方式与评价
- 下一篇: 什么是智慧校园?