自己实现 SpringMVC 底层机制 详细版

230 阅读26分钟

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

 

搭建 SpringMVC 底层机制开发环境

1、创建 Maven 项目 wyx-springmvc [提示: 我们在讲解 手动实现tomcat 时,我们已经使用过,不知道可以看我的博客

2、对 wyx-springmvc 进行配置: 修改 D:\idea_java_projects\wyx-springmvc\pom.xml , 将 1.7 修改成 1.8

<properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <maven.compiler.source>1.8</maven.compiler.source>

        <maven.compiler.target>1.8</maven.compiler.target>

</properties>

3、对 wyx-springmvc 进行配置: 创建 main 和 test 相关的源码目录和资源目录和测试目录 (这是 Maven 工程的开发规范,前面讲过..), 如图

 

 

提示:

 

引入需要的基本的 jar 包, 修改 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,解析xml文件-->
        <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>

 


 

实现任务阶段 1- 开发 WyxDispatcherServlet

 

说明:

编写 WyxDispatcherServlet 充当原生的 DispatcherServlet(即核心控制器)

分析示意图

 创建WyxDispatcherServlet.java类

public class WyxDispatcherServlet 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);
    }
}

创建wyxspringmvc.xml

充当 原生的 applicationContext-mvc.xml 文件 (就是 spring 的容器配置文件  比如指定要扫描 哪些包下的类) 先创建给空的文件

修改web.xml, 完成 WyxDispatcherServlet 的

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  
  <!--配置WyxDispatcherServlet, 作为我们自己的前端控制器-->
  <servlet>
    <servlet-name>WyxDispatcherServlet</servlet-name>
    <servlet-class>com.wyxdu.wyxspringmvc.servlet.WyxDispatcherServlet</servlet-class>
    <!--给WyxDispatcherServlet配置参数,指定要操作的spring容器配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:wyxspringmvc.xml</param-value>
    </init-param>
    <!--WyxDispatcherServlet在tomcat启动时,就自动加载-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>WyxDispatcherServlet</servlet-name>
    <!--因为WyxDispatcherServlet作为前端控制器,所以需要拦截所有请求,url-pattern配置 /-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

配置 Tomcat, 完成测试

配置 Tomcat   具体步骤我们在讲解 JavaWeb 时  已经操作过 不知道请看 链接

修改WyxDispatcherServlet.java

public class WyxDispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
            ServletException, IOException {
        System.out.println("WyxDispatcherServlet doGet()被调用");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
            ServletException, IOException {
        System.out.println("WyxDispatcherServlet doPost()被调用");
    }
}

启动 Tomcat完成测试

 


 

实现任务阶段 2- 完成客户端/浏览器可以请求控制层 

 

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

● 分析示意图

 创建MonsterController.java

public class MonsterController {
    public void listMonsters(HttpServletRequest request, HttpServletResponse
            response) {
        response.setContentType("text/html;charset=utf-8");
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write("<h1>妖怪列表</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

创建自定义注解Controller

annotation\Controller.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}

创建自定义注解RequestMapping

annotation\RequestMapping

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}

配置 wyxspringmvc.xml

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


<?xml version="1.0" encoding="UTF-8" ?>
    <beans>
        <component-scan base-package="com.wyxedu.controller"></component-scan>
    </beans>

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

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

创建XMLPaser.java类

package com.wyxdu.wyxspringmvc.xml;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;

/**
 * XMLParser 用于解析spring配置文件
 */
public class XMLParser {

