手动实现SpringMVC底层机制

news2024/10/6 6:49:17

手动实现SpringMVC底层机制

  • 🐟准备工作
    • 🍍搭建SpringMVC底层机制开发环境
  • 实现任务阶段一
    • 🍍开发ZzwDispatcherServlet
      • 🥦说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
      • 🥦分析+代码实现
      • 🥦配置Tomcat, 完成测试
  • 实现任务阶段二
    • 🍍完成客户端/浏览器可以请求控制层
      • 🥦1.创建自己的Controller和自定义注解
      • 🥦2.配置zzwspringmvc.xml
      • 🥦3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
      • 🥦4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
      • 🥦5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
      • 🥦6.完成请求URL和控制器方法的映射关系
      • 🥦7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
  • 实现任务阶段三
    • 🍍从web.xml动态获取zzwspringmvc.xml
  • 实现任务阶段四
    • 🍍完成自定义@Service注解功能
  • 实现任务阶段五
    • 🍍完成Spring容器对象的自动装配-@Autowired
  • 实现任务阶段六
    • 🍍完成控制器方法获取参数-@RequestParam
      • 1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
      • 2.🥦在方法参数 指定 @RequestParam 的参数封装到参数数组, 进行反射调用
      • 3.🥦在方法参数 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
  • 实现任务阶段七
    • 🍍完成简单视图解析
  • 实现任务阶段八
    • 🍍完成返回JSON格式数据-@ResponseBody
      • 🥦分析+代码实现
      • 🥦完成测试
  • 总结

在这里插入图片描述

🐟准备工作

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

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

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

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

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

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

实现任务阶段一

🍍开发ZzwDispatcherServlet

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

🥦分析+代码实现

在这里插入图片描述

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

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

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

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

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

在这里插入图片描述

<!--先空着-->

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

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

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

🥦配置Tomcat, 完成测试

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

2.更改ZzwDispatcherServlet代码, 启动Tomcat, 开始测试

public class ZzwDispatcherServlet extends HttpServlet {

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

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

随便请求一个网址
在这里插入图片描述在这里插入图片描述

实现任务阶段二

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

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

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

1.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返回信息
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        writer.print("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        writer.flush();
        writer.close();
    }
}

1.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.3在该包下新建注解类RequestMapping

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

1.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返回信息
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        writer.print("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        writer.flush();
        writer.close();
    }
}

🥦2.配置zzwspringmvc.xml

2.配置zzwspringmvc.xml 后面别忘了测试Service

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

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

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

/**
 * @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);
        }
    }
}

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

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

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

4.开发ZzwWebApplicationContext.java, 充当Spring容器. 得到扫描类的全路径列表.
把指定的目录包括子目录下的java类的全路径扫描到集合中, 比如 ArrayList [java基础]
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) {
        //得到包所在的工作路径[对应的绝对路径]
        ClassLoader classLoader = this.getClass().getClassLoader();
        //下面这句话的含义是 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
        //比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvczzw-springmvc/target/classes/com/zzw/controller
        URL resource = classLoader.getResource(pack.replace(".", "/"));
        //细节说明:
        // 1.不要直接使用Junit测试, 否则 url返回null
        // 2.启动Tomcat测试, 才能得到这个类路径
        System.out.println("resource=" + resource);
    }
}

4.1前端控制器ZzwDispatcherServlet增加init方法

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

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

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

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

启动Tomcat, 访问http://localhost:8080/zzw_springmvc2/monster/list, 进行测试
在这里插入图片描述

4.2开发自己的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);
        //classFullPathList=[com.zzw.controller.MonsterController,
        //                   com.zzw.controller.xx.GoodsController,
        //                   com.zzw.controller.xx.OrderController]
        System.out.println("classFullPathList=" + classFullPathList);
    }

    /**
     * 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
     *
     * @param pack 表示要扫描的包, 比如 com.zzw.controller
     */
    public void scanPackage(String pack) {
        //得到包所在的工作路径[对应的绝对路径]
        ClassLoader classLoader = this.getClass().getClassLoader();
        //下面这句话的含义是 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
        //比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
        URL resource = classLoader.getResource(pack.replace(".", "/"));
        //细节说明:
        // 1.不要直接使用Junit测试, 否则 url返回null
        // 2.启动Tomcat测试, 才能得到这个类路径
        System.out.println("resource=" + resource);
        //根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathList
        // path = D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
        String path = resource.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);
            }
        }
    }
}

4.3在Controller包下随机建立子包以及Controller, 重启Tomcat, 测试
在这里插入图片描述

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

