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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected 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.获取rootElementElement 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 {@Testpublic 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/controllerClassLoader 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 {@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);ZzwWebApplicationContext zzwWebApplicationContext =new ZzwWebApplicationContext();zzwWebApplicationContext.init();}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected 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);//根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathListString 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);//说明当前这个类有@Controllerif (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;@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);zzwWebApplicationContext = new ZzwWebApplicationContext();zzwWebApplicationContext.init();//调用 initHandlerMapping, 完成url和控制器方法的映射initHandlerMapping();System.out.println("handlerList初始化的结果=" + handlerList);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected 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是Controllerif (clazz.isAnnotationPresent(Controller.class)) {//取出它所有的方法Method[] declaredMethods = clazz.getDeclaredMethods();//遍历方法for (Method declaredMethod : declaredMethods) {//判断该方法是否有@RequestMappingif (declaredMethod.isAnnotationPresent(RequestMapping.class)) {//取出@RequestMapping值 -> 就是映射路径RequestMapping requestMappingAnnotation =declaredMethod.getDeclaredAnnotation(RequestMapping.class);String url = requestMappingAnnotation.value();//创建ZzwHandler对象->就是一个映射关系 [保存映射关系]ZzwHandler zzwHandler =new ZzwHandler(url, entry.getValue(), declaredMethod);//放入到 handlerListhandlerList.add(zzwHandler);}}}}}
}

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

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

示意图[分析说明]

在这里插入图片描述

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

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


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

public class ZzwDispatcherServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {super.init(config);//创建自己的spring容器zzwWebApplicationContext = new ZzwWebApplicationContext();zzwWebApplicationContext.init();//调用 initHandlerMapping, 完成url和控制器方法的映射initHandlerMapping();System.out.println("handlerList初始化的结果=" + handlerList);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//System.out.println("ZzwDispatcherServlet doPost()...");//调用方法, 完成请求转发executeDispatcher(req, resp);}//编写方法, 通过request对象, 返回ZzwHandler对象//如果没有, 就返回nullprivate 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();//遍历 handlerListfor (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 {@Overridepublic 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 {//这里我们模拟数据->DBpublic 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());}//如果有其它注解, 可以拓展!! 处理@Serviceelse if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Service//先获取到@Service的value值 => 就是注入时的beanNameService 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底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

摄像头画面显示于unity场景

&#x1f43e; 个人主页 &#x1f43e; &#x1faa7;阿松爱睡觉&#xff0c;横竖醒不来 &#x1f3c5;你可以不屠龙&#xff0c;但不能不磨剑&#x1f5e1; 目录 一、前言二、UI画面三、显示于场景四、结语 一、前言 由于标题限制&#xff0c;这篇文章主要是讲在unity中调用摄…

【网络安全常用术语解读 :什么是0day、1day、nday漏洞】

脆弱性攻击的时间窗被称作脆弱性窗口。通常情况下&#xff0c;一个安全漏洞的时间越久&#xff0c;攻击者就会有更多的机会去攻击它。 2. 0day 漏洞 0天漏洞&#xff0c;也被称作"零日漏洞"&#xff0c;是指尚未由供应商公布的缺陷&#xff0c;表示攻击者已知晓该缺…

Go 与 Java 字符编码选择:UTF-8 与 UTF-16 的较量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮

, mWidth - mCircleWidth, mHeight - mCircleWidth); canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint); //前景文字 mHintPaint.setColor(mHintFgColor); // 计算Baseline绘制的起点X轴坐标 int baseX (int) (mWidth / 2 - mHintPaint.m…

Vue3+TypeScript项目实战——打造雨雪交加的智慧城市

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

将WIN10的wifi上网分享给以太网接口

目录 打开网络设置设置属性点这里的设置将wlan主机的以太网接口IP设为自动获取 如果连接不成功&#xff0c;拔网线重连一次 打开网络设置 设置属性 点这里的设置 将wlan主机的以太网接口IP设为自动获取 如果连接不成功&#xff0c;拔网线重连一次

Interview preparation--elasticSearch倒排索引原理

搜索引擎应该具备哪些要求 查询速度快 优秀的索引结构设计高效率的压缩算法快速的编码和解码速度 结果准确 ElasiticSearch 中7.0 版本之后默认使用BM25 评分算法ElasticSearch 中 7.0 版本之前使用 TP-IDF算法 倒排索引原理 当我们有如下列表数据信息&#xff0c;并且系统…

网易被裁,腾讯面试被怼,幸得字节内推,5面顺利拿下offer

幸好经过师兄的内推拿到了字节跳动的面试机会&#xff0c;最终历经5面斩获了字节跳动&#xff08;抖音Android岗&#xff09;offer&#xff0c;不得不感叹一下自己的工作生涯实在是太顺了。下面简单分享一下我这次5面字节跳动的一个真题情况&#xff0c;希望能够对大家有所帮助…

Redis 学习笔记(2)

