SpringMVC系列七: 手动实现SpringMVC底层机制-上

手动实现SpringMVC底层机制

      • 博客的技术栈分析 🛠️
      • 具体实现细节
      • 总结
  • 🐟准备工作
    • 🍍搭建SpringMVC底层机制开发环境
  • 实现任务阶段一
    • 🍍开发ZzwDispatcherServlet
      • 🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
      • 🥦分析+代码实现
      • 🥦配置Tomcat, 完成测试
  • 实现任务阶段二
    • 🍍完成客户端/浏览器可以请求控制层
      • 🥦1.创建自己的Controller和自定义注解
      • 🥦2.配置zzwspringmvc.xml
      • 🥦3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
      • 🥦4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
      • 🥦5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
      • 🥦6.完成请求URL和控制器方法的映射关系
      • 🥦7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
  • 实现任务阶段三
    • 🍍从web.xml动态获取zzwspringmvc.xml
  • 实现任务阶段四
    • 🍍完成自定义@Service注解功能


⬅️ 上一篇: SpringMVC系列六: 视图和视图解析器


🎉 欢迎来到 SpringMVC系列七: 手动实现SpringMVC底层机制-上 🎉

在本篇文章中,我们将深入探讨如何手动实现SpringMVC的底层机制。通过理解这些机制,可以更好地掌握SpringMVC的工作原理。


🔧 本篇需要用到的项目: zzw-springmvc项目


博客的技术栈分析 🛠️

主要技术

  • 🌐 前端框架: (此博客主要关注于 SpringMVC 后端实现,因此未涉及具体前端框架)
  • 🔧 后端框架: SpringMVC
    • SpringMVC 是 Spring Framework 的一个模块,用于构建基于 MVC (Model-View-Controller) 架构的 web 应用程序。
    • 博客中详细讲解了如何手动实现 SpringMVC 的核心机制,包括前端控制器、请求处理流程等。
  • 📦 依赖管理: Maven
    • Maven 是一个项目管理和构建工具,用于管理项目依赖和构建流程。
    • 通过配置 pom.xml 文件来管理项目的依赖项,如 Servlet API、Junit 等。
  • 📋 注解处理: 自定义注解
    • 博客中使用了自定义注解(如 @Controller@RequestMapping)来标识控制器类和方法,并通过反射实现注解处理。

辅助工具

  • 📄 XML 配置: Dom4j
    • Dom4j 是一个用于处理 XML 的开源 Java 库。博客中使用 Dom4j 解析 zzwspringmvc.xml 配置文件,以获取需要扫描的包路径。
  • 🔍 反射 API: Java 反射
    • Java 反射 API 被广泛用于动态获取类信息和调用方法。博客通过反射机制来扫描包、实例化类和调用控制器方法。
  • 🗂️ 集合框架: ConcurrentHashMap
    • 使用 ConcurrentHashMap 来存储 IoC 容器中的 bean 实例,确保线程安全。

功能模块

  • 核心控制器: ZzwDispatcherServlet
    • 继承 HttpServlet 类,通过覆盖 doGetdoPost 方法实现核心控制器功能,处理所有请求并将其分发到对应的控制器方法。
  • 自定义 IoC 容器: ZzwWebApplicationContext
    • 模拟 Spring 的 IoC 容器,扫描指定包路径下的类,并将带有注解的类实例化并存储到容器中。
  • 请求映射处理: ZzwHandler
    • 维护 URL 与控制器方法的映射关系,并在请求到达时根据 URL 查找并调用对应的控制器方法。

具体实现细节

  1. 核心控制器 (ZzwDispatcherServlet)

    • 通过在 web.xml 中配置,将所有请求映射到 ZzwDispatcherServlet,实现统一的请求分发。
  2. IoC 容器 (ZzwWebApplicationContext)

    • 扫描指定包路径下的类,判断是否包含特定注解(如 @Controller, @Service),并实例化这些类,存储到 ConcurrentHashMap 中。
  3. 请求映射 (ZzwHandler)

    • 使用自定义注解 @RequestMapping 指定控制器方法的 URL 映射,在请求到达时,通过 URL 找到对应的控制器方法并调用。
  4. 反射机制

    • 通过反射获取类的元数据和注解信息,动态调用方法。
  5. XML 解析

    • 使用 Dom4j 解析 Spring 配置文件 zzwspringmvc.xml,获取需要扫描的包路径,实现配置的灵活性。

总结

