手撕 MVC 框架

2022-01-17 15:38:50 浏览数 (1)

学会使用框架,慢慢的就需要提升自己,学会手写框架。我们先从简单的 MVC 开始。本篇文章

本代码已经上传到Gitee上面:

https://gitee.com/li_kun_zang/do-mvc

技术包含了:

  • 自定义注解
  • 类加载
  • 反射 & 注解扫描

主要实现功能

  • 统一请求控制
  • 请求参数数据类型转换
  • 统一请求转发、重定向
  • 响应JSON数据。

思想

M:Model 模型 代表 @Controller修饰的内容

V:View 视图 图中 返回的结果 404 500 JSON 等等

C:Controller 控制器 图中代表 DispatcherServlet

实操

jdk 8 环境、tomcat 8 环境、maven 环境

1、创建 webapp 项目

自己补全 java、resources、 test 文件夹

2、引入 Maven 依赖

这里是封装的 反射的 操作。

代码语言:javascript复制
    <dependency>
      <groupId>org.reflections</groupId>
      <artifactId>reflections</artifactId>
      <version>0.9.11</version>
    </dependency>

3、创建相关注解

自创建一个 annoation 文件夹 并在 下面创建 @Controller 、@RequestMapping 注解

代码如下

@Controller 注解

代码语言:javascript复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author : zanglikun
 * @date : 2021/2/23 11:50
 * @Version: 1.0
 * @Desc : 自定义的 Controller 注解 需要声明 他的运行周期,我们采用元注解 进行 注解 Controller 注解  如果 这里 不理解 ,请去访问 https://www.zanglikun.com/1078.html 学习
 */

@Target(ElementType.TYPE)   // 设置 只能修饰在类上
@Retention(RetentionPolicy.RUNTIME) // 设置 在运行期有效
public @interface Controller {

    /**
     * @Target(ElementType.METHOD)
     * @Retention(RetentionPolicy.SOURCE)
     */
    // @Override   //我们进入 此注解,获取它的 注解格式,我们修改即可
}

@RequeseMapping 注解

代码语言:javascript复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author : zanglikun
 * @date : 2021/2/23 11:54
 * @Version: 1.0
 * @Desc : RequestMapping 注解 并加入一个 value 值。
 */
@Target({ElementType.METHOD,ElementType.TYPE}) // 注解在 类和方法上
@Retention(RetentionPolicy.RUNTIME) // 注解此注解 只在运行期
public @interface RequestMapping {
    String value() default ""; // 设置注解 可传递的参数,默认值是 ""
}

4、去使用注解

创建 一个 Controller 文件夹 并创建 一个 Java 类

我们在写代码过程,就能看到我们自己的注解了。

写2个 Controller

UserController 其中 NoScanMethod 没有被 @RequestMapping 注解

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;

/**
 * @author : zanglikun
 * @date : 2021/2/23 12:27
 * @Version: 1.0
 * @Desc : 用户控制类
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/add")
    public void ADD(){
        System.out.println("UserController 执行了add方法");
    }

    @RequestMapping("/del")
    public void DEL(){
        System.out.println("UserController 执行了del方法");
    }

    @RequestMapping("/get")
    public void GET(){
        System.out.println("UserController 执行了get方法");
    }

    
    public void NoScanMethod(){
        System.out.println("UserController 执行了NoScanMethod方法");
    }

}

NewsController

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;

/**
 * @author : zanglikun
 * @date : 2021/2/23 12:27
 * @Version: 1.0
 * @Desc : 新闻控制类
 */
@Controller
@RequestMapping("/news")

public class NewsController {

    @RequestMapping("/add")

    public void ADD(){
        System.out.println("NewsController 执行了add方法");
    }

    @RequestMapping("/del")

    public void DEL(){
        System.out.println("NewsController 执行了del方法");
    }

    @RequestMapping("/get")

    public void GET(){
        System.out.println("NewsController 执行了get方法");
    }
}

5、创建一个 Model 包 下的UrlMapping 将来集合 收集 类的信息

代码语言:javascript复制
import java.lang.reflect.Method;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:04
 * @Version: 1.0
 * @Desc : 费劲,没啥好说的
 */
public class UrlMapping {
    private Object obj;
    private Method method;

    public UrlMapping() {
    }

    public UrlMapping(Object obj, Method method) {
        this.obj = obj;
        this.method = method;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    public Method getMethod() {
        return method;
    }

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

6、扫描注解

创建一个 utils 包,ClassUtils 类

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;
import com.zanglikun.framework.model.UrlMapping;
import org.reflections.Reflections;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 13:27
 * @Version: 1.0
 * @Desc : 费劲,没啥好说的
 */
public class ClassUtils {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();

    static {
        try {
            getUrlMappings("com.zanglikun");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    // 私有静态 代码块的方式,保证了 扫描 所有类的 注解、方法 信息 只执行一次
    // 传递 一个 基础的 包名
    private static Map<String, UrlMapping> getUrlMappings(String basePackageName) throws IllegalAccessException, InstantiationException, InvocationTargetException {

        // 创建反射对象,
        Reflections reflections = new Reflections(basePackageName);

        // 获取 被 @Controller 修饰的对象的字节码信息
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);

        // 遍历 这些 类的字节码信息
        for (Class<?> aClass : classes) {
            // 判断 类上有 @RequestMapping 注解的 类
            RequestMapping classAnnotation = aClass.getDeclaredAnnotation(RequestMapping.class);
            // 定义 类上的 注解 值
            String baseUri = "";
            // 判断 是否有这个对象
            if (classAnnotation != null) {
                // 如果有 获取 类的注解名
                String classAnnotationname = classAnnotation.value();
                // baseUri 拼接上类的注解名
                baseUri  = classAnnotationname;
            }

            Object obj = aClass.newInstance();

            // 拿到这些类,我们就获取其中所有的方法信息
            Method[] methods = aClass.getMethods();

            // 遍历 这些方法
            for (Method method : methods) {
                //判断 注解 其中被 @RequestMapping 修饰的方法
                RequestMapping methodAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
                if (methodAnnotation != null) {
                    // 定义 方法上的 注解值
                    String value = methodAnnotation.value();
                    // 获取 最终结果 类名   方法名   RequestMapping的 所有值
                    String requestUri = aClass.getName()   " -- "   method.getName()   " -- "   baseUri   value;
                    map.put(baseUri   value, new UrlMapping(obj, method));
                }
            }
        }
        return map;
    }

    public static UrlMapping getURLMapping(String url) {
        return map.get(url);

    }

    // 测试 调用方法
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
        // 获取 被RequestMapping 从类 修饰到 方法 匹配到 /news/add 类和方法的信息
        UrlMapping urlMapping = ClassUtils.getURLMapping("/news/add");
        Method method = urlMapping.getMethod();
        
        // 调用 获取到的类的信息, 准确的 有效的使用反射 进行调用方法!!!
        method.invoke(urlMapping.getObj());
    }
}

初步测试:

代码语言:javascript复制
NewsController 执行了add方法

7、

引入 Servlet

代码语言:javascript复制
<!-- 这里 封装了 反射的工具,我们一般 使用其中的 Reflections 对象 进行操作 -->
        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.11</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp.jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

8、创建 DispatcherSevlet

让其 继承于 HttpServlet 并重写 doGet、doPost 方法

代码语言:javascript复制
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet 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 {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req,resp);
    }
}