5.完善ZzwWebApplicationContext, 充当Spring容器, 实例化对象到容器中
将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service...)
增加ioc容器
增加executeInstance方法

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

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

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

    /**
     * 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
     *
     * @param pack 表示要扫描的包, 比如 com.zzw.controller
     */
    public void scanPackage(String pack) {
        //得到包所在的工作路径[对应的绝对路径]
        ClassLoader classLoader = this.getClass().getClassLoader();
        //下面这句话的含义是 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
        //比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
        URL resource = classLoader.getResource(pack.replace(".", "/"));
        //细节说明:
        // 1.不要直接使用Junit测试, 否则 url返回null
        // 2.启动Tomcat测试, 才能得到这个类路径
        System.out.println("resource=" + resource);
        //根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathList
        // path = D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
        String path = resource.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);
            }
        }
    }

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

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

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

6.完成请求URL和控制器方法的映射关系
将配置的@RequestMappingurl和 对应的 控制器-方法 映射关系保存到集合中

6.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方法
}

6.2修改ZzwDispatcherServlet
init方法内声明的zzwWebApplicationContext属性提到外面, 扩大它的作用域
定义属性 handlerList, 保存ZzwHandler[url和控制器方法的映射关系]
编写方法[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() throws ServletException {
        zzwWebApplicationContext = new ZzwWebApplicationContext();
        zzwWebApplicationContext.init();
        //调用 initHandlerMapping, 完成url和控制器方法的映射
        initHandlerMapping();
        System.out.println("handlerList初始化的结果=" + handlerList);
    }

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

    @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对象->就是一个映射关系
                        //我自己的理解:
                        //一个zzwHanler对象存放一个Controller对象, 以及这个Controller对象的一个方法, 以及这个方法的注解值url
                        ZzwHandler zzwHandler =
                                new ZzwHandler(url, entry.getValue(), declaredMethod);
                        //放入到 handlerList
                        handlerList.add(zzwHandler);
                    }
                }
            }
        }
    }
}

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

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

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

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

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

request.getRequestURI();在听课时, 这句代码不太理解

/**
 * 解读
 * 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() throws ServletException {
 		//创建自己的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);
    }

    //编写方法, 完成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);
                        //这里小伙伴可以工程路径+url
                        // getServletContext().getContextPath() + requestMappingAnnotation.value();
                        String url = requestMappingAnnotation.value();
                        //创建ZzwHandler对象->就是一个映射关系
                        //我自己的理解:
                        //一个zzwHanler对象存放一个Controller对象, 以及这个Controller对象的一个方法, 以及这个方法的注解值url
                        ZzwHandler zzwHandler =
                                new ZzwHandler(url, entry.getValue(), declaredMethod);
                        //放入到 handlerList
                        handlerList.add(zzwHandler);
                    }
                }
            }
        }
    }

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

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

测试

@Controller
public class OrderController {

    /**
     * 编写方法, 可以列出怪物列表
     * springmvc 是支持原生的servlet api, 为了看到底层机制
     * 这里我们涉及到两个参数
     */
    @RequestMapping(value = "/order/list")
    public void listOrder(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        writer.print("<h1>订单列表信息</h1>");
        writer.flush();
        writer.close();
    }

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

    /**
     * 编写方法, 可以列出怪物列表
     * springmvc 是支持原生的servlet api, 为了看到底层机制
     * 这里我们涉及到两个参数
     */
    @RequestMapping(value = "/goods/list")
    public void listGoods(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        writer.print("<h1>商品列表信息~</h1>");
        writer.flush();
        writer.close();
    }
}

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

实现任务阶段三

🍍从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 {

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

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

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

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

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

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

    //无参构造器
    public ZzwWebApplicationContext() {
    }

    private String configLocation;//属性, 表示spring容器配置文件
    
    public ZzwWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        //这里我们是写的固定的spring容器配置文件
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        scanPackage(basePackage);
        System.out.println("basePackage=" + basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ioc容器
        executeInstance();
        /*
        扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@3d4ca903,
                       orderController=com.zzw.controller.xx.OrderController@76ce738c,
                       monsterController=com.zzw.controller.MonsterController@bceda48}
        */
        System.out.println("扫描后的 ioc容器=" + ioc);
    }
    ........
}

测试…

实现任务阶段四

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

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

示意图[分析说明]

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

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

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

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

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

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

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

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

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

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

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

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

3.3更改ZzwWebApplicationContext.javainit()

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

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

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

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

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

5.测试-重启tomcat

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

实现任务阶段五

🍍完成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.1MonsterController添加属性monsterService, 标注@Autowired

