跟我一起造轮子 手写springmvc

  作为java程序员,项目中使用到的主流框架多多少少和spring有关联,在面试的过程难免会问一些spring springmvc spring boot的东西,比如设计模式的使用、 怎么实现springioc 怎么实现springmvc诸如此类的问题,今天我们就来探寻spring mvc的实现,然后自己实现一个简单的spring mvc

一.了解spring mvc的基本运行流程

 ps: 网上一大堆关于springmvc的详细讲解,在这里就不累赘了


  小结:spring mvc的核心是DispatcherServlet,DispatcherServlet继承于HttpServlet,可以说spring mvc是基于Servlet的一个实现,DispatcherServlet负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。


二. 梳理简单SpringMVC的设计思路

1. 初始化容器 

1.1 读取配置文件

          1.1.1.加载配置文件信息到DispatcherServlet

1.2  根据配置扫描包、初始化容器和组件

          1.2.1.根据配置信息递归扫描包

          1.2.2.把包下的类实例化 并且扫描注解

          1.2.3.根据类的方法和注解,初始化HandlerMapping

2. 处理业务请求

2.1 处理请求业务

2.2.1 首先拿到请求URI 

            2.2.2 根据URI,在HandlerMapping中查找和URI对应的Handler

               2.2.3 根据Handler里面的method中的参数名称和http中的请求参数匹配,填充method参数,反射调用


三. 没时间解释了,快上车

 ps :环境基于maven idea tomat(端口8080) servlet


  1.搭建一个基本web项目,并导入idea配置servlet 和javassist pom依赖 如下

创建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0

 pom依赖

  4.0.0  com.adminkk  adminkk-mvc  1.0-SNAPSHOT                    org.apache.maven.plugins        maven-compiler-plugin                  8          8                      war  adminkk-mvc  http://maven.apache.org</url>    UTF-8              junit      junit      3.8.1      test                      javax.servlet        javax.servlet-api        3.0.1        provided                    asm      asm      3.3.1                  org.javassist      javassist      3.23.1-GA     



2.创建mvc的注解 Controller RequestMapping 和统一异常处理类、方法参数工具类ParameterNameUtils   

package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)public@interface Controller {

    publicString value()default"";

    publicString description()default"";

}


package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)public@interface RequestMapping {

    publicString value()default"";

    publicString method()default"";

    publicString description()default"";

}

package com.adminkk.exception;

public  final  class MvcException extends RuntimeException{

    public MvcException() {

        super();

    }

    public MvcException(String message) {

        super(message);

    }

}

package com.adminkk.tools;importjavassist.*;import javassist.bytecode.CodeAttribute;import javassist.bytecode.LocalVariableAttribute;import javassist.bytecode.MethodInfo;import java.lang.reflect.Method;publicfinalclass ParameterNameUtils {

    publicfinalstaticString[] getParameterNamesByJavassist(finalClass clazz,final Method method) {

        ClassPool pool = ClassPool.getDefault();

        try {

            CtClass ctClass = pool.get(clazz.getName());

            CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());

            // 使用javassist的反射方法的参数名MethodInfo methodInfo = ctMethod.getMethodInfo();

            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();

            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute

                    .getAttribute(LocalVariableAttribute.tag);

            if(attr !=null) {

                String[] rtv =new String[ctMethod.getParameterTypes().length];

                intlen = ctMethod.getParameterTypes().length;

                // 非静态的成员函数的第一个参数是thisintpos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;

                for(inti = 0; i < len; i++) {

                    rtv[i] = attr.variableName(i + pos);

                }

                return rtv;

            }

        } catch (NotFoundException e) {

            System.out.println("获取异常"+ e.getMessage());

        }

        returnnewString[0];

    }

}



3.创建 HandlerMapping类 主要是两个方法  doInit初始化 doService处理请求 相关代码如下