9、配置 Servlet

这里 强调 上述代码,使用的Tomcat 版本 是:apache-tomcat-8.5.31 ,经过 残忍的测试,长了教训,Tomcat 不同版本 ,支持的不同协议,以及实现方式 都发生变化。起码经过我测试,Tomcat10 就会报错

代码语言:javascript复制
jakarta.servlet.ServletException: 类com.zanglikun.framework.servlet.DispatcherServlet不是Servlet

我们 学过 Java Web 都知道 Servlet 配置 有2 中方式 一个是 注解 @Servlet 另一个 是 webapps 下面的 WEB-INF 的 web.xml

代码语言:javascript复制
DispatcherServlet
代码语言:javascript复制
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet 方法 拦截到了:"  req.getRequestURI());
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req,resp);
    }
}

因为 注解 形式,不好控制 他的初始化顺序,建议,使用 web.xml 进行控制。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- XML 按照 顺序 加载,优先加载上面的,下面不会覆盖上面的 -->
    <!-- 在 servlet-mapping 中,当 servlet-name,相同时,url-pattern 必须不同 -->

    <!-- 放行这里 一些前端的文件 为什么这样配置?理由:需要我们到 Tomcat的 源码 conf/web.xml查看一下 一个 叫 DefaultServlet 的 servlet
    从命名 我们就能看出 默认的 servlet  同时 上面注释信息 写了:

    这是个所有应用的默认servlet,他可以提供静态资源(放行),他处理所有 未映射 到其他服务上的映射

    The default servlet for all web applications, that serves static resources.
    It processes all requests that are not mapped to other servlets with servlet mappings
    -->

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.css</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.jpg</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.img</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>


    <!-- 首页 -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.jpg</welcome-file>
    </welcome-file-list>

    <!-- 配置自己的解析器 -->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>com.zanglikun.framework.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <!-- 拦截所有 资源 -->
        <!--  <url-pattern>/*</url-pattern>   -->

        <!-- 不拦截 .jsp 和 Servlet -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

访问 一些资源看看吧

10、配置 404、500 页面

我们需要修改我们的 DispatcherServlet

删除 doget 方法里的 super.doGet(req, resp);

修改 doget 方法立马的内容

最终结果如下:

代码语言:javascript复制
package com.zanglikun.framework.servlet;

import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);


            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            // 执行方法
            method.invoke(obj);

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }

        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

11、模拟一个框架不足点

首先 修改 NewsController 中 add()方法,我们要加入参数了

最终 add() 变为:

代码语言:javascript复制
@RequestMapping("/add")
    public void ADD(HttpServletRequest request, HttpServletResponse response){
        System.out.println("request" request);
        System.out.println("response" response);
        //System.out.println("name" name);
        System.out.println("NewsController 执行了add方法");
    }

我们重启服务器 再去访问 http://localhost:8080/news/add ,看下是什么结果

500 错误,我们去控制台看一下吧

参数数量 异常,这来自与哪里呢?我们联想,反射的时候,需要传递参数的,不能不传递参数调用。

异常 展示完成,请看接下来如何解决

12、解决 req 与 resp 传参问题

我们 去修改 DispatcherServlet 内容 新增 蓝色部分

代码语言:javascript复制
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            // 获取方法上参数类型
            Class<?>[] paramTypes = method.getParameterTypes();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[paramTypes.length];
            // 定义一个 下标
            int index = 0;
            // 遍历一下 获取方法参数
            for (Class<?> parameterType : paramTypes) {
                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if(parameterType == HttpServletRequest.class){
                    argurments[index] = req;
                }
                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                else if(parameterType == HttpServletResponse.class){
                    argurments[index] = resp;
                }else {
                    // 这里 是处理 其他参数的
                    argurments[index] = null;
                }
                // 相当于 for循环 的i
                index  ;
            }
            System.out.println(argurments.length);

            // 执行方法
            method.invoke(obj,argurments);

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

我们去测试 访问

http://localhost:8080/news/add

http://localhost:8080/news/del

控制台正常输出,通过

其实 这里 还有小问题,我们正常处理的是 req 和 resp 对象传递,其他参数,就无法处理。我们留着下面解决

13、根据 方法返回值进行 请求转发 与 重定向

通过方法的返回值,决定是请求转发,还是重定向,请求到那个路径上

在WEB-INF下面创建jsp页面 创建 news_del.jsp

代码语言:javascript复制
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>news_del</title>
</head>
<body>
我是 news_del 请求转发成功
</body>
</html>

修改 NewsController 的 ADD、DEL 方法 新增返回值 最终结果如下

代码语言:javascript复制
    @RequestMapping("/add")
    public String ADD(HttpServletRequest request, HttpServletResponse response){
        System.out.println("request" request);
        System.out.println("response" response);
        //System.out.println("name" name);
        System.out.println("NewsController 执行了add方法");
        return "redirect:/indexA.html";

    }

    @RequestMapping("/del")
    public String DEL(){
        System.out.println("NewsController 执行了del方法");
        return "news_del";
    }

最后还是要修改 DispatcherServlet

代码语言:javascript复制
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            // 获取方法上参数类型
            Class<?>[] paramTypes = method.getParameterTypes();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[paramTypes.length];
            // 定义一个 下标
            int index = 0;
            // 遍历一下 获取方法参数
            for (Class<?> parameterType : paramTypes) {
                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index] = req;
                }
                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                else if (parameterType == HttpServletResponse.class) {
                    argurments[index] = resp;
                } else {
                    // 这里 是处理 其他参数的
                    argurments[index] = null;
                }
                // 相当于 for循环 的i
                index  ;
            }
            System.out.println(argurments.length);
            // 执行方法
            Object result = method.invoke(obj, argurments);
            // 如果方法有返回值
            if (result != null) {
                // 判断是不是 String
                if (result instanceof String) {
                    String viewname = (String) result;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        System.out.println(viewname.replace("redirect:", ""));
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher("/WEB-INF/jsp/"   viewname   ".jsp").forward(req,resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们回头在处理
                else {

                }

            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

14 返回结果是Json : 响应到 浏览器上

引入阿里巴巴的 fastJson

代码语言:javascript复制
<!-- FastJson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.67</version>
        </dependency>

继续 看:DispatcherServlet

代码语言:javascript复制
import com.alibaba.fastjson.JSON;
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            // 获取方法上参数类型
            Class<?>[] paramTypes = method.getParameterTypes();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[paramTypes.length];
            // 定义一个 下标
            int index = 0;
            // 遍历一下 获取方法参数
            for (Class<?> parameterType : paramTypes) {
                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index] = req;
                }
                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                else if (parameterType == HttpServletResponse.class) {
                    argurments[index] = resp;
                } else {
                    // 这里 是处理 其他参数的
                    argurments[index] = null;
                }
                // 相当于 for循环 的i
                index  ;
            }
            System.out.println(argurments.length);
            // 执行方法
            Object modelview = method.invoke(obj, argurments);
            // 如果方法有返回值
            if (modelview != null) {
                // 判断是不是 String
                if (modelview instanceof String) {
                    String viewname = (String) modelview;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        System.out.println(viewname.replace("redirect:", ""));
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher("/WEB-INF/jsp/"   viewname   ".jsp").forward(req, resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们现在处理 :按照Json 进行处理
                // 1、修改 result 更名未 modelview
                else {
                    resp.setContentType("application/json;charset=UTF-8");
                    // modelview 调用 阿里的 fastJson 转为 Json 打印在浏览器上
                    resp.getWriter().write(JSON.toJSONString(modelview).toString());
                }


            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

修改 NewsController 的 Get 方法

代码语言:javascript复制
    @RequestMapping("/get")
    public Map<String,Object> GET(){
        System.out.println("NewsController 执行了get方法");
        Map<String,Object> map = new HashMap<>();
        map.put("姓名","张三");
        map.put("年龄","80");
        map.put("爱好",new String[]{"抽烟","喝酒","烫头"});
        return map;

    }

启动测试:http://localhost:8080/news/get

测试 Json 处理 成功

15、映射其他基本类型参数

首先,原生的JDK 反射包 获取不到参数名称,能获取参数类型

所以 我们 还需要引入 新的java类库 javassist 以获取 参数 名称 方便 将来 参数做映射

时间类型 :只处理 JDK 1.8 新的时间类型,更在的JDK时间类型,不处理。并且,将来框架只支持JDK1.8

代码语言:javascript复制
        <!-- 利用反射获取 方法的参数名称 因为原生JDK 无法获取参数的名称 -->
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version>
        </dependency>

我们 修改 UrlMapping 新增加新的字段

代码语言:javascript复制
private Map<String,Class<?>> parameter;

重写 get set 构造方法

ClassUtils 更变为

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;
import com.zanglikun.framework.model.UrlMapping;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.reflections.Reflections;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 13:27
 * @Version: 1.0
 * @Desc : 费劲,没啥好说的
 */
