自己实现spring核心功能 二
前言
上一篇我們講了spring的一些特點(diǎn)并且分析了需要實(shí)現(xiàn)哪些功能,已經(jīng)把準(zhǔn)備工作都做完了,這一篇我們開(kāi)始實(shí)現(xiàn)具體功能。
容器加載過(guò)程
?我們知道,在spring中refesh()方法做了很多初始化的工作,它幾乎涵蓋了spring的核心流程
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//刷新之前的準(zhǔn)備工作,包括設(shè)置啟動(dòng)時(shí)間,是否激活標(biāo)識(shí)位,初始化屬性源(property source)配置 prepareRefresh();//由子類(lèi)去刷新BeanFactory(如果還沒(méi)創(chuàng)建則創(chuàng)建),并將BeanFactory返回ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//準(zhǔn)備BeanFactory以供ApplicationContext使用 prepareBeanFactory(beanFactory);try {//子類(lèi)可通過(guò)格式此方法來(lái)對(duì)BeanFactory進(jìn)行修改 postProcessBeanFactory(beanFactory);//實(shí)例化并調(diào)用所有注冊(cè)的BeanFactoryPostProcessor對(duì)象 invokeBeanFactoryPostProcessors(beanFactory);//實(shí)例化并調(diào)用所有注冊(cè)的BeanPostProcessor對(duì)象 registerBeanPostProcessors(beanFactory);//初始化MessageSource initMessageSource();//初始化事件廣播器 initApplicationEventMulticaster();//子類(lèi)覆蓋此方法在刷新過(guò)程做額外工作 onRefresh();//注冊(cè)應(yīng)用監(jiān)聽(tīng)器ApplicationListener registerListeners();//實(shí)例化所有non-lazy-init bean finishBeanFactoryInitialization(beanFactory);//刷新完成工作,包括初始化LifecycleProcessor,發(fā)布刷新完成事件等 finishRefresh();}catch (BeansException ex) {// Destroy already created singletons to avoid dangling resources. destroyBeans();// Reset 'active' flag. cancelRefresh(ex);// Propagate exception to caller.throw ex;}} }?做的東西比較復(fù)雜,而我們實(shí)現(xiàn)做些基本的就好了。
?我們?cè)贑JDispatcherServlet?類(lèi)的init方法中,實(shí)現(xiàn)如下業(yè)務(wù)邏輯,就能將spring功能給初始化了,就可以使用依賴(lài)注入了
@Overridepublic void init(ServletConfig config) {//加載配置//獲取要掃描的包地址//掃描要加載的類(lèi)//實(shí)例化要加載的類(lèi)//加載依賴(lài)注入,給屬性賦值//加載映射地址 }?
加載配置
String contextConfigLocation = config.getInitParameter("contextConfigLocation");loadConfig(contextConfigLocation);這里會(huì)獲取到web.xml中init-param節(jié)點(diǎn)中的值
具體指向的是spring文件下的application.properties配置文件,里面只有一行配置
?
通過(guò)配置的key名字可以知道,這是指定了需要掃描的包路徑
代表的是掃描紅框中定義的所有類(lèi)
第二行代碼是創(chuàng)建了一個(gè)loadConfig方法,將包路徑傳進(jìn)去
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();}}}}黃色部分的代碼需要注意,這里使用了一個(gè)成員變量
private Properties properties = new Properties();在類(lèi)的上半部分定義就好了,這里的作用是獲取application.properties文件中的配置內(nèi)容加載到properties變量中,供后面使用。
獲取要掃描的包地址
??
//獲取要掃描的包地址String dirpath = properties.getProperty("scanner.package");這里使用配置中的key讀取出目錄地址
掃描要加載的類(lèi)
//掃描要加載的類(lèi)doScanner(dirpath);掃描類(lèi)我們定義一個(gè)doScanner方法,把包目錄地址傳進(jìn)去
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 }第二行代碼進(jìn)行了轉(zhuǎn)義替換
本方法內(nèi)的代碼作業(yè)是讀取指定路徑下的文件,如果是文件夾,則遞歸調(diào)用,如果是文件,把文件名稱(chēng)和路徑存進(jìn)集合容器內(nèi)
?需要注意黃色部分的變量,是在外部定義了一個(gè)成員變量
private List<String> beanNames = new ArrayList<>();?我們?cè)陬?lèi)的上半部分加上它。
?得到的beanName如下
從這里看出,它已經(jīng)把我們定義的注解給找出來(lái)了。
?
?
實(shí)例化要加載的類(lèi)
//實(shí)例化要加載的類(lèi)doInstance();剛才我們已經(jīng)得到了這些定義好的類(lèi)的名稱(chēng)列表,現(xiàn)在我們需要一個(gè)個(gè)實(shí)例化,并且保存在ioc容器當(dāng)中。
先定義個(gè)裝載類(lèi)的容器,使用HashMap就能做到,將它設(shè)為成員變量,在類(lèi)的上半部分定義
private Map<String, Object> ioc = new HashMap<>();接著創(chuàng)建一個(gè)方法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 //使用反射實(shí)例化對(duì)象 10 Object instance = cls.newInstance(); 11 //默認(rèn)類(lèi)名首字母小寫(xiě) 12 beanName = firstLowerCase(cls.getSimpleName()); 13 //寫(xiě)入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 //如果是接口,自動(dòng)注入它的實(shí)現(xiàn)類(lèi) 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 }
只要提供類(lèi)的完全限定名,通過(guò)Class.forName靜態(tài)方法,我們就能將類(lèi)信息加載到內(nèi)存中并且返回Class?對(duì)象,通過(guò)反射來(lái)實(shí)例化,見(jiàn)第10行代碼,
我們通過(guò)循環(huán)beanNames集合,來(lái)實(shí)例化每個(gè)類(lèi),并將實(shí)例化后的對(duì)象裝入HashMap中
注意:第12行將類(lèi)名的首字母小寫(xiě)后存入map,該方法定義如下
1 String firstLowerCase(String str) { 2 char[] chars = str.toCharArray(); 3 chars[0] += 32; 4 return String.valueOf(chars); 5 }這行代碼會(huì)將字符串轉(zhuǎn)成char數(shù)組,然后將數(shù)組中第一個(gè)字符轉(zhuǎn)為大寫(xiě),這里采用了一種比較巧妙的方式實(shí)現(xiàn),tom老師采用了一種比較騷的操作
實(shí)例化完成后,ioc容器中的數(shù)據(jù)如下:說(shuō)明:
圖片中可以看出,hashMap的key?都是小寫(xiě),value已經(jīng)是對(duì)象了 ,見(jiàn)紅框。
這里為什么要把藍(lán)框標(biāo)記出來(lái),是因?yàn)檫@是類(lèi)中的字段屬性,此時(shí)可以看到,雖然類(lèi)已經(jīng)被實(shí)例化了,可是屬性還是null呢
我這里為了測(cè)試依賴(lài)注入,所以加了2個(gè)接口和2個(gè)實(shí)現(xiàn)類(lèi)
接口定義如下: public interface IHomeService {String sayHi();String getName(Integer id,String no);String getRequestBody(Integer id, String no, GetUserInfo userInfo); }public interface IStudentService {String sayHi(); } View Code?
實(shí)現(xiàn)類(lèi): @JCService public class StudentService implements IStudentService{@Overridepublic String sayHi(){return "Hello world!";} } View Code @JCService public class HomeService implements IHomeService{@JCAutoWritedStudentService studentService;@Overridepublic String sayHi() {return studentService.sayHi();}@Overridepublic String getName(Integer id,String no) {return "SB0000"+id;}@Overridepublic String getRequestBody(Integer id, String no, GetUserInfo userInfo) {return "userName="+userInfo.getName()+" no="+no;} } View Code依賴(lài)實(shí)體:
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;} View Code?
加載依賴(lài)注入,給屬性賦值
//加載依賴(lài)注入,給屬性賦值doAutoWrited();? 現(xiàn)在我們實(shí)現(xiàn)依賴(lài)注入,需要定義一個(gè)無(wú)參的方法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 }?這個(gè)方法是通過(guò)循環(huán)ioc里面的實(shí)體,反射找出字段,看看是否有需要注入的標(biāo)記JCAutoWrited,如果加了標(biāo)記,就反射給字段賦值,類(lèi)型從ioc容器中獲取
?
?加載映射地址?
//加載映射地址doRequestMapping();?
?映射地址的作用是根據(jù)請(qǐng)求的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 }這里其實(shí)就是根據(jù)對(duì)象反射獲取到JCRequestMapping上面的value值
@JCRequestMapping("/sayHi")
?取到的就是/sayHi
另外注意的是:黃色部分使用的變量是一個(gè)hashMap,在類(lèi)上半部分定義的
private Map<String, Method> urlMapping = new HashMap<>();這里面存的是?url?和對(duì)應(yīng)的method對(duì)象。后面處理請(qǐng)求的時(shí)候要使用到的。
結(jié)尾
容器的初始化到這里就結(jié)束了,一共使用了4個(gè)容器來(lái)存放相關(guān)對(duì)象,后續(xù)servlet處理請(qǐng)求的時(shí)候會(huì)用到它們。
下一篇,將會(huì)繼續(xù)完善它,通過(guò)請(qǐng)求來(lái)驗(yàn)證是否可以達(dá)到預(yù)期效果。另外會(huì)實(shí)現(xiàn)參數(shù)綁定,能處理各類(lèi)請(qǐng)求并響應(yīng)。
完整代碼地址
轉(zhuǎn)載于:https://www.cnblogs.com/jingch/p/11369599.html
總結(jié)
以上是生活随笔為你收集整理的自己实现spring核心功能 二的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: git fetch 和git pull
- 下一篇: 自己实现spring核心功能 一