@Controller
public class MonsterController {

    //@Autowired表示要完成属性的装配.
    @Autowired
    private MonsterService monsterService;

	.....
}

2.ZzwWebApplicationContext.java增加方法executeAutowired()

//编写方法, 完成属性的自动装配
public void executeAutowired() {
    //判断ioc有没有要装配的对象
    if (ioc.isEmpty()) {
        return;//你也可以抛出异常, throw new RuntimeException("ioc 容器没有bean对象")
    }

    //遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性, 判断是否需要装配
    /**
     * entry => <String, Object> -> 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);
                }
            }
        }
    }
}

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();
    /*
    扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@3d4ca903,
                   orderController=com.zzw.controller.xx.OrderController@76ce738c,
                   monsterController=com.zzw.controller.MonsterController@bceda48}
    */
    System.out.println("扫描后的 ioc容器=" + ioc);

    //完成注入的bean对象,的属性的装配
    executeAutowired();
    System.out.println("装配后 ioc容器=" + ioc);

3.打断点, debug, 重启tomcat

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

4.修改MonsterControllerlistMonster方法

@Controller
public class MonsterController {

    //@Autowired表示要完成属性的装配.
    @Autowired
    private MonsterService monsterService;

    /**
     * 编写方法, 可以列出怪物列表
     * springmvc 是支持原生的servlet api, 为了看到底层机制
     * 这里我们涉及到两个参数
     */
    @RequestMapping(value = "/monster/list")
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");

        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");//单线程使用StringBuilder
        content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
        //调用monsterService
        List<Monster> monsters = monsterService.listMonster();
        for (Monster monster : monsters) {
            content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName() + "</td><td>"
                    + monster.getAge() + "</td><td>" + monster.getSkill() + "</td></tr>");
        }
        content.append("</table>");

        //获取writer返回信息
        try {
            PrintWriter writer = response.getWriter();
            writer.print(content.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

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("<h1>404 NOT FOUND!</h1>");
        } 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);
    }
}

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

1.MonsterService新添方法findMonsterByName, MonsterServiceImpl将其实现,

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

    //增加方法, 通过传入的name, 返回monster列表
    public List<Monster> findMonsterByName(String name);
}
@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;
    }
}

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

3.ZzwDispatcherServlet增添代码

//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
                               HttpServletResponse response) {
    try {
        ZzwHandler zzwHandler = getZzwHandler(request);
        if (zzwHandler == null) {//说明用户请求的路径/资源不存在
            response.getWriter().print("<h1>404 NOT FOUND!</h1>");
        } 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, String[]> 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 =
                        getIndexRequestParameterIndx(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 getIndexRequestParameterIndx(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;
}

4.MonsterController增加如下方法
method.getParameters(): 得到所有的形参
method.getParameterTypes(): 得到形参的类型

//增加方法, 通过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---\n" + name);
    StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");//单线程使用StringBuilder
    content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
    //调用monsterService
    List<Monster> monsters = monsterService.findMonsterByName(name);
    for (Monster monster : monsters) {
        content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName() + "</td><td>"
                + monster.getAge() + "</td><td>" + monster.getSkill() + "</td></tr>");
    }
    content.append("</table>");

    //获取writer返回信息
    try {
        PrintWriter writer = response.getWriter();
        writer.print(content.toString());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

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

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

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

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

//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
                               HttpServletResponse response) {
    try {
        ZzwHandler zzwHandler = getZzwHandler(request);
        if (zzwHandler == null) {//说明用户请求的路径/资源不存在
            response.getWriter().print("<h1>404 NOT FOUND!</h1>");
        } 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, String[]> 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 =
                        getIndexRequestParameterIndx(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;
}

3.parameter.getName()得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2…]. 我们需要引入一个插件.
pom.xmlbuild节点内插入以下代码

<build>
  <finalName>zzw-springmvc2</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.7.0</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArgs>
          <arg>-parameters</arg>
        </compilerArgs>
        <encoding>utf-8</encoding>
      </configuration>
    </plugin>
  </plugins>
</build>

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

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

实现任务阶段七

🍍完成简单视图解析

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

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

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

1.在webapp目录下新建
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>

login_ok.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
欢迎你: ${?}
</body>
</html>

login_error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录失败</title>
</head>
<body>
<h1>登陆失败</h1>
sorry, 登陆失败: ${?}
</body>
</html>

2.MonsterService增加一个方法login

public interface MonsterService {
	....
    //增加方法, 处理登录
    public boolean login(String name);
}

MonsterServiceImpl实现它

@Service
public class MonsterServiceImpl implements MonsterService {
	....
    @Override
    public boolean login(String name) {
        //实际上会到DB验证->这里我们模拟一下
        if ("白骨精".equals(name)) {
            return true;
        } else {
            return false;
        }
    }
}

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

接着 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>

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

如果输入中文, 发现提交的数据有中文乱码问题
在这里插入图片描述
在这里插入图片描述


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

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

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)) {//说明你希望请求转发
            //这里需要拼接Application  Context. 只不过这个项目的Application Context 正好是 /
            request.getRequestDispatcher(viewPage)
                    .forward(request, response);
        } else if ("redirect".equals(viewType)) {//说明你希望重定向
            response.sendRedirect(viewPage);
        }
    } else {//默认是请求转发
        request.getRequestDispatcher(viewName)
                .forward(request, response);
    }
}//这里还可以扩展

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

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

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

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
欢迎你: ${requestScope.mName}
</body>
</html>

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

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>登录失败</title>
</head>
<body>
<h1>登陆失败</h1>
sorry, 登陆失败: ${requestScope.mName}
</body>
</html>

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

