前言
上一篇我們講了spring的一些特點並且分析了需要實現哪些功能,已經把準備工作都做完了,這一篇我們開始實現具體功能。
容器加載過程
我們知道,在spring中refesh()方法做了很多初始化的工作,它幾乎涵蓋了spring的核心流程
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //刷新之前的準備工作,包括設置啓動時間,是否激活標識位,初始化屬性源(property source)配置 prepareRefresh(); //由子類去刷新BeanFactory(如果還沒創建則創建),並將BeanFactory返回 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //準備BeanFactory以供ApplicationContext使用 prepareBeanFactory(beanFactory); try { //子類可通過格式此方法來對BeanFactory進行修改 postProcessBeanFactory(beanFactory); //實例化並調用所有註冊的BeanFactoryPostProcessor對象 invokeBeanFactoryPostProcessors(beanFactory); //實例化並調用所有註冊的BeanPostProcessor對象 registerBeanPostProcessors(beanFactory); //初始化MessageSource initMessageSource(); //初始化事件廣播器 initApplicationEventMulticaster(); //子類覆蓋此方法在刷新過程做額外工作 onRefresh(); //註冊應用監聽器ApplicationListener registerListeners(); //實例化所有non-lazy-init bean finishBeanFactoryInitialization(beanFactory); //刷新完成工作,包括初始化LifecycleProcessor,發佈刷新完成事件等 finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
做的東西比較複雜,而我們實現做些基本的就好了。
我們在CJDispatcherServlet 類的init方法中,實現如下業務邏輯,就能將spring功能給初始化了,就可以使用依賴注入了
@Override public void init(ServletConfig config) { //加載配置 //獲取要掃描的包地址 //掃描要加載的類 //實例化要加載的類 //加載依賴注入,給屬性賦值 //加載映射地址 }
加載配置
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
loadConfig(contextConfigLocation);
這裏會獲取到web.xml中init-param節點中的值
具體指向的是spring文件下的application.properties配置文件,裏面只有一行配置
通過配置的key名字可以知道,這是指定了需要掃描的包路徑
代表的是掃描紅框中定義的所有類
第二行代碼是創建了一個loadConfig方法,將包路徑傳進去
void loadConfig(String contextConfigLocation) { InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } finally { if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
黃色部分的代碼需要注意,這裏使用了一個成員變量
private Properties properties = new Properties();
在類的上半部分定義就好了,這裏的作用是獲取application.properties文件中的配置內容加載到properties變量中,供後面使用。
獲取要掃描的包地址
//獲取要掃描的包地址 String dirpath = properties.getProperty("scanner.package");
這裏使用配置中的key讀取出目錄地址
掃描要加載的類
//掃描要加載的類 doScanner(dirpath);
掃描類我們定義一個doScanner方法,把包目錄地址傳進去
1 void doScanner(String dirpath) { 2 URL url = this.getClass().getClassLoader().getResource("/" + dirpath.replaceAll("\\.", "/")); 3 File dir = new File(url.getFile()); 4 File[] files = dir.listFiles(); 5 for (File file : files) { 6 if (file.isDirectory()) { 7 doScanner(dirpath + "." + file.getName()); 8 continue; 9 } 10 11 //取文件名 12 String beanName = dirpath + "." + file.getName().replaceAll(".class", ""); 13 beanNames.add(beanName); 14 } 15 }
第二行代碼進行了轉義替換
本方法內的代碼作業是讀取指定路徑下的文件,如果是文件夾,則遞歸調用,如果是文件,把文件名稱和路徑存進集合容器內
需要注意黃色部分的變量,是在外部定義了一個成員變量
private List<String> beanNames = new ArrayList<>();
我們在類的上半部分加上它。
得到的beanName如下
從這裏看出,它已經把我們定義的註解給找出來了。
實例化要加載的類
//實例化要加載的類 doInstance();
剛纔我們已經得到了這些定義好的類的名稱列表,現在我們需要一個個實例化,並且保存在ioc容器當中。
先定義個裝載類的容器,使用HashMap就能做到,將它設爲成員變量,在類的上半部分定義
private Map<String, Object> ioc = new HashMap<>();
接着創建一個方法doInstance
1 void doInstance() { 2 if (beanNames.isEmpty()) { 3 return; 4 } 5 for (String beanName : beanNames) { 6 try { 7 Class cls = Class.forName(beanName); 8 if (cls.isAnnotationPresent(JCController.class)) { 9 //使用反射實例化對象 10 Object instance = cls.newInstance(); 11 //默認類名首字母小寫 12 beanName = firstLowerCase(cls.getSimpleName()); 13 //寫入ioc容器 14 ioc.put(beanName, instance); 15 16 17 } else if (cls.isAnnotationPresent(JCService.class)) { 18 Object instance = cls.newInstance(); 19 JCService jcService = (JCService) cls.getAnnotation(JCService.class); 20 21 String alisName = jcService.value(); 22 if (null == alisName || alisName.trim().length() == 0) { 23 beanName = cls.getSimpleName(); 24 } else { 25 beanName = alisName; 26 } 27 beanName = firstLowerCase(beanName); 28 ioc.put(beanName, instance); 29 //如果是接口,自動注入它的實現類 30 Class<?>[] interfaces = cls.getInterfaces(); 31 for (Class<?> c : 32 interfaces) { 33 ioc.put(firstLowerCase(c.getSimpleName()), instance); 34 } 35 } else { 36 continue; 37 } 38 } catch (ClassNotFoundException e) { 39 e.printStackTrace(); 40 } catch (IllegalAccessException e) { 41 e.printStackTrace(); 42 } catch (InstantiationException e) { 43 e.printStackTrace(); 44 } 45 } 46 }
只要提供類的完全限定名,通過Class.forName靜態方法,我們就能將類信息加載到內存中並且返回Class 對象,通過反射來實例化,見第10行代碼,
我們通過循環beanNames集合,來實例化每個類,並將實例化後的對象裝入HashMap中
注意:第12行將類名的首字母小寫後存入map,該方法定義如下
1 String firstLowerCase(String str) { 2 char[] chars = str.toCharArray(); 3 chars[0] += 32; 4 return String.valueOf(chars); 5 }
這行代碼會將字符串轉成char數組,然後將數組中第一個字符轉爲大寫,這裏採用了一種比較巧妙的方式實現,tom老師採用了一種比較騷的操作
實例化完成後,ioc容器中的數據如下:
說明:
圖片中可以看出,hashMap的key 都是小寫,value已經是對象了 ,見紅框。
這裏爲什麼要把藍框標記出來,是因爲這是類中的字段屬性,此時可以看到,雖然類已經被實例化了,可是屬性還是null呢
我這裏爲了測試依賴注入,所以加了2個接口和2個實現類
接口定義如下:
public interface IHomeService { String sayHi(); String getName(Integer id,String no); String getRequestBody(Integer id, String no, GetUserInfo userInfo); } public interface IStudentService { String sayHi(); }
實現類:
@JCService public class StudentService implements IStudentService{ @Override public String sayHi(){ return "Hello world!"; } }
@JCService public class HomeService implements IHomeService{ @JCAutoWrited StudentService studentService; @Override public String sayHi() { return studentService.sayHi(); } @Override public String getName(Integer id,String no) { return "SB0000"+id; } @Override public String getRequestBody(Integer id, String no, GetUserInfo userInfo) { return "userName="+userInfo.getName()+" no="+no; } }
依賴實體:
public class GetUserInfo { public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public BigDecimal getGrowthValue() { return growthValue; } public void setGrowthValue(BigDecimal growthValue) { this.growthValue = growthValue; } private String name; private Integer age; private BigDecimal growthValue; }
加載依賴注入,給屬性賦值
//加載依賴注入,給屬性賦值 doAutoWrited();
現在我們實現依賴注入,需要定義一個無參的方法doAutoWrite
1 void doAutoWrited() { 2 for (Map.Entry<String, Object> obj : ioc.entrySet()) { 3 try { 4 for (Field field : obj.getValue().getClass().getDeclaredFields()) { 5 if (!field.isAnnotationPresent(JCAutoWrited.class)) { 6 continue; 7 } 8 JCAutoWrited autoWrited = field.getAnnotation(JCAutoWrited.class); 9 String beanName = autoWrited.value(); 10 if ("".equals(beanName)) { 11 beanName = field.getType().getSimpleName(); 12 } 13 14 field.setAccessible(true); 15 16 field.set(obj.getValue(), ioc.get(firstLowerCase(beanName))); 17 } 18 } catch (IllegalAccessException e) { 19 e.printStackTrace(); 20 } 21 22 } 23 24 25 }
這個方法是通過循環ioc裏面的實體,反射找出字段,看看是否有需要注入的標記JCAutoWrited,如果加了標記,就反射給字段賦值,類型從ioc容器中獲取
加載映射地址
//加載映射地址 doRequestMapping();
映射地址的作用是根據請求的url匹配method方法
1 void doRequestMapping() { 2 if (ioc.isEmpty()) { 3 return; 4 } 5 for (Map.Entry<String, Object> obj : ioc.entrySet()) { 6 if (!obj.getValue().getClass().isAnnotationPresent(JCController.class)) { 7 continue; 8 } 9 Method[] methods = obj.getValue().getClass().getMethods(); 10 for (Method method : methods) { 11 if (!method.isAnnotationPresent(JCRequestMapping.class)) { 12 continue; 13 } 14 String baseUrl = ""; 15 if (obj.getValue().getClass().isAnnotationPresent(JCRequestMapping.class)) { 16 baseUrl = obj.getValue().getClass().getAnnotation(JCRequestMapping.class).value(); 17 } 18 JCRequestMapping jcRequestMapping = method.getAnnotation(JCRequestMapping.class); 19 if ("".equals(jcRequestMapping.value())) { 20 continue; 21 } 22 String url = (baseUrl + "/" + jcRequestMapping.value()).replaceAll("/+", "/"); 23 urlMapping.put(url, method); 24 System.out.println(url); 25 } 26 } 27 }
這裏其實就是根據對象反射獲取到JCRequestMapping上面的value值
@JCRequestMapping("/sayHi")
取到的就是/sayHi
另外注意的是:黃色部分使用的變量是一個hashMap,在類上半部分定義的
private Map<String, Method> urlMapping = new HashMap<>();
這裏面存的是 url 和對應的method對象。後面處理請求的時候要使用到的。
結尾
容器的初始化到這裏就結束了,一共使用了4個容器來存放相關對象,後續servlet處理請求的時候會用到它們。
下一篇,將會繼續完善它,通過請求來驗證是否可以達到預期效果。另外會實現參數綁定,能處理各類請求並響應。