欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringMVC架构模拟

程序员文章站 2022-05-23 22:00:31
这次来学习一下SpringMVC的源码. 对于常见的项目架构模式,比如大名鼎鼎的SSM(SpringMVC,Spring,Mybatis)框架. SpringMVC ->web层(Controller层) Spring ->service层 mybatis ->dao层 从SpringMVC层面上讲 ......

这次来学习一下springmvc的源码.

对于常见的项目架构模式,比如大名鼎鼎的ssm(springmvc,spring,mybatis)框架.

springmvc ->web层(controller层)

spring ->service层

mybatis ->dao层

从springmvc层面上讲,他的构成如下:

model ->数据

view ->视图

controller ->业务

经过上面的分层,使得数据,视图(展示效果),业务逻辑进行分离,每一层的变化可以不影响其他层,增加程序的可维护性和可扩展性。

SpringMVC架构模拟

 

  1. 浏览器发出用户请求,处于web.xml中的dispacherservlet前端控制器进行接收,此时这个前端控制器并不处理请求.而是将用户发送的url请求转发到handlemapping处理器映射器
  2. 第二步:handlermapping根据请求路径找到相对应的handleradapter处理器适配器(处理器适配器就是那些拦截器或者是controller)
  3. 第三步:handleradapter处理器适配器,可以处理一些功能请求,返回一个modelandview对象(包括模型数据/逻辑视图名)
  4. 第四步:viewresolver视图解析器,先根据 modelandview中设置的view解析具体视图
  5. 第五步:然后再将model模型中的数据渲染到view中

下面我们实际在项目中进行操作

一丶创建一个带有web.xml的maven项目

二丶首先自己写一个类继承httpservlet类并重写它的doget,dopost方法

package com.spring.mvc.config;

import javax.servlet.servletexception;
import javax.servlet.http.httpservlet;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;

/**
 * @author: xiaozhe
 * @description:
 * @date: created in 17:39 2019/12/16
 */