测试

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

7.1演示默认方式(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";
    }
}

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

7.2 将登录页面提交的参数名改成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>

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

7.3测试@RequestParam
要修改的地方: 在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";
    }
}

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

实现任务阶段八

🍍完成返回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 "";
}

2.MonsterController添加方法listMonsterByJson

@Controller
public class MonsterController {

    //@Autowired表示要完成属性的装配.
    @Autowired
    private MonsterService monsterService;

    /**
     * 编写方法, 返回json格式的数据
     * 1.老师梳理
     * 2.目标方法返回的结果是给springmvc底层通过反射调用的位置
     * 3.我们在springmvc底层反射调用的位置, 接收到结果并解析即可
     * 4.方法上标注了@ResponseBody 表示希望以json格式返回给客户端/浏览器
     * @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;
    }
}

3.pom.xml引入jackson

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

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

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.也可以在线转成规整的json格式
JSON工具在线解析
在这里插入图片描述

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

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于点标签的目标检测与计数深度学习框架盘点

(1)P2PNet <1>P2PNet提出 论文出处&#xff1a;Rethinking Counting and Localization in Crowds: A Purely Point-Based Framework 论文链接&#xff1a;https://arxiv.org/abs/2107.12746 开源代码&#xff1a;https://github.com/TencentYoutuResearch/CrowdCount…

物联网AI MicroPython传感器学习 之 MQ136硫化氢传感器

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 MQ136 是一种硫化氢检测传感器&#xff0c;感应范围为 1 - 200ppm。传感元件是 SnO2&#xff0c;它在清洁空气中的电导率较低。当存在 H₂S 气体时&#xff0c;传感器的电导率随着气体浓度的升…

Gralloc ION DMABUF in Camera Display

目录 Background knowledge Introduction ia pa va and memory addressing Memory Addressing Page Frame Management Memory area management DMA IOVA and IOMMU Introduce DMABUF What is DMABUF DMABUF 关键概念 DMABUF APIS –The Exporter DMABUF APIS –The…

PyTorch模型的多种导出方式提供给其他程序使用

PyTorch模型的多种导出方式提供给其他程序使用 flyfish PyTorch模型的多种导出方式 PyTorch模型的多种导出方式提供给其他程序使用1 模型可视化2 预训练模型3 ONNX模型导出有输入有输出TRAINING导出方式EVAL导出方式 4 自定义输入输出的名字&#xff0c;并可批量推理5 导出JI…

PyG两个data Datsaset v.s. InMemoryDataset

可以看到InMemoryDataset 对CPU更加友好 https://pytorch-geometric.readthedocs.io/en/latest/modules/data.html#pytorch-lightning-wrappers

Linux下C++编程-进度条

引言&#xff1a;本篇主要在linux下的C实现进度条的功能。按照多文件编程&#xff0c;同时使用Makefile文件完成多文件的编译、连接。 首先创建头文件&#xff1a; 1. progress.h #pragma once #include <iostream> #include <cstring> #include <iomanip>…

Navicat定时任务

Navicat定时任务 1、启动Navicat for MySQL工具&#xff0c;连接数据库。 2、查询定时任务选项是否开启 查询命令&#xff1a;SHOW VARIABLES LIKE ‘%event_scheduler%’; ON表示打开&#xff0c;OFF表示关闭。 打开定时任务命令 SET GLOBAL event_scheduler 0; 或者 SET G…

elasticsearch 8.5.3问题记录

一&#xff1a;解决 elasticsearch 高版本 warning: ignoring JAVA_HOMEC:\Program Files\Java\jdk-11&#xff1b; using bundled JDK if defined JAVA_HOME (set JAVA_HOME%JAVA_HOME%; )示例版本Elasticsearch 8.5.3 可以与 JDK 11 兼容&#xff0c;但不支持 JDK 17。确保选…

离散数学 期末复习

离散数学 期末复习 图片过多&#xff0c;若无法显示&#xff0c;请转至 https://chenhaotian.top/study/discrete-mathematics-final-review/ 访问全文 发布于 2023-06-18 第 1 章 命题逻辑 1.2 等值演算 真值表法 等值演算法 题&#xff1a;等值演算 题&#xff1a;等值演…

python 学习随笔 5

函数 在python中&#xff0c;函数也是对象。 def func():""" 这是文档字符串"""print("Hello World")fun带参数函数 函数和变量注解 def calc(a:int, b:int) -> int: # 为函数的形参和返回值标注类型return a b print(calc(1,3…

C语言重点突破(2)指针(三)

本章重点 1. 函数指针 2. 函数指针数组3. 指向函数指针数组的指针 4. 回调函数 1.函数指针 首先可以明确的是&#xff0c;函数指针和数组指针的类型都是指针类型&#xff0c;用来存放地址的&#xff0c;数组指针存放数组的地址&#xff0c;而函数指针存放的便是函数的地址。 …

ESPHome 通过http获取设备的状态

substitutions: { desc: 传感器, devicename: sensor }esphome:name: $devicenameplatform: ESP8266board: nodemcuv2arduino_version: latest# Enable logging logger:# Enable Home Assistant API api:encryption:key: "MhXiJqKKyCXTqjZWqtegaP1tQSUpPtbnXP9iV1i2TzE&qu…

SSM - Springboot - MyBatis-Plus 全栈体系(二十四)

第五章 SSM 二、SSM 整合配置实战 1. 依赖整合添加 1.1 数据库准备 依然沿用 mybatis 数据库测试脚本&#xff01; CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIM…

视频号双11激励政策,快来看一看

双十一即将来临&#xff0c;不少平台都公布了自己的双十一政策。这篇文章&#xff0c;我们来看看视频号推出的激励政策&#xff0c;看有哪些需要准备的。

var、const、let的区别

目录 一、作用域 二、变量提升&#xff08;Hoisting&#xff09; 三、重复声明 四、 赋值和重新赋值 五、全局对象属性 六、使用场景 七、声明和赋值的数据类型 一、作用域 var 声明的变量在函数作用域内有效&#xff0c;如果在函数外声明&#xff0c;它将成为全局变量…

C++-Mongoose(3)-http-server-https-restful

1.url 结构 2.http和 http-restful区别在于对于mg_tls_opts的赋值 2.1 http和https 区分 a) port地址 static const char *s_http_addr "http://0.0.0.0:8000"; // HTTP port static const char *s_https_addr "https://0.0.0.0:8443"; // HTTP…

Springboot全局异常和自定义异常

目录 依赖 一、全局异常处理 1、系统异常 2、业务异常 &#xff08;1&#xff09;、接口配置 &#xff08;2&#xff09;、服务配置 二、自定义异常 依赖 <!-- validator&#xff0c;用于接口参数校验 --><dependency><groupId>org.hibernate.valida…

淘宝京东拼多多品牌价格监控API接口

淘宝、京东、拼多多品牌价格监控API接口需要从官方平台获取&#xff0c;以下是具体步骤&#xff1a; 登录京东、天猫、淘宝、拼多多、苏宁、国美、唯品会等电商平台&#xff0c;注册并获取开发者账号和API接口权限。通过开发者账号和API接口权限&#xff0c;访问京东、天猫、淘…

linux中单节点安装greenplum数据库

目录 一、安装包准备 二、关闭防火墙 三、安装greenplum依赖包 四、添加配置 五、新建greenplum用户 六、安装greenplum安装包 七、设置环境变量 八、权限互通 九、修改数据库配置文件 十、初始化数据库 十一、配置远程连接文件 十二、说明 一、安装包准备 下载准备…

Windows网络管理及诊断命令整理

目录 traceroute&#xff1a; ping&#xff1a; pathping: netstat: ipconfig&#xff1a; nslookup&#xff1a; route&#xff1a; ARP: FTP: netsh&#xff1a; nbtstat&#xff1a; sniffer(嗅探器)&#xff1a; winipcfg&#xff1a; traceroute&#xff1a; …