    public static String getBasePackage(String xmlFile) {

        //这个解析的过程,是前面讲过的
        SAXReader saxReader = new SAXReader();
        //通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]
        InputStream inputStream =
                XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
        try {
            //得到xmlFile文档
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            Element componentScanElement =
                    rootElement.element("component-scan");
            Attribute attribute = componentScanElement.attribute("base-package");
            String basePackage = attribute.getText();
            return basePackage;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

创建WyxSpringMvcTest

public class HspSpringMvcTest {
    @Test
    public void readXML() {
        String basePackage = XMLPaser.getbasePackage("wyxspringmvc.xml");
        System.out.println("basePackage= " + basePackage);
    }
}

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

完成的功能说明

- 把指定的目录包括子目录下的 java 类的全路径扫描到集合中,

比如 ArrayList -如图[对 java 基础知识的使用

扫描后的

classFullPathList=[com.wyxdu.controller.MonsterController, com.wyxedu.service.impl.MonsterServiceImpl, com.wyxedu.service.MonsterService]

创建WyxWebApplicationContext.java

public class WyxWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList

public void init() {
        //这里是写的固定的spring容器配置文件.?=>做活
        String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");

        //这时basePackage => com.wyxdu.controller,com.wyxdu.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
    }

 public void scanPackage(String pack) {

        //得到包所在的工作路径[绝对路径]
        //下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
        //比如 "com.hspedu.controller" => url 是 D:\hspedu_springmvc\hsp-springmvc\target\hsp-springmvc\WEB-INF\classes\com\hspedu\controller
        //细节说明: 1. 不要直接使用Junit测试, 否则 url null
        //             2. 启动tomcat来吃测试
        URL url =
                this.getClass().getClassLoader()
                        .getResource("/" + pack.replaceAll("\\.", "/"));

        System.out.println("urlss=" + url);
        //根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList

        String path = url.getFile();
        System.out.println("path= " + path);
        //在io中,把目录,视为一个文件
        File dir = new File(path);
        //遍历dir[文件/子目录]
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {//如果是一个目录,需要递归扫描
                scanPackage(pack + "." + f.getName());
            } else {
                //说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
                //就算是.class, 也存在是不是需要注入到容器
                //目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
                String classFullPath =
                        pack + "." + f.getName().replaceAll(".class", "");
                classFullPathList.add(classFullPath);
            }
        }

    }


}

修改WyxDispatcherServlet.java类

@Override
public void init(ServletConfig servletConfig) throws ServletException {
         //创建自己的spring容器
        wyxWebApplicationContext =
                new WyxWebApplicationContext(configLocation);

        wyxWebApplicationContext.init();
}

启动 Tomcat 完成测试,

看看扫描是否成功. 需要使用 Tomcat 启动方式完成测试,直接用 Junit 测试 URL 是null

15:03:30.297 信 息 [RMI TCP Connection(3)-127.0.0.1]
org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet
contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were
scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can
improve startup time and JSP compilation time.

扫描后

classFullPathList=[com.hspedu.controller.MonsterController, com.wyxdu.service.impl.MonsterServiceImpl, com.wyxdu.service.MonsterService]

完善 WyxWebApplicationContext,充当 Spring 容器-实例化对象到容器中

 public void init() {
        String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");

        //这时basePackage => com.wyxdu.controller,com.wyxdu.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ico容器
        executeInstance();
        System.out.println("扫描后的 ioc容器= " + ioc);
}

public void executeInstance() {
        //判断是否扫描到类
        if (classFullPathList.size() == 0) {//说明没有扫描到类
            return;
        }
        try {
            //遍历classFullPathList,进行反射
            for (String classFullPath : classFullPathList) {
                Class<?> clazz = Class.forName(classFullPath);
                //说明当前这个类有@Controller
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //得到类名首字母小写
                    String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
                            clazz.getSimpleName().substring(1);
                    ioc.put(beanName, clazz.newInstance());
                } //如果有其它的注解,可以扩展 , 来处理@Service
                else if()
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

完成测试

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

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

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

示意图分析

将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中

-如图 

handlerList初始化的结果= [WyxHandler{url='/order/list', controller=com.wyxdu.controller.OrderController@79b8c11d, method=public void com.wyxdu.controller.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

创建WyxHandler 类

public class WyxHandler {
    private String url;
    private Object controller;
    private Method method;

    public WyxHandler(String url, Object controller, Method method) {
        this.url = url;
        this.controller = controller;
        this.method = method;
    }

    public WyxHandler() {
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "WyxHandler{" +
                "url='" + url + '\'' +
                ", controller=" + controller +
                ", method=" + method +
                '}';
    }
}

修改WyxDispatcherServlet.java类

public class WyxDispatcherServlet extends HttpServlet {

    //定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]
    private List<WyxHandler> handlerList =
            new ArrayList<>();
    //定义属性 wyxWebApplicationContext,自己的spring容器
    WyxWebApplicationContext wyxWebApplicationContext = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

        //创建自己的spring容器
        wyxWebApplicationContext =
                new WyxWebApplicationContext();

        wyxWebApplicationContext.init();

        //调用 initHandlerMapping , 完成url和控制器方法的映射

        initHandlerMapping();
        //输出handlerList
        System.out.println("handlerList初始化的结果= " + handlerList);
    }

    private void initHandlerMapping() {
        if(wyxWebApplicationContext.ioc.isEmpty()) {
            throw new RuntimeException("spring ioc 容器为空");
        }
        for(Map.Entry<String,Object> entry:
                wyxWebApplicationContext.ioc.entrySet()) {
             //先取出注入的Object的clazz对象
            Class<?> clazz = entry.getValue().getClass();

            //如果注入的Bean是Controller
            if(clazz.isAnnotationPresent(Controller.class)) {

                //取出它的所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                //遍历方法
                for (Method declaredMethod : declaredMethods) {
                 //判断该方法是否有@RequestMapping
                    if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //取出@RequestMapping值->就是映射路径
                        RequestMapping requestMappingAnnotation =
                                declaredMethod.getAnnotation(RequestMapping.class);
                        //这里小伙伴可以把工程路径+url
                        //getServletContext().getContextPath()
                        // /springmvc/monster/list

                        String url = requestMappingAnnotation.value();

                        handlerList.add(new
                                WyxHandler(url,entry.getValue(),declaredMethod));
                    }
                }
            }
        }
}

完成测试

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

 

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

示意图 

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

 

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

 

 修改WyxDispatcherServlet.java类

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //System.out.println("--WyxDispatcherServlet--doPost---");
        //调用方法,完成分发请求
        executeDispatch(req, resp);
    }

    private void executeDispatch(HttpServletRequest req,
                                 HttpServletResponse response) {
        WyxHandler wyxHandler = getWyxHandler(req);
        try {
            if (null == wyxHandler) {//没有匹配的 Handler
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//有匹配的 Handler, 就调用
                wyxHandler.getMethod().invoke(wyxHandler.getController(), req,
                        response);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    private void initHandlerMapping() {
        if(wyxWebApplicationContext.ioc.isEmpty()) {
            throw new RuntimeException("spring ioc 容器为空");
        }
        for(Map.Entry<String,Object> entry:
                wyxWebApplicationContext.ioc.entrySet()) {
             //先取出注入的Object的clazz对象
            Class<?> clazz = entry.getValue().getClass();

            //如果注入的Bean是Controller
            if(clazz.isAnnotationPresent(Controller.class)) {

                //取出它的所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                //遍历方法
                for (Method declaredMethod : declaredMethods) {
                 //判断该方法是否有@RequestMapping
                    if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //取出@RequestMapping值->就是映射路径
                        RequestMapping requestMappingAnnotation =
                                declaredMethod.getAnnotation(RequestMapping.class);
                        //这里小伙伴可以把工程路径+url
                        //getServletContext().getContextPath()
                        // /springmvc/monster/list

                        String url = requestMappingAnnotation.value();

                        handlerList.add(new
                                WyxHandler(url,entry.getValue(),declaredMethod));
                    }
                }
            }
        }
    }

完成测试(启动 Tomcat) 

 

 

 增加方法和控制器再看看, 创建OrderController.java

@Controller
public class OrderController {

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

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

 

 


 

 实现任务阶段 3- 从 web.xml动态获取   wyxspringmvc.xml

说明:

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

 分图析示意

-WyxDispatcherServlet 在创建并初始化 WyxWebApplicationContext,动态的从 web.xml 中获 取到配置文件 

修改 WyxWebApplicationContext

public class WyxWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/子包的类的全路径
    private List<String> classFullPathList =
            new ArrayList<>();
    //定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
    public ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

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

    private String configLocation;//属性,表示spring容器配置文件

    public WyxWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }
    public void init() {
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackage => com.wyxdu.controller,com.wyxdu.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ico容器
        executeInstance();
        System.out.println("扫描后的 ioc容器= " + ioc);
}

修改WyxDispatcherServlet.java类

 public void init(ServletConfig servletConfig) throws ServletException {

        //获取到web.xml中的 contextConfigLocation
        /*
         <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:wyxspringmvc.xml</param-value>
        </init-param>
         */
        String configLocation =
                servletConfig.getInitParameter("contextConfigLocation");

        //创建自己的spring容器
        wyxWebApplicationContext =
                new WyxWebApplicationContext(configLocation);

        wyxWebApplicationContext.init();
        //调用 initHandlerMapping , 完成url和控制器方法的映射
        initHandlerMapping();
        //输出handlerList
        System.out.println("handlerList初始化的结果= " + handlerList);
    }

完成测试

(启动 tomcat 方式, 修改后,redeploye 即可)

 


 

实现任务阶段 4- 完成自定义@Service 注解功能

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

分析示意图 

 

 给 Service 类标注@Service, 可以将对象注入到 Spring 容器中

- 并可以通过接口名支持多级, 类名来获取到 Service Bean

结果为

monsterService=com.wyxdu.service.impl.MonsterServiceImpl@6cc86233, monsterServiceImpl=com.wyxdu.service.impl.MonsterServiceImpl@6cc86233,

 创建Monster类

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

    public Monster(Integer id, String name, String skill, Integer age) {
        this.id = id;
        this.name = name;
        this.skill = skill;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", skill='" + skill + '\'' +
                ", age=" + age +
                '}';
    }
}

创建自定义Service注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

 创建Service接口

public interface MonsterService{

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

创建MonsterServiceImpl实现类

@Service
public class MonsterServiceImpl implements MonsterService {
    @Override
    public List<Monster> listMonsters() {
        ArrayList<Monster> monsters = new ArrayList<>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 500));
        monsters.add(new Monster(200, "蜘蛛精", "吐口水", 200));
        return monsters;
    }
}

 修改wyxspringmvc.xml

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

修改WyxWebApplicationContext

public void executeInstance() {
        //判断是否扫描到类
        if (classFullPathList.size() == 0) {//说明没有扫描到类
            return;
        }
        try {
            //遍历classFullPathList,进行反射
            for (String classFullPath : classFullPathList) {
                Class<?> clazz = Class.forName(classFullPath);
                //说明当前这个类有@Controller
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //得到类名首字母小写
                    String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
                            clazz.getSimpleName().substring(1);
                    ioc.put(beanName, clazz.newInstance());
                } //如果有其它的注解,可以扩展 , 来处理@Service
                else if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Serivce注解

                    //先获取到Service的value值=> 就是注入时的beanName
                    Service serviceAnnotation =
                            clazz.getAnnotation(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) {
            e.printStackTrace();
        }
    }

完成测试

(启动 Tomcat, 自动加载 HspDispatcherServlet, 完成 IOC 容器的注入) 前面有结果

 


 

实现任务阶段 5- 完成 Spring 容器对象的自动装配 -@Autowried

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

分析示意图 

 

 

- 浏览器输入 http://localhost:8080/monster/list, 返回列表信息 

 

● 代码实现, 说明,整个实现思路,就是参考 SpringMVC 规范

 创建自定义注解AutoWired

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWired {
    String value() default "";
}

修改MonsterController

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=utf-8");

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

        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

修改WyxWebApplicationContext

public class WyxWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/子包的类的全路径
    private List<String> classFullPathList =
            new ArrayList<>();
    //定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
    public ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

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

    private String configLocation;//属性,表示spring容器配置文件

    public WyxWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }

    //编写方法,完成自己的spring容器的初始化
    public void init() {
        //这里是写的固定的spring容器配置文件.?=>做活
        //String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackage => com.wyxedu.controller,com.wyxdu.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ico容器
        executeInstance();
        System.out.println("扫描后的 ioc容器= " + ioc);

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

 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.getAnnotation(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 (null == ioc.get(beanName)) {//说明你指定的名字对应的bean不在ioc容器
                        throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
                    }
                    //防止属性是private, 我们需要暴力破解
                    declaredField.setAccessible(true);
                    //可以装配属性
                    try {
                        declaredField.set(bean, ioc.get(beanName));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }

        }

    }

启动 Tomcat, 完成测试

浏览器输入 http://localhost:8080/monster/list

 


 

 实现任务阶段 6- 完成控制器方法获取参数-@RequestParam

 

 功能说明:

自定义@RequestParam 和 方法参数名获取参数

完成任务说明

- 后端 Handler 的目标方法

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name) {
//代码.... 

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

● 代码实现, 说明,整个实现思路,就是参考 SpringMVC 规范 

 修改WyxDispatcherServlet.java类

    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        WyxHandler wyxHandler = getWyxHandler(request);
        try {
            if (null == wyxHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        wyxHandler.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;
                    }
                }
                    //wyxHandler.getMethod()
                //        .invoke(wyxHandler.getController(),request,response);

                //反射调用目标方法
                Object result = wyxHandler.getMethod()
                        .invoke(wyxHandler.getController(), params);
}

 完成测试

(启动 tomcat), 浏览器输入 http://localhost:8080/monster/list , 仍然可以看 到正确的返回

 

完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组,进行反射调用 

- 测试页面

 - 后端 Handler 的目标方法

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name) {
//代码.... }

 创建自定义注解RequestParam

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}

 修改WyxDispatcherServlet.java接口类

public interface MonsterService {
    public List<Monster> listMonsters();
    public List<Monster> findMonstersByName(String name);
}

修改MonsterSer viceImpl.java实现类

@Service
public class MonsterServiceImpl implements MonsterService {
    @Override
    public List<Monster> listMonster() {
        //这里就模拟数据->DB
        List<Monster> monsters =
                new ArrayList<>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "老猫妖怪", "抓老鼠", 200));
        return monsters;
    }

