当前位置: 首页 > news >正文

手写 Spring - 详细思路与实践

手写 Spring - 详细思路与实践

之前的一篇 手写 Spring,这样的文章很多,到处都是,要说真的能简单手写出 Spring MVC 其实不多,因为要理解,记忆,实践才能掌握,这不是一篇博客就能实现的,可能需要两篇。。再说面试的时候,要不要说呢,自己还没底。。自定义命名部分为加 X- 前缀,请自行理解

一、必背思路框架

1、创建项目,准备 Jar 包
2、properties 和 web.xml
3、自定义注解(XController,XRequestMapping,XService,XAutowired)
4、XDispatchServlet(重点)
(1)继承 HttpServlet,重写 doGet,doPost 方法
(2)在 doPost 中先用注释,写出思路,7 步
(3)逐个填写方法

二、思路对应的详细步骤

这里代码有的为图片,建议手敲,需要的话可查看:手写 Spring

1、创建项目,准备 Jar 包

(1)创建项目,个人建议结构:

(2)需要一个 Jar 包

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
</dependency>
2、properties 和 web.xml

(1)web.xml 配置

<servlet> 和 一配一对
<init-param> 初始化的时候加载 properties 文件
<servlet-mapping> 中用 /*

在这里插入图片描述
(2)application.properties 配置要扫描的包名

scan-package=com.xiaopengwei
3、自定义注解

先只定义 4 个注解(XController,XRequestMapping,XService,XAutowired),定义的时候想想使用的地方,用于区别注解的 Target。

@Target 说明该 Annotation 可以修饰的对象范围
@Retention 描述注解的生命周期
@Documented 用于描述其它类型的 annotation 被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XAutowired {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XController {
    String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XRequestMapping {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XService {
    String value() default "";
}
4、XDispatchServlet

整理思路:

初始化,启动阶段

1、加载配置文件
2、扫描相关的类
3、初始化 IOC 容器
4、依赖注入
5、初始化 HandlerMapping
6、运行时进行匹配

运行,匹配阶段

1、处理请求,进行匹配

(1)继承 HttpServlet,重写 doGet,doPost,init 方法

提示:IDEA 快捷键,Alt + Insert 和 Ctrl + O 都可以重写方法

重写方法,将 deGet 也走 doPost

在这里插入图片描述

(2)在 init 方法中先用注释,写出思路,搭建骨架

1、加载配置文件
2、扫描相关的类
3、初始化 IOC 容器
4、依赖注入
5、初始化 HandlerMapping
6、运行时进行匹配

在这里插入图片描述

(3)加载配置文件 doLoadConfig()

在调用时添加参数,注意名称和 web.xml 中的参数名称一致
在这里插入图片描述

/**
     * 属性配置文件
     */
    private Properties contextConfig = new Properties();

    /**
     * servletConfig 参数是 web.xml 中配置的字符串为 application.properties
     * @param servletConfig servletConfig
     */
    private void doLoadConfig(String servletConfig) {

        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(servletConfig);

        try {
            contextConfig.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // InputStream 不会自动关闭
            if (resourceAsStream != null) {
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

(4) 扫描相关的类 doScanner()

在调用时添加参数,注意名称和 web.xml 中的参数名称一致
在这里插入图片描述

 private List<String> classNameList = new ArrayList<>();

    /**
     * 扫描相关的类
     * @param scanPackage properties --> scan-package
     */
    private void doScanner(String scanPackage) {

        // . 转化为 /
        URL resourcePath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));

        // 递归结束条件
        if (resourcePath == null) { return; }

        // 或 filePath 对应的 File 对象
        File classPath = new File(resourcePath.getFile());

        for (File file : classPath.listFiles()) {

            if (file.isDirectory()) {
                // 子目录递归
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) { continue; }

                // 转换成 com.xiaopengwei.TestController 的形式
                String className = (scanPackage + "." + file.getName()).replace(".class", "");
                // 保存在内容
                classNameList.add(className);
            }
        }
    }

(5)初始化 IOC 容器 doInstance()

  /**
     * IOC 容器
     */
    Map<String, Object> iocMap = new HashMap<String, Object>();

    /**
     * 初始化 IOC 容器,将所有相关的类实例保存到 IOC 容器中
     */
    private void doInstance() {
        if (classNameList.isEmpty()) { return; }

        try {
            for (String className : classNameList) {

                Class<?> clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(XController.class)) {
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    Object instance = clazz.newInstance();

                    // 保存在 ioc 容器
                    iocMap.put(beanName, instance);

                } else if (clazz.isAnnotationPresent(XService.class)) {

                    String beanName = toLowerFirstCase(clazz.getSimpleName());

                    // 如果注解包含自定义名称, 例如 @Service("testService")
                    XService xService = clazz.getAnnotation(XService.class);
                    if (!"".equals(xService.value())) {
                        beanName = xService.value();
                    }

                    Object instance = clazz.newInstance();
                    iocMap.put(beanName, instance);

                    // getInterfaces 此方法返回这个类中实现接口的数组
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (iocMap.containsKey(i.getName())) {
                            throw new Exception("The Bean Name Is Exist.");
                        }
                        // 接口不能实例化,接口存实现类的实例
                        iocMap.put(i.getName(), instance);
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取类的首字母小写的名称
     *
     * @param className ClassName
     * @return java.lang.String
     */
    private String toLowerFirstCase(String className) {
        char[] charArray = className.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }

(6)依赖注入 doAutowired()

 /**
     * 依赖注入
     */
    private void doAutowired() {
        if (iocMap.isEmpty()) { return; }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

            // 获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的生命字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                if (!field.isAnnotationPresent(XAutowired.class)) {
                    continue;
                }

                // 获取注解对应的类
                XAutowired xAutowired = field.getAnnotation(XAutowired.class);
                String beanName = xAutowired.value().trim();

                // 获取 XAutowired 注解的值
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }

                // 只要加了注解,都要加载,不管是 private 还是 protect
                field.setAccessible(true);

                try {
                    field.set(entry.getValue(), iocMap.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

(7)初始化 HandleMapping

    Map<String, Method> handlerMapping = new HashMap<String, Method>();
    
	/**
     * 5、初始化 HandlerMapping
     */
    private void initHandlerMapping() {

        if (iocMap.isEmpty()) { return; }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(XController.class)) { continue; }

            String baseUrl = "";

            if (clazz.isAnnotationPresent(XRequestMapping.class)) {
                XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);
                baseUrl = xRequestMapping.value();
            }

            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(XRequestMapping.class)) { continue; }

                XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);

                String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+", "/");

                handlerMapping.put(url, method);
            }
        }

    }