package com.adminkk.handler;import com.adminkk.scan.FileScaner;import com.adminkk.scan.Scaner;import com.adminkk.scan.XmlScaner;import com.adminkk.tools.ParameterNameUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;publicfinalclass HandlerMapping {

    privatestaticfinalMap handlerMapping =newHashMap();

    privatestaticfinalList scaners =newArrayList<>(2);

    static {

        scaners.add(new XmlScaner());

        scaners.add(new FileScaner());

    }

    publicstaticvoidscanPackage(String scanUrl)throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException {

        for (Scaner scaner : scaners) {

            scaner.doScane(scanUrl);

        }

    }

    publicstaticvoiddoInit(String scanUrl)throws IllegalAccessException, ClassNotFoundException, InstantiationException {

        scanPackage(scanUrl);

    }

    publicstaticvoid doService(HttpServletRequest request, HttpServletResponse response) {

        String requestURI = request.getRequestURI();

        System.out.println("请求地址是="+ requestURI);

        Handler handler = handlerMapping.get(requestURI);

        if(handler ==null){

            System.out.println("请求地址是="+ requestURI+" 没有配置改路径");

            return;

        }

        Method method = handler.getMethod();

        Object instance = handler.getInstance();

        response.setCharacterEncoding("UTF-8");

        //response.setContentType("application/json; charset=utf-8");PrintWriter writer =null;

        try {

            //这里是简单的解析 可以像springmvc那样解析处理Map parameterMap = request.getParameterMap();

            String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method);

            Object[]  parameter =new Object[parameters.length];

            if(parameters !=null&& parameters.length > 0){

                for(inti = 0; i < parameters.length; i++) {

                    finalString simpleName = parameters[i];

                    StringBuilder parameterSb =new  StringBuilder();

                    finalString[] parameterStr = parameterMap.get(simpleName);

                    if(parameterStr !=null){

                        for(intj = 0; j < parameterStr.length; j++) {

                            parameterSb.append(parameterStr[j]);

                        }

                    }

                    parameter[i] = parameterSb.toString();

                }

            }

            writer = response.getWriter();

            String result = (String) method.invoke(instance,parameter);

            writer.print(result);

        } catch (Exception e) {

            e.printStackTrace();

            System.out.println("请求地址是="+ requestURI+" 执行异常");

            writer.print("业务执行异常");

        }finally {

            writer.flush();

            writer.close();

        }

    }

    publicstatic Handler addHandlerMapping(String url,Handler handler) {

        return handlerMapping.put(url,handler);

    }

    publicstatic Handler getHandlerMapping(String url) {

        return handlerMapping.get(url);

    }

}

 扫描包

package com.adminkk.scan;publicinterface Scaner {

    voiddoScane(String scanUrl)throws IllegalAccessException, InstantiationException, ClassNotFoundException;

}

package com.adminkk.scan;

import com.adminkk.exception.MvcException;

import com.adminkk.factory.BeanPostProcessor;

import com.adminkk.factory.MvcBeanPostProcessor;

import com.adminkk.factory.ServiceBeanPostProcessor;

import com.adminkk.handler.HandlerMapping;

import javassist.ClassClassPath;

import javassist.ClassPool;

import java.io.File;

import java.util.ArrayList;

import java.util.List;

public final  class FileScaner implements Scaner{

public FileScaner() {

}

public static final List beanPostProcessorList = new ArrayList<>();

static {

beanPostProcessorList.add(new MvcBeanPostProcessor());

beanPostProcessorList.add(new ServiceBeanPostProcessor());

}

@Override

public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

if(scanUrl == null || scanUrl.length() == 0){

throw new MvcException("容器基础扫描路径为空,请检查参数配置");

}

String baseUrl = HandlerMapping.class.getResource("/").getPath();

String codeUrl = scanUrl.replaceAll("\\.", "/");

String path =  baseUrl + codeUrl;

File file = new File(path);

if(file == null || !file.exists()){

throw new MvcException("找不到对应扫描路径,请检查参数配置");

}

recursionRedFile(scanUrl,file);

}

//递归读取文件

private  void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException {

if(!file.exists()){

return;

}

//读取java文件

if(file.isFile()){

String beanName = scanUrl.replaceAll(".class","");

Class forName = Class.forName(beanName);

//放到Javassist容器里面

ClassPool pool = ClassPool.getDefault();

ClassClassPath classPath = new ClassClassPath(forName);

pool.insertClassPath(classPath);

if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){

return;

}

Object newInstance = forName.newInstance();

//前置执行

for (int i = 0; i < beanPostProcessorList.size() ; i++) {

BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);

beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName);

}

//后置执行

for (int i = beanPostProcessorList.size()-1; i > 0  ; i++) {

BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);

beanPostProcessor.postProcessAfterInitialization(newInstance,beanName);

}

return;

}

//文件夹下面的文件都递归处理

if(file.isDirectory()){

File[] files = file.listFiles();

if(files != null && files.length >0){

for (int i = 0; i < files.length; i++) {

File targetFile = files[i];

recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile);

}

}

}

}

}


package com.adminkk.scan;

public final class XmlScaner implements Scaner{

    public XmlScaner() {

    }

    @Override

    public void doScane(String scanUrl) {

        //可自行扩展

    }

}

  扫描bean

