😀前言
本文是自己实现 SpringMVC 底层机制的第二篇之完成实现任务阶段 2- 完成客户端浏览器可以请求控制层
🏠个人主页:尘觉主页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TabAxc96-1692497234107)(https://picgoowyx.oss-cn-guangzhou.aliyuncs.com/imags/202308152043025.png)]
🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉
在csdn获奖荣誉: 🏆csdn城市之星2名
💓Java全栈群星计划top前5
🤗 端午大礼包获得者
💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰
如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦😊
文章目录
- 🥰自己实现 SpringMVC 底层机制 系列之--实现任务阶段 2- 完成客户端浏览器可以请求控制层
- 😀 创建 自己的 Controller 和自定义注解
- ● 分析示意图
- 代码实现
- 创建MonsterController.java
- 创建自定义注解Controller
- 创建自定义注解RequestMapping
- 配置 wyxspringmvc.xml
- 😉编写 XMLParser 工具类,可以解析 wyxspringmvc.xml
- 创建XMLPaser.java类
- 创建WyxSpringMvcTest
- 💖开发 WyxWebApplicationContext,充当 Spring 容器-得到扫描类的全路径列
- 扫描后的
- 创建WyxWebApplicationContext.java
- 修改WyxDispatcherServlet.java类
- 启动 Tomcat 完成测试,
- 💝完善 WyxWebApplicationContext,充当 Spring 容器-实例化对象到容器中
- 完成测试
- 💞完成请求 URL 和控制器方法的映射关系
- 示意图分析
- 将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中
- -如图
- 创建WyxHandler 类
- 修改WyxDispatcherServlet.java类
- 完成测试
- 💕完成 WyxDispatcherServlet 分发请求到对应控制器方法
- 示意图
- 修改WyxDispatcherServlet.java类
- 完成测试(启动 Tomcat)
- 增加方法和控制器再看看, 创建OrderController.java
- 😄总结
🥰自己实现 SpringMVC 底层机制 系列之–实现任务阶段 2- 完成客户端浏览器可以请求控制层
😀 创建 自己的 Controller 和自定义注解
● 分析示意图
代码实现
创建MonsterController.java
public class MonsterController {public void listMonsters(HttpServletRequest request, HttpServletResponseresponse) {response.setContentType("text/html;charset=utf-8");try {PrintWriter printWriter = response.getWriter();printWriter.write("<h1>妖怪列表</h1>");} catch (IOException e) {e.printStackTrace();}}
}
创建自定义注解Controller
annotation\Controller.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
创建自定义注解RequestMapping
annotation\RequestMapping
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
配置 wyxspringmvc.xml
在该文件指定,我们的 springmvc 要扫描的包
<?xml version="1.0" encoding="UTF-8" ?><beans><component-scan base-package="com.wyxedu.controller"></component-scan></beans>
😉编写 XMLParser 工具类,可以解析 wyxspringmvc.xml
完成功能说明: -编写 XMLParser 工具类, 可以解析 wyxringmvc.xml, 得到要扫描的包-如图
创建XMLPaser.java类
package com.wyxdu.wyxspringmvc.xml;import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;/*** XMLParser 用于解析spring配置文件*/
public class XMLParser {public static String getBasePackage(String xmlFile) {//这个解析的过程,是前面讲过的SAXReader saxReader = new SAXReader();//通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]InputStream inputStream =XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);try {//得到xmlFile文档Document document = saxReader.read(inputStream);Element rootElement = document.getRootElement();Element componentScanElement =rootElement.element("component-scan");Attribute attribute = componentScanElement.attribute("base-package");String basePackage = attribute.getText();return basePackage;} catch (Exception e) {e.printStackTrace();}return "";}
}
创建WyxSpringMvcTest
public class HspSpringMvcTest {@Testpublic void readXML() {String basePackage = XMLPaser.getbasePackage("wyxspringmvc.xml");System.out.println("basePackage= " + basePackage);}
}
💖开发 WyxWebApplicationContext,充当 Spring 容器-得到扫描类的全路径列
完成的功能说明
- 把指定的目录包括子目录下的 java 类的全路径扫描到集合中,比如 ArrayList -如图[对 java 基础知识的使用
扫描后的
classFullPathList=[com.wyxdu.controller.MonsterController, com.wyxedu.service.impl.MonsterServiceImpl, com.wyxedu.service.MonsterService]
创建WyxWebApplicationContext.java
public class WyxWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayListpublic void init() {//这里是写的固定的spring容器配置文件.?=>做活String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");//这时basePackage => com.wyxdu.controller,com.wyxdu.serviceString[] basePackages = basePackage.split(",");//遍历basePackages, 进行扫描if (basePackages.length > 0) {for (String pack : basePackages) {scanPackage(pack);//扫描}}System.out.println("扫描后的= classFullPathList=" + classFullPathList);}public void scanPackage(String pack) {//得到包所在的工作路径[绝对路径]//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]//比如 "com.hspedu.controller" => url 是 D:\hspedu_springmvc\hsp-springmvc\target\hsp-springmvc\WEB-INF\classes\com\hspedu\controller//细节说明: 1. 不要直接使用Junit测试, 否则 url null// 2. 启动tomcat来吃测试URL url =this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));System.out.println("urlss=" + url);//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathListString path = url.getFile();System.out.println("path= " + path);//在io中,把目录,视为一个文件File dir = new File(path);//遍历dir[文件/子目录]for (File f : dir.listFiles()) {if (f.isDirectory()) {//如果是一个目录,需要递归扫描scanPackage(pack + "." + f.getName());} else {//说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件//就算是.class, 也存在是不是需要注入到容器//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理String classFullPath =pack + "." + f.getName().replaceAll(".class", "");classFullPathList.add(classFullPath);}}}}
修改WyxDispatcherServlet.java类
@Override
public void init(ServletConfig servletConfig) throws ServletException {//创建自己的spring容器wyxWebApplicationContext =new WyxWebApplicationContext(configLocation);wyxWebApplicationContext.init();
}
启动 Tomcat 完成测试,
看看扫描是否成功. 需要使用 Tomcat 启动方式完成测试,直接用 Junit 测试 URL 是null
15:03:30.297 信 息 [RMI TCP Connection(3)-127.0.0.1]
org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet
contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were
scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can
improve startup time and JSP compilation time.
.扫描后
classFullPathList=[com.hspedu.controller.MonsterController, com.wyxdu.service.impl.MonsterServiceImpl, com.wyxdu.service.MonsterService]
💝完善 WyxWebApplicationContext,充当 Spring 容器-实例化对象到容器中
public void init() {String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");//这时basePackage => com.wyxdu.controller,com.wyxdu.serviceString[] basePackages = basePackage.split(",");//遍历basePackages, 进行扫描if (basePackages.length > 0) {for (String pack : basePackages) {scanPackage(pack);//扫描}}System.out.println("扫描后的= classFullPathList=" + classFullPathList);//将扫描到的类, 反射到ico容器executeInstance();System.out.println("扫描后的 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)) {//得到类名首字母小写String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +clazz.getSimpleName().substring(1);ioc.put(beanName, clazz.newInstance());} //如果有其它的注解,可以扩展 , 来处理@Serviceelse if()}} catch (Exception e) {e.printStackTrace();}}
完成测试
(提示: 启动 tomcat 来加载 WyxDispatcherServlet 的方式测试
扫描后的 ioc容器= {goodsController=com.wyxdu.controller.xx.GoodsController@29e91c96,
💞完成请求 URL 和控制器方法的映射关系
示意图分析
将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中
-如图
handlerList初始化的结果= [WyxHandler{url=‘/order/list’, controller=com.wyxdu.controller.OrderController@79b8c11d, method=public void com.wyxdu.controller.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
创建WyxHandler 类
public class WyxHandler {private String url;private Object controller;private Method method;public WyxHandler(String url, Object controller, Method method) {this.url = url;this.controller = controller;this.method = method;}public WyxHandler() {}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public Object getController() {return controller;}public void setController(Object controller) {this.controller = controller;}public Method getMethod() {return method;}public void setMethod(Method method) {this.method = method;}@Overridepublic String toString() {return "WyxHandler{" +"url='" + url + '\'' +", controller=" + controller +", method=" + method +'}';}
}
修改WyxDispatcherServlet.java类
public class WyxDispatcherServlet extends HttpServlet {//定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]private List<WyxHandler> handlerList =new ArrayList<>();//定义属性 wyxWebApplicationContext,自己的spring容器WyxWebApplicationContext wyxWebApplicationContext = null;@Overridepublic void init(ServletConfig servletConfig) throws ServletException {//创建自己的spring容器wyxWebApplicationContext =new WyxWebApplicationContext();wyxWebApplicationContext.init();//调用 initHandlerMapping , 完成url和控制器方法的映射initHandlerMapping();//输出handlerListSystem.out.println("handlerList初始化的结果= " + handlerList);}private void initHandlerMapping() {if(wyxWebApplicationContext.ioc.isEmpty()) {throw new RuntimeException("spring ioc 容器为空");}for(Map.Entry<String,Object> entry:wyxWebApplicationContext.ioc.entrySet()) {//先取出注入的Object的clazz对象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.getAnnotation(RequestMapping.class);//这里小伙伴可以把工程路径+url//getServletContext().getContextPath()// /springmvc/monster/listString url = requestMappingAnnotation.value();handlerList.add(newWyxHandler(url,entry.getValue(),declaredMethod));}}}}
}
完成测试
(启动 Tomcat , 加载 HspDispatcherServlet 方式), 测试结果前面已经展示了
💕完成 WyxDispatcherServlet 分发请求到对应控制器方法
示意图
当用户发出请求,根据用户请求 url 找到对应的控制器-方法, 并反射调用
- 如果用户请求的路径不存在,返回 404
修改WyxDispatcherServlet.java类
@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("--WyxDispatcherServlet--doPost---");//调用方法,完成分发请求executeDispatch(req, resp);}private void executeDispatch(HttpServletRequest req,HttpServletResponse response) {WyxHandler wyxHandler = getWyxHandler(req);try {if (null == wyxHandler) {//没有匹配的 Handlerresponse.getWriter().print("<h1>404 NOT FOUND</h1>");} else {//有匹配的 Handler, 就调用wyxHandler.getMethod().invoke(wyxHandler.getController(), req,response);}} catch (Exception e) {e.printStackTrace();}}private WyxHandler getWyxHandler(HttpServletRequest request) {//1.先获取的用户请求的uri 比如http://localhost:8080/springmvc/monster/list// uri = /springmvc/monster/list//2. 这里要注意得到uri 和 保存url 是有一个工程路径的问题// 两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context =>/// 第二个方案 保存 hsphandler对象 url 拼接 getServletContext().getContextPath()String requestURI = request.getRequestURI();//遍历handlerListfor (WyxHandler wyxHandler : handlerList) {if (requestURI.equals(wyxHandler.getUrl())) {//说明匹配成功return wyxHandler;}}return null;}private void initHandlerMapping() {if(wyxWebApplicationContext.ioc.isEmpty()) {throw new RuntimeException("spring ioc 容器为空");}for(Map.Entry<String,Object> entry:wyxWebApplicationContext.ioc.entrySet()) {//先取出注入的Object的clazz对象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.getAnnotation(RequestMapping.class);//这里小伙伴可以把工程路径+url//getServletContext().getContextPath()// /springmvc/monster/listString url = requestMappingAnnotation.value();handlerList.add(newWyxHandler(url,entry.getValue(),declaredMethod));}}}}}
完成测试(启动 Tomcat)
增加方法和控制器再看看, 创建OrderController.java
@Controller
public class OrderController {@RequestMapping(value = "/order/list")public void listOrder(HttpServletRequest request,HttpServletResponse response) {//设置编码和返回类型response.setContentType("text/html;charset=utf-8");//获取writer返回信息try {PrintWriter printWriter = response.getWriter();printWriter.write("<h1>订单列表信息</h1>");} catch (IOException e) {e.printStackTrace();}}@RequestMapping(value = "/order/add")public void addOrder(HttpServletRequest request,HttpServletResponse response) {//设置编码和返回类型response.setContentType("text/html;charset=utf-8");//获取writer返回信息try {PrintWriter printWriter = response.getWriter();printWriter.write("<h1>添加订单...</h1>");} catch (IOException e) {e.printStackTrace();}}
}
😄总结
本篇完成了任务阶段 2- 完成客户端浏览器可以请求控制层下一篇为实现任务阶段 3- 从 web.xml动态获取 wyxspringmvc.xml
😉 自己实现 SpringMVC 底层机制 核心分发 控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器 方法获取参数 + 视图解析 + 返回 JSON 格式数系列
第一篇->自己实现 SpringMVC 底层机制 系列之搭建 SpringMVC 底层机制开发环境和开发 WyxDispatcherServlet_springmvc分发器
😁热门专栏推荐
想学习vue的可以看看这个
java基础合集
数据库合集
redis合集
nginx合集
linux合集
等等等还有许多优秀的合集在主页等着大家的光顾感谢大家的支持
🤔欢迎大家加入我的社区 尘觉社区
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😁
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🍻
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