使用springmvc運(yùn)行流程分析,手寫spring框架嘗試
該文章主要是分析Springmvc啟動的流程(配置階段、初始化階段和運(yùn)行階段),可以讓自己對spring框架有更深一層的理解。對框架比較感興趣的朋友都可以了解閱讀下,對于我所描述的內(nèi)容有錯(cuò)誤的還望能不吝指出。
對于springmvc中的整個(gè)流程我個(gè)人把他分為這幾個(gè)階段,包括個(gè)人手寫的spring也是參照此按階段實(shí)現(xiàn):
1.配置階段

根據(jù)web.xml ,先定義DispatcherServlet并且定義該sevlet傳入的參數(shù)和路徑。
2.初始化階段

初始化階段中又可以分為IOC、DI和MVC階段:
- (1)IOC:初始化配置文件和IOC容器,掃描配置的包下的類,通過反射機(jī)制將需要實(shí)例化的類放入IOC容器,既將帶有spring注解的類進(jìn)行實(shí)例化后存放到 IOC 容器中。IOC容器的實(shí)質(zhì)就是一個(gè)集合;
- (2)DI:DI階段(其實(shí)就是依賴注入)。對需要賦值的實(shí)例屬性進(jìn)行賦值(一般較多都是處理帶有注解的@Autowrized的屬性)
- (3)MVC:構(gòu)造出HandlerMapping集合,主要作用就是用于存放對外公開的API和Method之間的關(guān)系,一個(gè)API一般會對應(yīng)一個(gè)可執(zhí)行的Method.
3.運(yùn)行階段

運(yùn)行階段中,當(dāng)接受到一個(gè)url后,會到HandleMapping集合中,找到對應(yīng)Method、通過反射機(jī)制去執(zhí)行invoker,再返回結(jié)果給調(diào)用方。
這樣就大體完成了springmvc整個(gè)運(yùn)行階段,所描述的都僅為個(gè)人觀點(diǎn),如果有誤請?jiān)谠u論中指出。
其整體流程可以參照下圖:

