手动实现SpringMVC底层机制

手动实现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张图片

准备工作

搭建SpringMVC底层机制开发环境

1.创建maven-web项目
手动实现SpringMVC底层机制_第2张图片

出现了点小插曲. 项目建成后, 没有src目录, 且右下角报错 Cannot find JRE '1.7
做如下修改
手动实现SpringMVC底层机制_第3张图片
改成1.8
手动实现SpringMVC底层机制_第4张图片

缺少的文件夹需自己手动创建
手动实现SpringMVC底层机制_第5张图片
pom.xml配置

<dependencies>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.11version>
        <scope>testscope>
    dependency>

    
    <dependency>
        <groupId>javax.servletgroupId>
        <artifactId>javax.servlet-apiartifactId>
        <version>3.1.0version>
        
        <scope>providedscope>
    dependency>
    
    <dependency>
        <groupId>dom4jgroupId>
        <artifactId>dom4jartifactId>
        <version>1.6.1version>
    dependency>
    
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-lang3artifactId>
        <version>3.5version>
    dependency>
dependencies>

实现任务阶段一

开发ZzwDispatcherServlet

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

分析+代码实现

手动实现SpringMVC底层机制_第6张图片

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的容器配置文件

手动实现SpringMVC底层机制_第7张图片


手动实现SpringMVC底层机制_第8张图片
对应的类路径
手动实现SpringMVC底层机制_第9张图片

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


<servlet>
  <servlet-name>ZzwDispatcherServletservlet-name>
  <servlet-class>com.zzw.zzwspringmvc.servlet.ZzwDispatcherServletservlet-class>
  
  <init-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:zzwspringmvc.xmlparam-value>
  init-param>
  
  
  <load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
  <servlet-name>ZzwDispatcherServletservlet-name>
  
  <url-pattern>/url-pattern>
servlet-mapping>

配置Tomcat, 完成测试

1.配置tomcat
手动实现SpringMVC底层机制_第10张图片
手动实现SpringMVC底层机制_第11张图片手动实现SpringMVC底层机制_第12张图片

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

随便请求一个网址
手动实现SpringMVC底层机制_第13张图片手动实现SpringMVC底层机制_第14张图片

实现任务阶段二

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

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

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

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

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

"
); writer.flush(); writer.close(); } }

2.配置zzwspringmvc.xml

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


<beans>
     
    <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测试类
手动实现SpringMVC底层机制_第15张图片
XMLParser类在很多包下都有, 别选错
手动实现SpringMVC底层机制_第16张图片

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, 进行测试
手动实现SpringMVC底层机制_第17张图片

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, 测试
手动实现SpringMVC底层机制_第18张图片

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手动实现SpringMVC底层机制_第19张图片

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类中, 逻辑是一样的
判断方法上有没有某种注解

  • 示意图分析
    手动实现SpringMVC底层机制_第20张图片
/**
 * 解读
 * 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 分发请求到对应控制器方法

  • 示意图[分析说明]
    手动实现SpringMVC底层机制_第21张图片

  • 当用户发出请求, 根据用户请求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("

404 NOT FOUND!

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

订单列表信息

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

添加订单...

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

商品列表信息~

"
); writer.flush(); writer.close(); } }

手动实现SpringMVC底层机制_第22张图片
手动实现SpringMVC底层机制_第23张图片
手动实现SpringMVC底层机制_第24张图片
手动实现SpringMVC底层机制_第25张图片

实现任务阶段三

从web.xml动态获取zzwspringmvc.xml

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

示意图[分析说明]

手动实现SpringMVC底层机制_第26张图片


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中的
        /*
            
                contextConfigLocation
                classpath:zzwspringmvc.xml
            
         */
        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容器

示意图[分析说明]

手动实现SpringMVC底层机制_第27张图片补充: DAO和DB由MyBatis接管, 和SpringMVC关系并不大. 所以我们暂时不考虑DAO和DB.
手动实现SpringMVC底层机制_第28张图片

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


<beans>
     
    <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容器中对象的注入/自动装配

示意图[分析说明]

手动实现SpringMVC底层机制_第29张图片
分析:
加入@Autowired注解, 进行对象属性的装配. -如图
手动实现SpringMVC底层机制_第30张图片

浏览器输入 http://localhost:8080/monster/list, 返回列表信息
手动实现SpringMVC底层机制_第31张图片

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就是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

手动实现SpringMVC底层机制_第32张图片
手动实现SpringMVC底层机制_第33张图片

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

妖怪列表信息

"
);//单线程使用StringBuilder content.append("");//调用monsterServiceList<Monster> monsters = monsterService.listMonster();for(Monster monster : monsters){ content.append("");} content.append("
" + monster.getId() + "" + monster.getName() + "" + monster.getAge() + "" + monster.getSkill() + "
"
); //获取writer返回信息 try { PrintWriter writer = response.getWriter(); writer.print(content.toString()); } catch (IOException e) { throw new RuntimeException(e); } } }

5.重启tomcat, 浏览器输入 http://localhost:8080/monster/list
手动实现SpringMVC底层机制_第34张图片