public class ClassUtils {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();

    static {
        try {
            getUrlMappings("com.zanglikun");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
    }

    // 私有静态 代码块的方式,保证了 扫描 所有类的 注解、方法 信息 只执行一次
    // 传递 一个 基础的 包名
    private static Map<String, UrlMapping> getUrlMappings(String basePackageName) throws IllegalAccessException, InstantiationException, InvocationTargetException, NotFoundException {

        // 创建反射对象,
        Reflections reflections = new Reflections(basePackageName);

        // 获取 被 @Controller 修饰的对象的字节码信息
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);

        // 遍历 这些 类的字节码信息
        for (Class<?> aClass : classes) {
            // 判断 类上有 @RequestMapping 注解的 类
            RequestMapping classAnnotation = aClass.getDeclaredAnnotation(RequestMapping.class);
            // 定义 类上的 注解 值
            String baseUri = "";
            // 判断 是否有这个对象
            if (classAnnotation != null) {
                // 如果有 获取 类的注解名
                String classAnnotationname = classAnnotation.value();
                // baseUri 拼接上类的注解名
                baseUri  = classAnnotationname;
            }

            Object obj = aClass.newInstance();

            // 拿到这些类,我们就获取其中所有的方法信息
            Method[] methods = aClass.getMethods();

            // 遍历 这些方法
            for (Method method : methods) {
                //判断 注解 其中被 @RequestMapping 修饰的方法
                RequestMapping methodAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
                // 如果有被标注的方法
                if (methodAnnotation != null) {
                    // 定义 方法上的 注解值
                    String value = methodAnnotation.value();
                    // 获取 最终结果 类名   方法名   RequestMapping的 所有值
                    String requestUri = aClass.getName()   " -- "   method.getName()   " -- "   baseUri   value;


                    // 获取 新对象信息 即 方法参数 名字
                    ClassPool classPool = ClassPool.getDefault();
                    classPool.insertClassPath(new ClassClassPath(aClass));
                    CtMethod cm = classPool.getMethod(aClass.getName(), method.getName());
                    MethodInfo methodInfo = cm.getMethodInfo();
                    CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
                    LocalVariableAttribute attribute =
                            (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);

                    // 我们需要定义一个集合,收取 参数名称、类型 等信息因为参数有顺序,我们K 防止 参数名称,Value 防止参数类型
                    Map<String,Class<?>> parameters = new LinkedHashMap<>();
                    Class<?>[] classTYPE = method.getParameterTypes();
                    if (attribute != null) {
                        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
                        for (int i = 0; i < method.getParameterCount(); i  ) {
                            String argName = attribute.variableName(i   pos);
                            //System.out.println(argName);
                            parameters.put(argName,classTYPE[i]);
                        }
                    }
                    map.put(baseUri   value, new UrlMapping(obj, method,parameters));

                }
            }
        }
        return map;
    }

    public static UrlMapping getURLMapping(String url) {
        return map.get(url);

    }

    // 测试 调用方法
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
        // 获取 被RequestMapping 从类 修饰到 方法 匹配到 /news/add 类和方法的信息
        UrlMapping urlMapping = ClassUtils.getURLMapping("/news/add");
        Method method = urlMapping.getMethod();

        // 调用 获取到的类的信息, 准确的 有效的使用反射 进行调用方法!!!
        method.invoke(urlMapping.getObj());
    }
}

DispatcherServlet 更变为

