1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        昨晚擼了一個(gè)簡易版的 Spring MVC 框架

        共 6180字,需瀏覽 13分鐘

         ·

        2021-10-30 02:42

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

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

        • 了解SpringMVC運(yùn)行流程及九大組件

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

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

        一、了解SpringMVC運(yùn)行流程及九大組件

        1. SpringMVC的運(yùn)行流程

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

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

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

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

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

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

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

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

        ⑼ ViewReslover解析后返回具體View

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

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

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

        2. SpringMVC的九大組件

        protected?void?initStrategies(ApplicationContext context) {
        ??//用于處理上傳請(qǐng)求。處理方法是將普通的request包裝成MultipartHttpServletRequest,后者可以直接調(diào)用getFile方法獲取File.
        ??initMultipartResolver(context);
        ??//SpringMVC主要有兩個(gè)地方用到了Locale:一是ViewResolver視圖解析的時(shí)候;二是用到國際化資源或者主題的時(shí)候。
        ??initLocaleResolver(context);
        ??//用于解析主題。SpringMVC中一個(gè)主題對(duì)應(yīng)一個(gè)properties文件,里面存放著跟當(dāng)前主題相關(guān)的所有資源、
        ??//如圖片、css樣式等。SpringMVC的主題也支持國際化,
        ??initThemeResolver(context);
        ??//用來查找Handler的。
        ??initHandlerMappings(context);
        ??//從名字上看,它就是一個(gè)適配器。Servlet需要的處理方法的結(jié)構(gòu)卻是固定的,都是以request和response為參數(shù)的方法。
        ??//如何讓固定的Servlet處理方法調(diào)用靈活的Handler來進(jìn)行處理呢?這就是HandlerAdapter要做的事情
        ??initHandlerAdapters(context);
        ??//其它組件都是用來干活的。在干活的過程中難免會(huì)出現(xiàn)問題,出問題后怎么辦呢?
        ??//這就需要有一個(gè)專門的角色對(duì)異常情況進(jìn)行處理,在SpringMVC中就是HandlerExceptionResolver。
        ??initHandlerExceptionResolvers(context);
        ??//有的Handler處理完后并沒有設(shè)置View也沒有設(shè)置ViewName,這時(shí)就需要從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è)計(jì)思路

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

        ?1、讀取配置? ? ?

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

        因?yàn)楸疚闹皇菍?shí)現(xiàn)SpringMVC,對(duì)于Spring容器不做過多講解(有興趣同學(xué)可以看看我另一篇文章:向spring大佬低頭--大量源碼流出解析)。

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

        2、初始化階段

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

        • 加載配置文件

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

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

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


        3、運(yùn)行階段

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

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

        工程文件及目錄:
        ? ? ? ? ? ? ? ? ? ? ? ? ? ??
        首先,新建一個(gè)maven項(xiàng)目,在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>


        接著,我們?cè)赪EB-INF下創(chuàng)建一個(gè)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注解,它只能標(biāo)注在類上面:
        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public?@interface?MyController {
        ??/**
        ?????* 表示給controller注冊(cè)別名
        ?????* @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這個(gè)類,去繼承HttpServlet,重寫init方法、doGet、doPost方法,以及加上我們第二步分析時(shí)要實(shí)現(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.拿到掃描到的類,通過反射機(jī)制,實(shí)例化,并且放到ioc容器中(k-v beanName-bean) beanName默認(rèn)是首字母小寫
        ????doInstance();
        ????//4.初始化HandlerMapping(將url和method對(duì)應(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?{
        ??????//處理請(qǐng)求
        ??????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();
        ??
        ????//獲取請(qǐng)求的參數(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ù)類型已明確,這邊強(qiáng)轉(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;
        ?????????????}
        ????????????}
        ????????}
        ????//利用反射機(jī)制來調(diào)用
        ????try?{
        ??????method.invoke(this.controllerMap.get(url), paramValues);//第一個(gè)參數(shù)是method所對(duì)應(yīng)的實(shí)例 在ioc容器中
        ????} catch?(Exception e) {
        ??????e.printStackTrace();
        ????}
        ??}



        ??private?void??doLoadConfig(String?location){
        ????//把web.xml中的contextConfigLocation對(duì)應(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?{
        ????????//把類搞出來,反射來實(shí)例化(只有加@MyController需要實(shí)例化)
        ????????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時(shí),是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)在我們測(cè)試一下:
        @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 如下:

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


        往期推薦

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

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

        Spring最常用的7大類注解,史上最強(qiáng)整理!

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

        IDEA的后綴自動(dòng)補(bǔ)全候選項(xiàng)都在這里了,求求你這些代碼別手寫了!

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

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


        瀏覽 61
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            免费黄片在线播放 | аⅴ资源天堂资源库在线 | www.做爱网站 | 深爱激情丁香伊人 | 国产 欧美 日韩 在线 | 精品久久高清 | 边被c边被打屁股高潮小说 | 一女二男做爰3p太爽了 | 日韩一区观看 | 国产精品成人免费一区久久羞羞 |