    @Override
    public List<Monster> findMonsterByName(String name) {
        //这里就模拟数据->DB
        List<Monster> monsters =
                new ArrayList<>();
        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<>();
        //遍历monsters,返回满足条件
        for (Monster monster : monsters) {
            if (monster.getName().contains(name)) {
                findMonsters.add(monster);
            }
        }
        return findMonsters;
    }

修改MonsterController

@Controller
public class MonsterController {

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

    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request,
                                  HttpServletResponse response,
                                  @RequestParam(value="name") String name) {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        System.out.println("--接收到的name---" + name);
        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
        //调用monsterService
        List<Monster> monsters = monsterService.findMonsterByName(name);
        content.append("<table border='1px' width='400px' style='border-collapse:collapse'>");
        for (Monster monster : monsters) {
            content.append("<tr><td>" + monster.getId()
                    + "</td><td>" + monster.getName() + "</td><td>"
                    + monster.getSkill() + "</td><td>"
                    + monster.getAge() + "</td></tr>");
        }
        content.append("</table>");

        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

修改WyxDispatcherServlet.java类

    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        WyxHandler wyxHandler = getWyxHandler(request);
        try {
            if (null == wyxHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        wyxHandler.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=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组
                //
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap =
                        request.getParameterMap();

                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    //    这里做了简化,如果考虑多值情况,也不难..
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充
                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
        
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(wyxHandler.getMethod(), name);

                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    } else {
                //一会写
                }

                /**
                 * 解读
                 * 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
                 * 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
                 * 3. public Object invoke(Object obj, Object... args)..
                 */
                //wyxHandler.getMethod()
                //        .invoke(wyxHandler.getController(),request,response);

                //反射调用目标方法
                 wyxHandler.getMethod()
                        .invoke(wyxHandler.getController(), params);
        } catch (Exception e) {
        e.printStackTrace();
    }

getIndexRequestParameterIndex方法 

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

完成测试 (Redeploy Tomcat 即 可 )

浏览器输入http://localhost:8080/monster/find?name=牛魔王

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

修改MonsterController

    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request,
                                  HttpServletResponse response,
                                  String name) {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        System.out.println("--接收到的name---" + name);
        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
        //调用monsterService
        List<Monster> monsters = monsterService.findMonsterByName(name);
        content.append("<table border='1px' width='400px' style='border-collapse:collapse'>");
        for (Monster monster : monsters) {
            content.append("<tr><td>" + monster.getId()
                    + "</td><td>" + monster.getName() + "</td><td>"
                    + monster.getSkill() + "</td><td>"
                    + monster.getAge() + "</td></tr>");
        }
        content.append("</table>");

        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

修改WyxDispatcherServlet.java类

    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        WyxHandler wyxHandler = getWyxHandler(request);
        try {
            if (null == wyxHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        wyxHandler.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=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组
                //
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap =
                        request.getParameterMap();

                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    //    这里做了简化,如果小伙伴考虑多值情况,也不难..
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充
                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
   
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(wyxHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    } else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]

                        //思路
                        //1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
                        //2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
                        List<String> parameterNames =
                                getParameterNames(wyxHandler.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)
                 * 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
                 * 3. public Object invoke(Object obj, Object... args)..
                 */
                //wyxHandler.getMethod()
                //        .invoke(wyxHandler.getController(),request,response);

                //反射调用目标方法
                Object result = wyxHandler.getMethod()
                        .invoke(wyxHandler.getController(), params);
} catch (Exception e) {
e.printStackTrace();
}
    public List<String> getParameterNames(Method method) {

        List<String> parametersList = new ArrayList<>();
        //获取到所以的参数名->这里有一个小细节
        //在默认情况下 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;
    }

 

得 到 控 制 器 方 法 的 参 数 名 ,

比 如 public void findMonstersByName(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "name") String name)  request, response, name

注意:

1. 在默认情况下,返回的并不是 request, response ,name 而是 arg0, arg1,arg2
2. 需要使用到 jdk8 的新特性,并需要在 pom.xml 配置 maven 编译插件(可以百度搜索到),才能得到 request, response, name

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

点击 maven 管理,clean 项目,在重启一下 tomcat ,完成测试 

 

后台:

 如果请求参数和方法参数不一致:

 


 

实现任务阶段 7- 完成简单视图解析

功能说明:

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

 完成任务说明

- 用户输入白骨精,可以登录成功, 否则失败

- 根据登录的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp, 并显示妖怪名

 

 

 

 

思路分析示意图 

 

 

  修改MonsterService接口

public interface MonsterService{

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

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

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

修改MonsterServiceImpl实现类新增方法

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

修改MonsterController 增加方法 

    //处理妖怪登录的方法,返回要请求转发/重定向的字符串
    @RequestMapping("/monster/login")
    public String login(HttpServletRequest request,
                        HttpServletResponse response,
                        String mName) {

        System.out.println("--接收到mName---" + mName);
        //将mName设置到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";
        }
    }

创建login_ok.jsp

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

创建login_error.jsp

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

修改WyxDispatcherServlet.java类

    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        WyxHandler wyxHandler = getWyxHandler(request);
        try {
            if (null == wyxHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        wyxHandler.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=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组
                //
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap =
                        request.getParameterMap();

                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    //    这里做了简化,如果小伙伴考虑多值情况,也不难..
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充

                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
  
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(wyxHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;

                    } else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]

                        //思路
                        //1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
                        //2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
                        List<String> parameterNames =
                                getParameterNames(wyxHandler.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)
                 * 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
                 * 3. public Object invoke(Object obj, Object... args)..
                 */
                //wyxHandler.getMethod()
                //        .invoke(wyxHandler.getController(),request,response);

                //反射调用目标方法
                Object result = wyxHandler.getMethod()
                        .invoke(wyxHandler.getController(), params);

                //这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
                //这里直接解析,只要把视图解析的核心机制讲清楚就OK
                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)) {//说明你希望重定向
                            response.sendRedirect(viewPage);
                        }
                    } else {//默认是请求转发
                        request.getRequestDispatcher(viewName)
                                .forward(request,response);
                    }
 } catch (Exception e) {
            e.printStackTrace();
        }
}

