SpringMVC(基于Spring的Web层MVC框架) 中

手动实现SpringMVC底层机制

在这里插入图片描述

知识点

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

🐟准备工作

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

前提: 搭建maven环境

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 {
        doPost(req, resp);
    }

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

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

在这里插入图片描述

<!--先空着-->

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

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

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

🥦配置Tomcat, 完成测试

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

2.测试, 随便请求一个网址
在这里插入图片描述在这里插入图片描述

实现任务阶段二

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

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

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

1.在com.zzw.controller下新建MonsterController

public class MonsterController {

    //编写方法, 可以列出怪物列表
    //springmvc 是支持原生的servlet api, 为了看到底层机制
    //这里我们涉及两个参数
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.在com.zzw.zzwspringmvc.annotation下新建注解类@Controller
RetentionPolicy.RUNTIME: 编译器把注解记录在class文件中, 当运行Java程序时, JVM 会保留注解. 程序可以通过反射获取该注解

/**
 * @author 赵志伟
 * @version 1.0
 * 该注解用于标识一个控制器组件
 * 这里涉及到注解知识, 在java基础
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
	String value() default "";
}

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

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

4.在MonsterController中添加注解

@Controller
public class MonsterController {

    //编写方法, 可以列出怪物列表
    //springmvc 是支持原生的servlet api, 为了看到底层机制
    //这里我们涉及两个参数
    @RequestMapping(value = "/monster/list")
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>妖怪名信息: 孙悟空--猪八戒--沙僧</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

🥦2.配置zzwspringmvc.xml

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

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

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

/**
 * @author 赵志伟
 * @version 1.0
 * XMLParser 用于解析spring配置文件
 */
@SuppressWarnings({"all"})
public class XMLParser {

    public static String getBasePackage(String xmlFile) {
        //1.得到解析器
        SAXReader reader = new SAXReader();
        //2.得到类的加载路径 => 获取到spring配置文件[对应的资源流]
        InputStream inputStream =
                XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
        try {
            //3.得到xml文件的文档
            Document document = reader.read(inputStream);
            //4.获取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);
        }
    }
}

2.在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容器-得到扫描类的全路径列表.

把指定的目录包括子目录下的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);
    }
}

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

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

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

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

测试
在这里插入图片描述

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

功能说明: 将配置的@RequestMappingurl和 对应的 控制器-方法 映射关系保存到集合中

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

1.在com.zzw.zzwspringmvc.handler下新建ZzwHandler

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwHandler 对象记录请求的url 和 控制器方法映射关系
 */
@SuppressWarnings({"all"})
public class ZzwHandler {
    private String url;
    private Object controller;
    private Method method;

   public ZzwHandler(String url, Object controller, Method method) {
       this.url = url;
       this.controller = controller;
       this.method = method;
   }
   
	//getter, setter, toString方法
}

2.修改ZzwDispatcherServlet

  1. init方法内声明的zzwWebApplicationContext属性提到外面, 扩大它的作用域
  2. 定义属性 handlerList, 保存ZzwHandler[url 和 控制器-方法的映射关系]
  3. 编写方法[initHandlerMapping], 完成url 和 控制器-方法的映射 (initHandlerMapping也可以写在HandlerMapping类中, 逻辑是一样的)
/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 * 3.提示: 这里我们需要使用到 java web 讲解的Servlet
 */
public class ZzwDispatcherServlet extends HttpServlet {

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

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

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

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

2.OrderController增加两个方法listOrder(), addOrder() 别忘了加Controller注解

@Controller
public class OrderController {

