• 手动实现SpringMVC底层机制


    手动实现SpringMVC底层机制

    在这里插入图片描述

    知识点

    1. 判断方法上有没有某种注解

    🐟准备工作

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

    前提: 搭建maven环境

    1.创建maven-web项目
    在这里插入图片描述

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

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

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.11version>
            <scope>testscope>
        dependency>
    
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            
            <scope>providedscope>
        dependency>
        
        <dependency>
            <groupId>dom4jgroupId>
            <artifactId>dom4jartifactId>
            <version>1.6.1version>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.5version>
        dependency>
    dependencies>
    
    • 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

    实现任务阶段一

    🍍开发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()...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    在这里插入图片描述

    
    
    • 1

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

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

    
    <servlet>
      <servlet-name>ZzwDispatcherServletservlet-name>
      <servlet-class>com.zzw.zzwspringmvc.servlet.ZzwDispatcherServletservlet-class>
      
      <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:zzwspringmvc.xmlparam-value>
      init-param>
      
      
      <load-on-startup>1load-on-startup>
    servlet>
    <servlet-mapping>
      <servlet-name>ZzwDispatcherServletservlet-name>
      
      <url-pattern>/url-pattern>
    servlet-mapping>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    🥦配置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("

    妖怪名信息: 孙悟空--猪八戒--沙僧

    "
    ); } catch (IOException e) { throw new RuntimeException(e); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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 "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    /**
     * @author 赵志伟
     * @version 1.0
     * RequestMapping 注解用于指定控制器-方法的映射路径
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestMapping {
    	String value() default "";;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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("

    妖怪名信息: 孙悟空--猪八戒--沙僧

    "
    ); } catch (IOException e) { throw new RuntimeException(e); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    🥦2.配置zzwspringmvc.xml

    
    <beans>
         
        <component-scan base-package="com.zzw.controller"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🥦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);
            }
        }
    }
    
    • 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

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

    public class ZzwSpringMVCTest {
        @Test
        public void readXML() {
            String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
            System.out.println(basePackage);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    🥦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);
        }
    }
    
    • 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

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

    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);
                }
            }
        }
    }
    
    • 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

    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);
            }
        }
    }
    
    • 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

    我这里输出的时候乱码, 我的解决方案是. 全改成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方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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);
                        }
                    }
                }
            }
        }
    }
    
    • 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

    🥦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("

    404 NOT FOUND!

    "
    ); } else {//匹配成功, 反射调用控制器的方法 zzwHandler.getMethod(). invoke(zzwHandler.getController(), request, response); } } catch (Exception e) { throw new RuntimeException(e); } } }
    • 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

    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("

    订单列表信息

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

    添加订单信息

    "
    ); } catch (IOException e) { throw new RuntimeException(e); } } }
    • 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

    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("

    商品列表信息...

    "
    ); } catch (IOException e) { throw new RuntimeException(e); } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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中的
            /*
                
                    contextConfigLocation
                    classpath:zzwspringmvc.xml
                
             */
            String configLocation = servletConfig.getInitParameter("contextConfigLocation");
            //创建自己的spring容器
            zzwWebApplicationContext = new ZzwWebApplicationContext(configLocation);
            zzwWebApplicationContext.init();
            //调用 initHandlerMapping, 完成url和控制器方法的映射
            initHandlerMapping();
            System.out.println("handlerList初始化的结果=" + handlerList);
        }
    		
    	.......
    }
    
    • 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

    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);
        }
        ........
    }
    
    • 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

    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方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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 "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    
    <beans>
         
        <component-scan base-package="com.zzw.controller,com.zzw.service"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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);
                }
            }
            ........
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
        }
    }
    
    • 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

    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}

    实现任务阶段五

    🍍完成Spring容器对象的自动装配-@Autowired

    说明: 完成Spring容器中对象的注入/自动装配

    示意图[分析说明]

    在这里插入图片描述

    分析:
    加入@Autowired注解, 进行对象属性的装配. -如图
    在这里插入图片描述

    测试:
    浏览器输入 http://localhost:8080/monster/list, 返回列表信息
    在这里插入图片描述

    代码实现:
    1.在com.zzw.zzwspringmvc.annotation下新建@Autowired

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Autowired {
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.1MonsterController添加属性monsterService, 标注@Autowired

    @Controller
    public class MonsterController {
    
        //@Autowired表示要完成属性的装配.
        @Autowired
        private MonsterService monsterService;
    
    	.....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.ZzwWebApplicationContext增加方法executeAutowired()

    //编写方法, 完成属性的自动装配
    public void executeAutowired() {
        //判断ioc有没有要装配的对象
        if (ioc.isEmpty()) {
            return;//你也可以抛出异常, throw new RuntimeException("ioc 容器没有bean对象")
        }
    
        //遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性, 判断是否需要装配
        /**
         * entry =>  -> String 就是你注入对象时的名称, Object就是bean对象
         */
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            
            //String key = entry.getKey();
            Object bean = entry.getValue();
            
            //获取bean的所有字段/属性
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断当前这个字段, 是否有@Autowired
                if (declaredField.isAnnotationPresent(Autowired.class)) {//有@Autowired
                    //得到当前这个字段的@Autowired
                    Autowired autowiredAnnotation = declaredField.getDeclaredAnnotation(Autowired.class);
                    String beanName = autowiredAnnotation.value();
                    if ("".equals(beanName)) {//如果没有设置value, 按照默认规则
                        //即得到字段类型名称的首字母小写, 作为名字来进行装配
                        Class<?> type = declaredField.getType();
                        beanName = type.getSimpleName().substring(0, 1).toLowerCase()
                                + type.getSimpleName().substring(1);
                    }
    
                    //如果设置了value, 直接按照beanName来装配
                    //从ioc容器中获取bean
                    if (ioc.get(beanName) == null) {//说明你指定的名字对应的bean不在ioc容器
                        throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
                    }
                    //防止属性是private, 我们需要暴力破解
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(bean, ioc.get(beanName));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
    
    • 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

    3.ZzwWebApplicationContext.javainit()方法的最后添加三行代码

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        //这里我们写的是固定的spring容器配置文件 => 做活
        //String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackages => com.zzw.controller, com.zzw.service
        String[] basePackages = basePackage.split(",");
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack.trim());
            }
        }
        System.out.println("basePackage=" + basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ioc容器
        executeInstance();
        System.out.println("扫描后 ioc容器=" + ioc);
    
        //完成注入的bean对象,的属性的装配
        executeAutowired();
        System.out.println("装配后 ioc容器=" + ioc);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.打断点, debug, 重启tomcat

    在这里插入图片描述
    在这里插入图片描述

    4.修改MonsterControllerlistMonster方法

    @RequestMapping(value = "/monster/list")
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf-8");
    
        StringBuilder content = new StringBuilder("

    妖怪列表信息

    "
    );//单线程使用StringBuilder content.append("");//调用monsterServiceList<Monster> monsters = monsterService.listMonster();for(Monster monster : monsters){ content.append("");} content.append("
    " + monster.getId() + "" + monster.getName() + "" + monster.getAge() + "" + monster.getSkill() + "
    "
    ); //获取writer返回信息 try { response.getWriter().write(content.toString()); } catch (IOException e) { throw new RuntimeException(e); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5.重启tomcat, 浏览器输入 http://localhost:8080/monster/list
    在这里插入图片描述

    实现任务阶段六

    🍍完成控制器方法获取参数-@RequestParam

    功能说明: 自定义@RequestParam 和 方法参数名获取参数
    完成: 将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
    完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组, 进行反射调用
    完成: 在方法参数 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用

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

    1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用

    修改ZzwDispatcherServletexecuteDispatcher()方法

    //编写方法, 完成分发请求任务
    private void executeDispatcher(HttpServletRequest request,
                                   HttpServletResponse response) {
        try {
            ZzwHandler zzwHandler = getZzwHandler(request);
            if (zzwHandler == null) {//说明用户请求的路径/资源不存在
                response.getWriter().print("

    404 NOT FOUND!

    "
    ); } else {//匹配成功, 反射调用控制器的方法 //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组 //1. 得到目标方法的所有形参参数信息[返回对应的数组] Class<?>[] parameterTypes = zzwHandler.getMethod().getParameterTypes(); //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到 Object[] params = new Object[parameterTypes.length]; //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中 for (int i = 0; i < parameterTypes.length; i++) { //取出每一个形参"类型" Class<?> parameterType = parameterTypes[i]; //如果这个形参类型是HttpServletRequest, 将request填充到params //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if("HttpServletResponse".equals(parameterType.getSimpleName())){ params[i] = response; } } /** * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response) * zzwHandler.getMethod() * .invoke(zzwHandler.getController(), request, response) * 2.这里我们准备将需要传递给目标方法的 实参 => 封装到参数数组 * => 然后以反射调用的方式传递给目标方法 * 源码: public Object invoke(Object var1, Object... var2)... */ zzwHandler.getMethod(). invoke(zzwHandler.getController(), params); } } catch (Exception e) { throw new RuntimeException(e); } }
    • 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

    2.🥦在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用

    1.MonsterService新添方法findMonsterByName

    public interface MonsterService {
        //增加方法, 通过传入的name, 返回对应的monster列表
        public List<Monster> findMonsterByName(String name);
    }
    
    • 1
    • 2
    • 3
    • 4

    MonsterServiceImpl将其实现

    @Service
    public class MonsterServiceImpl implements MonsterService {
    
        public List<Monster> findMonsterByName(String name) {
            //这里我们模拟数据->DB
            List<Monster> monsters = new ArrayList<Monster>();
            monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
            monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
            monsters.add(new Monster(300, "红孩儿", "三昧真火", 100));
            monsters.add(new Monster(400, "黄袍怪", "吐烟雾", 300));
            monsters.add(new Monster(500, "白骨精", "美人计", 800));
    
            //创建集合并且返回查询到的monster集合
            List<Monster> findMonsters = new ArrayList<Monster>();
            //遍历monsters集合, 返回满足条件的对象
            for (Monster monster : monsters) {
                if (monster.getName().contains(name)) {
                    findMonsters.add(monster);
                }
            }
            return findMonsters;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.com.zzw.zzwspringmvc.annotation下新建@RequestParam

    /**
     * @author 赵志伟
     * @version 1.0
     * RequestParam 注解 标注在目标方法的参数上, 表示对应http请求的参数
     */
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)//runtime表示在反射时可以拿到这个注解
    @Documented
    public @interface RequestParam {
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.ZzwDispatcherServlet增添代码

    注意点
    1)method.getParameters(): 得到所有的形参
    2)method.getParameterTypes(): 得到形参的类型

    //编写方法, 完成分发请求任务
    private void executeDispatcher(HttpServletRequest request,
                                   HttpServletResponse response) {
        try {
            ZzwHandler zzwHandler = getZzwHandler(request);
            if (zzwHandler == null) {//说明用户请求的路径/资源不存在
                response.getWriter().print("

    404 NOT FOUND!

    "
    ); } else {//匹配成功, 反射调用控制器的方法 //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组 //1. 得到目标方法的所有形参参数信息[对应的数组] Class<?>[] parameterTypes = zzwHandler.getMethod().getParameterTypes(); //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到 Object[] params = new Object[parameterTypes.length]; //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中 for (int i = 0; i < parameterTypes.length; i++) { //取出每一个形参类型 Class<?> parameterType = parameterTypes[i]; //如果这个形参是HttpServletRequest, 将request填充到params //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) { params[i] = response; } } //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题 👈 //1.获取http请求的参数集合 //老韩解读 //2.返回的Map String: 表示http请求的参数名 // String[]: 表示http请求的参数值, 想一下为什么是数组? //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox) Map<String, String[]> parameterMap = request.getParameterMap(); //3.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { //取出key. 这个name就是对应请求的参数名 String name = entry.getKey(); //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据 // 老师这里做了简化, 如果考虑多值情况, 也不难 String value = entry.getValue()[0]; //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充 //这里专门编写一个方法, 得到请求参数对应的是第几个形参 //1. API 2.java内力真正增加 3.老韩忠告.. int indexRequestParameterIndx = getIndexRequestParameterIndex(zzwHandler.getMethod(), name); if (indexRequestParameterIndx != -1) {//找到对应的位置 params[indexRequestParameterIndx] = value; } else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行匹配[待...] //一会再写 } } 👈 /** * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response) * zzwHandler.getMethod() * .invoke(zzwHandler.getController(), request, response) * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法 * public Object invoke(Object var1, Object... var2)... */ zzwHandler.getMethod(). invoke(zzwHandler.getController(), params); } } catch (Exception e) { throw new RuntimeException(e); } } //编写方法, 返回请求参数是目标方法的第几个形参 /** * @param method 目标方法 * @param name 请求的参数名 * @return 是目标方法的第几个形参 */ public int getIndexRequestParameterIndex(Method method, String name) { //1.得到method的所有形参参数 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //取出当前的参数 Parameter parameter = parameters[i]; //判断parameter是不是有@RequestParam注解 boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class); if (annotationPresent) {//说明有@RequestParam //取出当前这个参数的@RequestParam(value = "xxx") RequestParam requestParamAnnotation = parameter.getAnnotation(RequestParam.class); String value = requestParamAnnotation.value(); //这里就是匹配的比较 if (name.equals(value)) { return i;//找到请求的参数, 对应的目标方法的形参的位置 } } } //如果没有匹配成功, 就返回-1 return -1; }
    • 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
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    4.MonsterController增加如下方法

    //增加方法, 通过name返回对应的monster对象
    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request,
                                  HttpServletResponse response,
                                  @RequestParam(value = "name") String name) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        System.out.println("---接收到的name---" + name);👈
        StringBuilder content = new StringBuilder("

    妖怪列表信息

    "
    );//单线程使用StringBuilder content.append("");//调用monsterServiceList<Monster> monsters = monsterService.findMonsterByName(name);👈 for(Monster monster : monsters){ content.append("");} content.append("
    " + monster.getId() + "" + monster.getName() + "" + monster.getAge() + "" + monster.getSkill() + "
    "
    ); //获取writer返回信息 try { response.getWriter().write(content.toString()); } catch (IOException e) { throw new RuntimeException(e); } }
    • 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

    5.测试
    在这里插入图片描述在这里插入图片描述

    3.🥦在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用

    1.去掉MonsterControllerfindMonsterByName()方法的name字段的@RequestParam注解

    //增加方法, 通过name返回对应的monster对象
    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request,
                                  HttpServletResponse response,
                                  String name) {
            .....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.ZzwDispatcherServletexecuteDispatcher()else分支内增加如下代码, 并增加方法getParameterNames

    //编写方法, 完成分发请求任务
    private void executeDispatcher(HttpServletRequest request,
                                   HttpServletResponse response) {
        try {
            ZzwHandler zzwHandler = getZzwHandler(request);
            if (zzwHandler == null) {//说明用户请求的路径/资源不存在
                response.getWriter().print("

    404 NOT FOUND!

    "
    ); } else {//匹配成功, 反射调用控制器的方法 //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组 //1. 得到目标方法的所有形参参数信息[对应的数组] Class<?>[] parameterTypes = zzwHandler.getMethod().getParameterTypes(); //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到 Object[] params = new Object[parameterTypes.length]; //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中 for (int i = 0; i < parameterTypes.length; i++) { //取出每一个形参类型 Class<?> parameterType = parameterTypes[i]; //如果这个形参是HttpServletRequest, 将request填充到params //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) { params[i] = response; } } //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题 //1.获取http请求的参数集合 //老韩解读 //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox) //2.返回的Map String: 表示http请求的参数名 // String[]: 表示http请求的参数值, 为什么是数组 // Map<String, String[]> parameterMap = request.getParameterMap(); //2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { //取出key. 这个name就是对应请求的参数名 String name = entry.getKey(); //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据 // 老师这里做了简化, 如果考虑多值情况, 也不难 String value = entry.getValue()[0]; //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充 //这里专门编写一个方法, 得到请求参数对应的是第几个形参 //1. API 2.java内力真正增加 3.老韩忠告.. int indexRequestParameterIndx = getIndexRequestParameterIndex(zzwHandler.getMethod(), name); if (indexRequestParameterIndx != -1) {//找到对应的位置 params[indexRequestParameterIndx] = value; 👉👉👉👉👉👉👉} else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行配置[待...] //思路 //1.得到目标方法的所有形参的名称, 而不是形参类型的名称-专门编写一个方法获取形参名 //2.对得到的目标方法的所有形参名进行遍历, 如果匹配就把当前请求的参数值, 填充到params List<String> parameterNames = getParameterNames(zzwHandler.getMethod()); for (int i = 0; i < parameterNames.size(); i++) { //如果请求参数名和目标方法的形参名一样, 说明匹配成功 if (name.equals(parameterNames.get(i))) { params[i] = value;//填充到实参数组 break; } } } } /** * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response) * zzwHandler.getMethod() * .invoke(zzwHandler.getController(), request, response) * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法 * public Object invoke(Object var1, Object... var2)... */ zzwHandler.getMethod(). invoke(zzwHandler.getController(), params); } } catch (Exception e) { throw new RuntimeException(e); } } //编写方法, 得到目标方法的所有形参的名称, 并放入到集合中返回 /** * @param method 目标方法 * @return 所有形参的名称, 并放入到集合中返回 */ public List<String> getParameterNames(Method method) { List<String> parametersList = new ArrayList<String>(); //获取到所有的参数名称, 而不是参数类型的名称 //这里有一个小细节-->在默认情况下 parameter.getName() //得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2...] //, 这里我们要引入一个插件, 使用java8的特性, 这样才能解决 Parameter[] parameters = method.getParameters(); //遍历parameters, 取出名字, 放入parametersList for (Parameter parameter : parameters) { parametersList.add(parameter.getName()); } System.out.println("目标方法的形参参数列表=" + parametersList); return parametersList; }
    • 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
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    3.parameter.getName()得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2…].
    Parameter[] parameters = method.getParameters();
    parameters=[javax.servlet.http.HttpServletRequest arg0,
                         javax.servlet.http.HttpServletResponse arg1,
                         java.lang.String arg2]

    parameter.getName() ⇒ [arg0, arg1, arg2]

    我们需要引入一个插件.
    pom.xmlbuild节点内插入以下代码 -parameters, 点击右上角刷新

    <build>
      <finalName>zzw-springmvc2(你的文件名)finalName>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.pluginsgroupId>
          <artifactId>maven-compiler-pluginartifactId>
          <version>3.7.0version>
          <configuration>
            <source>1.8source>
            <target>1.8target>
            <compilerArgs>
              <arg>-parametersarg>
            compilerArgs>
            <encoding>utf-8encoding>
          configuration>
        plugin>
      plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.1点击maven管理, Lifecycle目录下, clean项目, 重启(不是重新部署)tomcat.
    在这里插入图片描述

    5.测试
    在这里插入图片描述在这里插入图片描述

    实现任务阶段七

    🍍完成简单视图解析

    功能说明: 通过方法返回的String, 转发或者重定向到指定页面

    ●完成任务说明
    -用户输入白骨精, 可以登陆成功, 否则失败
    -根据登陆的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp, 并显示妖怪名

    -思路分析示意图
    在这里插入图片描述

    1.在webapp目录下新建

    1)login.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登陆页面title>
    head>
    <body>
    <h1>登陆页面h1>
    <form action="?" method="post">
        妖怪名: <input type="text" name="mName"/><br/>
        <input type="submit" value="登录">
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2)login_ok.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登录成功title>
    head>
    <body>
    <h1>登陆成功h1>
    欢迎你: ${?}
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3)login_error.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登录失败title>
    head>
    <body>
    <h1>登陆失败h1>
    sorry, 登陆失败 ${?}
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.MonsterService增加一个方法login

    public interface MonsterService {
    	....
        //增加方法, 处理登录
        public boolean login(String name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    MonsterServiceImpl实现它

    @Service
    public class MonsterServiceImpl implements MonsterService {
    	....
        @Override
        public boolean login(String name) {
            //实际上会到DB验证->这里我们模拟一下
            if ("白骨精".equals(name)) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.MonsterController添加一个方法login

    //处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
        System.out.println("---接收到mName---" + mName);
        boolean b = monsterService.login(mName);
        if (b) {//登陆成功
            return "forward:/login_ok.jsp";
        } else {//登陆失败
            return "forward:/login_error.jsp";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接着 login.jsp填充action="/monster/login"

    <h1>登陆页面h1>
    <%--第一个/会被解析 http://localhost:8080
    /monster/login => http://localhost:8080/monster/login--%>
    <form action="/monster/login" method="post">
        妖怪名: <input type="text" name="mName"/><br/>
        <input type="submit" value="登录">
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.测试
    在这里插入图片描述在这里插入图片描述

    如果输入中文, 发现提交的数据有中文乱码问题, 因为是post请求.
    在这里插入图片描述
    在这里插入图片描述


    解决方案
    我们在底层解决乱码问题.
    ZzwDispatcherServlet前端控制器完成分发请求任务executeDispatcher()方法内, 添加如下代码 request.setCharacterEncoding("utf-8");即可

    //2.返回的Map String: 表示http请求的参数名
    //  String[]: 表示http请求的参数值, 为什么是数组
    //处理提交的数据中文乱码问题
    request.setCharacterEncoding("utf-8");
    Map<String, String[]> parameterMap = request.getParameterMap();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试
    在这里插入图片描述


    5.在ZzwDispatcherServletexecuteDispatcher方法, 添加如下代码

    //上面代码省略...
    
    /**
     * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
     * zzwHandler.getMethod()
     *              .invoke(zzwHandler.getController(), request, response)
     * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
     * public Object invoke(Object var1, Object... var2)...
     */
    
    //反射调用目标方法
    Object result = zzwHandler.getMethod()
            .invoke(zzwHandler.getController(), params);
    
    //这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
    //这里老师让我们直接解析, 只要把视图解析器的核心机制表达清楚就OK
    //instanceof 判断 运行类型
    if (result instanceof String) {
        String viewName = (String) result;
        if (viewName.contains(":")) {//说明你返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
            String viewType = viewName.split(":")[0];//forward | redirect
            String viewPage = viewName.split(":")[1];//表示你要跳转的页面
            //判断是forward 还是 redirect
            if ("forward".equals(viewType)) {//说明你希望请求转发
                request.getRequestDispatcher(viewPage)
                        .forward(request, response);
            } else if ("redirect".equals(viewType)) {//说明你希望重定向
                //如果是redirect, 那么这里需要拼接Application  Context. 只不过这个项目的Application Context 正好是 /
                response.sendRedirect(viewPage);
            }
        } else {//默认是请求转发
            request.getRequestDispatcher(viewName)
                    .forward(request, response);
        }
    }//这里还可以扩展
    
    • 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

    6.测试
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    解决{?}:将信息保存到request域, 在页面显示.
    6.MonsterController修改login方法, 将mName保存到request域

    //处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
        System.out.println("---接收到mName---" + mName);
        //将nName设置到request域
        request.setAttribute("mName", mName);
        boolean b = monsterService.login(mName);
        if (b) {//登陆成功
            return "forward:/login_ok.jsp";
        } else {//登陆失败
            return "forward:/login_error.jsp";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6.1修改login_ok.jsp. 设置isELIgnored="false", 否则el表达式不会生效, 默认是true

    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <html>
    <head>
        <title>登录成功title>
    head>
    <body>
    <h1>登陆成功h1>
    欢迎你: ${requestScope.mName}
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6.2修改login_error.jsp.设置isELIgnored="false", 否则el表达式不会生效 默认是true

    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <html>
    <head>
        <title>登录失败title>
    head>
    <body>
    <h1>登陆失败h1>
    sorry, 登陆失败: ${requestScope.mName}
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6.3测试
    在这里插入图片描述在这里插入图片描述

    7.演示redirect.
    MonsterController修改login方法

    //处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
        System.out.println("---接收到mName---" + mName);
        //将nName设置到request域
        request.setAttribute("mName", mName);
        boolean b = monsterService.login(mName);
        if (b) {//登陆成功
            //return "forward:/login_ok.jsp";
            //测试从定向
            return "redirect:/login_ok.jsp";👈
        } else {//登陆失败
            return "forward:/login_error.jsp";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    7.1测试

    在这里插入图片描述之所有不显示, 是因为请求方式是重定向.
    在这里插入图片描述
    在这里插入图片描述

    7.2演示默认方式(forward)
    MonsterController修改login方法

    //处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
        System.out.println("---接收到mName---" + mName);
        //将nName设置到request域
        request.setAttribute("mName", mName);
        boolean b = monsterService.login(mName);
        if (b) {//登陆成功
            //return "forward:/login_ok.jsp";
            //测试从定向
            //return "redirect:/login_ok.jsp";
            //测试默认方式(forward)
            return "/login_ok.jsp";👈
        } else {//登陆失败
            return "forward:/login_error.jsp";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    8 将登录页面提交的参数名改成monsterName, 还会不会登陆成功?

    <h1>登陆页面h1>
    <%--第一个/会被解析 http://localhost:8080
    /monster/login => http://localhost:8080/monster/login--%>
    <form action="/monster/login" method="post">
        妖怪名: <input type="text" name="monsterName"/><br/>
        <input type="submit" value="登录">
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8.1测试
    在这里插入图片描述在这里插入图片描述

    8.2 解决方案: 在MonsterControllerlogin方法, 形参mName加上@RequestParam(value = "monsterName")

    //处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request,
                        HttpServletResponse response,
                        @RequestParam(value = "monsterName") String mName) {
        System.out.println("---接收到mName---" + mName);
        //将nName设置到request域
        request.setAttribute("mName", mName);
        boolean b = monsterService.login(mName);
        if (b) {//登陆成功
            //return "forward:/login_ok.jsp";
            //测试从定向
            //return "redirect:/login_ok.jsp";
            //测试默认方式-forward
            return "/login_ok.jsp";
        } else {//登陆失败
            return "forward:/login_error.jsp";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    8.3测试
    在这里插入图片描述在这里插入图片描述

    实现任务阶段八

    🍍完成返回JSON格式数据-@ResponseBody

    功能说明: 通过自定义@ResponseBody, 返回JSON格式数据

    ●完成任务说明
    -在实际开发中, 比如前后端分离的项目, 通常是直接返回json数据给客户端/浏览器
    -客户端/浏览器接收到数据后, 自己决定如何处理和显示
    -测试页面 浏览器输入: http://localhost:8080/monster/list/json

    🥦分析+代码实现

    在这里插入图片描述

    1.在com.zzw.zzwspringmvc.annotation下新建@ResponseBody

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

    2.MonsterController添加方法listMonsterByJson

    @Controller
    public class MonsterController {
    
        //@Autowired表示要完成属性的装配.
        @Autowired
        private MonsterService monsterService;
    
        /**
         * 编写方法, 返回json格式的数据
         * 老师梳理
         * 1.目标方法返回的结果是给springmvc底层反射调用的位置使用
         * 2.我们在springmvc底层反射调用的位置, 接收到结果并解析即可
         * 3.方法上标注了@ResponseBody 表示希望以json格式返回给客户端/浏览器
         * 4.目标方法的实参, 在springmvc底层通过封装好的参数数组, 传入...
         * @param request
         * @param respons
         * @return
         */
        @RequestMapping("/monster/list/json")
        @ResponseBody
        public List<Monster> listMonsterByJson(HttpServletRequest request,
                                                    HttpServletResponse respons) {
            List<Monster> monsters = monsterService.listMonster();
            return monsters;
        }
    }
    
    • 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

    3.pom.xml引入jackson, 刷新. 注意: 这里我们不使用gson
    注意: jackson-databind

    <!--引入jackson, 使用它的工具类可以进行json操作-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.4</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.1com.zzw包下新建一个测试类ZzwTest

    public class ZzwTest {
        public static void main(String[] args) {
            List<Monster> monsters = new ArrayList<Monster>();
            monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
            monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
    
            //把monsters 转成json
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String monsterJson = objectMapper.writeValueAsString(monsters);
                System.out.println("monsterJson=" + monsterJson);
    
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.ZzwDispatcherServletexecuteDispatcher, 在反射调用目标方法的位置扩展代码

    if (result instanceof String) {
        String viewName = (String) result;
        ......
    }//这里还可以扩展
    else if (result instanceof ArrayList) {//如果是ArrayList
        //判断目标方法是否有@ResponseBody注解
        Method method = zzwHandler.getMethod();
        if (method.isAnnotationPresent(ResponseBody.class)) {
            //把result [ArrayList] 转成json格式数据->返回
            //这里我们需要使用到java中如何将 ArrayList 转成 json
            //这里我们需要使用jackson包下的工具类可以轻松地搞定
            //这里先演示一下如何操作
            ObjectMapper objectMapper = new ObjectMapper();
            String resultJson = objectMapper.writeValueAsString(result);
            //这里我们简单地处理, 就直接返回
            response.setContentType("text/html;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(resultJson);
            writer.flush();
            writer.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    🥦完成测试

    1.浏览器测试
    在这里插入图片描述
    2.也可以在线转成规整的json格式
    JSON工具在线解析
    在这里插入图片描述

    3.也可以使用Postman进行测试
    在这里插入图片描述

    总结

    还在补充中…
    在这里插入图片描述

  • 相关阅读:
    uniapp实战项目 (仿知识星球App) - - 引入uView框架
    WPF文本框中加提示语
    SpringMVC源码(1)-文件上传请求
    Java学习笔记3.5.3 继承 - super关键字
    java-php-python-互联网保险网站计算机毕业设计
    clickhouse出现数据重复问题
    终于有人把面试必考的动态规划、链表、二叉树、字符串全部撸完了
    LeetCode --- 1422. Maximum Score After Splitting a String 解题报告
    DVWA 之 SQL注入(非盲注)
    物联网手势控制小车所遇问题与解决方案
  • 原文地址:https://blog.csdn.net/qq_18817831/article/details/132597002