package com.adminkk.factory;import com.adminkk.exception.MvcException;publicinterface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException;

    Object postProcessAfterInitialization(Object object, String beanName) throws MvcException;

}


package com.adminkk.factory;import com.adminkk.annotation.Controller;import com.adminkk.annotation.RequestMapping;import com.adminkk.exception.MvcException;import com.adminkk.handler.Handler;import com.adminkk.handler.HandlerMapping;import java.lang.reflect.Method;publicclassMvcBeanPostProcessorimplements BeanPostProcessor{

    //扫描Controller业务    @Override

    publicObject postProcessBeforeInitialization(Object object, String beanName)throws MvcException {

        Class objectClass = object.getClass();

        if(objectClass.getAnnotation(Controller.class) !=null){

            RequestMapping calssRequestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);

            StringBuilder urlSb =new StringBuilder();

            if(calssRequestMappingAnnotation !=null){

                urlSb.append(calssRequestMappingAnnotation.value());

            }

            Method[] methods = objectClass.getMethods();

            if(methods !=null&& methods.length > 0 ){

                for(inti = 0; i < methods.length; i++) {

                    Method method = methods[i];

                    RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);

                    if(methodAnnotation !=null){

                        String methodValue = methodAnnotation.value();

                        String url =new StringBuilder().append(urlSb).append(methodValue).toString();

                        Handler handler = HandlerMapping.getHandlerMapping(url);

                        if(handler ==null){

                            handler =new Handler();

                            handler.setMethod(method);

                            handler.setInstance(object);

                            HandlerMapping.addHandlerMapping(url,handler);

                        }else {

                            thrownewMvcException("请求路径"+ url + "已经存在容器中");

                        }

                    }

                }

            }

        }

        return object;

    }

    @Override

    publicObject postProcessAfterInitialization(Object object, String beanName)throws MvcException {

        returnnull;

    }

}

package com.adminkk.factory;

import com.adminkk.exception.MvcException;

public class ServiceBeanPostProcessor implements BeanPostProcessor {

    @Override

    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {

        //可自行扩展

        return null;

    }

    @Override

    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {

        //可自行扩展

        return null;

    }

}

5.创建 DispatcherServlet

package com.adminkk.servlet;

import com.adminkk.handler.HandlerMapping;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})

public final  class DispatcherServlet extends HttpServlet {

    public static final String BASE_SCAN_URL = "com.adminkk";

    //初始化容器

    @Override

    public void init() throws ServletException {

        doInit();

    }

    //处理业务请求

    @Override

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        doService(req,resp);

    }

    private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {

        try {

            HandlerMapping.doService(req,resp);

        }catch (Exception e){

            e.printStackTrace();

            throw new ServletException(e.getMessage());

        }

    }

    private void doInit() throws ServletException {

        try {   

            HandlerMapping.doInit(this.BASE_SCAN_URL);

        }catch (Exception e){

            e.printStackTrace();

            throw new ServletException(e.getMessage());

        }

    }

}

 好了,目前为止我们就写好了简版的springmvc 下面开始测试

package com.adminkk.controller;

import com.adminkk.annotation.Controller;

import com.adminkk.annotation.RequestMapping;

@Controller

@RequestMapping("/mvc")

public class MvcController {

    @RequestMapping("/index")

    public String index(){

        return  "adminkk-mvc system is running";

    }

    @RequestMapping("/arg")

    public String parameter(String argOne, String argTwo){

        return  "argOne = " + argOne + " argTwo = " + argTwo;

    }

}

      访问地址 http://localhost:8080/mvc/index


      访问地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo


总结:整体实现简单的springmvc,设计上还可以扩展更多,难点在于method 获取方法上的参数名称,由于jdk1.8以前是不支持的,需要借用第三方工具 比如 asm javassist黑科技工具包来帮助实现,spring-core使用的是LocalVariableTableParameterNameDiscoverer底层是调用asm,我们这里使用的是javassist。延用这套思路还可以和spring项目结合,写一个 基于spring的springmvc项目

源代码 : https://gitee.com/chenchenche/mvc

写博客不容易,希望大家多多提建议 

下一篇预告     跟我一起造轮子 手写分布式IM系统(上)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光剑书架上的书阅读 3,868评论 2 8
  • ©著作权归作者所有:来自51CTO博客作者优秀android的原创作品,如需转载,请注明出处,否则将追究法律责任 ...
    传奇内服号阅读 1,067评论 0 9
  • 4月4日晨读感悟 1、最近在一个乔布斯的采访视频,给我的感觉是对于新观念接受很快,对生活充满好奇心,执行力很强,确...
    时光的小偷阅读 47评论 0 0