(8)进行匹配 doDispatch()

    Map<String, Method> handlerMapping = new HashMap<String, Method>();

    /**
     * 5、初始化 HandlerMapping
     */
    private void initHandlerMapping() {

        if (iocMap.isEmpty()) { return; }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(XController.class)) { continue; }

            String baseUrl = "";

            if (clazz.isAnnotationPresent(XRequestMapping.class)) {
                XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);
                baseUrl = xRequestMapping.value();
            }

            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(XRequestMapping.class)) { continue; }

                XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);

                String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+", "/");

                handlerMapping.put(url, method);
            }
        }

    }

提示:手写 Spring 中代码更全

5. 使用与测试

TestController

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * <p>
 * 前置控制器
 *
 * @author XiaoPengwei
 * @since 2019-07-19
 */
@XController
@XRequestMapping("/test")
public class TestController {

    @XAutowired
    ITestXService testXService;

    /**
     * 测试方法 /test/query
     *
     * @param req  请求体
     * @param resp 响应体
     */
    @XRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp) {

        if (req.getParameter("username") == null) {

            try {
                resp.getWriter().write("param username is null");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {

            String paramName = req.getParameter("username");

            try {
                resp.getWriter().write("param username is " + paramName);
            } catch (IOException e) {
                e.printStackTrace();
            }

            System.out.println("[INFO-req] New request param username-->" + paramName);
        }
    }

    /**
     * 测试方法 /test/listClassName
     *
     * @param req  请求体
     * @param resp 响应体
     */
    @XRequestMapping("/listClassName")
    public void listClassName(HttpServletRequest req, HttpServletResponse resp) {

        String str = testXService.listClassName();

        System.out.println("testXService----------=-=-=>" + str);
        try {
            resp.getWriter().write(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

ITestService

public interface ITestXService {

    String listClassName();
}

TestServiceImpl

@XService
public class TestXServiceImpl implements ITestXService {

    @Override
    public String listClassName() {

        // 假装来自数据库

        return "123456TestXServiceImpl";
    }

}

相关文章:

  • JVM 字节码指令手册 - 查看 Java 字节码
  • 浮点数为什么不精确
  • 不能使用 float 和 double 来表示金额等精确的值
  • 金额工具类
  • 为什么包装类型间的相等判断应该用 equals
  • 为什么重写 equals() 要重写 hashCode()? hashCode 值相等,两个对象不一定相等?
  • Ubuntu 18 搜狗输入法 - 输入汉字时候选栏乱码问题
  • Ubuntu 18 boot 分区空间不足-解决方法
  • CentOS7 下 Redis 的安装、配置、启动、关闭、开启远程连接
  • 告别吧 - 单链表反转(Java 实现)
  • IDEA 注释模板配置(新安装 IDEA 需要的配置)
  • MySQL 中 TIMESTAMP 类型返回日期时间数据中带有 T
  • HTML 页面跳转时传递参数(jquery.params.js)
  • Springboot 使用 Shiro 模板引擎时使用 swagger-ui 时的问题
  • Lambda、函数式接口、Stream - 从入门到入坑
  • interface和setter,getter
  • javascript 哈希表
  • laravel 用artisan创建自己的模板
  • PAT A1017 优先队列
  • React-flux杂记
  • Spring核心 Bean的高级装配
  • tweak 支持第三方库
  • 浮动相关
  • 回顾 Swift 多平台移植进度 #2
  • 前端相关框架总和
  • 区块链分支循环
  • 一起参Ember.js讨论、问答社区。
  • 正则表达式
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • 阿里云API、SDK和CLI应用实践方案
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • #考研#计算机文化知识1(局域网及网络互联)
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • ( 10 )MySQL中的外键
  • (1)(1.9) MSP (version 4.2)
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (C语言)二分查找 超详细
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (十五)使用Nexus创建Maven私服
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)linux下的时间函数使用
  • (转)树状数组
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .Net Core缓存组件(MemoryCache)源码解析
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net MySql
  • .NET中使用Protobuffer 实现序列化和反序列化
  • @RequestMapping用法详解
  • [ 云计算 | AWS ] 对比分析:Amazon SNS 与 SQS 消息服务的异同与选择
  • [30期] 我的学习方法
  • [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated c