<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      前言


      學(xué)習如何使用Spring,SpringMVC是很快的,但是在往后使用的過程中難免會想探究一下框架背后的原理是什么,本文將通過講解如何手寫一個簡單版的springMVC框架,直接從代碼上看框架中請求分發(fā),控制反轉(zhuǎn)和依賴注入是如何實現(xiàn)的。

      建議配合示例源碼閱讀,github地址如下:

      https://github.com/liuyj24/mini-spring

      項目搭建

      項目搭建可以參考github中的項目,先選好jar包管理工具,Maven和Gradle都行,本項目使用的是Gradle。

      然后在項目下建兩個模塊,一個是framework,用于編寫框架;另外一個是test,用于應(yīng)用并測試框架(注意test模塊要依賴framework模塊)。

      接著在framework模塊下按照spring創(chuàng)建好beans,core,context,web等模塊對應(yīng)的包,完成后便可以進入框架的編寫了。

      請求分發(fā)

      在講請求分發(fā)之前先來梳理一下整個web模型:

      * 首先用戶在客戶端發(fā)送一個請求到服務(wù)器,經(jīng)操作系統(tǒng)的TCP/IP棧解析后會交到在某個端口監(jiān)聽的web服務(wù)器。
      *
      web服務(wù)器程序監(jiān)聽到請求后便會把請求分發(fā)給對應(yīng)的程序進行處理。比如Tomcat就會將請求分發(fā)給對應(yīng)的java程序(servlet)進行處理,web服務(wù)器本身是不進行請求處理的。
      本項目的web服務(wù)器選擇Tomcat,而且為了能讓項目直接跑起來,選擇了在項目中內(nèi)嵌Tomcat,這樣框架在做測試的時候就能像spring
      boot一樣一鍵啟動,方便測試。

      Servlet


      既然選擇了使用Java編寫服務(wù)端程序,那就不得不提到Servlet接口了。為了規(guī)范服務(wù)器與Java程序之間的通信方式,Java官方制定了Servlet規(guī)范,服務(wù)端的Java應(yīng)用程序必須實現(xiàn)該接口,把Java作為處理語言的服務(wù)器也必須要根據(jù)Servlet規(guī)范進行對接。

      在還沒有spring之前
      ,人們是這么開發(fā)web程序的:一個業(yè)務(wù)邏輯對應(yīng)一個Servlet,所以一個大項目中會有多個Servlet,這大量的Servlet會被配置到一個叫web.xml的配置文件中,當服務(wù)器運行的時候,tomcat會根據(jù)請求的uri到web.xml文件中尋找對應(yīng)的Servlet業(yè)務(wù)類處理請求。


      但是你想,每來一個請求就創(chuàng)建一個Servlet,而且一個Servlet實現(xiàn)類中我們通常只重寫一個service方法,另外四個方法都只是給個空實現(xiàn),這太浪費資源了。而且編起程序來創(chuàng)建很多Servlet還很難管理。能不能改進一下?

      Spring的DispatcherServlet

      方法確實有:




      從上圖可以看到,我們原來是經(jīng)過web服務(wù)器把請求分發(fā)到不同的Servlet;我們可以換個思路,讓web服務(wù)器把請求都發(fā)送到一個Servlet,再由這個Servlet把請求按照uri分發(fā)給不同的方法進行處理。


      這樣一來,不管收到什么請求,web服務(wù)器都會分發(fā)到同一個Servlet(DispatcherServlet),避免了多個Servlet所帶來的問題,有以下好處:

      * 把分發(fā)請求這一步從web服務(wù)器移動到框架內(nèi),這樣更容易控制,也方便擴展。
      * 可以把同一個業(yè)務(wù)的處理方法集中到同一個類里,把這種類起名為controller,一個controller中有多個處理方法,這樣配置分散不雜亂。
      * 配置uri映射路徑的時候可以不使用配置文件,直接在處理方法上用注解配置即可,解決了配置集中,大而雜的問題。
      實操

      建議配合文章開頭給出的源碼進行參考

      *
      首先在web.mvc包中創(chuàng)建三個注解:Controller,RequestMapping,RequestParam,有了注解我們才能在框架啟動時動態(tài)獲得配置信息。
      * 由于處理方法都是被注解的,要想解析被注解的類,首先得獲得項目中相關(guān)的所有類,對應(yīng)是源碼中core包下的ClassScanner類 public
      class ClassScanner { public static List<Class<?>> scanClass(String packageName)
      throws IOException, ClassNotFoundException { //用于保存結(jié)果的容器 List<Class<?>>
      classList = new ArrayList<>(); //把文件名改為文件路徑 String path =
      packageName.replace(".", "/"); //獲取默認的類加載器 ClassLoader classLoader =
      Thread.currentThread().getContextClassLoader(); //通過文件路徑獲取該文件夾下所有資源的URL
      Enumeration<URL> resources = classLoader.getResources(path); int index = 0;//測試
      while(resources.hasMoreElements()){ //拿到下一個資源 URL resource =
      resources.nextElement(); //先判斷是否是jar包,因為默認.class文件會被打包為jar包
      if(resource.getProtocol().contains("jar")){ //把URL強轉(zhuǎn)為jar包鏈接 JarURLConnection
      jarURLConnection = (JarURLConnection)resource.openConnection();
      //根據(jù)jar包獲取jar包的路徑名 String jarFilePath =
      jarURLConnection.getJarFile().getName(); //把jar包下所有的類添加的保存結(jié)果的容器中
      classList.addAll(getClassFromJar(jarFilePath, path)); }else{//也有可能不是jar文件,先放下
      //todo } } return classList; } /** * 獲取jar包中所有路徑符合的類文件 * @param jarFilePath *
      @param path * @return */ private static List<Class<?>> getClassFromJar(String
      jarFilePath, String path) throws IOException, ClassNotFoundException {
      List<Class<?>> classes = new ArrayList<>();//保存結(jié)果的集合 JarFile jarFile = new
      JarFile(jarFilePath);//創(chuàng)建對應(yīng)jar包的句柄 Enumeration<JarEntry> jarEntries =
      jarFile.entries();//拿到j(luò)ar包中所有的文件 while(jarEntries.hasMoreElements()){ JarEntry
      jarEntry = jarEntries.nextElement();//拿到一個文件 String entryName =
      jarEntry.getName();//拿到文件名,大概是這樣:com/shenghao/test/Test.class if
      (entryName.startsWith(path) && entryName.endsWith(".class")){//判斷是否是類文件 String
      classFullName = entryName.replace("/", ".") .substring(0, entryName.length() -
      6); classes.add(Class.forName(classFullName)); } } return classes; } }
      *
      然后在handler包創(chuàng)建MappingHandler類,在將來框架運行的過程中,一個MappingHandler就對應(yīng)一個業(yè)務(wù)邏輯,比如說增加一個用戶。所以一個MappingHandler中要有“請求uri,處理方法,方法的參數(shù),方法所處的類”這四個字段,其中請求uri用于匹配請求uri,后面三個參數(shù)用于運行時通過反射調(diào)用該處理方法
      public class MappingHandler { private String uri; private Method method;
      private Class<?> controller; private String[] args; MappingHandler(String uri,
      Method method, Class<?> cls, String[] args){ this.uri = uri; this.method =
      method; this.controller = cls; this.args = args; } public boolean
      handle(ServletRequest req, ServletResponse res) throws IllegalAccessException,
      InstantiationException, InvocationTargetException, IOException { //拿到請求的uri
      String requestUri = ((HttpServletRequest)req).getRequestURI();
      if(!uri.equals(requestUri)){//如果和自身uri不同就跳過 return false; } Object[] parameters
      = new Object[args.length]; for(int i = 0; i < args.length; i++){ parameters[i]
      = req.getParameter(args[i]); } Object ctl = BeanFactory.getBean(controller);
      Object response = method.invoke(ctl, parameters);
      res.getWriter().println(response.toString()); return true; } }
      *
      接下來在handler包創(chuàng)建HandlerManager類,這個類擁有一個靜態(tài)的MappingHandler集合,這個類的作用是從獲得的所有類中,找到被@controller注解的類,并將controller類中每個被@ReqeustMapping注解的方法封裝成一個MappingHandler,然后把MappingHandler放入靜態(tài)集合中
      public class HandlerManager { public static List<MappingHandler>
      mappingHandlerList = new ArrayList<>(); /** * 處理類文件集合,挑出MappingHandler * @param
      classList */ public static void resolveMappingHandler(List<Class<?>>
      classList){ for(Class<?> cls : classList){
      if(cls.isAnnotationPresent(Controller.class)){//MappingHandler會在controller里面
      parseHandlerFromController(cls);//繼續(xù)從controller中分離出一個個MappingHandler } } }
      private static void parseHandlerFromController(Class<?> cls) {
      //先獲取該controller中所有的方法 Method[] methods = cls.getDeclaredMethods();
      //從中挑選出被RequestMapping注解的方法進行封裝 for(Method method : methods){
      if(!method.isAnnotationPresent(RequestMapping.class)){ continue; } String uri =
      method.getDeclaredAnnotation(RequestMapping.class).value();//拿到RequestMapping定義的uri
      List<String> paramNameList = new ArrayList<>();//保存方法參數(shù)的集合 for(Parameter
      parameter : method.getParameters()){
      if(parameter.isAnnotationPresent(RequestParam.class)){//把有被RequestParam注解的參數(shù)添加入集合
      paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
      } } String[] params = paramNameList.toArray(new
      String[paramNameList.size()]);//把參數(shù)集合轉(zhuǎn)為數(shù)組,用于反射 MappingHandler mappingHandler =
      new MappingHandler(uri, method, cls, params);//反射生成MappingHandler
      mappingHandlerList.add(mappingHandler);//把mappingHandler裝入集合中 } } }
      *
      完成上面四步后,我們在框架啟動的時候就獲得了一個MappingHandler集合,當請求來到時,我們只要根據(jù)請求的uri從集合中找到對應(yīng)的MappingHandler,就可以通過反射調(diào)用對應(yīng)的處理方法,到此也就完成了框架請求分發(fā)的功能。
      控制反轉(zhuǎn)和依賴注入

      完成了請求分發(fā)功能后,進一步想這么一個問題:

      假設(shè)現(xiàn)在處理一個請求需要創(chuàng)建A,B,C三個對象,而
      A 有個字段 D
      B 有個字段 D
      C 有個字段 B

      如果按照順序創(chuàng)建ABC的話,
      首先要創(chuàng)建一個D,然后創(chuàng)建一個A;
      接著先創(chuàng)建一個D,然后創(chuàng)建一個B;
      接著先創(chuàng)建一個D,然后創(chuàng)建一個B,才能創(chuàng)建出一個C
      總共創(chuàng)建了一個A,兩個B,一個C,三個D。

      上述是我們編寫程序的一方創(chuàng)建對象的方式,可以看到由于對象不能被重復(fù)引用,導(dǎo)致創(chuàng)建了大量重復(fù)對象。

      為了解決這個問題,spring提出了bean這么個概念,你可以把一個bean理解為一個對象,但是他對比普通的對象有如下特點:

      * 不像普通對象一樣朝生暮死,聲明周期較長
      * 在整個虛擬機內(nèi)可見,不像普通對象只在某個代碼塊中可見
      * 維護成本高,以單例形式存在

      為了制作出上述的bean,我們得有個bean工廠,bean工廠的原理也很簡單:在框架初始化的時候創(chuàng)建相關(guān)的bean(也可以在用到的時候創(chuàng)建),當需要使用bean的時候直接從工廠中拿。也就是我們把創(chuàng)建對象的權(quán)力交給框架,這就是
      控制反轉(zhuǎn)

      有了bean工廠后按順序創(chuàng)建ABC的過程如下:
      首先創(chuàng)建一個D,把D放入工廠,然后創(chuàng)建一個A,把A放入工廠;
      接著從工廠拿出一個D,創(chuàng)建一個B,把B也放入工廠;
      接著從工廠拿出一個B,創(chuàng)建一個C,把C也放入工廠;
      總共創(chuàng)建了一個A,一個B,一個C,一個D
      達到了對象重復(fù)利用的目的

      至于創(chuàng)建出一個D,然后把D設(shè)置為A的一個字段這么個過程,叫做依賴注入

      所以控制反轉(zhuǎn)和依賴注入的概念其實很好理解,控制反轉(zhuǎn)是一種思想,而依賴注入是控制反轉(zhuǎn)的一種具體實現(xiàn)。

      實操

      * 首先在bean包下創(chuàng)建@Bean和@AutoWired兩個注解,同樣是用于框架解析類的。
      *
      接著在bean包下創(chuàng)建BeanFactory,BeanFactory要能提供一個根據(jù)類獲取實例的功能,這就要求他要有一個靜態(tài)的getBean()方法,和一個保存Bean的映射集合。
      *
      為了初始化Bean,要有一個根據(jù)類文件集合解析出bean的方法。該方法會遍歷集合中所有的類,把有注解的,屬于bean的類提取出來,創(chuàng)建該類的對象并放到靜態(tài)集合中。
      *
      在這里有個有意思的點——按什么順序創(chuàng)建bean?在本文給出的源碼中,用了一個循環(huán)來創(chuàng)建bean,如果該bean沒有依賴其他的bean就直接創(chuàng)建,如果有依賴其他bean就看其他bean有沒被創(chuàng)建出來,如果沒有就跳過當前的bean,如果有就創(chuàng)建當前的bean。
      * 在循環(huán)創(chuàng)建bean的過程中可能出現(xiàn)一種bean之間相互依賴的現(xiàn)象,源碼中暫時對這種現(xiàn)象拋出異常,沒作處理。 public class
      BeanFactory { //保存Bean實例的映射集合 private static Map<Class<?>, Object> classToBean
      = new ConcurrentHashMap<>(); /** * 根據(jù)class類型獲取bean * @param cls * @return */
      public static Object getBean(Class<?> cls){ return classToBean.get(cls); } /**
      * 初始化bean工廠 * @param classList 需要一個.class文件集合 * @throws Exception */ public
      static void initBean(List<Class<?>> classList) throws Exception {
      //先創(chuàng)建一個.class文件集合的副本 List<Class<?>> toCreate = new ArrayList<>(classList);
      //循環(huán)創(chuàng)建bean實例 while(toCreate.size() != 0){ int remainSize =
      toCreate.size();//記錄開始時集合大小,如果一輪結(jié)束后大小沒有變證明有相互依賴 for(int i = 0; i <
      toCreate.size(); i++){//遍歷創(chuàng)建bean,如果失敗就先跳過,等下一輪再創(chuàng)建
      if(finishCreate(toCreate.get(i))){ toCreate.remove(i); } } if(toCreate.size()
      == remainSize){//有相互依賴的情況先拋出異常 throw new Exception("cycle dependency!"); } } }
      private static boolean finishCreate(Class<?> cls) throws
      IllegalAccessException, InstantiationException {
      //創(chuàng)建的bean實例僅包括Bean和Controller注釋的類 if(!cls.isAnnotationPresent(Bean.class) &&
      !cls.isAnnotationPresent(Controller.class)){ return true; } //先創(chuàng)建實例對象 Object
      bean = cls.newInstance(); //看看實例對象是否需要執(zhí)行依賴注入,注入其他bean for(Field field :
      cls.getDeclaredFields()){ if(field.isAnnotationPresent(AutoWired.class)){
      Class<?> fieldType = field.getType(); Object reliantBean =
      BeanFactory.getBean(fieldType); if(reliantBean == null){//如果要注入的bean還未被創(chuàng)建就先跳過
      return false; } field.setAccessible(true); field.set(bean, reliantBean); } }
      classToBean.put(cls, bean); return true; } }
      * 有了bean工廠之后,凡是用到bean的地方都能直接通過bean工廠拿了
      *
      最后我們可以寫一個小Demo測試一下自己的框架是否能正確地處理請求完成響應(yīng)。相信整個迷你框架擼下來,Spring的核心功能,以及控制反轉(zhuǎn),依賴控制等名詞在你腦海中不再只是概念,而是一行行清晰的代碼了。

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          热久久在线 | japanese少妇高潮呻吟 | 摸少妇的奶她呻吟不断爽视频 | 操逼网站在线免费观看 | 台湾精品一区二区三区 | 日夜激情电影 | 国产gay男性玩奴sm调教小蓝 | 免费看涩涩视频软件 | 欧美一级大胆视频 | 大尺度床戏无遮观看免费软件 |