美文网首页
跟我一起造轮子 手写springmvc

跟我一起造轮子 手写springmvc

作者: adminkkk | 来源:发表于2018-10-21 22:35 被阅读0次

  作为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系统(上)

相关文章

网友评论

      本文标题:跟我一起造轮子 手写springmvc

      本文链接:https://www.haomeiwen.com/subject/zziizftx.html