public class mydispatchservlet extends httpservlet{
    @override
    protected void doget(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {
        system.out.println("这是调用了doget方法");
    }

    @override
    protected void dopost(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {
        system.out.println("这是调用了dopost方法");
    }
}

 

三丶修改web.xml文件

<!doctype web-app public
 "-//sun microsystems, inc.//dtd web application 2.3//en"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <!--注册servlet-->
  <servlet>
    <!--自己继承了httpservlet类的名字-->
    <servlet-name>httpservlettest</servlet-name>
    <!--自己继承了httpservlet类的所在路径-->
    <servlet-class>com.spring.mvc.servlet.mydispatchservlet</servlet-class>
  </servlet>
  <!--映射servlet-->
  <servlet-mapping>
    <!--上面自定义的servlet-name-->
    <servlet-name>httpservlettest</servlet-name>
    <!--拦截路径/* -->
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

 

 

四丶 启动项目,在地址栏输入项目地址并回车

可以看到控制台打印输出了我们定义的话

这是调用了doget方法
这是调用了doget方法

 

五丶创建几个注解@controller,@requestmapping

用过springmvc框架的人都知道在类上打了@controller注解的才能被认作是一个controller,而打了@requestmapping才能被请求映射。

@mycontroller

package com.spring.mvc.annotation;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

/**
 * @author: zhengzhe
 * @description: 作用于class上的功能类似于spring的@controller注解
 * @date: created in 10:34 2019/12/17
 */
@target(elementtype.type)//标识此注解只能作用在类上面
@retention(retentionpolicy.runtime)//标识此注解一直存活,可被反射获取
public @interface mycontroller {
}

 

@myrequestmapping

package com.spring.mvc.annotation;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

/**
 * @author: zhengzhe
 * @description: 作用于class或者method上的功能类似于spring的@requestmapping注解
 * @date: created in 10:38 2019/12/17
 */
@target({elementtype.type,elementtype.method})//标识此注解只能作用在类或者方法上面
@retention(retentionpolicy.runtime)//标识此注解一直存活,可被反射获取
public @interface myrequestmapping {
    string value();//用来存储对应的url , 网络请求路径
}

 

六丶dispatchservlet

dispatchservlet在mvc引导着非常强大的作用,网络中的请求传到dispatchservlet中,由dispatchservlet进行截取分析并传到对应的由@controller和@requestmapping注解的类或方法中,使得网路请求能正确的请求到对应的资源上.

下面我们自定义一个dispatchservlet实现他所实现的功能

首先看一下源码中mvc做了什么

protected void initstrategies(applicationcontext context) {
        this.initmultipartresolver(context);
        this.initlocaleresolver(context);
        this.initthemeresolver(context);
        this.inithandlermappings(context);
        this.inithandleradapters(context);
        this.inithandlerexceptionresolvers(context);
        this.initrequesttoviewnametranslator(context);
        this.initviewresolvers(context);
        this.initflashmapmanager(context);
    }

 

从上面我们可以看到初始化方法的参数是applicationcontext,这个是ioc的初始化容器,我之前的博客中解析过ioc的源码,不懂的可以去里面解读.

initstrategies方法的目的就是从容器中获取已经解析出来的bean资源,并获取其带有@controller和@requestmapping注解的bean资源.

protected void doservice(httpservletrequest request, httpservletresponse response) throws exception {
        if (this.logger.isdebugenabled()) {
            string resumed = webasyncutils.getasyncmanager(request).hasconcurrentresult() ? " resumed" : "";
            this.logger.debug("dispatcherservlet with name '" + this.getservletname() + "'" + resumed + " processing " + request.getmethod() + " request for [" + getrequesturi(request) + "]");
        }

        map<string, object> attributessnapshot = null;
        if (webutils.isincluderequest(request)) {
            attributessnapshot = new hashmap();
            enumeration attrnames = request.getattributenames();

            label108:
            while(true) {
                string attrname;
                do {
                    if (!attrnames.hasmoreelements()) {
                        break label108;
                    }

                    attrname = (string)attrnames.nextelement();
                } while(!this.cleanupafterinclude && !attrname.startswith("org.springframework.web.servlet"));

                attributessnapshot.put(attrname, request.getattribute(attrname));
            }
        }

        request.setattribute(web_application_context_attribute, this.getwebapplicationcontext());
        request.setattribute(locale_resolver_attribute, this.localeresolver);
        request.setattribute(theme_resolver_attribute, this.themeresolver);
        request.setattribute(theme_source_attribute, this.getthemesource());
        flashmap inputflashmap = this.flashmapmanager.retrieveandupdate(request, response);
        if (inputflashmap != null) {
            request.setattribute(input_flash_map_attribute, collections.unmodifiablemap(inputflashmap));
        }

        request.setattribute(output_flash_map_attribute, new flashmap());
        request.setattribute(flash_map_manager_attribute, this.flashmapmanager);

        try {
            this.dodispatch(request, response);
        } finally {
            if (!webasyncutils.getasyncmanager(request).isconcurrenthandlingstarted() && attributessnapshot != null) {
                this.restoreattributesafterinclude(request, attributessnapshot);
            }

        }

    }

 

doservice方法其实目的就是解析用户的请求路径,根据请求路径找到对应类和方法,使用反射调用.

七丶mydispatchservlet(自定义前端控制器)

我们自己写代码来实现对应的init方法和service方法的功能.

package com.spring.mvc.servlet;

import com.spring.mvc.annotation.mycontroller;
import com.spring.mvc.annotation.myrequestmapping;

import javax.servlet.servletexception;
import javax.servlet.http.httpservlet;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.file;
import java.io.ioexception;
import java.lang.reflect.method;
import java.net.url;
import java.util.enumeration;
import java.util.map;
import java.util.set;
import java.util.concurrent.concurrenthashmap;
import java.util.concurrent.concurrentmap;

/**
 * @author: zhengzhe
 * @description:
 * @date: created in 10:56 2019/12/17
 */
public class mydispatchservlet extends httpservlet{
    //我们定义两个集合去存储扫描到的带有@mycontroller 和 @myrequestmapping注解的类或者方法
    //存放 被@myrequestmapping注解修饰的类或者方法
    private concurrenthashmap<string,method> mymethodscollection = new concurrenthashmap();
    //存放 被@mycontroller注解修饰的类
    private concurrenthashmap<string,object> mycontrollercollection = new concurrenthashmap();

    @override
    protected void doget(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {

        myinit(req,resp);

        myservice(req,resp);

    }

    /**
     * 作用是 : 根据配置的包名,扫描所有包含@mycontroller注解修饰的类和@myrequestmapping注解修饰的类或者方法
     *并将对应的 beanname放入上面定义的 集合中
     * @param req
     * @param resp
     */
    private void myinit(httpservletrequest req, httpservletresponse resp) {
        //在源码中,有ioc实现了容器的扫描和初始化,mvc中只是直接拿出来,但是这个方法中我们就不通过容器去获取了
        //直接扫描并且存入上面的集合中就可以了
        string basepackage ="com.spring.mvc";
        //根据传入的包路径,扫描包,此时只是将该包下所有的文件资源存入集合中,但是并没有筛选加了@mycontroller和
        //@requestmapping注解的类,即扫描所有.class字节码对象并保存起来
        concurrenthashmap<string, class<?>> scannerclass = scannerclass(basepackage);

        //下面直接进行筛选@mycontroller和@requestmapping注解的类
        set<map.entry<string, class<?>>> entryset = scannerclass.entryset();
        for (map.entry<string, class<?>> entry : entryset) {
            //获取key : 类名称
            string classname = entry.getkey();
            //获取value : 对应的.class字节码对象
            class<?> clazz = entry.getvalue();
            //定义myrequestmapping的url
            string classurl = "";
            try {
                //该类是否标记了mycontroller注解
                if (clazz.isannotationpresent(mycontroller.class)){
                    //该类是否标记了myrequestmapping注解
                    if (clazz.isannotationpresent(myrequestmapping.class)){
                        //如果该类被myrequestmapping注解所标识,获取其属性url值
                        myrequestmapping requestmapping = clazz.getannotation(myrequestmapping.class);
                        classurl = requestmapping.value();
                    }
                    //将被标识了mycontroller注解的类存入集合中
                    mycontrollercollection.put("classname",clazz.newinstance());
                    //判断该类中的方法是否标识了@myrequestmapping注解,如果标识了存入mymethodscollection集合中
                    //获取该类下的方法
                    method[] methods = clazz.getmethods();//获取该类中的方法数组
                    //遍历该数组
                    for (method method : methods) {
                        //判断是否被@myrequestmapping注解标识
                        if (method.isannotationpresent(myrequestmapping.class)){
                            //已被@myrequestmapping注解标识
                            //获取其url
                            myrequestmapping myrequestmapping = method.getannotation(myrequestmapping.class);
                            //获取method上的url
                            string methodurl = myrequestmapping.value();
                            //拼接两端mycontroller和method的url
                            mymethodscollection.put(classurl+methodurl,method);
                        }
                    }


                }
            } catch (exception e) {
                e.printstacktrace();
            }
        }
    }
    //根据传入的包路径,进行bean资源的获取,一般可以在xml中设置包路径.但是我们直接给出即可(简单)
    private static concurrenthashmap<string, class<?>> scannerclass(string basepackage) {
        concurrenthashmap<string, class<?>> result = new concurrenthashmap<>();
        //把com.spring.mvc 换成com/spring/mvc再类加载器读取文件
        string basepath = basepackage.replaceall("\\.", "/");
        try {
            //得到com/spring/mvc的绝对地址 /d:xxxxx/com/spring/mvc
            string rootpath = mydispatchservlet.class.getclassloader().getresource(basepath).getpath();
            //只留com/ming/mvc 目的为了后续拼接成一个全限定名
            if (rootpath != null) {
                rootpath = rootpath.substring(rootpath.indexof(basepath));
            }
            enumeration<url> enumeration = mydispatchservlet.class.getclassloader().getresources(basepath);
            while (enumeration.hasmoreelements()) {
                url url = enumeration.nextelement();
                if (url.getprotocol().equals("file")) {//如果是个文件
                    file file = new file(url.getpath().substring(1));
                    scannerfile(file, rootpath, result);
                }
            }

        } catch (ioexception e) {
            e.printstacktrace();
        }
        return result;

    }

    //递归扫描文件
    private static void scannerfile(file folder, string rootpath, concurrenthashmap<string, class<?>> classes) {
        try {
            file[] files = folder.listfiles();
            for (int i = 0; files != null && i < files.length; i++) {
                file file = files[i];
                if (file.isdirectory()) {
                    scannerfile(file, rootpath + file.getname() + "/", classes);
                } else {
                    if (file.getname().endswith(".class")) {
                        string classname = (rootpath + file.getname()).replaceall("/", ".");
                        classname = classname.substring(0, classname.indexof(".class"));//去掉扩展名得到全限定名
                        //map容器存储全限定名和class
                        classes.put(classname, class.forname(classname));
                    }
                }

            }
        } catch (exception e) {
            e.printstacktrace();
        }
    }



    /**
     * 作用是 : 解析用户的请求路径,,根据请求路径找到对应的类和方法,并使用反射调用其方法
     * @param req
     * @param resp
     */
    private void myservice(httpservletrequest req, httpservletresponse resp) {
        //在myinit中我们已将被@mycontroller和@myrequestmapping注解标识的类或者方法存入对应的集合中了;
        //下面我们需要将网络请求中的url和我们容器中初始化好的url进行匹配,如果匹配成功,那么直接执行此方法
        //返回除去host(域名或者ip)部分的路径(包含)
        string requesturi = req.getrequesturi();//类似test/test
        //返回工程名部分,如果工程映射为/,此处返回则为空 (工程名即项目名)
        string contextpath = req.getcontextpath();//类似test
        //获取实际除 ip,端口,项目名外的请求路径
        //如web.xml中的servlet拦截路径设置的为/* 采用下面的方法,如果采用的是/*.do或者/*.action类似的后缀,需要把后面的也去掉
        string requestmappingpath = requesturi.substring(contextpath.length());
        //通过截取到的实际的请求url为key获取对应的方法
        method method = mymethodscollection.get(requestmappingpath);
        try {
            if (method == null){
                //此时就是大名鼎鼎的404 了~
                //直接返回404
                resp.senderror(404);
                return;
            }
            //存在,那么直接执行
            //获取方法所对应的的class<?>字节码文件
            class<?> declaringclass = method.getdeclaringclass();
            //下面按照源码来说还需要去判断是否是单例等操作,我们直接省去
            method.invoke(declaringclass.newinstance());
        } catch (exception e) {
            e.printstacktrace();
        }
    }

    @override
    protected void dopost(httpservletrequest req, httpservletresponse resp) throws servletexception, ioexception {
        system.out.println("这是调用了dopost方法");
    }
}

 

八丶创建controllertest测试类

package com.spring.mvc.controller;

import com.spring.mvc.annotation.mycontroller;
import com.spring.mvc.annotation.myrequestmapping;

import javax.servlet.http.httpservlet;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
import java.io.printwriter;
import java.util.hashmap;
import java.util.map;

/**
 * @author: zhengzhe
 * @description:
 * @date: created in 15:07 2019/12/17
 */
@mycontroller
@myrequestmapping("/hello")
public class controllertest {


    @myrequestmapping("/world")
    public void helloworld(){
        system.out.println("自定义mvc测试成功~ ,现在时间是"+system.currenttimemillis());
    }
}

 

查看控制台输出:

信息: at least one jar was scanned for tlds yet contained no tlds. enable debug logging for this logger for a complete list of jars that were scanned but no tlds were found in them. skipping unneeded jars during scanning can improve startup time and jsp compilation time.
[2019-12-17 03:33:26,276] artifact mvc:war exploded: artifact is deployed successfully
[2019-12-17 03:33:26,276] artifact mvc:war exploded: deploy took 565 milliseconds
自定义mvc测试成功~ ,现在时间是1576568012163

 

————————————————

本人免费整理了java高级资料,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osb-bol6w-zltstttkqmpq