接下來就來嘗試手寫一個(gè)類似springmvc的框架了,這個(gè)手寫的過程還是相當(dāng)有成就感的!
1.創(chuàng)建一個(gè)空的JavaWeb工程,引入依賴,其實(shí)因?yàn)槲覀兪且謱憇pring,所以基本不需要什么外部的依賴工具,只需要導(dǎo)入servlet-api即可,如下:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
2.根據(jù)上述的流程描述,接下來就是對web.xml進(jìn)行配置:
<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其實(shí)就是個(gè)人自定義一個(gè)作用與spring中DispatcherServlet相同的Servlet,此處先創(chuàng)建一個(gè)空的CwDispatcherServlet,繼承 javax.servlet.http.HttpServlet即可,具體實(shí)現(xiàn)后面會描述。
此處因?yàn)槭鞘謱憇pring的部分功能,所以配置也不用寫太多,此處僅拿一個(gè)包掃描的配置(scanPackage),各位少俠可自行拓展。
CwDispatcherServlet中初始化的配置文件application.properties內(nèi)容如下:
scanPackage=com.wangcw
3.相信spring中又一部分注解都是大家比較熟悉的,接下來我們先從這幾個(gè)注解著手吧。(此處就不指出各個(gè)注解的作用了,相信百度上已經(jīng)很多了)
| spring注解 | 自定義注解 |
| @Controller | @CwController |
| @Autowired | @CwAutowired |
| @RequestMapping | @CwRequestMapping |
| @RequestParam | @CwRequestParam |
| @Service | @CwService |
然后實(shí)現(xiàn)下各個(gè)自定義的注解,直接貼代碼:
/*
* 創(chuàng)建一個(gè)類似@Controller作用的注解類
*/
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwController {
String value() default "";
}
/*
* 創(chuàng)建一個(gè)類似@Autowried作用的注解類
*/
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwAutowried {
String value() default "";
}
/*
* 創(chuàng)建一個(gè)類似@RequestMapping作用的注解類
*/
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwRequestMapping {
String value() default "";
}
/*
* 創(chuàng)建一個(gè)類似@RequsetParam作用的注解類
*/
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwRequestParam {
String value() default "";
}
/*
* 創(chuàng)建一個(gè)類似@Service作用的注解類
*/
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwService {
String value() default "";
}
4.創(chuàng)建一個(gè)簡單的控制層和業(yè)務(wù)層交互 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層已經(jīng)把我們上述的自定注解都使用上去了,接下來我們開始手寫spring的核心功能了,也就是實(shí)現(xiàn)CwDispatcherServlet.java這個(gè)HttpServlet的子類。
首先需要重寫父類中的init方法,因?yàn)槲覀円贗nit過程中實(shí)現(xiàn)出跟spring一樣的效果。
理一理init()過程中都需要做哪些事情呢?整理了一下init()中主要需要以下幾步操作
@Override
public void init(ServletConfig config) {
/* 1.加載配置文件*/
doLoadConfig(config.getInitParameter("contextConfigLocation"));
/* 2.掃描scanPackage配置的路徑下所有相關(guān)的類*/
doScanner(contextConfig.getProperty("scanPackage"));
/* 3.初始化所有相關(guān)聯(lián)的實(shí)例,放入IOC容器中*/
doInstance();
/*4.實(shí)現(xiàn)自動依賴注入 DI*/
doAutowired();
/*5.初始化HandlerMapping */
initHandlerMapping();
}
第一步很簡單,在類中定義一個(gè)Properties實(shí)例,用于存放Servlet初始化的配置文件。導(dǎo)入配置代碼略過,IO常規(guī)讀寫即可。
private Properties contextConfig = new Properties();
第二步通過上面獲取到的配置,取到需要掃描的包路徑,然后在根據(jù)路徑找到對應(yīng)文件夾,做一個(gè)遞歸掃描即可。將掃描到的文件名去除后綴,保存到一個(gè)集合中,那么該集合就存放了包下所有類的類名。
String scanFileDir = contextConfig.getProperty("scanPackage");
/* 用于存放掃描到的類 */
private List<String> classNames = new ArrayList<String>();
/*掃描獲取到對應(yīng)的class名,便于后面反射使用*/
String className = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(className);
第三步就是IOC階段,簡而言之就是對上面集合中所有的類進(jìn)行遍歷,并且創(chuàng)建一個(gè)IOC容器,將帶有@CwController和@CwService的類置于容器內(nèi)。
/* 創(chuàng)建一個(gè)IOC容器 */
private Map<String, Object> IOC = new HashMap<String, Object>();
for (String classNme : classNames){
if( 對加了 @CwController 注解的類進(jìn)行初始化){
/* 對于初始化的類還需要放入IOC容器,
對于存入IOC的實(shí)例,key值是有一定規(guī)則的,默認(rèn)類名首字母小寫;*/
/* toLowerFirstCase是自定義的一個(gè)工具方法,用于將傳入的字符串首字母小寫 */
String beanName = toLowerFirstCase(clazz.getSimpleName());
IOC.put(beanName, clazz.newInstance());
} else if (對加了 @CwService 注解的類進(jìn)行初始化){
/* 對于存入IOC的實(shí)例,key值是有一定規(guī)則的,而Service層的規(guī)則相對上面更復(fù)雜一些,因?yàn)樽⒔饪梢杂凶远x實(shí)例名,并且可能是接口實(shí)現(xiàn)類 */
IOC.put(beanName, instance);
} else {
//對于掃描到的沒有注解的類,忽略初始化行為
continue;
}
}
第四步是DI操作,將IOC容器中需要賦值的實(shí)例屬性進(jìn)行賦值,即帶有Autowired注解的實(shí)例屬性。偽代碼如下:
/*遍歷IOC中的所有實(shí)例*/
for(Map.Entry<String, Object> entry : IOC.entrySet()){
/* 使用getDeclaredFields暴力反射 */
Field [] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields){
/*1.判斷屬性是否有加注解@CwAutowried.對于有注解的屬性才需要賦值*/
....
/*屬性授權(quán)*/
field.setAccessible(true);
field.set(entry.getValue(), IOC.get(beanName));
}
}
第五步要處理Controller層的Method與請求url的匹配關(guān)系,讓請求能準(zhǔn)確的請求到對應(yīng)的url。篇幅問題,此處還是上傳偽代碼。
/* 創(chuàng)建HandlerMapping存放url,method的匹配關(guān)系
其中類Handler是我自己定義的一個(gè)利用正則去匹配url和method,
只要用戶傳入url,Handler就可以響應(yīng)出其對應(yīng)的method*/
private List<Handler> handlerMapping = new ArrayList<Handler>();
/* 遍歷IOC容器 */
for (Map.Entry<String, Object> entry : IOC.entrySet()){
Class<?> clazz = entry.getValue().getClass();
/* 只對帶有CwController注解的類進(jìn)行處理 */
定義一個(gè)url,由帶有CwController的實(shí)例類上的@CwRequestMapping注解的值和Method上@CwRequestMapping注解的值組成
/* (1).判斷類上是否有CwRequestMapping注解 ,進(jìn)行拼接 url*/
/* (2).遍歷實(shí)例下每個(gè)Method,并且需要判斷該方法是否有【 @CwRequestMapping 】注解,拼接url*/
/* 最后將匹配關(guān)系以正則的形式,放到HandlerMapping集合中 */
String regex = (url);
Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(pattern,method));
}
到這里就基本完成了springmvc的初始化階段,之后的工作就是重寫一下CwDispatcherServlet.java父類的doGet()/doPost()方法。根據(jù)request中的URI和參數(shù)來執(zhí)行對應(yīng)的Method,并且響應(yīng)結(jié)果。
/* 利用反射執(zhí)行其所匹配的方法 */
handler.method.invoke(handler.controller, paramValues);
到此整個(gè)步驟就完成了,此時(shí)可以愉快的啟動項(xiàng)目,并訪問對應(yīng)的url進(jìn)行測試了。
根據(jù)上面Controller定義的方法可以知道其匹配的url為 : /demo/query 和 /demo/add,并且有使用@CwRequestParam注解定義了其各個(gè)參數(shù)的名稱。
測試結(jié)果如下:
http://localhost:8080/spring/demo/query?name=James

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