本博客深入剖析了 SpringMVC 的底层实现机制,通过手动实现类似 SpringMVC 的功能,展示了 Java 反射、注解处理、XML 解析等技术的应用。通过这种方式,读者能够更好地理解 SpringMVC 的工作原理,提升自身的编程能力和框架理解能力。

在这里插入图片描述

🐟准备工作

🍍搭建SpringMVC底层机制开发环境

前提: 搭建maven环境

1.创建zzw-springmvc项目, 这是一个maven-web项目
在这里插入图片描述

出现了点小插曲. 项目建成后, 没有src目录, 且右下角报错 Cannot find JRE '1.7
做如下修改
在这里插入图片描述
改成1.8
在这里插入图片描述

缺少的文件夹需自己手动创建
在这里插入图片描述
pom.xml配置

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

    <!--引入原生servlet依赖的jar包-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <!--解读
        1.scope标签表示引入的jar包的作用范围
        2.provided:表示该项目在打包, 放到生产环境时, 不需要带上servlet-api.jar包
        3.因为tomcat本身是有servlet的jar包, 到时直接使用tomcat本身的servlet-api.jar包, 防止版本冲突
        4.到后面会再次学习maven.
        -->
        <scope>provided</scope>
    </dependency>
    <!--引入dom4j-->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!--引入常用工具类的jar包-该jar包含有很多常用的类-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
    </dependency>
</dependencies>

实现任务阶段一

🍍开发ZzwDispatcherServlet

🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)

🥦分析+代码实现

在这里插入图片描述

1.com.zzw.zzwspringmvc.servlet包下新建ZzwDispatcherServlet.java

/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 */
public class ZzwDispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("ZzwDispatcherServlet doPost()...");
    }
}

2.src/main/resources(类路径)下新建 zzwspringmvc.xml, spring的容器配置文件

在这里插入图片描述

<!--先空着-->

在这里插入图片描述
对应的类路径
在这里插入图片描述

3.webapp/WEB-INF配置web.xml
load-on-startup讲解

<!--配置ZzwDispatcherServlet, 作为我们自己的前端控制器-->
<servlet>
  <servlet-name>ZzwDispatcherServlet</servlet-name>
  <servlet-class>com.zzw.zzwspringmvc.servlet.ZzwDispatcherServlet</servlet-class>
  <!--给ZzwDispatcherServlet配置参数, 指定要操作的spring容器配置文件-->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:zzwspringmvc.xml</param-value>
  </init-param>
  
  <!--ZzwDispatcherServlet在tomcat启动时, 就会自动加载. 调用init方法-->
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>ZzwDispatcherServlet</servlet-name>
  <!--因为ZzwDispatcherServlet作为前端控制器, 所以需要拦截所有请求-->
  <url-pattern>/</url-pattern>
</servlet-mapping>

🥦配置Tomcat, 完成测试

1.配置tomcat
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

2.测试, 随便请求一个网址
在这里插入图片描述在这里插入图片描述

实现任务阶段二

🍍完成客户端/浏览器可以请求控制层

🥦1.创建自己的Controller和自定义注解

示意图[分析说明]
在这里插入图片描述

1.在com.zzw.controller下新建MonsterController

public class MonsterController {

    //编写方法, 可以列出怪物列表
    //springmvc 是支持原生的servlet api, 为了看到底层机制
    //这里我们涉及两个参数
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.在com.zzw.zzwspringmvc.annotation下新建注解类@Controller
RetentionPolicy.RUNTIME: 编译器把注解记录在class文件中, 当运行Java程序时, JVM 会保留注解. 程序可以通过反射获取该注解

/**
 * @author 赵志伟
 * @version 1.0
 * 该注解用于标识一个控制器组件
 * 这里涉及到注解知识, 在java基础
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
	String value() default "";
}

3.在该包下新建注解类RequestMapping

/**
 * @author 赵志伟
 * @version 1.0
 * RequestMapping 注解用于指定控制器-方法的映射路径
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
	String value() default "";;
}

4.在MonsterController中添加注解

@Controller
public class MonsterController {

    //编写方法, 可以列出怪物列表
    //springmvc 是支持原生的servlet api, 为了看到底层机制
    //这里我们涉及两个参数
    @RequestMapping(value = "/monster/list")
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

🥦2.配置zzwspringmvc.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--指定要扫描的基本包以及子包的java类--> 
    <component-scan base-package="com.zzw.controller"/>
</beans>

🥦3.编写XMLParser工具类, 可以解析zzwspringmvc.xml

Dom4j解析配置文件代码实现
1.在com.zzw.zzwspringmvc.xml编写XMLParser工具类, 可以解析zzwspringmvc.xml, 得到要扫描的包

/**
 * @author 赵志伟
 * @version 1.0
 * XMLParser 用于解析spring配置文件
 */
@SuppressWarnings({"all"})
public class XMLParser {