目录 1 Redis的持久化1.1 RDB持久化方案1.2 AOF持久化方案 2 Redis架构2.1 主从复制架构2.2 哨兵集群设计2.3 哨兵集群设计 3 Redis事务机制4 Redis过期策略与内存淘汰机制4.1 过期策略4.2 内存淘汰机制 5 Redis高频面试题4.1 缓存穿透4.2 缓存击穿4.3 缓存雪崩 1 Redis的持久化…

Centos 配置安装Mysql

linux安装配置mysql的方法主要有yum安装和配置安装两种&#xff0c;由于yum安装比较简单&#xff0c;但是会将文件分散到不同的目录结构下面&#xff0c;配置起来比较麻烦&#xff0c;这里主要研究一下配置安装mysql的方法 1、环境说明 centos 7.9 mysql 5.7.372、环境检查 …

Day2: 双指针977 滑动窗口209 循环不变量原则59

题目977. 有序数组的平方 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> sortedSquares(vector<int>& nums) {int left0;int rightnums.size()-1;vector<int> result(nums.size(),0);int iright;while(left<right){i…

快速生成基于vue-element的后台管理框架,实现短时间二次开发

你是否遇到过当你想要独立开发一个项目时对反复造轮子的烦扰&#xff1f; 这种流水线的操作实在让人受不了 而vue-element-template很好的帮你解决了这个烦恼 只需克隆下来&#xff0c;改改图标&#xff0c;模块名&#xff0c;甚至样式&#xff0c;就会变成一个全新的自己的项目…

Redis进阶 - Redis 淘汰策略

我们知道Redis是分布式内存数据库&#xff0c;基于内存运行&#xff0c;可是有没有想过比较好的服务器内存也不过几百G&#xff0c;能存多少数据呢&#xff0c;当内存占用满了之后该怎么办呢&#xff1f;Redis的内存是否可以设置限制&#xff1f; 过期的key是怎么从内存中删除的…

【独家发布】怎样有效发挥公司现有的资源优势

新上任的汪总发现公司存在管控力度弱、职责不清、职能执行不足等问题&#xff0c;阻碍了公司优势的发挥&#xff0c;因此决定对组织架构进行重新设计&#xff0c;但是&#xff0c;考虑到内部人力资源管理人才缺乏&#xff0c;而且组织架构的调整会涉及到复杂的人事变动和利益调…

Charles抓包工具

一、charles简介 1&#xff0c;charles是什么 Charles中文名叫青花瓷&#xff0c;它是一款基于HTTP协议的代理服务器&#xff0c;通过成为电脑或者浏览器的代理&#xff0c;然后截取请求和请求结果达到分析抓包的目的。 特点:跨平台、半免费 2&#xff0c;charles工作原理 前…

英码科技携手昇腾打造“三位一体”智慧化工解决方案,使能化工产业管理更高效、智能

我国是世界公认的化工大国。然而&#xff0c;大部分化工园区的日常管理方式较为传统&#xff0c;各园区、厂区的门禁、视频、停车场等子系统犹如一个个独立的“岛屿”&#xff0c;每个“岛屿”需要耗费大量人力及时间成本进行巡检、记录、上报&#xff0c;且不能做到全域、全时…

基于matlab的不同边缘检测算子的边缘检测

1 原理 1.1 边缘检测概述 边缘检测是图像处理和计算机视觉中的基本问题&#xff0c;其目的在于标识数字图像中亮度变化明显的点。这些变化通常反映了图像属性的重要事件和变化&#xff0c;如深度不连续、表面方向不连续、物质属性变化和场景照明变化等。边缘检测在特征提取中…

Windows环境利用 OpenCV 中 CascadeClassifier 分类器识别人眼 c++

Windows环境中配置OpenCV 关于在Windows环境中配置opencv的说明&#xff0c;具体可以参考&#xff1a;VS2022 配置OpenCV开发环境详细教程。 CascadeClassifier 分类器 CascadeClassifier 是 OpenCV 库中的一个类&#xff0c;它用于实现一种快速的物体检测算法&#xff0c;称…

AlmaLinux 更换CN镜像地址

官方镜像列表 官方列表&#xff1a;https://mirrors.almalinux.org/CN 开头的站点&#xff0c;不同区域查询即可 一键更改镜像地址脚本 以下是更改从默认更改到阿里云地址 cat <<EOF>>/AlmaLinux_Update_repo.sh #!/bin/bash # -*- coding: utf-8 -*- # Author:…

多功能投票系统(ThinkPHP+FastAdmin+Uniapp)

让决策更高效&#xff0c;更民主&#x1f31f; ​基于ThinkPHPFastAdminUniapp开发的多功能系统&#xff0c;支持图文投票、自定义选手报名内容、自定义主题色、礼物功能(高级授权)、弹幕功能(高级授权)、会员发布、支持数据库私有化部署&#xff0c;Uniapp提供全部无加密源码…