代码语言:javascript复制
import com.alibaba.fastjson.JSON;
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            Map<String, Class<?>> parameters = urlMapping.getParameter();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[method.getParameterTypes().length];
            // 定义一个 下标
            int index = 0;

            for (String key : parameters.keySet()) {
                // 请求的参数名称是 key
                // 请求参数 的类型
                Class<?> parameterType = parameters.get(key);

                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index] = req;
                }
                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                else if (parameterType == HttpServletResponse.class) {
                    argurments[index] = resp;
                } else {

                    String parameter = req.getParameter(key);
                    if (parameter != null) {
                        // 这里 是处理 8大基本类型 与String 利用请求参数类型与 方法参数类型进行匹配 成功就赋值
                        try {
                            if (parameterType == java.lang.String.class) {
                                argurments[index] = parameter;
                            } else if (parameterType == java.lang.Integer.class) {
                                argurments[index] = Integer.parseInt(parameter);
                            } else if (parameterType == java.lang.Double.class) {
                                argurments[index] = Double.parseDouble(parameter);
                            } else if (parameterType == java.lang.Float.class) {
                                argurments[index] = Float.parseFloat(parameter);
                            } else if (parameterType == java.lang.Short.class) {
                                argurments[index] = Short.parseShort(parameter);
                            } else if (parameterType == java.lang.Byte.class) {
                                argurments[index] = Byte.parseByte(parameter);
                            } else if (parameterType == java.lang.Long.class) {
                                argurments[index] = Long.parseLong(parameter);
                            } else if (parameterType == java.lang.Character.class) {
                                argurments[index] = parameter.toCharArray()[0];
                            } else if (parameterType == java.lang.Boolean.class) {
                                argurments[index] = Boolean.parseBoolean(parameter);
                            } else if (parameterType == java.time.LocalDate.class) {
                                argurments[index] = LocalDate.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                            } else if (parameterType == java.time.LocalDateTime.class) {
                                argurments[index] = LocalDateTime.parse(parameter,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            }
                            else {
                                // 非 Java 基本类型 和 String 类型 在这处理
                                argurments[index] = null;
                            }
                        } catch (Exception e) {
                            // 这里 可能会出现 类型转换异常
                            e.printStackTrace();
                            argurments[index] = null;
                        }
                    } else {
                        argurments[index] = null;
                    }
                }
                // 相当于 for循环 的i
                index  ;
            }
            // 执行方法
            Object modelview = method.invoke(obj, argurments);
            // 如果方法有返回值
            if (modelview != null) {
                // 判断是不是 String
                if (modelview instanceof String) {
                    String viewname = (String) modelview;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher("/WEB-INF/jsp/"   viewname   ".jsp").forward(req, resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们现在处理 :按照Json 进行处理
                // 1、修改 result 更名未 modelview
                else {
                    resp.setContentType("application/json;charset=UTF-8");
                    // modelview 调用 阿里的 fastJson 转为 Json 打印在浏览器上
                    resp.getWriter().write(JSON.toJSONString(modelview).toString());
                }

            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

测试

新写一个 DispatcherServlet 下的 put方法

代码语言:javascript复制
    @RequestMapping("/put")
    public Map<String,Object> PUT(String string, Boolean bo, Short s, Byte by, Long l, Float f, Double d, Character c, Integer i, LocalDate localDate, LocalDateTime localDateTime){
        System.out.println("NewsController 执行了PUT方法");
        Map<String,Object> map = new HashMap<>();
        map.put("String",string);
        map.put("Boolean",bo);
        map.put("Short",s);
        map.put("Byte",by);
        map.put("Long",l);
        map.put("Float",f);
        map.put("Double",d);
        map.put("Character",c);
        map.put("Integer",i);
        map.put("LocalDate",localDate);
        map.put("LocalDateTime",localDateTime);
        return map;

    }

浏览器发送请求,测试:

http://localhost:8080/news/put?string=你好&bo=true&s=1&by=1&l=88888&f=1.1&d=2.34&c=A&i=1&localDate=2020-01-01&localDateTime=2020-01-01 00:00:01

结果:

所有参数均获取

成功

16、处理 普通对象 类型

引入beanUtils

代码语言:javascript复制
        <!--  -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>

NewsController 新增 HEAD 方法

代码语言:javascript复制
    @RequestMapping("/head")
    public Map<String,Object> HEAD(News news){
        System.out.println("NewsController 执行了head方法");
        Map<String,Object> map = new HashMap<>();
        if(news != null){
            map.put("ID",news.getId());
            map.put("Title",news.getTitle());
            map.put("LocalDate",news.getLocalDate());
            map.put("LocalDateTine",news.getLocalDateTime());
        }
        return map;
    }

在model 创建实体类 News

代码语言:javascript复制
import java.time.LocalDate;
import java.time.LocalDateTime;

/**
 * @author : zanglikun
 * @date : 2021/2/25 15:44
 * @Version: 1.0
 * @Desc : 新闻测试类
 */
public class News {
    private Integer id;
    private String title;
    private LocalDate localDate;
    private LocalDateTime localDateTime;

    public News() {
    }

    public News(Integer id, String title, LocalDate localDate, LocalDateTime localDateTime) {
        this.id = id;
        this.title = title;
        this.localDate = localDate;
        this.localDateTime = localDateTime;
    }

    public LocalDate getLocalDate() {
        return localDate;
    }

    public void setLocalDate(LocalDate localDate) {
        this.localDate = localDate;
    }

    public LocalDateTime getLocalDateTime() {
        return localDateTime;
    }

    public void setLocalDateTime(LocalDateTime localDateTime) {
        this.localDateTime = localDateTime;
    }

    public Integer getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

创建 时间转换工具类 DateTimeConverter

代码语言:javascript复制
import org.apache.commons.beanutils.Converter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author : zanglikun
 * @date : 2021/2/25 16:43
 * @Version: 1.0
 * @Desc : 费劲,没啥好说的
 */
public class DateTimeConverter implements Converter {

    // 重写方法
    @Override
    public Object convert(Class aClass, Object o) {
        if (o == null || "".equals(o)){
            return null;
        }
        if (o instanceof java.lang.String){
            String value = o.toString().trim();
            if(aClass.equals(LocalDate.class)){
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                return LocalDate.parse(value,dateTimeFormatter);
            }
            if(aClass.equals(LocalDateTime.class)){
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                return LocalDateTime.parse(value,dateTimeFormatter);
            }

        }
        return o;
    }
}

新增 DispatcherServlet

代码语言:javascript复制
package com.zanglikun.framework.servlet;

import com.alibaba.fastjson.JSON;
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;
import com.zanglikun.framework.utils.DateTimeConverter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 用户请求的 相对路径
        String replace = req.getRequestURI().replace(req.getContextPath(), "");
        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");
            System.out.println(replace);
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = ClassUtils.getURLMapping(replace);

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());

                // 结束 下面代码执行
                return;
            }
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            Map<String, Class<?>> parameters = urlMapping.getParameter();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[method.getParameterTypes().length];
            // 定义一个 下标
            int index = 0;

            for (String key : parameters.keySet()) {
                // 请求的参数名称是 key
                // 请求参数 的类型
                Class<?> parameterType = parameters.get(key);

                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index] = req;
                }
                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                else if (parameterType == HttpServletResponse.class) {
                    argurments[index] = resp;
                } else {

                    // 用方法名 获取 方法参数 与请求参数进行匹配
                    String parameter = req.getParameter(key);
                    if (parameter != null) {
                        // 这里 是处理 8大基本类型 与String 利用请求参数类型与 方法参数类型进行匹配 成功就赋值
                        try {
                            if (parameterType == java.lang.String.class) {
                                argurments[index] = parameter;
                            } else if (parameterType == java.lang.Integer.class) {
                                argurments[index] = Integer.parseInt(parameter);
                            } else if (parameterType == java.lang.Double.class) {
                                argurments[index] = Double.parseDouble(parameter);
                            } else if (parameterType == java.lang.Float.class) {
                                argurments[index] = Float.parseFloat(parameter);
                            } else if (parameterType == java.lang.Short.class) {
                                argurments[index] = Short.parseShort(parameter);
                            } else if (parameterType == java.lang.Byte.class) {
                                argurments[index] = Byte.parseByte(parameter);
                            } else if (parameterType == java.lang.Long.class) {
                                argurments[index] = Long.parseLong(parameter);
                            } else if (parameterType == java.lang.Character.class) {
                                argurments[index] = parameter.toCharArray()[0];
                            } else if (parameterType == java.lang.Boolean.class) {
                                argurments[index] = Boolean.parseBoolean(parameter);
                            } else if (parameterType == java.time.LocalDate.class) {
                                argurments[index] = LocalDate.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                            } else if (parameterType == java.time.LocalDateTime.class) {
                                argurments[index] = LocalDateTime.parse(parameter,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            }
                            else {
                                argurments[index] = parameter;
                            }
                        } catch (Exception e) {
                            // 这里 可能会出现 类型转换异常
                            e.printStackTrace();
                            argurments[index] = null;
                        }
                    } else {
                        // 处理 Java Bean 对象
                        try {
                            Object paramObj = parameterType.newInstance();
                            BeanUtils.populate(paramObj,req.getParameterMap());
                            argurments[index] = paramObj;

                        }catch (Exception e){
                            e.printStackTrace();
                            argurments[index] = null;
                        }

                    }
                }
                // 相当于 for循环 的i
                index  ;
            }
            // 执行方法
            Object modelview = method.invoke(obj, argurments);
            // 如果方法有返回值
            if (modelview != null) {
                // 判断是不是 String
                if (modelview instanceof String) {
                    String viewname = (String) modelview;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher("/WEB-INF/jsp/"   viewname   ".jsp").forward(req, resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们现在处理 :按照Json 进行处理
                // 1、修改 result 更名未 modelview
                else {
                    resp.setContentType("application/json;charset=UTF-8");
                    // modelview 调用 阿里的 fastJson 转为 Json 打印在浏览器上
                    resp.getWriter().write(JSON.toJSONString(modelview).toString());
                }

            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

ClassUtils 更变为:

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;
import com.zanglikun.framework.model.UrlMapping;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.beanutils.ConvertUtils;
import org.reflections.Reflections;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 13:27
 * @Version: 1.0
 * @Desc : 费劲,没啥好说的
 */
public class ClassUtils {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();

    static {
        try {
            // 注册时间解析
            ConvertUtils.register(new DateTimeConverter(), LocalDate.class);
            ConvertUtils.register(new DateTimeConverter(), LocalDateTime.class);

            getUrlMappings("com.zanglikun");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 私有静态 代码块的方式,保证了 扫描 所有类的 注解、方法 信息 只执行一次
    // 传递 一个 基础的 包名
    private static Map<String, UrlMapping> getUrlMappings(String basePackageName) throws IllegalAccessException, InstantiationException, InvocationTargetException, NotFoundException {

        // 创建反射对象,
        Reflections reflections = new Reflections(basePackageName);

        // 获取 被 @Controller 修饰的对象的字节码信息
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);

        // 遍历 这些 类的字节码信息
        for (Class<?> aClass : classes) {
            // 判断 类上有 @RequestMapping 注解的 类
            RequestMapping classAnnotation = aClass.getDeclaredAnnotation(RequestMapping.class);
            // 定义 类上的 注解 值
            String baseUri = "";
            // 判断 是否有这个对象
            if (classAnnotation != null) {
                // 如果有 获取 类的注解名
                String classAnnotationname = classAnnotation.value();
                // baseUri 拼接上类的注解名
                baseUri  = classAnnotationname;
            }

            Object obj = aClass.newInstance();

            // 拿到这些类,我们就获取其中所有的方法信息
            Method[] methods = aClass.getMethods();

            // 遍历 这些方法
            for (Method method : methods) {
                //判断 注解 其中被 @RequestMapping 修饰的方法
                RequestMapping methodAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
                // 如果有被标注的方法
                if (methodAnnotation != null) {
                    // 定义 方法上的 注解值
                    String value = methodAnnotation.value();
                    // 获取 最终结果 类名   方法名   RequestMapping的 所有值
                    String requestUri = aClass.getName()   " -- "   method.getName()   " -- "   baseUri   value;


                    // 获取 新对象信息 即 方法参数 名字
                    ClassPool classPool = ClassPool.getDefault();
                    classPool.insertClassPath(new ClassClassPath(aClass));
                    CtMethod cm = classPool.getMethod(aClass.getName(), method.getName());
                    MethodInfo methodInfo = cm.getMethodInfo();
                    CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
                    LocalVariableAttribute attribute =
                            (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);

                    // 我们需要定义一个集合,收取 参数名称、类型 等信息因为参数有顺序,我们K 防止 参数名称,Value 防止参数类型
                    Map<String,Class<?>> parameters = new LinkedHashMap<>();
                    Class<?>[] classTYPE = method.getParameterTypes();
                    if (attribute != null) {
                        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
                        for (int i = 0; i < method.getParameterCount(); i  ) {
                            String argName = attribute.variableName(i   pos);
                            //System.out.println(argName);
                            parameters.put(argName,classTYPE[i]);
                        }
                    }
                    map.put(baseUri   value, new UrlMapping(obj, method,parameters));
                }
            }
        }
        return map;
    }

    public static UrlMapping getURLMapping(String url) {
        return map.get(url);

    }

    // 测试 调用方法
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
        // 获取 被RequestMapping 从类 修饰到 方法 匹配到 /news/add 类和方法的信息
        UrlMapping urlMapping = ClassUtils.getURLMapping("/news/add");
        Method method = urlMapping.getMethod();

        // 调用 获取到的类的信息, 准确的 有效的使用反射 进行调用方法!!!
        method.invoke(urlMapping.getObj());
    }
}

测试

http://localhost:8080/news/head?id=1&title=张三&localDate=2020-01-01&localDateTime=2020-01-01 01:01:01

结果:

成功

17、代码优化

因为 serlvet 只有在访问的时候 ClassUtils才会初始化。故 ClassUtils 需要提前加载

重写 DispatcherServlet 重写的 init(ServletConfig config) 方法

同时 我们提升DispatcherServlet加载时间,让其随着类加载而加载 ,

记录能处理的类型,不能处理 统一以 Java类型处理

修改 ClassUtils的代码,删除很多内容

代码语言:javascript复制
import com.zanglikun.framework.annoation.Controller;
import com.zanglikun.framework.annoation.RequestMapping;
import com.zanglikun.framework.model.UrlMapping;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.reflections.Reflections;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 13:27
 * @Version: 1.0
 * @Desc : ClassUtils 扫描我们自定义注解的类 和 方法
 */
public class ClassUtils {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();
    
    
    /** 此方法 用于扫描被 Controller 和 RequestMapping 类和方法的 集合,将来可以用来判断 */
    public static Map<String, UrlMapping> getUrlMappings(String basePackageName) throws IllegalAccessException, InstantiationException, NotFoundException {

        // 创建反射对象,
        Reflections reflections = new Reflections(basePackageName);

        // 获取 被 @Controller 修饰的对象的字节码信息
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);

        // 遍历 这些 类的字节码信息
        for (Class<?> aClass : classes) {
            // 判断 类上有 @RequestMapping 注解的 类
            RequestMapping classAnnotation = aClass.getDeclaredAnnotation(RequestMapping.class);
            // 定义 类上的 注解 值
            String baseUri = "";
            // 判断 是否有这个对象
            if (classAnnotation != null) {
                // 如果有 获取 类的注解名
                String classAnnotationname = classAnnotation.value();
                // baseUri 拼接上类的注解名
                baseUri  = classAnnotationname;
            }
            Object obj = aClass.newInstance();

            // 拿到这些类,我们就获取其中所有的方法信息
            Method[] methods = aClass.getMethods();

            // 遍历 这些方法
            for (Method method : methods) {
                //判断 注解 其中被 @RequestMapping 修饰的方法
                RequestMapping methodAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
                // 如果有被标注的方法
                if (methodAnnotation != null) {
                    // 定义 方法上的 注解值
                    String value = methodAnnotation.value();
                    // 获取 新对象信息 即 方法参数 名字
                    ClassPool classPool = ClassPool.getDefault();
                    classPool.insertClassPath(new ClassClassPath(aClass));
                    CtMethod cm = classPool.getMethod(aClass.getName(), method.getName());
                    MethodInfo methodInfo = cm.getMethodInfo();
                    CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
                    LocalVariableAttribute attribute =
                            (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);

                    // 我们需要定义一个集合,收取 参数名称、类型 等信息因为参数有顺序,我们K 防止 参数名称,Value 防止参数类型
                    Map<String,Class<?>> parameters = new LinkedHashMap<>();
                    Class<?>[] classTYPE = method.getParameterTypes();
                    if (attribute != null) {
                        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
                        for (int i = 0; i < method.getParameterCount(); i  ) {
                            String argName = attribute.variableName(i   pos);
                            parameters.put(argName,classTYPE[i]);
                        }
                    }
                    map.put(baseUri   value, new UrlMapping(obj, method,parameters));
                }
            }
        }
        return map;
    }

}

去修改 web.xml的内容

作用是 让DispatcherSerlvet 随着类加载而加载

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- XML 按照 顺序 加载,优先加载上面的,下面不会覆盖上面的 -->
    <!-- 在 servlet-mapping 中,当 servlet-name,相同时,url-pattern 必须不同 -->

    <!-- 放行这里 一些前端的文件 为什么这样配置?理由:需要我们到 Tomcat的 源码 conf/web.xml查看一下 一个 叫 DefaultServlet 的 servlet
    从命名 我们就能看出 默认的 servlet  同时 上面注释信息 写了:

    这是个所有应用的默认servlet,他可以提供静态资源(放行),他处理所有 未映射 到其他服务上的映射

    The default servlet for all web applications, that serves static resources.
    It processes all requests that are not mapped to other servlets with servlet mappings
    -->

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.css</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.jpg</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.img</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.png</url-pattern>
    </servlet-mapping>


    <!-- 首页 -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.jpg</welcome-file>
    </welcome-file-list>

    <!-- 配置自己的解析器 -->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>com.zanglikun.framework.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>prefix</param-name>
            <param-value>/WEB-INF/jsp</param-value>
        </init-param>
        <init-param>
            <param-name>suffix</param-name>
            <param-value>.jsp</param-value>
        </init-param>
        <init-param>
            <param-name>basePackage</param-name>
            <param-value>com.zanglikun</param-value>
        </init-param>
        <!-- 设为1 让其 随着类加载而加载 -->
        <load-on-startup>1</load-on-startup>

    </servlet>

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <!-- 拦截所有 资源 -->
        <!--  <url-pattern>/*</url-pattern>   -->

        <!-- 不拦截 .jsp 和 Servlet -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

DispatcherServlet 变化较大 定义了一些参数值 更多的修复 :减少循环次数 与 if 条件判断

代码语言:javascript复制
import com.alibaba.fastjson.JSON;
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;
import com.zanglikun.framework.utils.DateTimeConverter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();

    private static final String REQUEST_PREFIX = "prefix";  // 请求前缀
    private static final String REQUEST_SUFFIX = "suffix";   // 请求后缀
    private static final String BASKPACKAGE = "basePackage";    //controller 的包路径

    private String prefix = "/WEB-INF/jsp";
    private String suffix = ".jsp";
    private String basePackage = "com.zanglikun";

    // 可以处理的Java类型,作用是 如果不可以处理,就让Java对象处理
    Set<Class> doCopy = new HashSet<>();


    @Override
    public void init(ServletConfig config) {
        // 注册时间解析
        ConvertUtils.register(new DateTimeConverter(), LocalDate.class);
        ConvertUtils.register(new DateTimeConverter(), LocalDateTime.class);

        try {
            // 如果初始化 配置了值,就会使用web.xml 的信息
            if (config.getInitParameter(REQUEST_PREFIX) != null) {
                prefix = config.getInitParameter(REQUEST_PREFIX);
            }
            if (config.getInitParameter(REQUEST_SUFFIX) != null) {
                suffix = config.getInitParameter(REQUEST_SUFFIX);
            }
            if (config.getInitParameter(BASKPACKAGE) != null) {
                basePackage = config.getInitParameter(BASKPACKAGE);
            }
            map = ClassUtils.getUrlMappings(basePackage);

            // 记录 能处理的类型,作用是 如果方法参数类型 不是记录的值,就以Java 对象处理
            doCopy.add(java.lang.String.class);
            doCopy.add(java.lang.Integer.class);
            doCopy.add(java.lang.Long.class);
            doCopy.add(java.lang.Double.class);
            doCopy.add(java.lang.Float.class);
            doCopy.add(java.lang.Short.class);
            doCopy.add(java.lang.Byte.class);
            doCopy.add(java.lang.Boolean.class);
            doCopy.add(java.lang.Character.class);
            doCopy.add(java.time.LocalDateTime.class);
            doCopy.add(java.time.LocalDate.class);


        } catch (Exception e) {

        }

    }


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

        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");

            /** 处理请求路径 */
            String requestURI = req.getRequestURI();
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = map.get(requestURI.replace(req.getContextPath(), ""));

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());
                // 结束 下面代码执行
                return;
            }

            /** 处理请求参数 */
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            Map<String, Class<?>> parameters = urlMapping.getParameter();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[method.getParameterTypes().length];
            // 定义一个 下标
            int index = 0;

            for (String key : parameters.keySet()) {
                // 请求的参数名称是 key

                // 获取方法参数的Java类型
                Class<?> parameterType = parameters.get(key);

                // 请求参数值 通过request 获取与 方法参数名称匹配的 参数值
                String parameter = req.getParameter(key);

                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index  ] = req;
                    continue;
                }

                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                if (parameterType == HttpServletResponse.class) {
                    argurments[index  ] = resp;
                    continue;
                }

                if (parameter != null) {
                    // 这里 是处理 8大基本类型 与String 利用请求参数类型与 方法参数类型进行匹配 成功就赋值
                    try {

                        if (parameterType == java.lang.String.class) {
                            argurments[index  ] = parameter;
                            continue;
                        }

                        /** 基本数据类型转换开始 */
                        if (parameterType == java.lang.Integer.class) {
                            argurments[index  ] = Integer.parseInt(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Double.class) {
                            argurments[index  ] = Double.parseDouble(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Float.class) {
                            argurments[index  ] = Float.parseFloat(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Short.class) {
                            argurments[index  ] = Short.parseShort(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Byte.class) {
                            argurments[index  ] = Byte.parseByte(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Long.class) {
                            argurments[index  ] = Long.parseLong(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Character.class) {
                            argurments[index  ] = parameter.toCharArray()[0];
                            continue;
                        }
                        if (parameterType == java.lang.Boolean.class) {
                            argurments[index  ] = Boolean.parseBoolean(parameter);
                            continue;
                        }
                        // 基本数据类型转换结束

                        /** 日期类型转换开始 */
                        if (parameterType == java.time.LocalDate.class) {
                            argurments[index  ] = LocalDate.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                            continue;
                        }
                        if (parameterType == java.time.LocalDateTime.class) {
                            argurments[index  ] = LocalDateTime.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            continue;
                        }
                        /** 日期类型转换结束 */

                        // 处理不了的参数 暂时以原来的值处理
                        else {
                            argurments[index  ] = parameter;
                            continue;
                        }
                    } catch (Exception e) {
                        // 这里 可能会出现 类型转换异常
                        e.printStackTrace();
                        argurments[index  ] = null;
                        continue;
                    }
                } else {
                    // 如果不是 我们记录的能处理的类型 那就以Java对象转化
                    if (!doCopy.contains(parameterType)) {
                        /** Java 对象 转化开始 */
                        try {
                            Object paramObj = parameterType.newInstance();
                            BeanUtils.populate(paramObj, req.getParameterMap());
                            argurments[index  ] = paramObj;
                            continue;

                        } catch (Exception e) {
                            e.printStackTrace();
                            argurments[index  ] = null;
                            continue;
                        }
                        /** Java 对象 转化结束 */
                    }
                }

            }

            // 执行方法
            Object modelview = method.invoke(obj, argurments);

            /**
            处理方法的返回值
            */
            if (modelview != null) {
                // 判断是不是 String
                if (modelview instanceof String) {
                    String viewname = (String) modelview;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher(prefix   "/"   viewname   suffix).forward(req, resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们现在处理 :按照Json 进行处理
                // 1、修改 result 更名未 modelview
                else {
                    resp.setContentType("application/json;charset=UTF-8");
                    // modelview 调用 阿里的 fastJson 转为 Json 打印在浏览器上
                    resp.getWriter().write(JSON.toJSONString(modelview).toString());
                }

            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

优化完成

19、数组处理

DispatcherServlet 变更为:

代码语言:javascript复制
import com.alibaba.fastjson.JSON;
import com.zanglikun.framework.model.UrlMapping;
import com.zanglikun.framework.utils.ClassUtils;
import com.zanglikun.framework.utils.DateTimeConverter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author : zanglikun
 * @date : 2021/2/23 14:46
 * @Version: 1.0
 * @Desc : 配置 DispatcherServlet
 */

public class DispatcherServlet extends HttpServlet {

    // 创建 收集数据的对象
    private static Map<String, UrlMapping> map = new HashMap<>();

    private static final String REQUEST_PREFIX = "prefix";  // 请求前缀
    private static final String REQUEST_SUFFIX = "suffix";   // 请求后缀
    private static final String BASKPACKAGE = "basePackage";    //controller 的包路径

    private String prefix = "/WEB-INF/jsp";
    private String suffix = ".jsp";
    private String basePackage = "com.zanglikun";

    // 可以处理的Java类型,作用是 如果不可以处理,就让Java对象处理
    Set<Class> doCopy = new HashSet<>();


    @Override
    public void init(ServletConfig config) {
        // 注册时间解析
        ConvertUtils.register(new DateTimeConverter(), LocalDate.class);
        ConvertUtils.register(new DateTimeConverter(), LocalDateTime.class);

        try {
            // 如果初始化 配置了值,就会使用web.xml 的信息
            if (config.getInitParameter(REQUEST_PREFIX) != null) {
                prefix = config.getInitParameter(REQUEST_PREFIX);
            }
            if (config.getInitParameter(REQUEST_SUFFIX) != null) {
                suffix = config.getInitParameter(REQUEST_SUFFIX);
            }
            if (config.getInitParameter(BASKPACKAGE) != null) {
                basePackage = config.getInitParameter(BASKPACKAGE);
            }
            map = ClassUtils.getUrlMappings(basePackage);

            // 记录 能处理的类型,作用是 如果方法参数类型 不是记录的值,就以Java 对象处理
            doCopy.add(java.lang.String.class);
            doCopy.add(java.lang.Integer.class);
            doCopy.add(java.lang.Long.class);
            doCopy.add(java.lang.Double.class);
            doCopy.add(java.lang.Float.class);
            doCopy.add(java.lang.Short.class);
            doCopy.add(java.lang.Byte.class);
            doCopy.add(java.lang.Boolean.class);
            doCopy.add(java.lang.Character.class);
            doCopy.add(java.time.LocalDateTime.class);
            doCopy.add(java.time.LocalDate.class);


        } catch (Exception e) {

        }

    }

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

        try {
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html;charset=UTF-8");

            /** 处理请求路径 */
            String requestURI = req.getRequestURI();
            // 调用我们的 工具类 通过 请求的相对路径 获取 需要执行操作 类的信息
            UrlMapping urlMapping = map.get(requestURI.replace(req.getContextPath(), ""));

            // 人为 测试 制作服务端错误 500
            //int i = 1/0;

            // 如果 请求路径不存在
            if (urlMapping == null) {
                resp.setStatus(404);
                resp.getWriter().write("请求路径不存在:<h1>404</h1> 请求路径是"   req.getRequestURI());
                // 结束 下面代码执行
                return;
            }

            /** 处理请求参数 */
            Method method = urlMapping.getMethod();
            Object obj = urlMapping.getObj();
            Map<String, Class<?>> parameters = urlMapping.getParameter();
            // 创建 数组 将来放参数的地方
            Object[] argurments = new Object[method.getParameterTypes().length];
            // 定义一个 下标
            int index = 0;

            for (String key : parameters.keySet()) {
                // 请求的参数名称是 key

                // 获取方法参数的Java类型
                Class<?> parameterType = parameters.get(key);

                // 请求参数值 通过request 获取与 方法参数名称匹配的 参数值
                String parameter = req.getParameter(key);

                // 请求参数是数组形式的 请求参数值
                String[] parameterValues = req.getParameterValues(key);

                // 获取数组的长度
                int shuzulength = 0;
                if(parameterValues != null){
                    shuzulength = parameterValues.length;
                }
                // 请求参数如果是数组形式 获取数组数据类型
                Class<?> shuzuType = parameterType.getComponentType();



                // 如果是 HttpServletRequest 对象 就放进去 doGet的 req
                if (parameterType == HttpServletRequest.class) {
                    argurments[index  ] = req;
                    continue;
                }

                // 如果是 HttpServletResponse 对象 就放进去 doGet的 resp
                if (parameterType == HttpServletResponse.class) {
                    argurments[index  ] = resp;
                    continue;
                }

                if (parameter != null) {
                    // 这里 是处理 8大基本类型 与String 利用请求参数类型与 方法参数类型进行匹配 成功就赋值
                    try {

                        if (parameterType == java.lang.String.class) {
                            argurments[index  ] = parameter;
                            continue;
                        }

                        /** 基本数据类型转换开始 */
                        if (parameterType == java.lang.Integer.class) {
                            argurments[index  ] = Integer.parseInt(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Double.class) {
                            argurments[index  ] = Double.parseDouble(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Float.class) {
                            argurments[index  ] = Float.parseFloat(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Short.class) {
                            argurments[index  ] = Short.parseShort(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Byte.class) {
                            argurments[index  ] = Byte.parseByte(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Long.class) {
                            argurments[index  ] = Long.parseLong(parameter);
                            continue;
                        }
                        if (parameterType == java.lang.Character.class) {
                            argurments[index  ] = parameter.toCharArray()[0];
                            continue;
                        }
                        if (parameterType == java.lang.Boolean.class) {
                            argurments[index  ] = Boolean.parseBoolean(parameter);
                            continue;
                        }
                        // 基本数据类型转换结束

                        /** 日期类型转换开始 */
                        if (parameterType == java.time.LocalDate.class) {
                            argurments[index  ] = LocalDate.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                            continue;
                        }
                        if (parameterType == java.time.LocalDateTime.class) {
                            argurments[index  ] = LocalDateTime.parse(parameter, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            continue;
                        }
                        /** 日期类型转换结束 */

                        /** 处理数组 开始*/
                        if (parameterValues != null && shuzuType != null && shuzulength >0){
                            if (shuzuType == java.lang.String.class) {
                                String[] str = new String[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    str[i] = parameterValues[i];
                                }
                                argurments[index  ] = str;
                                continue;
                            }
                            if (shuzuType == java.lang.Integer.class) {
                                Integer[] arr = new Integer[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    arr[i] = Integer.valueOf(parameterValues[i]);
                                }
                                argurments[index  ] = arr;
                                continue;
                            }
                            if (shuzuType == java.lang.Double.class) {
                                Double[] dob = new Double[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    dob[i] = Double.parseDouble(parameterValues[i]);
                                }
                                argurments[index  ] = dob;
                                continue;
                            }
                            if (shuzuType == java.lang.Float.class) {
                                Float[] flo = new Float[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    flo[i] = Float.parseFloat(parameterValues[i]);
                                }
                                argurments[index  ] = flo;
                                continue;
                            }
                            if (shuzuType == java.lang.Short.class) {
                                Short[] sho = new Short[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    sho[i] = Short.valueOf(parameterValues[i]);
                                }
                                argurments[index  ] = sho;
                                continue;
                            }
                            if (shuzuType == java.lang.Byte.class) {
                                Byte[] byt = new Byte[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    byt[i] = Byte.valueOf(parameterValues[i]);
                                }
                                argurments[index  ] = byt;
                                continue;
                            }
                            if (shuzuType == java.lang.Long.class) {
                                Long[] lon = new Long[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    lon[i] = Long.valueOf(parameterValues[i]);
                                }
                                argurments[index  ] = lon;
                                continue;
                            }
                            if (shuzuType == java.lang.Character.class) {
                                Character[] cha = new Character[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    cha[i] = parameterValues[i].toCharArray()[0];
                                }
                                argurments[index  ] = cha;
                                continue;
                            }
                            if (shuzuType == java.lang.Boolean.class) {
                                Boolean[] bol = new Boolean[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    bol[i] = Boolean.valueOf(parameterValues[i]);
                                }
                                argurments[index  ] = bol;
                                continue;
                            }
                            if (shuzuType == java.time.LocalDate.class) {
                                LocalDate[] lol = new LocalDate[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    lol[i] = LocalDate.parse(parameterValues[i], DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                                }
                                argurments[index  ] = lol;
                                continue;
                            }
                            if (shuzuType == java.time.LocalDateTime.class) {
                                LocalDateTime[] lot = new LocalDateTime[shuzulength];
                                for (int i = 0; i < shuzulength; i  ) {
                                    lot[i] = LocalDateTime.parse(parameterValues[i],DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                                }
                                argurments[index  ] = lot;
                                continue;
                            }
                        }
                        // 处理不了的参数 暂时以原来的值处理
                        else {
                            argurments[index  ] = parameter;
                            continue;
                        }
                    } catch (Exception e) {
                        // 这里 可能会出现 类型转换异常
                        e.printStackTrace();
                        argurments[index  ] = null;
                        continue;
                    }
                    /** 处理数组结束 */

                }
                else {
                    // 如果不是 我们记录的能处理的类型 那就以Java对象转化
                    if (!doCopy.contains(parameterType) && shuzuType ==null) {
                        /** Java 对象 转化开始 */
                        try {
                            Object paramObj = parameterType.newInstance();
                            BeanUtils.populate(paramObj, req.getParameterMap());
                            argurments[index  ] = paramObj;
                            continue;

                        } catch (Exception e) {
                            e.printStackTrace();
                            argurments[index  ] = null;
                            continue;
                        }
                        /** Java 对象 转化结束 */
                    }
                }
            }
            for (int i = 0; i < argurments.length; i  ) {
                System.out.println(argurments[i]);
            }
            // 执行方法
            Object modelview = method.invoke(obj, argurments);

            /**
            处理方法的返回值
            */
            if (modelview != null) {
                // 判断是不是 String
                if (modelview instanceof String) {
                    String viewname = (String) modelview;
                    // 如果是重定向,走 重定向
                    if (viewname.startsWith("redirect:")) {
                        // 重定向 路径拼接 并发送重定向
                        // 这里有个 小Bug 没有 处理Https 协议,以及内外部资源 我们为了更好的处理,我们直接默认http 以及内部资源
                        resp.sendRedirect(viewname.replace("redirect:", ""));
                    }
                    // 如果 是重定向 那就走 重定向
                    else {
                        // 确保你的文件,在/WEB-INF/ 下 而不是 webapp 下 切记 我调试了 1个多小时,跳转不了,最后发现路径错了
                        req.getRequestDispatcher(prefix   "/"   viewname   suffix).forward(req, resp);
                    }
                }
                // 如果 返回 不是 String 类型的 我们现在处理 :按照Json 进行处理
                // 1、修改 result 更名未 modelview
                else {
                    resp.setContentType("application/json;charset=UTF-8");
                    // modelview 调用 阿里的 fastJson 转为 Json 打印在浏览器上
                    resp.getWriter().write(JSON.toJSONString(modelview).toString());
                }

            }

        } catch (Exception e) {

            // 向浏览器展示 消息 利用 HttpServletResponse 的枚举 SC_INTERNAL_SERVER_ERROR 其含义 代表 500
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务端 发生错误,请联系系统管理员 Tel :110 ");

            /*
             sendError 与  getWriter 同时 出现,会执行 sendError() 方法
                resp.setStatus(500);
                resp.getWriter().println("兄弟 500 错误了 <br>");
                e.printStackTrace(resp.getWriter());
            */
            // 控制台打印信息
            e.printStackTrace();

        }
        //super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 配置 所有请求都走 doGet 方法
        // super.doPost(req, resp);
        doGet(req, resp);
    }
}

NewsController 新增 post 方法

代码语言:javascript复制
    @RequestMapping("/post")
    public Map<String,Object> post(String[] string, Boolean[] bo, Short[] s, Byte[] by, Long[] l, Float[] f, Double[] d, Character[] c, Integer[] i, LocalDate[] localDate, LocalDateTime[] localDateTime){
        System.out.println("NewsController 执行了post方法");
        Map<String,Object> map = new HashMap<>();
        map.put("String",string);
        map.put("Boolean",bo);
        map.put("Short",s);
        map.put("Byte",by);
        map.put("Long",l);
        map.put("Float",f);
        map.put("Double",d);
        map.put("Character",c);
        map.put("Integer",i);
        map.put("LocalDate",localDate);
        map.put("LocalDateTime",localDateTime);
        return map;
    }

请求:http://localhost:8080/news/post?string=你好&bo=true&s=1&by=1&l=88888&f=1.1&d=2.34&c=A&i=1&localDate=2020-01-01&localDateTime=2020-01-01 00:00:01&string=滴滴&bo=false&s=2&by=2&l=66666&f=2.3&d=5.67&c=B&i=2&localDate=2020-02-02&localDateTime=2020-02-02 00:00:02

结果如下

成功

结果

我们目前的项目:可有处理String、8大基本数据类型、LocalDate、LocalDateTime 、JavaBean、数组 等等

源代码

源代码:下载

请配合 Tomcat8 使用

特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!

0 人点赞