zoukankan      html  css  js  c++  java
  • 跟我一起造轮子 手写springmvc

      原创地址:https://www.cnblogs.com/xrog/p/9820168.html

      作为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依赖

    <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.0</modelVersion>
    
      <groupId>com.adminkk</groupId>
      <artifactId>adminkk-mvc</artifactId>
      <version>1.0-SNAPSHOT</version>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>8</source>
              <target>8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <packaging>war</packaging>
    
      <name>adminkk-mvc</name>
      <url>http://maven.apache.org</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
    
        <!--servlet-->
          <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
          </dependency>
    
        <!-- asm -->
        <dependency>
          <groupId>asm</groupId>
          <artifactId>asm</artifactId>
          <version>3.3.1</version>
        </dependency>
    
        <!-- javassist -->
        <dependency>
          <groupId>org.javassist</groupId>
          <artifactId>javassist</artifactId>
          <version>3.23.1-GA</version>
        </dependency>
    
    
      </dependencies>
    </project>

        

    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<String,Handler> handlerMapping = new HashMap<String, Handler>();
    
        private static  final List<Scaner> 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<String, String[]> 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<BeanPostProcessor> 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 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 (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

          

          访问地址: 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系统(上)

  • 相关阅读:
    Sql Server 创建表(可重复执行--范本)
    Sql Server 存储过程(可重复执行--范本)
    table设置了colspan之后出现td宽度显示不正常
    textarea 自适应高度
    获取系统文件
    牛客网前端编程:计算给定数组 arr 中所有元素的总和
    牛客网前端编程:删除数组中特定元素
    牛客网前端编程:找出元素 item 在给定数组 arr 中的位置
    element+vue:将Unix时间戳转化标准格式
    vue-router使用
  • 原文地址:https://www.cnblogs.com/xrog/p/9820168.html
Copyright © 2011-2022 走看看