<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          昨晚擼了一個簡易版的 Spring MVC 框架

          共 6180字,需瀏覽 13分鐘

           ·

          2021-10-30 02:42

          想要了解Spring MVC框架的原理,探究框架是如何設(shè)計的,不錯的學(xué)習(xí)方式是閱讀源碼,然后自己手寫一個框架。本文帶領(lǐng)大家簡化的手寫一個Spring MVC框架。
          Spring框架對于Java后端程序員來說再熟悉不過了,以前只知道它用的反射實現(xiàn)的,但了解之后才知道有很多巧妙的設(shè)計在里面。
          如果不看Spring的源碼,你將會失去一次和大師學(xué)習(xí)的機會:它的代碼規(guī)范,設(shè)計思想很值得學(xué)習(xí)。我們程序員大部分人都是野路子,不懂什么叫代碼規(guī)范。寫了一個月的代碼,最后還得其他老司機花3天時間重構(gòu),相信大部分老司機都很頭疼看新手的代碼。

          廢話不多說,我們進入今天的正題,在Web應(yīng)用程序設(shè)計中,MVC模式已經(jīng)被廣泛使用。SpringMVC以DispatcherServlet為核心,負責協(xié)調(diào)和組織不同組件以完成請求處理并返回響應(yīng)的工作,實現(xiàn)了MVC模式。想要實現(xiàn)自己的SpringMVC框架,需要從以下幾點入手:

          • 了解SpringMVC運行流程及九大組件

          • 梳理自己的SpringMVC的設(shè)計思路

          • 實現(xiàn)自己的SpringMVC框架

          一、了解SpringMVC運行流程及九大組件

          1. SpringMVC的運行流程

          ⑴ 用戶發(fā)送請求至前端控制器DispatcherServlet

          ⑵ DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。

          ⑶ 處理器映射器根據(jù)請求url找到具體的處理器,生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。

          ⑷ DispatcherServlet通過HandlerAdapter處理器適配器調(diào)用處理器

          ⑸ 執(zhí)行處理器(Controller,也叫后端控制器)。

          ⑹ Controller執(zhí)行完成返回ModelAndView

          ⑺ HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet

          ⑻ DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

          ⑼ ViewReslover解析后返回具體View

          ⑽ DispatcherServlet對View進行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)

          ⑾ DispatcherServlet響應(yīng)用戶。

          從上面可以看出,DispatcherServlet有接收請求,響應(yīng)結(jié)果,轉(zhuǎn)發(fā)等作用。有了DispatcherServlet之后,可以減少組件之間的耦合度。

          2. SpringMVC的九大組件

          protected?void?initStrategies(ApplicationContext context) {
          ??//用于處理上傳請求。處理方法是將普通的request包裝成MultipartHttpServletRequest,后者可以直接調(diào)用getFile方法獲取File.
          ??initMultipartResolver(context);
          ??//SpringMVC主要有兩個地方用到了Locale:一是ViewResolver視圖解析的時候;二是用到國際化資源或者主題的時候。
          ??initLocaleResolver(context);
          ??//用于解析主題。SpringMVC中一個主題對應(yīng)一個properties文件,里面存放著跟當前主題相關(guān)的所有資源、
          ??//如圖片、css樣式等。SpringMVC的主題也支持國際化,
          ??initThemeResolver(context);
          ??//用來查找Handler的。
          ??initHandlerMappings(context);
          ??//從名字上看,它就是一個適配器。Servlet需要的處理方法的結(jié)構(gòu)卻是固定的,都是以request和response為參數(shù)的方法。
          ??//如何讓固定的Servlet處理方法調(diào)用靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情
          ??initHandlerAdapters(context);
          ??//其它組件都是用來干活的。在干活的過程中難免會出現(xiàn)問題,出問題后怎么辦呢?
          ??//這就需要有一個專門的角色對異常情況進行處理,在SpringMVC中就是HandlerExceptionResolver。
          ??initHandlerExceptionResolvers(context);
          ??//有的Handler處理完后并沒有設(shè)置View也沒有設(shè)置ViewName,這時就需要從request獲取ViewName了,
          ??//如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了。
          ??initRequestToViewNameTranslator(context);
          ??//ViewResolver用來將String類型的視圖名和Locale解析為View類型的視圖。
          ??//View是用來渲染頁面的,也就是將程序返回的參數(shù)填入模板里,生成html(也可能是其它類型)文件。
          ??initViewResolvers(context);
          ??//用來管理FlashMap的,F(xiàn)lashMap主要用在redirect重定向中傳遞參數(shù)。
          ??initFlashMapManager(context);
          }


          二、梳理SpringMVC的設(shè)計思路

          本文只實現(xiàn)自己的@Controller、@RequestMapping、@RequestParam注解起作用,其余SpringMVC功能讀者可以嘗試自己實現(xiàn)。

          ?1、讀取配置? ? ?

          從圖中可以看出,SpringMVC本質(zhì)上是一個Servlet,這個 Servlet 繼承自 HttpServlet。FrameworkServlet負責初始化SpringMVC的容器,并將Spring容器設(shè)置為父容器。

          因為本文只是實現(xiàn)SpringMVC,對于Spring容器不做過多講解(有興趣同學(xué)可以看看我另一篇文章:向spring大佬低頭--大量源碼流出解析)。

          為了讀取web.xml中的配置,我們用到ServletConfig這個類,它代表當前Servlet在web.xml中的配置信息。通過web.xml中加載我們自己寫的MyDispatcherServlet和讀取配置文件。

          2、初始化階段

          在前面我們提到DispatcherServlet的initStrategies方法會初始化9大組件,但是這里將實現(xiàn)一些SpringMVC的最基本的組件而不是全部,按順序包括:

          • 加載配置文件

          • 掃描用戶配置包下面所有的類

          • 拿到掃描到的類,通過反射機制,實例化。并且放到ioc容器中(Map的鍵值對? beanName-bean) beanName默認是首字母小寫

          • 初始化HandlerMapping,這里其實就是把url和method對應(yīng)起來放在一個k-v的Map中,在運行階段取出


          3、運行階段

          ?每一次請求將會調(diào)用doGet或doPost方法,所以統(tǒng)一運行階段都放在doDispatch方法里處理,它會根據(jù)url請求去HandlerMapping中匹配到對應(yīng)的Method,然后利用反射機制調(diào)用Controller中的url對應(yīng)的方法,并得到結(jié)果返回。按順序包括以下功能:
          • 異常的攔截
          • 獲取請求傳入的參數(shù)并處理參數(shù)
          • 通過初始化好的handlerMapping中拿出url對應(yīng)的方法名,反射調(diào)用

          三、實現(xiàn)自己的SpringMVC框架

          工程文件及目錄:
          ? ? ? ? ? ? ? ? ? ? ? ? ? ??
          首先,新建一個maven項目,在pom.xml中導(dǎo)入以下依賴:
          <project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"?xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          ??<modelVersion>4.0.0modelVersion>

          ??<groupId>com.liughgroupId>
          ??<artifactId>liughMVCartifactId>
          ??<version>0.0.1-SNAPSHOTversion>
          ??<packaging>warpackaging>
          ??
          ??<properties>
          ????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          ????<maven.compiler.source>1.8maven.compiler.source>
          ????<maven.compiler.target>1.8maven.compiler.target>
          ????<java.version>1.8java.version>
          ??properties>
          ??
          ??<dependencies>
          ???????<dependency>
          ?????????<groupId>javax.servletgroupId>?
          ???????<artifactId>javax.servlet-apiartifactId>?
          ???????<version>3.0.1version>?
          ???????<scope>providedscope>
          ????dependency>
          ????dependencies>
          project>


          接著,我們在WEB-INF下創(chuàng)建一個web.xml,如下配置:
          xml version="1.0"?encoding="UTF-8"?><web-app?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ??xmlns="http://java.sun.com/xml/ns/javaee"?xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          ??xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
          ??version="3.0">

          ??<servlet>
          ????<servlet-name>MySpringMVCservlet-name>

          ????<servlet-class>com.liugh.servlet.MyDispatcherServletservlet-class>

          ????<init-param>
          ??????<param-name>contextConfigLocationparam-name>
          ??????<param-value>application.propertiesparam-value>
          ????init-param>
          ????<load-on-startup>1load-on-startup>
          ??servlet>
          ??<servlet-mapping>
          ????<servlet-name>MySpringMVCservlet-name>
          ????<url-pattern>/*url-pattern>
          ??servlet-mapping>web-app>


          application.properties文件中只是配置要掃描的包到SpringMVC容器中。
          scanPackage=com.liugh.core
          創(chuàng)建自己的Controller注解,它只能標注在類上面:
          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public?@interface?MyController {
          ??/**
          ?????* 表示給controller注冊別名
          ?????* @return
          ?????*/

          ????String?value() default?"";

          }

          RequestMapping注解,可以在類和方法上:

          @Target({ElementType.TYPE,ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public?@interface?MyRequestMapping {
          ??/**
          ?????* 表示訪問該方法的url
          ?????* @return
          ?????*/

          ????String?value() default?"";

          }

          RequestParam注解,只能注解在參數(shù)上

          @Target(ElementType.PARAMETER)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface?MyRequestParam {
          ??/**
          ?????* 表示參數(shù)的別名,必填
          ?????* @return
          ?????*/

          ????String?value();

          }

          然后創(chuàng)建MyDispatcherServlet這個類,去繼承HttpServlet,重寫init方法、doGet、doPost方法,以及加上我們第二步分析時要實現(xiàn)的功能

          public?class?MyDispatcherServlet extends?HttpServlet{
          ??
          ??private?Properties properties = new?Properties();
          ??private?List<String> classNames = new?ArrayList<>();
          ??private?Map<String, Object> ioc = new?HashMap<>();
          ??private?Map<String, Method> handlerMapping = new??HashMap<>();
          ??private?Map<String, Object> controllerMap =new?HashMap<>();

          ??@Override
          ??public?void?init(ServletConfig config) throws ServletException {
          ????
          ????//1.加載配置文件
          ????doLoadConfig(config.getInitParameter("contextConfigLocation"));
          ????//2.初始化所有相關(guān)聯(lián)的類,掃描用戶設(shè)定的包下面所有的類
          ????doScanner(properties.getProperty("scanPackage"));
          ????//3.拿到掃描到的類,通過反射機制,實例化,并且放到ioc容器中(k-v beanName-bean) beanName默認是首字母小寫
          ????doInstance();
          ????//4.初始化HandlerMapping(將url和method對應(yīng)上)
          ????initHandlerMapping();
          ??}

          ??@Override
          ??protected?void?doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          ????this.doPost(req,resp);
          ??}

          ??@Override
          ??protected?void?doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          ????try?{
          ??????//處理請求
          ??????doDispatch(req,resp);
          ????} catch?(Exception e) {
          ??????resp.getWriter().write("500!! Server Exception");
          ????}

          ??}

          ??private?void?doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
          ????if(handlerMapping.isEmpty()){
          ??????return;
          ????}
          ????
          ????String?url =req.getRequestURI();
          ????String?contextPath = req.getContextPath();
          ????
          ????url=url.replace(contextPath, "").replaceAll("/+", "/");
          ????
          ????if(!this.handlerMapping.containsKey(url)){
          ??????resp.getWriter().write("404 NOT FOUND!");
          ??????return;
          ????}
          ????
          ????Method method =this.handlerMapping.get(url);
          ????
          ????//獲取方法的參數(shù)列表
          ????Class[] parameterTypes = method.getParameterTypes();
          ??
          ????//獲取請求的參數(shù)
          ????Map<String, String[]> parameterMap = req.getParameterMap();
          ????
          ????//保存參數(shù)值
          ????Object?[] paramValues= new?Object[parameterTypes.length];
          ????
          ????//方法的參數(shù)列表
          ????????for?(int i = 0; i????????????//根據(jù)參數(shù)名稱,做某些處理
          ????????????String?requestParam = parameterTypes[i].getSimpleName();
          ????????????
          ????????????
          ????????????if?(requestParam.equals("HttpServletRequest")){
          ????????????????//參數(shù)類型已明確,這邊強轉(zhuǎn)類型
          ??????????????paramValues[i]=req;
          ????????????????continue;
          ????????????}
          ????????????if?(requestParam.equals("HttpServletResponse")){
          ??????????????paramValues[i]=resp;
          ????????????????continue;
          ????????????}
          ????????????if(requestParam.equals("String")){
          ??????????????for?(Entry<String, String[]> param : parameterMap.entrySet()) {
          ???????????????String?value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
          ???????????????paramValues[i]=value;
          ?????????????}
          ????????????}
          ????????}
          ????//利用反射機制來調(diào)用
          ????try?{
          ??????method.invoke(this.controllerMap.get(url), paramValues);//第一個參數(shù)是method所對應(yīng)的實例 在ioc容器中
          ????} catch?(Exception e) {
          ??????e.printStackTrace();
          ????}
          ??}



          ??private?void??doLoadConfig(String?location){
          ????//把web.xml中的contextConfigLocation對應(yīng)value值的文件加載到流里面
          ????InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
          ????try?{
          ??????//用Properties文件加載文件里的內(nèi)容
          ??????properties.load(resourceAsStream);
          ????} catch?(IOException e) {
          ??????e.printStackTrace();
          ????}finally?{
          ??????//關(guān)流
          ??????if(null!=resourceAsStream){
          ????????try?{
          ??????????resourceAsStream.close();
          ????????} catch?(IOException e) {
          ??????????e.printStackTrace();
          ????????}
          ??????}
          ????}
          ????
          ??}
          ??
          ??private?void?doScanner(String?packageName) {
          ????//把所有的.替換成/
          ????URL url =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
          ????File dir = new?File(url.getFile());
          ????for?(File file : dir.listFiles()) {
          ??????if(file.isDirectory()){
          ????????//遞歸讀取包
          ????????doScanner(packageName+"."+file.getName());
          ??????}else{
          ????????String?className =packageName +"."?+file.getName().replace(".class", "");
          ????????classNames.add(className);
          ??????}
          ????}
          ??}
          ??
          ??
          ??
          ??private?void?doInstance() {
          ????if?(classNames.isEmpty()) {
          ??????return;
          ????}
          ????for?(String?className : classNames) {
          ??????try?{
          ????????//把類搞出來,反射來實例化(只有加@MyController需要實例化)
          ????????Class clazz =Class.forName(className);
          ?????????if(clazz.isAnnotationPresent(MyController.class)){
          ??????????ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
          ????????}else{
          ??????????continue;
          ????????}
          ????????
          ????????
          ??????} catch?(Exception e) {
          ????????e.printStackTrace();
          ????????continue;
          ??????}
          ????}
          ??}


          ??private?void?initHandlerMapping(){
          ????if(ioc.isEmpty()){
          ??????return;
          ????}
          ????try?{
          ??????for?(Entry<String, Object> entry: ioc.entrySet()) {
          ????????Classextends
          ?Object> clazz = entry.getValue().getClass();
          ????????if(!clazz.isAnnotationPresent(MyController.class)){
          ??????????continue;
          ????????}
          ????????
          ????????//拼url時,是controller頭的url拼上方法上的url
          ????????String?baseUrl ="";
          ????????if(clazz.isAnnotationPresent(MyRequestMapping.class)){
          ??????????MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
          ??????????baseUrl=annotation.value();
          ????????}
          ????????Method[] methods = clazz.getMethods();
          ????????for?(Method method : methods) {
          ??????????if(!method.isAnnotationPresent(MyRequestMapping.class)){
          ????????????continue;
          ??????????}
          ??????????MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
          ??????????String?url = annotation.value();
          ??????????
          ??????????url =(baseUrl+"/"+url).replaceAll("/+", "/");
          ??????????handlerMapping.put(url,method);
          ??????????controllerMap.put(url,clazz.newInstance());
          ??????????System.out.println(url+","+method);
          ????????}
          ????????
          ??????}
          ??????
          ????} catch?(Exception e) {
          ??????e.printStackTrace();
          ????}
          ????
          ??}


          ??/**
          ???* 把字符串的首字母小寫
          ???* @param name
          ???* @return
          ???*/

          ??private?String?toLowerFirstWord(String?name){
          ????char[] charArray = name.toCharArray();
          ????charArray[0] += 32;
          ????return?String.valueOf(charArray);
          ??}
          ??
          ????
          }
          這里我們就開發(fā)完了自己的SpringMVC,現(xiàn)在我們測試一下:
          @MyController
          @MyRequestMapping("/test")
          public class TestController {
          ??
          ???@MyRequestMapping("/doTest")
          ????public void test1(HttpServletRequest request, HttpServletResponse response,
          ????????@MyRequestParam("param") String param){
          ?????System.out.println(param);
          ??????try?{
          ????????????response.getWriter().write( "doTest method success! param:"+param);
          ????????} catch?(IOException e) {
          ????????????e.printStackTrace();
          ????????}
          ????}
          ???
          ???@MyRequestMapping("/doTest2")
          ????public?void?test2(HttpServletRequest request, HttpServletResponse response){
          ????????try?{
          ????????????response.getWriter().println("doTest2 method success!");
          ????????} catch?(IOException e) {
          ????????????e.printStackTrace();
          ????????}
          ????}
          }

          訪問 http://localhost:8080/liughMVC/test/doTest?param=liugh 如下:

          訪問一個不存在的試試:
          到這里我們就大功告成了!
          源碼地址:https://github.com/qq53182347/liughMVC
          來源:my.oschina.net/liughDevelop/blog/1622646


          往期推薦

          基于 Spring Boot+Bootstrap 的迷你天貓商城項目,可二次開發(fā)接私活!

          重磅推薦:一款功能強大、非常全面的Java權(quán)限認證框架!

          Spring最常用的7大類注解,史上最強整理!

          SpringCloud+SpringBoot+OAuth2+Spring Security+Redis實現(xiàn)的微服務(wù)統(tǒng)一認證授權(quán)

          IDEA的后綴自動補全候選項都在這里了,求求你這些代碼別手寫了!

          Spring Boot + Vue + Shiro 實現(xiàn)前后端分離,寫得太好了!

          基于springBoot 實現(xiàn)webSocket方式的掃碼登錄


          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  女人久久 | 91久久婷婷 | 日韩免费看毛片 | 操屄在线观看 | 欧美激情精品久久久久久变态 |