springmvc運行流程分析,手寫spring框架嘗試

      該文章主要是分析Springmvc啓動的流程(配置階段、初始化階段和運行階段),可以讓自己對spring框架有更深一層的理解。對框架比較感興趣的朋友都可以瞭解閱讀下,對於我所描述的內容有錯誤的還望能不吝指出。

 

對於springmvc中的整個流程我個人把他分爲這幾個階段,包括個人手寫的spring也是參照此按階段實現:

1.配置階段

根據web.xml ,先定義DispatcherServlet並且定義該sevlet傳入的參數和路徑。

 

2.初始化階段

初始化階段中又可以分爲IOC、DI和MVC階段:

        (1)IOC:初始化配置文件和IOC容器,掃描配置的包下的類,通過反射機制將需要實例化的類放入IOC容器,既將帶有spring註解的類進行實例化後存放到 IOC 容器中。IOC容器的實質就是一個集合;

        (2)DI:DI階段(其實就是依賴注入)。對需要賦值的實例屬性進行賦值(一般較多都是處理帶有註解的@Autowrized的屬性)

        (3)MVC:構造出HandlerMapping集合,主要作用就是用於存放對外公開的API和Method之間的關係,一個API一般會對應一個可執行的Method.

 

3.運行階段

運行階段中,當接受到一個url後,會到HandleMapping集合中,找到對應Method、通過反射機制去執行invoker,再返回結果給調用方。

這樣就大體完成了springmvc整個運行階段,所描述的都僅爲個人觀點,如果有誤請在評論中指出。

 其整體流程可以參照下圖:

 

 

接下來就來嘗試手寫一個類似springmvc的框架了,這個手寫的過程還是相當有成就感的!

 

1.創建一個空的JavaWeb工程,引入依賴,其實因爲我們是要手寫spring,所以基本不需要什麼外部的依賴工具,只需要導入servlet-api即可,如下:

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
 </dependency>

2.根據上述的流程描述,接下來就是對web.xml進行配置:

     <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>com.wangcw.cwframework.sevlet.CwDispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/*</url-pattern>
     </servlet-mapping>

 

對於配置中的CwDispatcherServlet其實就是個人自定義一個作用與spring中DispatcherServlet相同的Servlet,此處先創建一個空的CwDispatcherServlet,繼承 javax.servlet.http.HttpServlet即可,具體實現後面會描述。

此處因爲是手寫spring的部分功能,所以配置也不用寫太多,此處僅拿一個包掃描的配置(scanPackage),各位少俠可自行拓展。

CwDispatcherServlet中初始化的配置文件application.properties內容如下:

 scanPackage=com.wangcw

 

3.相信spring中又一部分註解都是大家比較熟悉的,接下來我們先從這幾個註解着手吧。(此處就不指出各個註解的作用了,相信百度上已經很多了)

 

spring註解 自定義註解
@Controller @CwController
@Autowired @CwAutowired
@RequestMapping @CwRequestMapping
@RequestParam @CwRequestParam
@Service @CwService

然後實現下各個自定義的註解,直接貼代碼:

/*
* 創建一個類似@Controller作用的註解類
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE})         
@Retention(RetentionPolicy.RUNTIME)  
@Documented
public @interface CwController {

    String value() default "";
}
/*
* 創建一個類似@Autowried作用的註解類
*/

import java.lang.annotation.*;

@Target({ElementType.FIELD})        
@Retention(RetentionPolicy.RUNTIME)  
@Documented
public @interface CwAutowried {

    String value() default "";
}
/*
* 創建一個類似@RequestMapping作用的註解類
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})          
@Retention(RetentionPolicy.RUNTIME) 
@Documented
public @interface CwRequestMapping {

    String value() default "";
}
/*
* 創建一個類似@RequsetParam作用的註解類
*/

import java.lang.annotation.*;

@Target({ElementType.PARAMETER})        
@Retention(RetentionPolicy.RUNTIME)  
@Documented
public @interface CwRequestParam {

    String value() default "";
}

 

/*
* 創建一個類似@Service作用的註解類
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE})         
@Retention(RetentionPolicy.RUNTIME) 
@Documented
public @interface CwService {

    String value() default "";
}

4.創建一個簡單的控制層和業務層交互 Demo,加上自定的註解,具體註解的功能,後面贅述。

Controller.java

@CwController  
@CwRequestMapping("/demo")
public class Controller {  
  
    @CwAutowried
    private Service service;  
  
   @CwRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,@CwRequestParam("name") String name) throws IOException {  
            resp.getWriter().write(service.query(name));  
    }  
  
    @CwRequestMapping("/add") 
    public void add(HttpServletRequest req, HttpServletResponse resp, @CwRequestParam("a") Integer a, @CwRequestParam("b") Integer b) throws IOException {  
        resp.getWriter().write("a+b="+(a+b));  
    }  
}  

 

Service.java

 

public interface Service {

         String query(String name);

}

ServiceImpl.java

@CwService
public class ServiceImpl implements Service{  
  
    @Override  
    public String query(String name) {  
  
        return "I am "+name;  
    }  
}  

 

 

5.上面的controller層和service層已經把我們上述的自定註解都使用上去了,接下來我們開始手寫spring的核心功能了,也就是實現CwDispatcherServlet.java這個HttpServlet的子類。

 

 

首先需要重寫父類中的init方法,因爲我們要在Init過程中實現出跟spring一樣的效果。

理一理init()過程中都需要做哪些事情呢?整理了一下init()中主要需要以下幾步操作

 @Override
    public void init(ServletConfig config)  {

        /* 1.加載配置文件*/
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
		
        /* 2.掃描scanPackage配置的路徑下所有相關的類*/
        doScanner(contextConfig.getProperty("scanPackage"));
		
        /* 3.初始化所有相關聯的實例,放入IOC容器中*/
        doInstance();
		
        /*4.實現自動依賴注入 DI*/
        doAutowired();
		
        /*5.初始化HandlerMapping  */
        initHandlerMapping();

    }