实现任务阶段六

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

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

示意图[分析说明]
手动实现SpringMVC底层机制_第35张图片

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

修改ZzwDispatcherServletexecuteDispatcher()方法

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

404 NOT FOUND!

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

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

404 NOT FOUND!

"
); } else {//匹配成功, 反射调用控制器的方法 //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组 //1. 得到目标方法的所有形参参数信息[对应的数组] Class<?>[] parameterTypes = zzwHandler.getMethod().getParameterTypes(); //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到 Object[] params = new Object[parameterTypes.length]; //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中 for (int i = 0; i < parameterTypes.length; i++) { //取出每一个形参类型 Class<?> parameterType = parameterTypes[i]; //如果这个形参是HttpServletRequest, 将request填充到params //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) { params[i] = response; } } //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题 //1.获取http请求的参数集合 //老韩解读 //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox) //2.返回的Map String: 表示http请求的参数名 // String[]: 表示http请求的参数值, 为什么是数组 // Map<String, String[]> parameterMap = request.getParameterMap(); //2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { //取出key. 这个name就是对应请求的参数名 String name = entry.getKey(); //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据 // 老师这里做了简化, 如果考虑多值情况, 也不难 String value = entry.getValue()[0]; //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充 //这里专门编写一个方法, 得到请求参数对应的是第几个形参 //1. API 2.java内力真正增加 3.老韩忠告.. int indexRequestParameterIndx = 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("

妖怪列表信息

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

404 NOT FOUND!

"
); } else {//匹配成功, 反射调用控制器的方法 //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组 //1. 得到目标方法的所有形参参数信息[对应的数组] Class<?>[] parameterTypes = zzwHandler.getMethod().getParameterTypes(); //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到 Object[] params = new Object[parameterTypes.length]; //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中 for (int i = 0; i < parameterTypes.length; i++) { //取出每一个形参类型 Class<?> parameterType = parameterTypes[i]; //如果这个形参是HttpServletRequest, 将request填充到params //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配 if ("HttpServletRequest".equals(parameterType.getSimpleName())) { params[i] = request; } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) { params[i] = response; } } //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题 //1.获取http请求的参数集合 //老韩解读 //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox) //2.返回的Map String: 表示http请求的参数名 // String[]: 表示http请求的参数值, 为什么是数组 // Map<String, String[]> parameterMap = request.getParameterMap(); //2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { //取出key. 这个name就是对应请求的参数名 String name = entry.getKey(); //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据 // 老师这里做了简化, 如果考虑多值情况, 也不难 String value = entry.getValue()[0]; //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充 //这里专门编写一个方法, 得到请求参数对应的是第几个形参 //1. API 2.java内力真正增加 3.老韩忠告.. int indexRequestParameterIndx = 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-springmvc2finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.pluginsgroupId>
      <artifactId>maven-compiler-pluginartifactId>
      <version>3.7.0version>
      <configuration>
        <source>1.8source>
        <target>1.8target>
        <compilerArgs>
          <arg>-parametersarg>
        compilerArgs>
        <encoding>utf-8encoding>
      configuration>
    plugin>
  plugins>
build>

4.点击maven管理, Lifecycle目录下, clean项目, 再重启(不是重新部署)一下tomcat, 完成测试.
手动实现SpringMVC底层机制_第36张图片

5.测试
手动实现SpringMVC底层机制_第37张图片
手动实现SpringMVC底层机制_第38张图片

实现任务阶段七

完成简单视图解析

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

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

-思路分析示意图
手动实现SpringMVC底层机制_第39张图片

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.测试
手动实现SpringMVC底层机制_第40张图片在这里插入图片描述

如果输入中文, 发现提交的数据有中文乱码问题
手动实现SpringMVC底层机制_第41张图片
在这里插入图片描述


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

//2.返回的Map 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);
    }
}//这里还可以扩展

测试
手动实现SpringMVC底层机制_第42张图片
手动实现SpringMVC底层机制_第43张图片
手动实现SpringMVC底层机制_第44张图片
手动实现SpringMVC底层机制_第45张图片

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

测试

手动实现SpringMVC底层机制_第46张图片
手动实现SpringMVC底层机制_第47张图片
手动实现SpringMVC底层机制_第48张图片

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

测试
手动实现SpringMVC底层机制_第49张图片手动实现SpringMVC底层机制_第50张图片

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>

测试
手动实现SpringMVC底层机制_第51张图片手动实现SpringMVC底层机制_第52张图片

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

手动实现SpringMVC底层机制_第53张图片手动实现SpringMVC底层机制_第54张图片

实现任务阶段八

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

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

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

分析+代码实现

手动实现SpringMVC底层机制_第55张图片

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.浏览器测试
手动实现SpringMVC底层机制_第56张图片
2.也可以在线转成规整的json格式
JSON工具在线解析
手动实现SpringMVC底层机制_第57张图片

3.也可以使用Postman进行测试
手动实现SpringMVC底层机制_第58张图片

总结

还在补充中…
手动实现SpringMVC底层机制_第59张图片

你可能感兴趣的:(SpringMVC,spring,java,tomcat)