造一个方形的轮子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 国际许可协议 进行许可。转载请注明出处!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容