1. 概述

Blade 是一个轻量级的 Java 8+ MVC 框架,从零开始设计,目标明确:自包含、高效、优雅、直观且极快。

它的设计灵感来自多个框架,包括 Node.js 的 Express、Python 的 Flask,以及 Go 语言的 MacaronMartini

Blade 是 Let’s Blade 项目的一部分,该项目还包含一系列小型库,涵盖了从验证码生成到 JSON 转换、模板引擎和数据库连接等功能。

不过,在本教程中,我们只聚焦于 Blade 的 MVC 功能。

2. 快速上手

首先,创建一个空的 Maven 项目,并在 pom.xml 中添加 最新的 Blade MVC 依赖

<dependency>
    <groupId>com.bladejava</groupId>
    <artifactId>blade-mvc</artifactId>
    <version>2.0.14.RELEASE</version>
</dependency>

2.1. 打包 Blade 应用

由于我们的应用是 JAR 包形式,不像 WAR 包那样有 /lib 目录,这就带来一个问题:如何将 blade-mvc 以及其它依赖打包进应用?

有多种方式可以实现,各有优劣,详情可参考 如何用 Maven 创建可执行 JAR

为简单起见,✅我们使用 Maven Assembly Plugin 方式,将所有依赖的 JAR 解压后合并成一个 uber-JAR。

2.2. 运行 Blade 应用

**Blade 基于 Netty**,这是一个高性能的异步事件驱动网络框架。因此,运行 Blade 应用无需外部应用服务器或 Servlet 容器,仅需 JRE 即可:

java -jar target/sample-blade-app.jar

启动后,应用默认可通过 http://localhost:9000 访问。

3. 架构理解

Blade 的架构非常清晰:

architecture

其请求处理流程如下:

  1. Netty 接收请求
  2. 执行中间件(可选)
  3. 执行 WebHook(可选)
  4. 路由匹配
  5. 返回响应给客户端
  6. 清理资源

后续我们会详细讲解这些组件。

4. 路由机制

简单来说,路由是将 URL 映射到 Controller 的机制。

Blade 提供两种路由方式:基础路由和注解路由。

4.1. 基础路由

基础路由适用于微服务或小型 Web 应用:

Blade.of()
  .get("/basic-routes-example", ctx -> ctx.text("GET called"))
  .post("/basic-routes-example", ctx -> ctx.text("POST called"))
  .put("/basic-routes-example", ctx -> ctx.text("PUT called"))
  .delete("/basic-routes-example", ctx -> ctx.text("DELETE called"))
  .start(App.class, args);

方法名即 HTTP 动词,简单粗暴。

虽然这里返回的是文本,但我们也可以渲染页面(后面会讲)。

4.2. 注解路由

更复杂的场景推荐使用注解路由,建议将路由逻辑放在独立的类中。

首先,使用 @Path 注解定义一个 Controller,Blade 会在启动时扫描它:

@Path
public class RouteExampleController {    
    
    @GetRoute("/routes-example") 
    public String get(){ 
        return "get.html"; 
    }
    
    @PostRoute("/routes-example") 
    public String post(){ 
        return "post.html"; 
    }
    
    @PutRoute("/routes-example") 
    public String put(){ 
        return "put.html"; 
    }
    
    @DeleteRoute("/routes-example") 
    public String delete(){ 
        return "delete.html"; 
    }
}

也可以使用通用的 @Route 注解并指定方法:

@Route(value="/another-route-example", method=HttpMethod.GET) 
public String anotherGet(){ 
    return "get.html" ; 
}

⚠️ 如果不指定方法,该路由会拦截所有 HTTP 请求(不论 GET、POST 等)。

4.3. 参数注入

Blade 支持多种参数注入方式,以下是一些常见用法:

  • 表单参数:
@GetRoute("/home")
public void formParam(@Param String name){
    System.out.println("name: " + name);
}
  • RESTful 路径参数:
@GetRoute("/users/:uid")
public void restfulParam(@PathParam Integer uid){
    System.out.println("uid: " + uid);
}
  • 文件上传:
@PostRoute("/upload")
public void fileParam(@MultipartParam FileItem fileItem){
    byte[] file = fileItem.getData();
}
  • 请求头参数:
@GetRoute("/header")
public void headerParam(@HeaderParam String referer){
    System.out.println("Referer: " + referer);
}
  • Cookie 参数:
@GetRoute("/cookie")
public void cookieParam(@CookieParam String myCookie){
    System.out.println("myCookie: " + myCookie);
}
  • JSON 请求体:
@PostRoute("/bodyParam")
public void bodyParam(@BodyParam User user){
    System.out.println("user: " + user.toString());
}
  • VO 参数(通过表单字段绑定):
@PostRoute("/voParam")
public void voParam(@Param User user){
    System.out.println("user: " + user.toString());
}
<form method="post">
    <input type="text" name="age"/>
    <input type="text" name="name"/>
</form>

5. 静态资源处理

Blade 可以自动处理静态资源,只需将文件放入 src/main/resources/static/ 目录下。

例如:src/main/resources/static/app.css 可通过 http://localhost:9000/static/app.css 访问。

5.1. 自定义静态路径

可以通过代码添加自定义路径:

blade.addStatics("/custom-static");

或者通过配置文件设置:

mvc.statics=/custom-static

5.2. 启用资源列表

出于安全考虑,默认不显示静态目录内容。如需启用:

blade.showFileList(true);

或在配置文件中:

mvc.statics.show-list=true

访问 http://localhost:9000/custom-static/ 即可查看目录内容。

5.3. 使用 WebJars

Blade 自动支持 WebJars,路径为 /webjars/

例如引入 Bootstrap:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.2.1</version>
</dependency>

访问路径为:http://localhost:9000/webjars/bootstrap/4.2.1/css/bootstrap.css

6. HTTP 请求处理

由于 Blade 不基于 Servlet 规范,其 RequestHttpRequest 对象与传统 Servlet 不同。

6.1. 表单参数

Blade 使用 Optional 处理表单参数查询结果:

Optional<String> query(String name)
Optional<Integer> queryInt(String name)
Optional<Long> queryLong(String name)
Optional<Double> queryDouble(String name)

也可带默认值:

String query(String name, String defaultValue)
int queryInt(String name, int defaultValue)
long queryLong(String name, long defaultValue)
double queryDouble(String name, double defaultValue)

可通过参数自动注入:

@PostRoute("/save")
public void formParams(@Param String username){
    // ...
}

或从 Request 对象读取:

@PostRoute("/save")
public void formParams(Request request){
    String username = request.query("username", "Baeldung");
}

6.2. JSON 数据

通过 @BodyParam 注解绑定 JSON 到 POJO:

curl -X POST http://localhost:9000/users -H 'Content-Type: application/json' \ 
  -d '{"name":"Baeldung","site":"baeldung.com"}'

POJO 示例(使用 Lombok):

public class User {
    @Getter @Setter private String name;
    @Getter @Setter private String site;
}

自动绑定:

@PostRoute("/users")
public void bodyParams(@BodyParam User user){
    // ...
}

手动读取:

@PostRoute("/users")
public void bodyParams(Request request) {
    String bodyString = request.bodyToString();
}

6.3. RESTful 参数

路径参数支持:

@GetRoute("/user/:id")
public void user(@PathParam Integer id){
    // ...
}

或通过 Request 读取:

@GetRoute("/user")
public void user(Request request){
    Integer id = request.pathInt("id");
}

支持 IntegerLongString 等类型。

6.4. 数据绑定

Blade 支持 JSON 和表单数据自动绑定:

@PostRoute("/users")
public void bodyParams(User user){}

6.5. 请求和会话属性

设置属性:

Session session = request.session();
request.attribute("request-val", "Some Request value");
session.attribute("session-val", 1337);

获取属性:

String requestVal = request.attribute("request-val");
String sessionVal = session.attribute("session-val"); //It's an Integer

支持泛型,无需手动转型。

6.6. 请求头

读取请求头:

String header1 = request.header("a-header");
String header2 = request.header("a-safe-header", "with a default value");
Map<String, String> allHeaders = request.headers();

6.7. 工具方法

提供如下便捷方法:

  • boolean isIE()
  • boolean isAjax()
  • String contentType()
  • String userAgent()

读取 Cookie:

Optional<Cookie> cookieRaw(String name);
String cookie(String name, String defaultValue);
Map<String, String> cookies = request.cookies();

7. HTTP 响应处理

通过方法参数获取 Response 对象:

@GetRoute("/")
public void home(Response response) {}

7.1. 简单输出

输出不同类型内容:

response.text("Hello World!");
response.html("<h1>Hello World!</h1>");
response.xml("<Msg>Hello World!</Msg>");
response.json("{\"The Answer\":42}");
response.json(user); // 自动序列化

7.2. 文件下载

response.download("the-file.txt", "/path/to/the/file.txt");

7.3. 模板渲染

response.render("admin/users.html");

默认模板路径:src/main/resources/templates/

7.4. 重定向

response.redirect("/target-route");
response.cookie("cookie-name", "Some value here");
response.removeCookie("cookie-name");

7.6. 其他操作

常见方法:

  • status(int status)
  • headers()
  • notFound()
  • cookies()
  • contentType(String contentType)
  • body(@NonNull byte[] data)
  • header(String name, String value)

8. WebHook

WebHook 是路由执行前后的拦截器。

实现 WebHook 接口:

@FunctionalInterface
public interface WebHook {

    boolean before(RouteContext ctx);

    default boolean after(RouteContext ctx) {
        return true;
    }
}

8.1. 全局拦截

使用 @Bean 注解全局生效:

@Bean
public class BaeldungHook implements WebHook {

    @Override
    public boolean before(RouteContext ctx) {
        System.out.println("[BaeldungHook] called before Route method");
        return true;
    }
}

8.2. 特定路径拦截

Blade.of()
  .before("/user/*", ctx -> System.out.println("Before: " + ctx.uri()));
  .start(App.class, args);

8.3. 中间件

中间件优先于普通 WebHook 执行:

public class BaeldungMiddleware implements WebHook {

    @Override
    public boolean before(RouteContext context) {
        System.out.println("[BaeldungMiddleware] called before Route method and other WebHooks");
        return true;
    }
}

注册方式:

Blade.of()
  .use(new BaeldungMiddleware())
  .start(App.class, args);

内置中间件:

  • BasicAuthMiddleware
  • CorsMiddleware
  • XssMiddleware
  • CsrfMiddleware

9. 配置管理

Blade 遵循约定优于配置,但可通过 application.properties 自定义。

9.1. 读取配置

启动时读取:

Blade.of()
  .on(EventType.SERVER_STARTED, e -> {
      Optional<String> version = WebContext.blade().env("app.version");
  })
  .start(App.class, args);

路由中读取:

@GetRoute("/some-route")
public void someRoute(){
    String authors = WebContext.blade().env("app.authors","Unknown authors");
}

通过 BladeLoader

@Bean
public class LoadConfig implements BladeLoader {

    @Override
    public void load(Blade blade) {
        Optional<String> version = WebContext.blade().env("app.version");
        String authors = WebContext.blade().env("app.authors","Unknown authors");
    }
}

9.2. 配置属性

配置项可按前缀分组读取:

Environment environment = blade.environment();
Map<String, Object> map = environment.getPrefix("app");
String version = map.get("version").toString();
String authors = map.get("authors","Unknown authors").toString();

9.3. 多环境支持

通过命令行指定环境:

java -jar target/sample-blade-app.jar --app.env=prod

框架会自动合并 application.propertiesapplication-prod.properties

10. 模板引擎

Blade 支持多种模板引擎,如 FreeMarker、Jetbrick、Pebble、Velocity。

10.1. 默认模板

<h1>Hello, ${name}!</h1>

10.2. 使用外部引擎

以 Jetbrick 为例:

<dependency>
    <groupId>com.bladejava</groupId>
    <artifactId>blade-template-jetbrick</artifactId>
    <version>0.1.3</version>
</dependency>

配置:

@Bean
public class TemplateConfig implements BladeLoader {

    @Override
    public void load(Blade blade) {
        blade.templateEngine(new JetbrickTemplateEngine());
    }
}

10.3. 自定义模板引擎

实现 TemplateEngine 接口:

void render (ModelAndView modelAndView, Writer writer) throws TemplateException;

11. 日志系统

Blade 使用 SLF4J,内置 blade-log

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

11.1. 自定义日志配置

通过系统属性设置:

com.blade.logger.rootLevel=info
com.blade.logger.dir=./logs

11.2. 排除内置日志

<exclusion>
    <groupId>com.bladejava</groupId>
    <artifactId>blade-log</artifactId>
</exclusion>

12. 自定义扩展

12.1. 异常处理

自定义异常处理器:

@Bean
public class GlobalExceptionHandler extends DefaultExceptionHandler {

    @Override
    public void handle(Exception e) {
        if (e instanceof BaeldungException) {
            BaeldungException baeldungException = (BaeldungException) e;
            String msg = baeldungException.getMessage();
            WebContext.response().json(RestResponse.fail(msg));
        } else {
            super.handle(e);
        }
    }
}

12.2. 错误页面

配置自定义错误页:

mvc.view.404=my-404.html
mvc.view.500=my-500.html

错误页中可使用变量:

<p>错误信息: "<strong>${message}</strong>"</p>
<pre> ${stackTrace} </pre>

13. 定时任务

使用 @Schedule 注解:

@Bean
public class ScheduleExample {

    @Schedule(name = "baeldungTask", cron = "0 */1 * * * ?")
    public void runScheduledTask() {
        System.out.println("This is a scheduled Task running once per minute.");
    }
}

管理任务:

List<Task> allScheduledTasks = TaskManager.getTasks();
Task myTask = TaskManager.getTask("baeldungTask");
boolean closed = TaskManager.stopTask("baeldungTask");

14. 事件监听

支持多种事件:

SERVER_STARTING, SERVER_STARTED, SERVER_STOPPING, SERVER_STOPPED,
SESSION_CREATED, SESSION_DESTROY, SOURCE_CHANGED, ENVIRONMENT_CHANGED

示例:

Blade.of()
  .on(EventType.SESSION_CREATED, e -> {
      Session session = (Session) e.attribute("session");
      session.attribute("name", "Baeldung");
  })
  .start(App.class, args);

15. 会话实现

默认为内存会话,可自定义:

@Bean
public class SessionConfig implements BladeLoader {
 
    @Override
    public void load(Blade blade) {
        blade.sessionType(new RedisSession());
    }
}

16. 命令行参数

支持以下参数:

--server.address=192.168.1.100
--server.port=8080
--app.env=prod

17. IDE 运行支持

17.1. Eclipse

右键运行 App 类即可。安装 ANSI 插件可修复控制台乱码。

17.2. IntelliJ IDEA

直接运行 App.main(),支持 ANSI 颜色。

17.3. Visual Studio Code

安装 Java 扩展包后,按 Ctrl+F5 即可运行。

18. 总结

Blade 是一个轻量高效的 Java MVC 框架,适合构建中小型 Web 应用。虽然文档主要为中文,但核心功能已在 GitHub 上有英文说明。

示例代码可在 GitHub 获取。


原始标题:Blade - A Complete Guidebook | Baeldung