摘要

Spring-Web的好处是,快速上手、快速成型,且成熟稳定无Bug。

但对于个人而言,这套框架太重了。由此探寻更好的轻量Web框架Jooby!

正文

本文源码地址meethigher/jooby-example: 基于Netty的轻量级Web框架Jooby使用示例

一、搭建项目

创建原生maven空项目,引入依赖

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-netty</artifactId>
    <version>2.16.1</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
</dependency>

创建启动类

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import io.jooby.Jooby;

public class Application extends Jooby {

    //构造函数快
    {
        get("/", ctx -> "Hello World");
    }

    public static void main(String[] args) {
        runApp(args, Application.class);
    }
}

image-20230617184811680.png

二、配置文件

Jooby同样支持解析多种类型的配置文件,只记录常用的application.conf

Jooby读取配置文件优先级顺序

  1. 优先应用根目录下。即System.getProperty("user.dir")
  2. 其次类路径下,即resources下或者jar包内的。

resources添加application.conf

conf
server.port = 8080
app.name = "内部"
database.url = "jdbc:mysql://localhost:3306/mydb"

示例接口

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import io.jooby.Jooby;
import io.jooby.MediaType;

import java.nio.charset.StandardCharsets;

public class Application extends Jooby {

    //构造函数快
    {
        // use Decorator.
        decorator(next -> ctx -> {
            // get response content-type.
            final MediaType responseType = ctx.getResponseType();
            // if content-type is text-specific
            if(responseType.isTextual())
                // override response type with current media type and always use UTF-8 charset!
                ctx.setResponseType(responseType, StandardCharsets.UTF_8);
            // pipe next response procedure.
            return next.apply(ctx);
        });
        get("/", ctx -> {

            String appName = getConfig().getString("app.name");
            return "Welcome to " + appName;
        });

        get("/database", ctx -> {
            String dbUrl = getConfig().getString("database.url");
            return "Database URL: " + dbUrl;
        });
    }

    public static void main(String[] args) {
        runApp(args, Application.class);
    }
}

image-20230617191248881.png

三、开发接口

3.1 Script API-仅支持简单路由

Script API 通常用于编写小型的、单一用途的路由处理器。

支持占位符路由规则,但是像拼参形式的就不支持。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import static io.jooby.Jooby.runApp;

public class ScriptAPIApplication {

    public static void main(String[] args) {
        //consumer函数式接口简写
        runApp(args, app -> {
            app.get("/param/{id}", ctx -> {
                String id = ctx.path("id").value();
                return id;
            });
            app.post("/param/{id}", ctx -> {
                return ctx.path("id").value();
            });
        });
    }
}

3.2 MVC API-支持复杂路由/静态资源/全局请求拦截/全局异常拦截

添加依赖

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- MVC API 核心模块 -->
<dependency>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-apt</artifactId>
    <version>2.16.1</version>
</dependency>
<!-- json模块 -->
<dependency>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-gson</artifactId>
    <version>2.16.1</version>
</dependency>

创建controller

java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
package top.meethigher.mvc.controller;

import io.jooby.MediaType;
import io.jooby.Multipart;
import io.jooby.annotations.*;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author chenchuancheng github.com/meethigher
 * @since 2023/6/17 23:31
 */
@Path("/mvc")
public class Controller {


    @GET("/get")
    public String get() {
        return "Hello World";
    }

    @GET("/pathParam/{id}")
    public String pathParam(@PathParam String id) {
        return id;
    }

    /**
     * @see <a href="https://jooby.io/#context-parameters-path">正则表达式校验</a>
     */
    @GET("/pathParam/{id:^[a-zA-Z0-9]{5}$}")
    public String pathParamRegex(@PathParam String id) {
        return id;
    }

    @GET("/queryParam")
    public String queryParam(@QueryParam String id, @QueryParam String name) {
        return name + "--->" + id;
    }

    /**
     * form表单,支持两种形式
     * application/x-www-form-urlencoded,不支持文件
     * multipart/form-data,支持文件
     */
    @POST("/formParam")
    public String formParam(@FormParam String name, @FormParam String age, @FormParam Multipart file) {
        if (file != null) {
            try (InputStream is = file.file("file").stream(); FileOutputStream fos = new FileOutputStream("test.txt")) {
                int b;
                while ((b = is.read()) != -1) {
                    fos.write(b);
                }
                fos.flush();
            } catch (IOException e) {
            }
        }
        return name + "--->" + age;
    }


    @POST("/bodyParam")
    @Consumes(MediaType.JSON)//可省略
    @Produces(MediaType.JSON)//可省略
    public Object bodyParam(Object body) {
        return body;
    }

    @POST("/pageQuery")
    public String pageQuery(PageRequest req) {
        return req.toString();
    }

    @GET("/failure")
    public String failure() {
        throw new RuntimeException("测试失败");
    }

    @GET("/error")
    public String error() {
        int i = 1 / 0;
        return null;
    }
}

class PageRequest {
    private Integer pageIndex;
    private Integer pageSize;

    public Integer getPageIndex() {
        return pageIndex;
    }

    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

注册controller

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import io.jooby.*;
import io.jooby.json.GsonModule;
import top.meethigher.mvc.controller.Controller;
import top.meethigher.mvc.utils.Resp;

import javax.annotation.Nonnull;
import java.nio.charset.StandardCharsets;

import static io.jooby.Jooby.runApp;

/**
 * @author chenchuancheng github.com/meethigher
 * @since 2023/6/17 23:30
 */
public class MVCApplication {
    public static void main(String[] args) {
        runApp(args, app -> {
            // 配置静态资源,优先应用路径下static,其次类路径下static
            app.assets("/**", "/static");
            // 设置全局装饰器
            app.decorator(new GlobalDecorator());
            // 设置全局异常处理
            app.error(new GlobalErrorHandler());
            // 设置 JSON 支持
            app.install(new GsonModule());
            // 注册控制器
            app.mvc(new Controller());
        });
    }
}

/**
 * 全局装饰器
 *
 * @author chenchuancheng github.com/meethigher
 * @since 2023/6/18 17:38
 */
class GlobalDecorator implements Route.Decorator {
    @Nonnull
    @Override
    public Route.Handler apply(@Nonnull Route.Handler next) {
        return ctx -> {
            final MediaType responseType = ctx.getResponseType();
            //设置编码为utf-8
            if (responseType.isTextual()) {
                ctx.setResponseType(responseType, StandardCharsets.UTF_8);
            }
            return next.apply(ctx);
        };
    }
}

/**
 * 全局异常处理器
 *
 * @author chenchuancheng github.com/meethigher
 * @since 2023/6/18 17:36
 */
class GlobalErrorHandler extends DefaultErrorHandler {
    @Override
    public void apply(@Nonnull Context ctx, @Nonnull Throwable cause, @Nonnull StatusCode code) {
        // 处理异常并返回适当的响应
//        if (cause instanceof RuntimeException) {//会存在多态问题,如继承。而我需要的是严格相等
        if (cause.getClass().equals(RuntimeException.class)) {
            // 处理自定义异常
            ctx.setResponseType(MediaType.json, StandardCharsets.UTF_8);
            ctx.send(Resp.getFailureResp(cause.getMessage()).toString());
        } else {
//            // 处理自定义异常
//            ctx.setResponseType(MediaType.json, StandardCharsets.UTF_8);
//            ctx.send(Resp.getErrorResp().toString());
            super.apply(ctx, cause, code);
        }
    }
}

image-20230617232617361.png

四、参考致谢

jooby:做更多! 更容易!