手写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可以访问文件资源的特性

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

posted @ 2019-06-20 23:54 hzhuan 阅读(...) 评论(...) 编辑 收藏