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

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

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

miMRVjM.png!web

小结: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

2MRf63J.png!web

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
  
    
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 {    public String value() default "";    public String 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 {    public String value() default "";    public String method() default "";    public String 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;import javassist.*;import javassist.bytecode.CodeAttribute;import javassist.bytecode.LocalVariableAttribute;import javassist.bytecode.MethodInfo;import java.lang.reflect.Method;public final class ParameterNameUtils {    public final static String[] getParameterNamesByJavassist(final Class
 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];                int len = ctMethod.getParameterTypes().length;                // 非静态的成员函数的第一个参数是this                int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;                for (int i = 0; i < len; i++) {                    rtv[i] = attr.variableName(i + pos);                }                return rtv;            }        } catch (NotFoundException e) {            System.out.println("获取异常"+ e.getMessage());        }        return new String[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;public final  class HandlerMapping {    private static  final Map
 handlerMapping = new HashMap
();    private static  final List
 scaners = new ArrayList<>(2);    static {        scaners.add(new XmlScaner());        scaners.add(new FileScaner());    }    public  static void scanPackage(String scanUrl) throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException {        for (Scaner scaner : scaners) {            scaner.doScane(scanUrl);        }    }    public static void doInit(String scanUrl) throws IllegalAccessException, ClassNotFoundException, InstantiationException {        scanPackage(scanUrl);    }    public static void 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 (int i = 0; i < parameters.length; i++) {                    final String simpleName = parameters[i];                    StringBuilder parameterSb = new  StringBuilder();                    final String[] parameterStr = parameterMap.get(simpleName);                    if(parameterStr != null){                        for (int j = 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();        }    }    public static Handler addHandlerMapping(String url,Handler handler) {        return handlerMapping.put(url,handler);    }    public static Handler getHandlerMapping(String url) {        return handlerMapping.get(url);    }}

扫描包

package com.adminkk.scan;public interface Scaner {    void doScane(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;public interface 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;public class MvcBeanPostProcessor implements BeanPostProcessor{    //扫描Controller业务    @Override    public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {        Class
 objectClass = object.getClass();        if(objectClass.getAnnotation(Controller.class) != null){            RequestMapping cal***equestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);            StringBuilder urlSb = new StringBuilder();            if(cal***equestMappingAnnotation != null){                urlSb.append(cal***equestMappingAnnotation.value());            }            Method[] methods = objectClass.getMethods();            if(methods != null && methods.length > 0 ){                for (int i = 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 {                            throw new MvcException("请求路径"+ url + "已经存在容器中");                        }                    }                }            }        }        return object;    }    @Override    public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {        return null;    }}
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

zQ3a2aJ.png!web

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

bAbIfuN.png!web

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