再來測試個(gè)url,是controller中沒有聲明出@CwRequestMapping注解的,看看結(jié)果。
http://localhost:8080/spring/demo/testNoUrl

springMVC介紹以及執(zhí)行流程
什么是SpringMVC?
SpringMVC是一個(gè)實(shí)現(xiàn)了MVC設(shè)計(jì)模式的輕量級web層框架,使用起來簡單方便。
SpringMVC的優(yōu)勢是什么?
1、清晰的角色劃分:
- 前端控制器(DispatcherServlet)
- 請求到處理器映射(HandlerMapping)
- 處理器適配器(HandlerAdapter)
- 視圖解析器(ViewResolver)
- 處理器或頁面控制器(Controller)
- 驗(yàn)證器( Validator)
- 命令對象(Command 請求參數(shù)綁定到的對象就叫命令對象)
- 表單對象(Form Object 提供給表單展示和提交到的對象就叫表單對象)。
2、分工明確,而且擴(kuò)展點(diǎn)相當(dāng)靈活,可以很容易擴(kuò)展,雖然幾乎不需要。
3、由于命令對象就是一個(gè) POJO,無需繼承框架特定 API,可以使用命令對象直接作為業(yè)務(wù)對象。
4、和 Spring 其他框架無縫集成,是其它 Web 框架所不具備的。
5、可適配,通過 HandlerAdapter 可以支持任意的類作為處理器。
6、可定制性, HandlerMapping、 ViewResolver 等能夠非常簡單的定制。
7、功能強(qiáng)大的數(shù)據(jù)驗(yàn)證、格式化、綁定機(jī)制。
8、利用 Spring 提供的 Mock 對象能夠非常簡單的進(jìn)行 Web 層單元測試。
9、本地化、主題的解析的支持,使我們更容易進(jìn)行國際化和主題的切換。
10、強(qiáng)大的 JSP 標(biāo)簽庫,使 JSP 編寫更容易。
還有比如RESTful風(fēng)格的支持、簡單的文件上傳、約定大于配置的契約式編程支持、基于注解的零配置支持等等。
與Struts2的對比:
共同點(diǎn):都是基于MVC設(shè)計(jì)模式的表現(xiàn)層框架,底層實(shí)現(xiàn)都離不開原始的Servlet,處理請求的機(jī)制都是一個(gè)核心控制器;
區(qū)別:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法設(shè)計(jì)的,而 Struts2 是基于類, Struts2 每次執(zhí)行都會創(chuàng)建一個(gè)動作類。所以 Spring MVC 會稍微比 Struts2 快些。
相比來說,SpringMVC已經(jīng)全面超越了Struts2。
執(zhí)行流程:
DispatcherServlet: 是整個(gè)springmvc框架的核心。
前端控制器/核心控制器:所有的請求和響應(yīng)都由此控制器進(jìn)行分配。
前端控制器的所有工作都是基于組件完成的:
三大組件:
HandlerMapping: 它負(fù)責(zé)根據(jù)客戶端的請求尋找對應(yīng)的hadler,找到以后把尋找的handler返回給DispatcherServlet;HandlerAdapter:它負(fù)責(zé)執(zhí)行尋找到的Handler的方法,方法執(zhí)行完后將返回值給HandlerAdapter, HandlerAdapter將返回值傳給DispatcherServlet;ViewResolver:它根據(jù)DispatcherServlet指定的返回結(jié)果尋找對應(yīng)的頁面,找到后將結(jié)果返回給DispatcherServlet。DispatcherServlet負(fù)責(zé)最終的響應(yīng),默認(rèn)是轉(zhuǎn)發(fā)的操作。
執(zhí)行流程圖:

圖畫的有些靈魂,但是大致的流程就是這樣,大家可以參考著理解下。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring?Boot?+?Spring?Batch?實(shí)現(xiàn)批處理任務(wù)的詳細(xì)教程
這篇文章主要介紹了Spring?Boot+Spring?Batch實(shí)現(xiàn)批處理任務(wù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
詳解springMVC之與json數(shù)據(jù)交互方法
本篇文章主要介紹了詳解springMVC之與json數(shù)據(jù)交互方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
搭建MyBatis-Plus框架并進(jìn)行數(shù)據(jù)庫增刪改查功能
這篇文章主要介紹了搭建MyBatis-Plus框架并進(jìn)行數(shù)據(jù)庫增刪改查,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
mybatis如何設(shè)置useGeneratedKeys=true
這篇文章主要介紹了mybatis如何設(shè)置useGeneratedKeys=true,具有很好的參考價(jià)值,希望對大家有所幫助。2022-01-01
springBoot的事件機(jī)制GenericApplicationListener用法解析
這篇文章主要介紹了springBoot的事件機(jī)制GenericApplicationListener用法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值的相關(guān)資料2019-09-09
Java對象轉(zhuǎn)json JsonFormat注解
這篇文章主要介紹了Java對象轉(zhuǎn)json JsonFormat注解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05

