• 手写Spring mvc框架 (一)


    手写Spring mvc框架

    一、依赖管理工具

    Maven、Gradle

    优点:

    • 自动化管理依赖(jar包)
    • 解决依赖冲突
    • 打包(mvn clean package)

    特性:

    • 约定大于配置

      比如src是约定好的代码目录

    • 同一个项目可以有很多模块,每个模块单独构建

    • 插件机制

    Gradle比起Maven的优点:

    • 使用json配置
    • 不用安装
    二、使用代码集成tomcat
    1. 引入jar包(Tomcat Embed Core

    2. Code:

      public class TomcatServer {
          private Tomcat tomcat;
          private String[] args;
      
          public TomcatServer(String[] args) {
              this.args = args;
          }
      
          public void startServer() throws LifecycleException {
              tomcat = new Tomcat();
              tomcat.setPort(6699);
              tomcat.start();
      
              Context context = new StandardContext();
              context.setPath("");
              context.addLifecycleListener(new Tomcat.FixContextListener());
      		
              DispatcherServlet servlet = new DispatcherServlet();
              Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);
              // "/":映射所有的url
              context.addServletMappingDecoded("/", "dispatcherServlet");
              tomcat.getHost().addChild(context);
      
              Thread awaitThread = new Thread("tomcat_await_thread"){
                  @Override
                  public void run() {
                      //内部类调用外部类的实例成员变量
                      //这行代码会阻塞,作用难道是让tomcat不关闭?
                      TomcatServer.this.tomcat.getServer().await();
                  }
              };
              awaitThread.setDaemon(false);
              awaitThread.start();
          }
      }
      
    三、一个请求的流程
    1. 前端请求经由http协议发送到tomcat监听的端口上,tomcat将请求经由解码、反序列化等操作封装成ServletRequest对象,然后会调用DispatcherServlet的service()

       public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
              for (MappingHandler mappingHandler : HandlerManager.mappingHandlerList) {
                  try {
                      if (mappingHandler.handle(req, res)) {
                          return;
                      }
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  } catch (InstantiationException e) {
                      e.printStackTrace();
                  } catch (InvocationTargetException e) {
                      e.printStackTrace();
                  }
              }
          }
      

      可以看到代码中只有一个操作:遍历mappingHandlerList

    2. 对请求url进行匹配,寻找对应的函数进行处理

      mappingHandlerList保存的是mappingHandler,mappingHandler中存储了请求path对应执行函数等信息

      (ps:由于path很少,代码中没有用map,而是直接遍历了)

      我们先看建立mappingHandlerList的建立过程,它先遍历出注解了@controller的类,再遍历@RequestMapping对应的方法,最后建立MappingHandler对象,将其加入list中

      public class HandlerManager {
          public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
      	//项目初始化时,这个静态方法会被调用
          public static void resolveMappingHandler(List<Class<?>> classList) {
              for (Class<?> cls : classList) {
                  if (cls.isAnnotationPresent(Controller.class)) {
                      parseHandlerFromController(cls);
                  }
              }
          }
      
          private static void parseHandlerFromController(Class<?> cls) {
              Method[] methods = cls.getDeclaredMethods();
              for (Method method : methods) {
                  //寻找加了@RequestMapping的函数
                  if (!method.isAnnotationPresent(RequestMapping.class)) {
                      continue;
                  }
                  String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
                  List<String> paramNameList = new ArrayList<>();
                  for (Parameter parameter : method.getParameters()) {
                      if (parameter.isAnnotationPresent(RequestParam.class)) {
                          paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
                      }
                  }
                  String[] params = paramNameList.toArray(new String[paramNameList.size()]);
                  MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
                  HandlerManager.mappingHandlerList.add(mappingHandler);
              }
          }
      }
      

      @Controller,@RequestMapping,@RequestParam注解定义的代码暂且省略。

      好了,请求来到了mappingHandler

      public class MappingHandler {
          private String uri;
          private Method method;
          private Class<?> controller;
          private String[] args;	//这个是添加了@RequestParam的参数的参数列表
      
          public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
              String requestUri = ((HttpServletRequest) req).getRequestURI();
              if (!uri.equals(requestUri)) {
                  return false;
              }
      
              Object[] parameters = new Object[args.length];
              for (int i=0;i<args.length;i++) {
                  parameters[i] = req.getParameter(args[i]);
              }
      
              Object ctl = BeanFactory.getBean(controller);
              Object response = method.invoke(ctl, parameters);
              //将返回结果写入ServletResponse对象
              res.getWriter().println(response.toString());
              return true;
          }
      
      
          MappingHandler(String uri, Method method, Class<?> cls, String[] args) {
              this.uri = uri;
              this.method = method;
              this.controller = cls;
              this.args = args;
          }
      }
      

    写入了数据的ServletResponse对象会转成字节发到前端,这个流程就结束了

    四、项目的初始化

    为啥我要最后再讲初始化,,似乎更好理解?

    回想我们使用spring的时候都会有一个Application类,就是下面这个熟悉的东西:

    public class Application {
        public static void main(String[] args) {
            MiniApplication.run(Application.class, args);
        }
    }
    

    我们来看看MiniApplication.run(Application.class, args)

        public static void run(Class<?> cls, String[] args) {
            System.out.println("Hello mini-spring!");
            TomcatServer tomcatServer = new TomcatServer(args);
            try {
                //1、启动服务器
                tomcatServer.startServer();
                //2、扫描src中的类文件
                List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
                //3、初始化BeanFactory
                BeanFactory.initBean(classList);
                //4、建立mappingHandlerList
                HandlerManager.resolveMappingHandler(classList);
                //5、打印类信息
                classList.forEach(it-> System.out.println(it.getName()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    其中第2步需要细说一下:

    • 先说一个注意点,我们传入的参数是:cls.getPackage().getName()

      就是说只会去扫描该包下的类文件,也就是说为什么我们要把Application.java文件放在最外面

    public class ClassScanner {
        public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
            List<Class<?>> classList = new ArrayList<>();
            String path = packageName.replace(".", "/");
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();
                if (resource.getProtocol().contains("jar")) {
                    JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                    String jarFilePath = jarURLConnection.getJarFile().getName();
                    classList.addAll(getClassesFromJar(jarFilePath, path));
                }else {
                    // todo
                }
            }
            return classList;
        }
    
        private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
            List<Class<?>> classes = new ArrayList<>();
            JarFile jarFile = new JarFile(jarFilePath);
            Enumeration<JarEntry> jarEntries = jarFile.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement();
                String entryName = jarEntry.getName();// com/mooc/zbs/test/Test.class
                if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                    String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
                    classes.add(Class.forName(classFullName));
                }
            }
            return classes;
        }
    }
    

    借助了classLoader可以访问文件资源的特性

    具体代码没有细读,就不说了。

  • 相关阅读:
    欧几里德算法
    int 和 string 相互转换(简洁版)
    骆驼吃香蕉
    链表反转 (Multi-method)
    二分查找 (最经典代码,及其边界条件的实践分析)
    mottoes
    欧拉函数,欧拉定理,费马小定理。
    深搜和广搜的对比
    Python基础
    马拉车求最大回文字串
  • 原文地址:https://www.cnblogs.com/lnu161403214/p/11062198.html
Copyright © 2020-2023  润新知