    public static String getBasePackage(String xmlFile) {
        //1.得到解析器
        SAXReader reader = new SAXReader();
        //2.得到类的加载路径 => 获取到spring配置文件[对应的资源流]
        InputStream inputStream =
                XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
        try {
            //3.得到xml文件的文档
            Document document = reader.read(inputStream);
            //4.获取rootElement
            Element rootElement = document.getRootElement();
            //5.获取component-scan节点
            Element componentScanElement =
                    (Element) rootElement.elements("component-scan").get(0);
            //6.获取component-scan节点的base-package属性值
            String basePackage = componentScanElement.attributeValue("base-package");
            //7.返回
            return basePackage;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.在com.zzw.test新建ZzwSpringMVCTest.java测试类
在这里插入图片描述
XMLParser类在很多包下都有, 别选错
在这里插入图片描述

public class ZzwSpringMVCTest {
    @Test
    public void readXML() {
        String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        System.out.println(basePackage);
    }
}

🥦4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.

把指定的目录包括子目录下的java类的全路径扫描到集合中, 比如 ArrayList [java基础]

示意图[分析说明]
在这里插入图片描述

1.在com.zzw.zzwspringmvc.context下新建ZzwWebApplicationContext.java

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwWebApplicationContext 表示我们自己的spring容器
 */
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/子包的类的全路径
    private List<String> classFullPathList =
            new ArrayList<String>();

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        scanPackage(basePackage);
    }

    /**
     * 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
     * @param pack 表示要扫描的包, 比如 com.zzw.controller
     */
    public void scanPackage(String pack) {
        //通过类的加载器, 得到指定的包所在的工作路径对应的绝对路径
       	//比如 com.zzw.controller => url = file:/D:/idea_project/zzw_springmvczzw-springmvc/target/classes/com/zzw/controller
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL url = classLoader.getResource(pack.replace(".", "/"));
        //细节说明:
        // 1.不要直接使用Junit测试, 否则 url返回null
        // 2.启动Tomcat测试, 才能得到这个类路径
        System.out.println("url=" + url);
    }
}

2.前端控制器ZzwDispatcherServlet增加init方法

/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 */
public class ZzwDispatcherServlet  extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
   		super.init(config);
        ZzwWebApplicationContext zzwWebApplicationContext =
                new ZzwWebApplicationContext();
        zzwWebApplicationContext.init();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("ZzwDispatcherServlet doPost()...");
    }
}

3.启动Tomcat, 进行测试
在这里插入图片描述

4.开发自己的spring容器

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwWebApplicationContext 表示我们自己的spring容器
 */
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/子包的类的全路径
    private List<String> classFullPathList =
            new ArrayList<String>();

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        scanPackage(basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
    }

    /**
     * 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
     *
     * @param pack 表示要扫描的包, 比如 com.zzw.controller
     */
    public void scanPackage(String pack) {
        //通过类的加载器, 得到指定的包所在的工作路径对应的绝对路径
        //比如 com.zzw.controller => url = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL url = classLoader.getResource(pack.replace(".", "/"));
        //细节说明:
        // 1.不要直接使用Junit测试, 否则 url返回null
        // 2.启动Tomcat测试, 才能得到这个类路径
        System.out.println("url=" + url);
        //根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathList
        String path = url.getFile();
        File dir = new File(path);//在io中, 目录也是文件
        //遍历dir[文件/子目录]
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {//如果是一个目录, 需要递归扫描
                scanPackage(pack + "." + f.getName());//f.getName() 子包的名称
            } else {
                //说明: 这时, 你扫描到的文件, 可能是.class文件, 也可以是其它文件
                // 就算是.class文件, 也存在是不是需要注入到容器中的问题
                // 目前先把所有.class文件的全路径都保存到集合中, 后面在注入对象到容器时, 再处理
                // 这里只考虑 .class文件
                String classFullPath = pack + "." + f.getName().replaceAll(".class", "");
                classFullPathList.add(classFullPath);
            }
        }
    }
}