完成测试

浏览器输入 测试http://localhost:8080/monster/login?mName=xxx

 

 


 

 实现任务阶段 8- 完成返回 JSON 格式数据-@ResponseBody

功能说明:

通自定义@ResponseBody 返回 JSON 格式 

分析示意图 

 

● 代码实现

说明,整个实现思路,就是参考 SpringMVC 

创建自定义注解ResponseBody

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}

修改MonsterController.java类   增加方法

编写方法,返回json格式的数据

1. 目标方法返回的结果是给springmvc底层通过反射调用的位置

2. 我们在springmvc底层反射调用的位置,接收到结果并解析即可

3. 方法上标注了 @ResponseBody 表示希望以json格式返回给客户端/浏览器

4. 目标方法的实参,在springmvc底层通过封装好的参数数组,传入..


    @RequestMapping("/monster/list/json")
    @ResponseBody
    public List<Monster> listMonsterByJson(HttpServletRequest request,
                                           HttpServletResponse response) {

        List<Monster> monsters = monsterService.listMonster();
        return monsters;
    }

修改pom.xml

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

修改WyxDispatcherServlet.java类

    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        WyxHandler wyxHandler = getWyxHandler(request);
        try {
            if (null == wyxHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        wyxHandler.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;
                    }
                }

                
                
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");

                //将http请求参数封装到params数组中, 提示,要注意填充实参的时候,顺序问题
                //1. 获取http请求的参数集合
                //解读
                //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组

                Map<String, String[]> parameterMap =
                        request.getParameterMap();

                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    //    这里做了简化,如果考虑多值情况,也不难..
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充
                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
                    
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(wyxHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    } else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]

                        //思路
                        //1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
                        //2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
                        List<String> parameterNames =
                                getParameterNames(wyxHandler.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)
                 * 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
                 * 3. public Object invoke(Object obj, Object... args)..
                 */
                //wyxHandler.getMethod()
                //        .invoke(wyxHandler.getController(),request,response);

                //反射调用目标方法
                Object result = wyxHandler.getMethod()
                        .invoke(wyxHandler.getController(), params);

                //这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
                //这里直接解析,只要把视图解析的核心机制清楚就OK
                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)) {//说明你希望重定向
                            response.sendRedirect(viewPage);
                        }
                    } else {//默认是请求转发
                        request.getRequestDispatcher(viewName)
                                .forward(request,response);
                    }

                }//这里还可以扩展
                else if(result instanceof ArrayList) {//如果是ArrayList

                    //判断目标方法是否有@ResponseBody
                    Method method = wyxHandler.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();

                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }