造一个方形的轮子7--Controller支持(下)

造一个方形轮子文章目录:造一个方形的轮子

01、添加DispatcherServlet

接上一篇《造一个方形的轮子6--Controller支持(上)》
接下来添加处理HTTP请求最核心的类DispatcherServlet,JAVA里提供的最原始的支持WEB的标准就是Servlet规范,类似Tomcat、Jetty都实现了Servlet规范,所以添加一个DispatcherServlet类配置到Tomcat容器中,接管所以路径的请求,在统一处理,就可以实现我们的目的,整理一下DispatcherServlet类的处理流程:

1、获取HTTP请求类型、ContextPath及RequestURI

2、使用HTTP请求类型:请求路径到Beans容器中获取对应的ControllerObject对象

3、如果没有对应的方法映射则返回404

4、有对应的方法,则根据参数列表从request中获取对应的参数

5、使用反射方法执行对应的方法并获取返回结果

6、如果返回结果是String类型直接返回,其它类型使用JSON序列化后输出

以下是DispatcherServlet.java代码:

package com.jisuye.core;
// import ...
/**
 * 统一请求Servlet处理
 * @author ixx
 * @date 2019-07-14
 */
public class DispatcherServlet extends HttpServlet {
    private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);
    private BeansMap beansMap = new BeansMap();
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 解析url
        String contextPath = req.getContextPath();
        String httpMethod = req.getMethod();
        String uri = req.getRequestURI();
        // 匹配到对应的controller
        String controllerKey = httpMethod.toLowerCase()+":"+uri.replace(contextPath, "");
        ControllerObject controllerObject = beansMap.getController(controllerKey);
        // 如果没有匹配,返回404
        if(controllerObject == null){
            resp.sendError(404);
        } else {
            // 执行对应方法
            Object obj = controllerObject.invoke(req);
            // 处理返回结果
            String json;
            if (obj instanceof String) {
                json = (String) obj;
            } else {
                json = JSON.toJSONString(obj);
                resp.setHeader("content-type", "application/json;charset=UTF-8");
            }
            log.info("http request path:" + controllerKey);
            log.info("exec method :" + controllerObject.getMethod().getName());
            log.info("response:" + json);
            resp.getWriter().print(json);
        }
    }
}

对应的修改ControllerObject类,添加如下方法:

/**
 * controller对象
 * @author ixx
 * @date 2019-07-14
 */
public class ControllerObject {
    ......
    /**
     * 反射执行controller方法
     * @param req
     * @return
     */
    public Object invoke(HttpServletRequest req){
        Object[] os = new Object[params.length];
        int i = 0;
        for (SquareParam param : params) {
            // 如果是String类型则当然参数名从req中取值 否则 当做类 去反射生成
            if(param.isParam()){
                os[i++] = toBasicDataType(req.getParameter(param.getParamName()), param.getClazz());
            } else {
                String body = getBody(req);
                Object tmp = JSON.toJavaObject(JSONObject.parseObject(body), param.getClazz());
                os[i++] = tmp;
            }
        }
        try {
            Object o = this.getMethod().invoke(this.getObject(), os);
            return o;
        } catch (Exception e) {
            log.error("Controller method.invoke() is error!", e);
            throw new SquareException("Controller method.invoke() is error!", e);
        }
    }

    private Object toBasicDataType(Object obj, Class clazz){
        if(obj == null || clazz == null){
            return obj;
        }
        switch (clazz.getName()){
            case "int":
            case "java.lang.Integer" : obj = Integer.parseInt(obj.toString()); break;
            case "long":
            case "java.lang.Long" : obj = Long.parseLong(obj.toString()); break;
            case "double":
            case "java.lang.Double" : obj = Double.parseDouble(obj.toString()); break;
            case "float":
            case "java.lang.Float" : obj = Float.parseFloat(obj.toString()); break;
            case "boolean":
            case "java.lang.Boolean" : obj = Boolean.parseBoolean(obj.toString()); break;
            case "char":
            case "java.lang.Character" : obj = obj.toString().charAt(0); break;
            case "byte":
            case "java.lang.Byte" : obj = Byte.parseByte(obj.toString()); break;
            default: break;
        }
        return obj;
    }
    
    public String getBody(HttpServletRequest req){
        String body = "";
        try {
            BufferedReader br = req.getReader();
            String tmp;
            while ((tmp = br.readLine()) != null){
                body += tmp;
            }
        } catch (IOException e) {
            log.error("getBody data error!", e);
            throw new SquareException("Controller parameter getBody data error!", e);
        }
        return body;
    }
    ......
}

02、配置DispatcherServlet

到目前为止基础的代码基本写完了,现在把DispatcherServlet配置到Tomcat中,修改SquareApplication.run方法:

public class SquareApplication {
    // ......
    public static void run(Class clzz, String[] args) {
       ......
            tomcat = new Tomcat();
            // 设置Tomcat工作目录
            tomcat.setBaseDir(classesPathUtil.getProjectPath() + "/Tomcat");
            tomcat.setPort(TOMCAT_PORT);
            Context context = tomcat.addWebapp(CONTEXT_PATH, classesPathUtil.getPublicPath());
            // 添加DsipatcherServlet
            Wrapper wrapper = Tomcat.addServlet(context, "DispatcherServlet", new DispatcherServlet());
            wrapper.addMapping("/");
            ......
            tomcat.start();
            ......
    }
}

03、添加测试类

现在添加测试类,测试一下Controller。

这里添加一个TestController,有三个方法分别测试Get、Post、Delete方法(Put方法跟Post相同就不做单独测试),参数覆盖@RequestParam和@RequestBody 两种,再结合JdbcTemplate测试一下数据库操作。

TestController.java:

package com.jisuye.service;
// import ...
@Controller("/test")
public class TestController {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @GetMapping("/hello")
    public List<AbcEntity> test(@RequestParam("name") String name, @RequestParam("a") String age){
        List<AbcEntity> list = jdbcTemplate.select("select * from abc where name=?", AbcEntity.class, name);
        return list;
    }

    @DeleteMapping
    public String testDel(@RequestParam("id") int id){
        int i = jdbcTemplate.delete("delete from abc where id=?", id);
        return "delete id : "+id+" is success";
    }

    @PostMapping("/post")
    public ResponseVo testPost(@RequestParam("id") int id, @RequestBody TestVo vo){
        ResponseVo responseVo = new ResponseVo();
        responseVo.setResId(id*10);
        responseVo.setResAge(vo.getAge()*2);
        responseVo.setResName(vo.getName());
        return responseVo;
    }
}

TestVo.java:

package com.jisuye.service;
public class TestVo {
    private String name;
    private int age;
    // getter and setter ...
}

ResponseVo.java:

package com.jisuye.service;
public class ResponseVo {
    private int resId;
    private String resName;
    private int resAge;
    // getter and setter ...
}

04、测试结果

启动程序,使用Postman测试。

测试Get请求

GET http://localhost:8888/abc/test/hello?name=ixx&a=23

响应结果:

[
    {
        "id": 2,
        "name": "ixx"
    },
    {
        "id": 3,
        "name": "ixx"
    },
    {
        "id": 4,
        "name": "ixx"
    }
]

测试Post请求

POST http://localhost:8888/abc/test/post?id=3

body:

{
    "name":"ixx",
    "age":18
}

响应结果:

{
    "resAge": 36,
    "resId": 30,
    "resName": "ixx"
}

测试Delete请求

DELETE http://localhost:8888/abc/test?id=2

响应结果:

delete id : 2 is success

测试404请求

测试一下如果路径不存在的情况:

GET http://localhost:8888/abc/test/hello4

响应结果:

<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title><style type="text/css">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Description</b> The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.</p><hr class="line" /><h3>Apache Tomcat/9.0.17</h3></body></html>

可以看到,返回了404的错误页面。

05、遗留问题

Controller最最基本的功能实现了,但还有很多问题没有处理,比如程序异常的处理(返回500)、form表单参数、文件上传,参数默认值等,下一篇有可能挑一部分解决一下吧...

本篇代码地址: https://github.com/iuv/square/tree/square6

本文作者: ixx
本文链接: http://jianpage.com/2019/07/17/square7
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

推荐阅读更多精彩内容