5.在com.zzw.controller.xx包下新建GoodsController, OrderController.

6.重启Tomcat, 测试
在这里插入图片描述

🥦5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中

功能说明: 将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service...时), 反射到ioc容器.

1.ZzwWebApplicationContext 增加ioc属性. 增加executeInstance方法

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwWebApplicationContext 表示我们自己的spring容器
 */
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {

    //定义属性ioc, 存放反射生成的bean对象
    public ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<String, Object>();

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        scanPackage(basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ioc容器
        executeInstance();
        System.out.println("扫描后的 ioc容器 " + ioc);
    }

    //编写方法, 将扫描到的类, 在满足条件的情况下, 反射到ioc容器
    public void executeInstance() {
        //判断是否扫描到类
        if (classFullPathList.size() == 0) {//说明没有扫描到类
            return;
        }
        try {
            //遍历classFullPathList, 进行反射
            for (String classFullPath : classFullPathList) {
                Class<?> clazz = Class.forName(classFullPath);
                //说明当前这个类有@Controller
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //beanName 假设是默认的, 即类名首字母小写
                    String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
                    ioc.put(beanName, clazz.newInstance());
                }//如果有其它注解, 可以拓展!!
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

我这里输出的时候乱码, 我的解决方案是. 全改成UTF-8在这里插入图片描述

测试
在这里插入图片描述

🥦6.完成请求URL和控制器方法的映射关系

功能说明: 将配置的@RequestMappingurl和 对应的 控制器-方法 映射关系保存到集合中

示意图[分析说明]
在这里插入图片描述

1.在com.zzw.zzwspringmvc.handler下新建ZzwHandler

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwHandler 对象记录请求的url 和 控制器方法映射关系
 */
@SuppressWarnings({"all"})
public class ZzwHandler {
    private String url;
    private Object controller;
    private Method method;

   public ZzwHandler(String url, Object controller, Method method) {
       this.url = url;
       this.controller = controller;
       this.method = method;
   }
   
	//getter, setter, toString方法
}

2.修改ZzwDispatcherServlet

  1. init方法内声明的zzwWebApplicationContext属性提到外面, 扩大它的作用域
  2. 定义属性 handlerList, 保存ZzwHandler[url 和 控制器-方法的映射关系]
  3. 编写方法[initHandlerMapping], 完成url 和 控制器-方法的映射 (initHandlerMapping也可以写在HandlerMapping类中, 逻辑是一样的)
/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 * 3.提示: 这里我们需要使用到 java web 讲解的Servlet
 */
public class ZzwDispatcherServlet extends HttpServlet {

    //定义属性 handlerList, 保存ZzwHandler[url 和 控制器-方法的映射关系]
    private List<ZzwHandler> handlerList
            = new ArrayList<ZzwHandler>();

    //定义属性 zzwWebApplicationContext, 自己的spring容器
    ZzwWebApplicationContext zzwWebApplicationContext = null;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        zzwWebApplicationContext = new ZzwWebApplicationContext();
        zzwWebApplicationContext.init();
        //调用 initHandlerMapping, 完成url和控制器方法的映射
        initHandlerMapping();
        System.out.println("handlerList初始化的结果=" + handlerList);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("ZzwDispatcherServlet doPost()...");
    }

    //编写方法, 完成url 和 控制器方法的映射
    private void initHandlerMapping() {
        if (zzwWebApplicationContext.ioc.isEmpty()) {
            //判断当前的ioc容器是否为空
            return;
        }

        //遍历ioc容器的bean对象, 然后进行url映射处理
        //java基础 map遍历
        for (Map.Entry<String, Object> entry : zzwWebApplicationContext.ioc.entrySet()) {
            //先取出实例, 转化为clazz对象[要获取类的内部信息, 类的实例对象不好用, 要用类的Class对象 反射知识]
            Class<?> clazz = entry.getValue().getClass();
            //如果注入的bean是Controller
            if (clazz.isAnnotationPresent(Controller.class)) {
                //取出它所有的方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                //遍历方法
                for (Method declaredMethod : declaredMethods) {
                    //判断该方法是否有@RequestMapping
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //取出@RequestMapping值 -> 就是映射路径
                        RequestMapping requestMappingAnnotation =
                                declaredMethod.getDeclaredAnnotation(RequestMapping.class);
                        String url = requestMappingAnnotation.value();
                        //创建ZzwHandler对象->就是一个映射关系 [保存映射关系]
                        ZzwHandler zzwHandler =
                                new ZzwHandler(url, entry.getValue(), declaredMethod);
                        //放入到 handlerList
                        handlerList.add(zzwHandler);
                    }
                }
            }
        }
    }
}

🥦7.完成ZzwDispatcherServlet 分发请求到对应控制器方法

功能说明: 完成ZzwDispatcherServlet 分发请求到对应控制器方法

示意图[分析说明]

在这里插入图片描述

-当用户发出请求, 根据用户请求url 找到对应的 控制器-方法, 并反射调用

-如果用户请求的路径不存在, 返回404


1.ZzwDispatcherServlet添加getZzwHandler()方法和executeDispatcher()方法, 在doPost中调用 executeDispatcher()方法

public class ZzwDispatcherServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
 		//创建自己的spring容器
        zzwWebApplicationContext = new ZzwWebApplicationContext();
        zzwWebApplicationContext.init();
        //调用 initHandlerMapping, 完成url和控制器方法的映射
        initHandlerMapping();
        System.out.println("handlerList初始化的结果=" + handlerList);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //System.out.println("ZzwDispatcherServlet doPost()...");
        //调用方法, 完成请求转发
        executeDispatcher(req, resp);
    }

    //编写方法, 通过request对象, 返回ZzwHandler对象
    //如果没有, 就返回null
    private ZzwHandler getZzwHandler(HttpServletRequest request) {
        //1.先获取到用户请求的url 比如http://localhost:8080/zzw_springmvc/monster/list
        // uri = /zzw_springmvc/monster/list
        //2.这里要注意得到的uri 和 保存的url 有一个工程路径的问题
        //两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context => /
        // 第二个方案: 保存 zzwHandler对象的url时, 拼接 this.getServletContext().getContextPath()
        String requestURI = request.getRequestURI();
        //遍历 handlerList
        for (ZzwHandler zzwHandler : handlerList) {
            if (requestURI.equals(zzwHandler.getUrl())) {//说明匹配成功
                return zzwHandler;
            }
        }
        return null;
    }

    //编写方法, 完成分发请求任务
    private void executeDispatcher(HttpServletRequest request,
                                   HttpServletResponse response) {
        try {
            ZzwHandler zzwHandler = getZzwHandler(request);
            if (zzwHandler == null) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND!</h1>");
            } else {//匹配成功, 反射调用控制器的方法
                zzwHandler.getMethod().
                        invoke(zzwHandler.getController(), request, response);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.OrderController增加两个方法listOrder(), addOrder() 别忘了加Controller注解

@Controller
public class OrderController {

    @RequestMapping(value = "/order/list")
    public void listOrder(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>订单列表信息</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @RequestMapping(value = "/order/add")
    public void addOrder(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>添加订单信息</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.GoodsController增加一个方法listGoods()

@Controller
public class GoodsController {

    @RequestMapping(value = "/goods/list")
    public void listGoods(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>商品列表信息...</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

4.测试(注意: 不要再加工程路径了)
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述
handlerList初始化的结果=
[ZzwHandler{url=‘/goods/list’, controller=com.zzw.controller.xx.GoodsController@79b1752f, method=public void com.zzw.controller.xx.GoodsController.listGoods(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/order/add’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.addOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/order/list’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/monster/list’, controller=com.zzw.controller.MonsterController@32128628, method=public void com.zzw.controller.MonsterController.listMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]

实现任务阶段三

🍍从web.xml动态获取zzwspringmvc.xml

说明: 前面我们加载zzwspringmvc.xml是硬编码, 现在做活. 从web.xml动态获取

示意图[分析说明]
在这里插入图片描述

1.ZzwDispatcherServlet在创建并初始化ZzwWebApplicationContext时, 动态地从web.xml中获取到spring配置文件.
servletConfig使用

/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 * 3.提示: 这里我们需要使用到 java web 讲解的Servlet
 */
public class ZzwDispatcherServlet extends HttpServlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //获取到web.xml中的
        /*
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:zzwspringmvc.xml</param-value>
            </init-param>
         */
        String configLocation = servletConfig.getInitParameter("contextConfigLocation");
        //创建自己的spring容器
        zzwWebApplicationContext = new ZzwWebApplicationContext(configLocation);
        zzwWebApplicationContext.init();
        //调用 initHandlerMapping, 完成url和控制器方法的映射
        initHandlerMapping();
        System.out.println("handlerList初始化的结果=" + handlerList);
    }
		
	.......
}

2.ZzwWebApplicationContext.java中添加一个属性configLocation, 和一个无参构造器, 一个有参构造器, 并修改init()方法

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwWebApplicationContext 表示我们自己的spring容器
 */
public class ZzwWebApplicationContext {
    //定义属性classFullPath, 保存扫描包/子包的类的全路径
    private List<String> classFullPathList
            = new ArrayList<String>();

    //定义属性ioc, 存放反射生成的bean对象 有Controller/Service注解
    public ConcurrentHashMap<String, Object> ioc
            = new ConcurrentHashMap<String, Object>();
         
    //创建一个属性, 表示spring容器配置文件
    private String configLocation;   
    
    //添加一个无参构造器
    public ZzwWebApplicationContext() {
    }

    //构建一个有参构造器
    public ZzwWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        //这里我们写的是固定的spring容器配置文件 => 做活
        //String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        scanPackage(basePackage);
        System.out.println("basePackage=" + basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ioc容器
        executeInstance();
        System.out.println("扫描后的 ioc容器=" + ioc);
    }
    ........
}

3.测试…

实现任务阶段四

🍍完成自定义@Service注解功能

说明: 如果给某个类加上@Service, 则可以将其注入到我们的Spring容器

示意图[分析说明]
在这里插入图片描述
补充: DAO和DB由MyBatis接管, 和SpringMVC关系并不大. 所以我们暂时不考虑DAO和DB.
在这里插入图片描述

1.在com.zzw.entity包下新建Monster

public class Monster {
    private Integer id;
    private String name;
    private String skill;
    private Integer age;

	//全参构造器, getter, setter, toString方法
}

2.在com.zzw.zzwspringmvc.annotation下新建@Service. 这个注解是springmvc框架要支持的东西, 所以要在zzwspringmvc包下

/**
 * @author 赵志伟
 * @version 1.0
 * Service 注解, 用于标识一个Service对象, 并注入到spring容器
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

3.在com.zzw.service下新建MonsterService接口.

public interface MonsterService {
    //增加方法-返回monster列表
    public List<Monster> listMonster();
}

3.1在com.zzw.service.impl新建MonsterServiceImpl实现类. 并标注@Service, 表示可以将对象注入到Spring容器

/**
 * @author 赵志伟
 * @version 1.0
 * MonsterServiceImpl 作为一个Service注入到spring容器
 */
@SuppressWarnings({"all"})
public class MonsterServiceImpl implements MonsterService {
    //这里我们模拟数据->DB
    public List<Monster> listMonster() {
        List<Monster> monsters = new ArrayList<Monster>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
        return monsters;
    }
}

3.2完善zzwspringmvc.xml , 加上com.zzw.service

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--指定要扫描的基本包以及子包的java类--> 
    <component-scan base-package="com.zzw.controller,com.zzw.service"/>
</beans>

3.3更改ZzwWebApplicationContext.javainit()

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        //这里我们写的是固定的spring容器配置文件 => 做活
        //String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时我们的basePackage => com.zzw.controller,com.zzw.service
        //scanPackage(basePackage);
        String[] basePackages = basePackage.split(",");
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        ........
	}

4.ZzwWebApplicationContextexecuteInstance增加一个else if分支. 并可以通过接口支持多级-类名来获取到Service Bean

//编写方法, 将扫描到的类, 在满足条件的情况下, 反射到ioc容器
public void executeInstance() {
    //判断是否扫描到类
    if (classFullPathList.size() == 0) {//说明没有扫描到类
        return;
    }
    try {
        //遍历classFullPathList, 进行反射
        for (String classFullPath : classFullPathList) {
            Class<?> clazz = Class.forName(classFullPath);
            //说明当前这个类有@Controller注解
            if (clazz.isAnnotationPresent(Controller.class)) {
                //beanName 假设是默认的, 即类名首字母小写
                String beanName = clazz.getSimpleName().substring(0, 1)
                        .toLowerCase() + clazz.getSimpleName().substring(1);
                ioc.put(beanName, clazz.newInstance());
            }//如果有其它注解, 可以拓展!! 处理@Service
            else if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Service

                //先获取到@Service的value值 => 就是注入时的beanName
                Service serviceAnnotation = clazz.getDeclaredAnnotation(Service.class);
                String beanName = serviceAnnotation.value();
                if ("".equals(beanName)) {//说明没有指定value, 我们就使用默认的机制注入Service
                    //可以通过 接口名/类名[首字母小写] 来注入ioc容器
                    //1.得到所有接口的名称=>接口
                    Class<?>[] interfaces = clazz.getInterfaces();
                    Object instance = clazz.newInstance();
                    //2.遍历接口, 然后通过多个接口名来注入
                    for (Class<?> anInterface : interfaces) {
                        //接口名->首字母小写
                        String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase()
                                + anInterface.getSimpleName().substring(1);
                        ioc.put(beanName2, instance);
                    }
                    //3.这里老师给留了个作业: 使用类名的首字母小写来注入bean
                    //  通过 clazz 来获取即可.
                    String beanName2 = clazz.getSimpleName().substring(0, 1).toLowerCase()
                            + clazz.getSimpleName().substring(1);
                    ioc.put(beanName2, instance);
                } else {//如果有指定名称, 就使用该名称注入即可
                    ioc.put(beanName, clazz.newInstance());
                }
            }
        }

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

5.测试-重启tomcat

扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@5fb9a20e,                               monsterService=com.zzw.service.impl.MonsterServiceImpl@3b03f989,                               monsterServiceImpl=com.zzw.service.impl.MonsterServiceImpl@3b03f989,                               orderController=com.zzw.controller.xx.OrderController@2f51e8b1,                               monsterController=com.zzw.controller.MonsterController@7a223f3b}


在这里插入图片描述


🔜 下一篇预告 🔜
敬请期待:SpringMVC系列八: 手动实现SpringMVC底层机制-下


📚 目录导航 📚

  1. SpringMVC系列一: 初识SpringMVC
  2. SpringMVC系列二: 请求方式介绍
  3. SpringMVC系列三: Postman(接口测试工具)
  4. SpringMVC系列四: Rest-优雅的url请求风格
  5. SpringMVC系列五: SpringMVC映射请求数据
  6. SpringMVC系列六: 视图和视图解析器
  7. SpringMVC系列七: 手动实现SpringMVC底层机制-上
  8. SpringMVC系列八: 手动实现SpringMVC底层机制-下

💬 读者互动 💬
在学习SpringMVC底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/734309.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

回溯算法练习题(2024/6/18)

1全排列 II 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,…

只有程序员才能看懂的笑话

哈喽&#xff0c;大家好&#xff0c;我是明智 今天又是周五摸鱼日&#xff5e; 咱们不聊技术&#xff0c;看点别的有意思的东西 往期摸鱼记录&#xff1a; 》》》程序员的那些经典段子 》》》写出bug不用怕&#xff0c;世界就是一个巨大的草台班子 【1】二进制 世界上有10种人&…

HTML静态网页成品作业(HTML+CSS)——游戏永劫无间网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有3个页面。 二、作品演示 三、代…

R语言数据分析案例33-基于logistic回归下的信用卡违约情况分析

一、选题背景 随着互联网产业的蓬勃发展&#xff0c;传统金融行业开始向着金融互联网化和互联网金融快速转型。网络信贷、信用卡等凭借门槛低、快速便捷、高收益等特点&#xff0c;借助互联网平台存在的优势&#xff0c;迅速成长。然而高收益的背后也存在着高风险&#xff0c;…

怎么用二维码来统计用户信息?获取数据能够随时长期存储查看

二维码现在经常被用来做用户信息收集的用途&#xff0c;通过这种方式可以快速收集用户数据&#xff0c;有效提升用户的个人体验&#xff0c;在很多的场景中都有应用&#xff0c;比如制作二维码签到、反馈、登记等类型。采用这种方式对于用户和制作者都有好处&#xff0c;下面来…

phpStudy安装sqli-labs

phpStudy安装sqli-labs git地址&#xff1a;https://github.com/Audi-1/sqli-labs 点击管理–>根目录 将git下载的sqli-labs文件放进去并解压 进入sql-connections修改 修改db-creds.inc文件为自己数据库的账号密码 更改php版本为5.*&#xff0c;因为这个程序只能在php 5.…

攻防世界-pdf

方法一&#xff1a;打开是pdf格式的文件&#xff0c;里面有一张图&#xff0c;题目提示图下面什么都没有&#xff1f;emmm用chrom打开pdf——ctrlf搜索flag&#xff0c;里面是有东西的&#xff0c;ctrla复制就可以了。 方法二&#xff1a;题目提示图下面什么都没有&#xff0c;…

警惕!最新17本期刊(含2本Top)被“镇压”,无影响因子无分区,这是被踢了吗?

本周投稿推荐 SSCI • 中科院2区&#xff0c;6.0-7.0&#xff08;录用友好&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.5-1.0&#xff08;录用…

超强开源全能日程助手—揭秘FullCalendar

引言 FullCalendar 是一个广受欢迎的开源 JavaScript 库&#xff0c;用于在网页上展示和管理事件和日程。 它最初是基于 jQuery 开发的&#xff0c;但随着时间的推移&#xff0c;经过 v1~v6 版本的迭代后&#xff0c;它已经全面支持React、Vue 和 Angular 的版本。FullCalend…

具有 Hudi、MinIO 和 HMS 的现代数据湖

Apache Hudi 已成为管理现代数据湖的领先开放表格式之一&#xff0c;直接在现代数据湖中提供核心仓库和数据库功能。这在很大程度上要归功于 Hudi 提供了表、事务、更新/删除、高级索引、流式摄取服务、数据聚类/压缩优化和并发控制等高级功能。 我们已经探讨了 MinIO 和 Hudi…

【Spine学习14】之 裁剪

1、新建裁剪 2、在页面中随便点几下 圈出对应位置 3、点编辑裁剪 或者按空格键 退出编辑模式&#xff0c; 页面就只剩下对应区域&#xff0c;这个区域可以任意拖动 放大缩小显示。 tips&#xff1a; 如果手动选择区域描绘不准确&#xff0c;可以启用对应图片的网格 然后复制…

深入理解Python中的并发与异步的结合使用

​ 在上一篇文章中&#xff0c;我们讨论了异步编程中的性能优化技巧&#xff0c;并简单介绍了trio和curio库。今天&#xff0c;我们将深入探讨如何将并发编程与异步编程结合使用&#xff0c;并详细讲解如何利用trio和curio库优化异步编程中的性能。 文章目录 并发与异步编程的区…

Android studio登录Google账号超时的解决方法

确保自己已经打开了代理&#xff08;科学上网&#xff09;在设置-外观与行为-系统设置-HTTP代理 中打开“自动检测代理设置”&#xff1a; 再次重新尝试登录Google账号&#xff0c;登陆成功&#xff01; 学术会议征稿 想要了解国内主办的覆盖学科最全最广的学术会议&#xff0c…

全面的WAS存储权限管理方案,了解一下

WAS存储权限管理通常指的是对Windows Azure Storage&#xff08;WAS&#xff09;的存储设备进行权限控制和管理。在企业中&#xff0c;随着数据量的飞速增长&#xff0c;对存储设备的安全性、效率和成本的关注也日益增加。有效的WAS存储权限管理可以确保数据的安全性&#xff0…

CentOS7 部署安装ClickHouse

一、什么是ClickHouse ClickHouse 是俄罗斯的Yandex于2016年开源的列式存储数据库&#xff08;DBMS&#xff09;&#xff0c;使用C语言编写&#xff0c;主要用于在线分析处理查询&#xff08;OLAP&#xff09;&#xff0c;能够使用SQL查询实时生成分析数据报告。 OLAP场景需要…

双阶段目标检测算法

双阶段目标检测算法 本文将系统的过一遍双阶段目标检测的经典算法&#xff0c;文献阅读顺序如下&#xff1a; R-CNN → \rightarrow → SPPnet → \rightarrow → Fast R-CNN → \rightarrow → Faster R-CNN → \rightarrow → Mask R-CNN R-CNN 一、研究背景 R-CNN可…

WIC 图像处理初体验——读取像素的值

先放上运行结果&#xff1a; 可以发现红绿蓝是从后往前的。 必须以C方式编译代码&#xff01; // 参考资料&#xff1a;https://learn.microsoft.com/zh-cn/windows/win32/wic/-wic-lh #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <wincodec.h>…

Go语言的诞生背景

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Centos Stream9 和Centos Stream10的下载网址

Index of /https://mirror.stream.centos.org/

在检索增强生成 (RAG) 管道中使用 MinIO Weaviate 和 Langchain 优化

作为 MinIO 专注于 AI 集成的开发人员&#xff0c;我一直在探索如何将我们的工具无缝集成到现代 AI 架构中&#xff0c;以提高效率和可扩展性。在本文中&#xff0c;我们将深入探讨使用 LangChain 将 MinIO 与检索增强生成 &#xff08;RAG&#xff09; 管道和 Weaviate 向量存…