使用springmvc運(yùn)行流程分析,手寫spring框架嘗試
該文章主要是分析Springmvc啟動(dòng)的流程(配置階段、初始化階段和運(yùn)行階段),可以讓自己對(duì)spring框架有更深一層的理解。對(duì)框架比較感興趣的朋友都可以了解閱讀下,對(duì)于我所描述的內(nèi)容有錯(cuò)誤的還望能不吝指出。
對(duì)于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í)就是依賴注入)。對(duì)需要賦值的實(shí)例屬性進(jìn)行賦值(一般較多都是處理帶有注解的@Autowrized的屬性)
- (3)MVC:構(gòu)造出HandlerMapping集合,主要作用就是用于存放對(duì)外公開的API和Method之間的關(guān)系,一個(gè)API一般會(huì)對(duì)應(yīng)一個(gè)可執(zhí)行的Method.
3.運(yùn)行階段
運(yùn)行階段中,當(dāng)接受到一個(gè)url后,會(huì)到HandleMapping集合中,找到對(duì)應(yīng)Method、通過反射機(jī)制去執(zhí)行invoker,再返回結(jié)果給調(diào)用方。
這樣就大體完成了springmvc整個(gè)運(yùn)行階段,所描述的都僅為個(gè)人觀點(diǎn),如果有誤請(qǐng)?jiān)谠u(píng)論中指出。
其整體流程可以參照下圖:
接下來就來嘗試手寫一個(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ù)上述的流程描述,接下來就是對(duì)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>
對(duì)于配置中的CwDispatcherServlet其實(shí)就是個(gè)人自定義一個(gè)作用與spring中DispatcherServlet相同的Servlet,此處先創(chuàng)建一個(gè)空的CwDispatcherServlet,繼承 javax.servlet.http.HttpServlet即可,具體實(shí)現(xiàn)后面會(huì)描述。
此處因?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è)簡(jiǎn)單的控制層和業(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)自動(dòng)依賴注入 DI*/ doAutowired(); /*5.初始化HandlerMapping */ initHandlerMapping(); }
第一步很簡(jiǎn)單,在類中定義一個(gè)Properties實(shí)例,用于存放Servlet初始化的配置文件。導(dǎo)入配置代碼略過,IO常規(guī)讀寫即可。
private Properties contextConfig = new Properties();
第二步通過上面獲取到的配置,取到需要掃描的包路徑,然后在根據(jù)路徑找到對(duì)應(yīng)文件夾,做一個(gè)遞歸掃描即可。將掃描到的文件名去除后綴,保存到一個(gè)集合中,那么該集合就存放了包下所有類的類名。
String scanFileDir = contextConfig.getProperty("scanPackage"); /* 用于存放掃描到的類 */ private List<String> classNames = new ArrayList<String>(); /*掃描獲取到對(duì)應(yīng)的class名,便于后面反射使用*/ String className = scanPackage + "." + file.getName().replace(".class", ""); classNames.add(className);
第三步就是IOC階段,簡(jiǎn)而言之就是對(duì)上面集合中所有的類進(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( 對(duì)加了 @CwController 注解的類進(jìn)行初始化){ /* 對(duì)于初始化的類還需要放入IOC容器, 對(duì)于存入IOC的實(shí)例,key值是有一定規(guī)則的,默認(rèn)類名首字母小寫;*/ /* toLowerFirstCase是自定義的一個(gè)工具方法,用于將傳入的字符串首字母小寫 */ String beanName = toLowerFirstCase(clazz.getSimpleName()); IOC.put(beanName, clazz.newInstance()); } else if (對(duì)加了 @CwService 注解的類進(jìn)行初始化){ /* 對(duì)于存入IOC的實(shí)例,key值是有一定規(guī)則的,而Service層的規(guī)則相對(duì)上面更復(fù)雜一些,因?yàn)樽⒔饪梢杂凶远x實(shí)例名,并且可能是接口實(shí)現(xiàn)類 */ IOC.put(beanName, instance); } else { //對(duì)于掃描到的沒有注解的類,忽略初始化行為 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.對(duì)于有注解的屬性才需要賦值*/ .... /*屬性授權(quán)*/ field.setAccessible(true); field.set(entry.getValue(), IOC.get(beanName)); } }
第五步要處理Controller層的Method與請(qǐng)求url的匹配關(guān)系,讓請(qǐng)求能準(zhǔn)確的請(qǐng)求到對(duì)應(yīng)的url。篇幅問題,此處還是上傳偽代碼。
/* 創(chuàng)建HandlerMapping存放url,method的匹配關(guān)系 其中類Handler是我自己定義的一個(gè)利用正則去匹配url和method, 只要用戶傳入url,Handler就可以響應(yīng)出其對(duì)應(yīng)的method*/ private List<Handler> handlerMapping = new ArrayList<Handler>(); /* 遍歷IOC容器 */ for (Map.Entry<String, Object> entry : IOC.entrySet()){ Class<?> clazz = entry.getValue().getClass(); /* 只對(duì)帶有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í)行對(duì)應(yīng)的Method,并且響應(yīng)結(jié)果。
/* 利用反射執(zhí)行其所匹配的方法 */ handler.method.invoke(handler.controller, paramValues);
到此整個(gè)步驟就完成了,此時(shí)可以愉快的啟動(dòng)項(xiàng)目,并訪問對(duì)應(yīng)的url進(jìn)行測(cè)試了。
根據(jù)上面Controller定義的方法可以知道其匹配的url為 : /demo/query 和 /demo/add,并且有使用@CwRequestParam注解定義了其各個(gè)參數(shù)的名稱。
測(cè)試結(jié)果如下:
http://localhost:8080/spring/demo/query?name=James
http://localhost:8080/spring/demo/add?a=222222&b=444444
再來測(cè)試個(gè)url,是controller中沒有聲明出@CwRequestMapping注解的,看看結(jié)果。
http://localhost:8080/spring/demo/testNoUrl
springMVC介紹以及執(zhí)行流程
什么是SpringMVC?
SpringMVC是一個(gè)實(shí)現(xiàn)了MVC設(shè)計(jì)模式的輕量級(jí)web層框架,使用起來簡(jiǎn)單方便。
SpringMVC的優(yōu)勢(shì)是什么?
1、清晰的角色劃分:
- 前端控制器(DispatcherServlet)
- 請(qǐng)求到處理器映射(HandlerMapping)
- 處理器適配器(HandlerAdapter)
- 視圖解析器(ViewResolver)
- 處理器或頁面控制器(Controller)
- 驗(yàn)證器( Validator)
- 命令對(duì)象(Command 請(qǐng)求參數(shù)綁定到的對(duì)象就叫命令對(duì)象)
- 表單對(duì)象(Form Object 提供給表單展示和提交到的對(duì)象就叫表單對(duì)象)。
2、分工明確,而且擴(kuò)展點(diǎn)相當(dāng)靈活,可以很容易擴(kuò)展,雖然幾乎不需要。
3、由于命令對(duì)象就是一個(gè) POJO,無需繼承框架特定 API,可以使用命令對(duì)象直接作為業(yè)務(wù)對(duì)象。
4、和 Spring 其他框架無縫集成,是其它 Web 框架所不具備的。
5、可適配,通過 HandlerAdapter 可以支持任意的類作為處理器。
6、可定制性, HandlerMapping、 ViewResolver 等能夠非常簡(jiǎn)單的定制。
7、功能強(qiáng)大的數(shù)據(jù)驗(yàn)證、格式化、綁定機(jī)制。
8、利用 Spring 提供的 Mock 對(duì)象能夠非常簡(jiǎn)單的進(jìn)行 Web 層單元測(cè)試。
9、本地化、主題的解析的支持,使我們更容易進(jìn)行國(guó)際化和主題的切換。
10、強(qiáng)大的 JSP 標(biāo)簽庫,使 JSP 編寫更容易。
還有比如RESTful風(fēng)格的支持、簡(jiǎn)單的文件上傳、約定大于配置的契約式編程支持、基于注解的零配置支持等等。
與Struts2的對(duì)比:
共同點(diǎn):都是基于MVC設(shè)計(jì)模式的表現(xiàn)層框架,底層實(shí)現(xiàn)都離不開原始的Servlet,處理請(qǐng)求的機(jī)制都是一個(gè)核心控制器;
區(qū)別:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法設(shè)計(jì)的,而 Struts2 是基于類, Struts2 每次執(zhí)行都會(huì)創(chuàng)建一個(gè)動(dòng)作類。所以 Spring MVC 會(huì)稍微比 Struts2 快些。
相比來說,SpringMVC已經(jīng)全面超越了Struts2。
執(zhí)行流程:
DispatcherServlet: 是整個(gè)springmvc框架的核心。
前端控制器/核心控制器:所有的請(qǐng)求和響應(yīng)都由此控制器進(jìn)行分配。
前端控制器的所有工作都是基于組件完成的:
三大組件:
HandlerMapping
: 它負(fù)責(zé)根據(jù)客戶端的請(qǐng)求尋找對(duì)應(yīng)的hadler,找到以后把尋找的handler返回給DispatcherServlet;HandlerAdapter
:它負(fù)責(zé)執(zhí)行尋找到的Handler的方法,方法執(zhí)行完后將返回值給HandlerAdapter, HandlerAdapter將返回值傳給DispatcherServlet;ViewResolver
:它根據(jù)DispatcherServlet指定的返回結(jié)果尋找對(duì)應(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ì),對(duì)大家的學(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ì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03mybatis如何設(shè)置useGeneratedKeys=true
這篇文章主要介紹了mybatis如何設(shè)置useGeneratedKeys=true,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-01-01springBoot的事件機(jī)制GenericApplicationListener用法解析
這篇文章主要介紹了springBoot的事件機(jī)制GenericApplicationListener用法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值的相關(guān)資料2019-09-09Java對(duì)象轉(zhuǎn)json JsonFormat注解
這篇文章主要介紹了Java對(duì)象轉(zhuǎn)json JsonFormat注解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05