第一步很簡單,在類中定義一個Properties實例,用於存放Servlet初始化的配置文件。導入配置代碼略過,IO常規讀寫即可。

 private Properties contextConfig = new Properties();

第二步通過上面獲取到的配置,取到需要掃描的包路徑,然後在根據路徑找到對應文件夾,做一個遞歸掃描即可。將掃描到的文件名去除後綴,保存到一個集合中,那麼該集合就存放了包下所有類的類名。

String  scanFileDir = contextConfig.getProperty("scanPackage");
 /* 用於存放掃描到的類 */
    private List<String> classNames = new ArrayList<String>();
	 /*掃描獲取到對應的class名,便於後面反射使用*/
                String  className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);

 

第三步就是IOC階段,簡而言之就是對上面集合中所有的類進行遍歷,並且創建一個IOC容器,將帶有@CwController和@CwService的類置於容器內。(爲了防止篇幅過長,所以沒有上傳所有代碼,僅以思想爲主,後續會把完整代碼上傳到CSDN資源和我的github)

 

 

 /* 創建一個IOC容器 */
    private Map<String, Object> IOC = new HashMap<String, Object>();
	for (String classNme : classNames){
                if( 對加了 @CwController 註解的類進行初始化){
                       /* 對於初始化的類還需要放入IOC容器,
                          對於存入IOC的實例,key值是有一定規則的,默認類名首字母小寫;*/    
			/* toLowerFirstCase是自定義的一個工具方法,用於將傳入的字符串首字母小寫 */
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    IOC.put(beanName, clazz.newInstance());
                } else if (對加了 @CwService 註解的類進行初始化){
                    /* 對於存入IOC的實例,key值是有一定規則的,而Service層的規則相對上面更復雜一些,因爲註解可以有自定義實例名,並且可能是接口實現類 */
		    IOC.put(beanName, instance);
                    
                } else {
                    //對於掃描到的沒有註解的類,忽略初始化行爲
                    continue;
                }
            }

 

第四步是DI操作,將IOC容器中需要賦值的實例屬性進行賦值,即帶有Autowired註解的實例屬性。僞代碼如下:

 

		/*遍歷IOC中的所有實例*/
        for(Map.Entry<String, Object> entry : IOC.entrySet()){
           /* 使用getDeclaredFields暴力反射 */
           Field [] fields = entry.getValue().getClass().getDeclaredFields();
           for (Field field : fields){
               /*1.判斷屬性是否有加註解@CwAutowried.對於有註解的屬性才需要賦值*/
				....
				
               /*屬性授權*/
               field.setAccessible(true);
			   field.set(entry.getValue(), IOC.get(beanName));
           }
        }

第五步要處理Controller層的Method與請求url的匹配關係,讓請求能準確的請求到對應的url。篇幅問題,此處還是上傳僞代碼。

 /* 創建HandlerMapping存放url,method的匹配關係 
		其中類Handler是我自己定義的一個利用正則去匹配url和method,
		只要用戶傳入url,Handler就可以響應出其對應的method*/
		private List<Handler> handlerMapping = new ArrayList<Handler>();
 
	 /* 遍歷IOC容器 */
	 for (Map.Entry<String, Object> entry : IOC.entrySet()){
			   Class<?> clazz = entry.getValue().getClass();
			   /* 只對帶有CwController註解的類進行處理 */
					
					定義一個url,由帶有CwController的實例類上的@CwRequestMapping註解的值和Method上@CwRequestMapping註解的值組成
					
					   /* (1).判斷類上是否有CwRequestMapping註解 ,進行拼接 url*/
					 

					   /* (2).遍歷實例下每個Method,並且需要判斷該方法是否有【 @CwRequestMapping 】註解,拼接url*/
			
				 

				   /* 最後將匹配關係以正則的形式,放到HandlerMapping集合中 */
				   String regex = (url);
				   Pattern pattern = Pattern.compile(regex);
				   handlerMapping.add(new Handler(pattern,method));
			   }

 

到這裏就基本完成了springmvc的初始化階段,之後的工作就是重寫一下CwDispatcherServlet.java父類的doGet()/doPost()方法。根據request中的URI和參數來執行對應的Method,並且響應結果。

 

/* 利用反射執行其所匹配的方法 */
        handler.method.invoke(handler.controller, paramValues);

到此整個步驟就完成了,此時可以愉快的啓動項目,並訪問對應的url進行測試了。

根據上面Controller定義的方法可以知道其匹配的url爲 : /demo/query /demo/add,並且有使用@CwRequestParam註解定義了其各個參數的名稱。

 

測試結果如下:

http://localhost:8080/spring/demo/query?name=James

 

http://localhost:8080/spring/demo/add?a=222222&b=444444

 

再來測試個url,是controller中沒有聲明出@CwRequestMapping註解的,看看結果。

http://localhost:8080/spring/demo/testNoUrl

 

 

注:文章中很多內容都是使用僞代碼進行實現的,主要原因是怕文章太長,看着太枯燥。手寫的spring整個工程已經在整理了,後續會發布到github,到時候會把鏈接補上。需要的也可以留下郵箱。歡迎大佬們指出有誤的地方。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章