mini-spring 第四期:类扫描器,控制器初始化

本贴最后更新于 1720 天前,其中的信息可能已经时移俗易

1.在 core 包中定义 ClassScanner,并定义 scanClass 方法,目的是给定一个包路径,扫描出该包下面的所有类
主要的流程是:
@1.拿到改包路径下的所有资源,遍历
@2.如果是 jar 包,就继续遍历这个 jar 包,把 jar 包的需要的 class 扫描进来,也就是以我们包名的开头的.class 文件
@3.jar 包中的 jarentryname 的格式是 cn/chenforcode/xxx.class,所以必须转换成包名,然后才可以用类加载器进行扫描。

package cn.chenforcode.core;

import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassSacnner {
    public static List<Class<?>> scanClass(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();
            //如果这个文件是一个jar包,需要单独处理
            if (resource.getProtocol().contains("jar")) {
                //拿到jar包连接
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                //拿到jar包路径
                String jarFilePath = jarURLConnection.getJarFile().getName();
                classList.addAll(getClassesFromJar(jarFilePath, path));
            } else {
                //todo 处理不是jar包类型的资源
            }
        }
        return classList;
    }

    public static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
        //todo 从jar包中获取所需要的类
        List<Class<?>> classes = new ArrayList<>();
        //获取这个jar文件
        JarFile jarFile = new JarFile(jarFilePath);
        //拿到jar文件的entry集合
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        //遍历jar文件
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            //拿到某个类的entryname,集体格式如cn/chenforcode/xxx.class
            String entryName = jarEntry.getName();
            //判断这个文件是否是我们需要的
            if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                String classFullName = entryName.replace("/", ".")
                        .substring(0, entryName.length() - 6);
                //如果是把它加载进去,并加入class的list返回
                classes.add(Class.forName(classFullName));
            }
        }
        return classes;
    }
}

2.开始编写 handler

package cn.chenforcode.web.handler;

import java.lang.reflect.Method;

public class MappingHandler {
    private String uri;
    private Method method;
    private Class<?> controller;
    private String[] args;

    MappingHandler(String uri, Method method, Class<?> cls, String[] args) {
        this.uri = uri;
        this.method = method;
        this.cls = cls;
        this.args = args;
    }
}

3.创建 handlerManager 来管理 mappingHandler

4.创建 resolveMappingHandler 方法,对给出的类列表进行遍历

/**
     * @Author <a href="http://www.chenforcode.cn">PKUCoder</a>
     * @Date 2019/11/6 4:50 下午
     * @Param [classList]
     * @Return void
     * @Description 对给出的一个类列表遍历,解析其中的方法
     **/
    public static void resolveMappingHandler(List<Class<?>> classList) {
        for (Class<?> cls : classList) {
            if (cls.isAnnotationPresent(Controller.class)) {
                parseHandlerFromController(cls);
            }
        }
    }

5.创建 parseHandlerFromController 方法,对某个类中的方法进行解析

/**
     * @Author <a href="http://www.chenforcode.cn">PKUCoder</a>
     * @Date 2019/11/6 5:17 下午
     * @Param [cls]
     * @Return void
     * @Description 对某个类中的方法进行解析
     **/
    private static void parseHandlerFromController(Class<?> cls) {
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            //如果没有被requestMapping注解修饰就不用解析了
            if (!method.isAnnotationPresent(RequestMapping.class)) {
                continue;
            }
            //得到mapping需要的参数
            //获取uri
            String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
            //获取参数
            List<String> paramNameList = new ArrayList<>();
            //遍历参数,如果带有了requestParam注解的话,就进行解析
            for (Parameter parameter: method.getParameters()) {
                if (parameter.isAnnotationPresent(RequsetParam.class)) {
                    //得到所有的参数名称,加入一个list
                    paramNameList.add(parameter.getDeclaredAnnotation(RequsetParam.class).value());
                }
            }
            //将参数list转化成参数数组
            String[] args = paramNameList.toArray(new String[paramNameList.size()]);
            //构造一个mappingHandler,注意这个handler是方法级别的,每一个方法都对应着一个handler
            MappingHandler mappingHandler = new MappingHandler(uri, method, cls, args);
            //加入到handler的集合中
            HandlerManager.mappingHandlerList.add(mappingHandler);
        }
    }

6.接下来是要在 dispatcherservlet 中使用 handler

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

7.编写 handler 的 handle 方法,即开始处理请求

public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String requestURI = ((HttpServletRequest)req).getRequestURI();
        //如果uri不相等,说明这个handler是不能处理的
        if (!uri.equals(requestURI)) {
            return false;
        }
        //开始处理请求
        Object[] parameters = new Object[args.length];
        //初始化参数
        for (int i = 0; i < args.length; i++) {
            parameters[i] = args[i];
        }
        Object ctl = controller.newInstance();
        //利用反射调用controller,这个response就相当于controller执行完返回的结果
        Object response = method.invoke(ctl, parameters);
        res.getWriter().println(response.toString());
        return true;
    }

8.这个时候可以捋一捋。。首先 dispatcherServlet 已经封装在了服务器中,并以一个“/”路径进行拦截,也就是说,每一个请求都会经过这个 servlet,并通过他的 service 方法。然后在这个方法里,会进行这次请求的 uri 比对,根据这次请求来的 uri,在所有的 handlermapping 中遍历,如果能够找到一个 hanlermapping 与之相对应,那么就让这个 handlermapping 进行处理,即调用 handle 方法。同时呢,在这个方法里,给 controller 实例化一个对象,然后利用 method 的 invoke 反射机制调用 controller,得到处理结果,放入 response 并返回。

9.这个时候重新打包项目,运行,已经能够响应 controller 的请求了。

10.写到这里我仍让有个疑问,mappinghandler 中保存的参数,我觉得应该仅仅是参数的名称的一个数组吧,如果仅仅把名称传入 invoke,是如何调用的呢???那个真正的参数值是什么时候传递过来的呢。。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    942 引用 • 1458 回帖 • 109 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖
  • 代码
    460 引用 • 591 回帖 • 8 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 224 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 561 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 378 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4599 回帖 • 701 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 383 回帖
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    83 引用 • 165 回帖 • 5 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    51 引用 • 190 回帖
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    69 引用 • 190 回帖 • 474 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 7 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    483 引用 • 906 回帖
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    19 引用 • 23 回帖 • 702 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    143 引用 • 3752 回帖
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1234 回帖 • 440 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    45 引用 • 113 回帖 • 276 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    14 引用 • 7 回帖 • 5 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 519 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖
  • 996
    13 引用 • 200 回帖 • 6 关注
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 188 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 523 关注
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    675 引用 • 535 回帖
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    148 引用 • 257 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    122 引用 • 73 回帖