    @RequestMapping(value = "/order/list")
    public void listOrder(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>订单列表信息</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

3.GoodsController增加一个方法listGoods()

@Controller
public class GoodsController {

    @RequestMapping(value = "/goods/list")
    public void listGoods(HttpServletRequest request, HttpServletResponse response) {
        //设置返回编码和返回类型
        response.setContentType("text/html;charset=utf8");
        //获取writer返回信息
        try {
            response.getWriter().write("<h1>商品列表信息...</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

4.测试(注意: 不要再加工程路径了)
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述
handlerList初始化的结果=
[ZzwHandler{url=‘/goods/list’, controller=com.zzw.controller.xx.GoodsController@79b1752f, method=public void com.zzw.controller.xx.GoodsController.listGoods(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/order/add’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.addOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/order/list’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
 
ZzwHandler{url=‘/monster/list’, controller=com.zzw.controller.MonsterController@32128628, method=public void com.zzw.controller.MonsterController.listMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]

实现任务阶段三

🍍从web.xml动态获取zzwspringmvc.xml

说明: 前面我们加载zzwspringmvc.xml是硬编码, 现在做活. 从web.xml动态获取

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

1.ZzwDispatcherServlet在创建并初始化ZzwWebApplicationContext时, 动态地从web.xml中获取到spring配置文件.
servletConfig使用

/**
 * 解读
 * 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
 * 2.本质是一个Servlet, 继承HttpServlet
 * 3.提示: 这里我们需要使用到 java web 讲解的Servlet
 */
public class ZzwDispatcherServlet extends HttpServlet {

    @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>();
         
    //创建一个属性, 表示spring容器配置文件
    private String configLocation;   
    
    //添加一个无参构造器
    public ZzwWebApplicationContext() {
    }

    //构建一个有参构造器
    public ZzwWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }

    //编写方法, 完成自己的spring容器的初始化
    public void init() {
        //这里我们写的是固定的spring容器配置文件 => 做活
        //String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        scanPackage(basePackage);
        System.out.println("basePackage=" + basePackage);
        System.out.println("classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ioc容器
        executeInstance();
        System.out.println("扫描后的 ioc容器=" + ioc);
    }
    ........
}

3.测试…

实现任务阶段四

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

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

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

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

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

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

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

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

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

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

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

/**
 * @author 赵志伟
 * @version 1.0
 * MonsterServiceImpl 作为一个Service注入到spring容器
 */
@SuppressWarnings({"all"})
public class MonsterServiceImpl implements MonsterService {
    //这里我们模拟数据->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]);
        //这时我们的basePackage => com.zzw.controller,com.zzw.service
        //scanPackage(basePackage);
        String[] basePackages = basePackage.split(",");
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        ........
	}

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

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

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

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("<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 {
        response.getWriter().write(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

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

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

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增添代码

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

//编写方法, 完成分发请求任务
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请求的参数集合
            //老韩解读
            //2.返回的Map<String, String[]> 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;
}

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("<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 {
        response.getWriter().write(content.toString());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

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

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

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.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>

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>

2)login_ok.jsp

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

3)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.测试
在这里插入图片描述在这里插入图片描述

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


解决方案
我们在底层解决乱码问题.
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)) {//说明你希望请求转发
            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);
    }
}//这里还可以扩展

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

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>

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>

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

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

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>

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

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

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

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>

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进行测试
在这里插入图片描述

总结

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

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
# demoWeb 一个基于SpringMVCweb框架 1.0.5 从web项目迁移成maven项目 1.0.6 增加菜单框架ext实现,类路径调整 1.0.7 增加http工具类,demo例子 1.0.8 socket工具类,权限组件,菜单组件,jdbc分页支持多种数据库,ant路径工具类,增加jquery easyUI 1.0.9 版本管理,服务根路径工具类,文件上传工具类 1.0.10 集成ueditor在线编辑器 1.0.11 地址联动 1.0.12 Excel工具类 Word工具类 Java NIO实现socket工具类 分布式session jdk升级到1.7 嵌入式redis服务(只支持linux) 1.0.13 修改默认的beanName生成策略,controller参数扩展 1.0.14 分布式session使用zookeeper 1.0.15 zookeeper工具类优化 增加工具类 1.0.16 页面html标志修改 httpclient文支持 工具类增强(zip,reflect,thread) 1.0.17 ftp服务端和客户端工具类,配置文件maven和web项目路径统一 1.1.0 soapui工具类(web版本) properties等工具类 1.1.1 工具类数据校验 jsp自定义标签 Spring自定义注解 默认requestMapping 1.1.2 代码生成器 1.1.3 首页修改 dateformat.js 时间参数转换 SpringMVC配置文件集 快递参数接口 1.1.4 des加解密字符串和文件 1.1.5 redis 加锁,redis升级成2.8.2 freemarker工具类 1.1.6 spring websocket 实现在线聊天 maven升级jdk1.8 jetty9.2.4 web升级jdk1.7 tomcat7 1.1.7(maven only) 包名修改 从此不再支持web版本,只支持maven版本 1.1.8 jquery 图片预览插件 图片滚动显示插件 1.1.9 jquery实现鼠标在按钮上显示窗口,离开窗口和按钮时消失 1.1.10 rabbitMQ集成 视频截图 图片缩略图旋转 集成Mybatis 使用数据库连接池druid dubbo使用 1.1.11 集成Spring Cache,FastJson Spring Cache增加redis缓存实现 Mybatis使用二级缓存,增加redis实现 增加reactJs 增加Mybatis插件pageHelper,Mapper doc内有相关文档

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~ 小团子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值