• 自己实现 SpringMVC 底层机制 系列之--实现任务阶段 2- 完成客户端浏览器可以请求控制层


    😀前言
    本文是自己实现 SpringMVC 底层机制的第二篇之完成实现任务阶段 2- 完成客户端浏览器可以请求控制层

    🏠个人主页尘觉主页
    在这里插入图片描述

    🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉

    在csdn获奖荣誉: 🏆csdn城市之星2名
    ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ 💓Java全栈群星计划top前5
    ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ 🤗 端午大礼包获得者

    💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰
    如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦😊

    🥰自己实现 SpringMVC 底层机制 系列之–实现任务阶段 2- 完成客户端浏览器可以请求控制层

    😀 创建 自己的 Controller 和自定义注解

    ● 分析示意图

    img

    代码实现

    创建MonsterController.java
    public class MonsterController {
        public void listMonsters(HttpServletRequest request, HttpServletResponse
                response) {
            response.setContentType("text/html;charset=utf-8");
            try {
                PrintWriter printWriter = response.getWriter();
                printWriter.write("

    妖怪列表

    "
    ); } catch (IOException e) { e.printStackTrace(); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    创建自定义注解Controller

    annotation\Controller.java

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Controller {
    String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    创建自定义注解RequestMapping

    annotation\RequestMapping

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestMapping {
    String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    配置 wyxspringmvc.xml

    在该文件指定,我们的 springmvc 要扫描的包

    
        
            
        
    
    • 1
    • 2
    • 3
    • 4

    😉编写 XMLParser 工具类,可以解析 wyxspringmvc.xml

    完成功能说明: -编写 XMLParser 工具类, 可以解析 wyxringmvc.xml, 得到要扫描的包-如图

    img

    创建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 "";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    创建WyxSpringMvcTest

    public class HspSpringMvcTest {
        @Test
        public void readXML() {
            String basePackage = XMLPaser.getbasePackage("wyxspringmvc.xml");
            System.out.println("basePackage= " + basePackage);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    💖开发 WyxWebApplicationContext,充当 Spring 容器-得到扫描类的全路径列

    完成的功能说明

    img

    - 把指定的目录包括子目录下的 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 ArrayList
    
    public void init() {
            //这里是写的固定的spring容器配置文件.?=>做活
            String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
    
            //这时basePackage => com.wyxdu.controller,com.wyxdu.service
            String[] 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);
            //根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
    
            String 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);
                }
            }
    
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    修改WyxDispatcherServlet.java类

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
             //创建自己的spring容器
            wyxWebApplicationContext =
                    new WyxWebApplicationContext(configLocation);
    
            wyxWebApplicationContext.init();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    启动 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.service
            String[] 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);
                    //说明当前这个类有@Controller
                    if (clazz.isAnnotationPresent(Controller.class)) {
                        //得到类名首字母小写
                        String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
                                clazz.getSimpleName().substring(1);
                        ioc.put(beanName, clazz.newInstance());
                    } //如果有其它的注解,可以扩展 , 来处理@Service
                    else if()
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    完成测试

    (提示: 启动 tomcat 来加载 WyxDispatcherServlet 的方式测试

    扫描后的 ioc容器= {goodsController=com.wyxdu.controller.xx.GoodsController@29e91c96,

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

    示意图分析

    img

    将配置的@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;
        }
    
        @Override
        public String toString() {
            return "WyxHandler{" +
                    "url='" + url + '\'' +
                    ", controller=" + controller +
                    ", method=" + method +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    修改WyxDispatcherServlet.java类
    public class WyxDispatcherServlet extends HttpServlet {
    
        //定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]
        private List<WyxHandler> handlerList =
                new ArrayList<>();
        //定义属性 wyxWebApplicationContext,自己的spring容器
        WyxWebApplicationContext wyxWebApplicationContext = null;
    
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
    
            //创建自己的spring容器
            wyxWebApplicationContext =
                    new WyxWebApplicationContext();
    
            wyxWebApplicationContext.init();
    
            //调用 initHandlerMapping , 完成url和控制器方法的映射
    
            initHandlerMapping();
            //输出handlerList
            System.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是Controller
                if(clazz.isAnnotationPresent(Controller.class)) {
    
                    //取出它的所有方法
                    Method[] declaredMethods = clazz.getDeclaredMethods();
                    //遍历方法
                    for (Method declaredMethod : declaredMethods) {
                     //判断该方法是否有@RequestMapping
                        if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                            //取出@RequestMapping值->就是映射路径
                            RequestMapping requestMappingAnnotation =
                                    declaredMethod.getAnnotation(RequestMapping.class);
                            //这里小伙伴可以把工程路径+url
                            //getServletContext().getContextPath()
                            // /springmvc/monster/list
    
                            String url = requestMappingAnnotation.value();
    
                            handlerList.add(new
                                    WyxHandler(url,entry.getValue(),declaredMethod));
                        }
                    }
                }
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    完成测试

    (启动 Tomcat , 加载 HspDispatcherServlet 方式), 测试结果前面已经展示了

    💕完成 WyxDispatcherServlet 分发请求到对应控制器方法

    示意图

    img

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

    img

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

    img

    修改WyxDispatcherServlet.java类

      @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("--WyxDispatcherServlet--doPost---");
            //调用方法,完成分发请求
            executeDispatch(req, resp);
        }
    
        private void executeDispatch(HttpServletRequest req,
                                     HttpServletResponse response) {
            WyxHandler wyxHandler = getWyxHandler(req);
            try {
                if (null == wyxHandler) {//没有匹配的 Handler
                    response.getWriter().print("

    404 NOT FOUND

    "
    ); } 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(); //遍历handlerList for (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是Controller if(clazz.isAnnotationPresent(Controller.class)) { //取出它的所有方法 Method[] declaredMethods = clazz.getDeclaredMethods(); //遍历方法 for (Method declaredMethod : declaredMethods) { //判断该方法是否有@RequestMapping if(declaredMethod.isAnnotationPresent(RequestMapping.class)) { //取出@RequestMapping值->就是映射路径 RequestMapping requestMappingAnnotation = declaredMethod.getAnnotation(RequestMapping.class); //这里小伙伴可以把工程路径+url //getServletContext().getContextPath() // /springmvc/monster/list String url = requestMappingAnnotation.value(); handlerList.add(new WyxHandler(url,entry.getValue(),declaredMethod)); } } } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    完成测试(启动 Tomcat)

    img

    img

    增加方法和控制器再看看, 创建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("

    订单列表信息

    "
    ); } 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("

    添加订单...

    "
    ); } catch (IOException e) { e.printStackTrace(); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    img

    😄总结

    本篇完成了任务阶段 2- 完成客户端浏览器可以请求控制层下一篇为实现任务阶段 3- 从 web.xml动态获取 wyxspringmvc.xml

    😉 自己实现 SpringMVC 底层机制 核心分发 控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器 方法获取参数 + 视图解析 + 返回 JSON 格式数系列

    第一篇->自己实现 SpringMVC 底层机制 系列之搭建 SpringMVC 底层机制开发环境和开发 WyxDispatcherServlet_springmvc分发器

    第二篇->自己实现 SpringMVC 底层机制 系列之–实现任务阶段 2- 完成客户端浏览器可以请求控制层

    第三篇->自己实现 SpringMVC 底层机制 系列之–实现任务阶段 3- 从 web.xml动态获取 wyxspringmvc.xml

    第四篇-> 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 4- 完成自定义@Service 注解功能

    第五篇-> 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 5- 完成 Spring 容器对象的自动装配 -@Autowried

    第六篇->自己实现 SpringMVC 底层机制 系列之-实现任务阶段 6-完成控制器方法获取参数-@RequestParam

    第七篇->自己实现 SpringMVC 底层机制 系列之-实现任务阶段 7- 完成简单视图解析

    第八篇->自己实现 SpringMVC 底层机制 系列之-实现任务阶段 8- 完成返回 JSON 格式数据-@ResponseBody

    😁热门专栏推荐
    想学习vue的可以看看这个

    java基础合集

    数据库合集

    redis合集

    nginx合集

    linux合集

    等等等还有许多优秀的合集在主页等着大家的光顾感谢大家的支持

    🤔欢迎大家加入我的社区 尘觉社区

    文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😁
    希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🍻
    如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞

  • 相关阅读:
    js看代码说输出
    阿里云金融创新峰会云原生分论坛圆满举办,加速金融行业落地云原生
    怎么提取音频中的文字?这篇文章告诉你
    win10+VS2015编译opencv4.5.1+opencv_contrib+CUDA详细教程
    阿里云活动内各云服务器实例处理器及主要适用场景介绍
    【博客495】k8s调度器如何自定义插件执行顺序
    Mysql kill session
    1359:围成面积
    position sticky与overflow冲突失效无作用,解决办法
    SaleSmartly新增AI意图识别触发器!让客户享受更精准的自动化服务
  • 原文地址:https://blog.csdn.net/apple_67445472/article/details/132389181