Blade – A Complete Guidebook – 刀片 – 完整的指导手册

最后修改: 2019年 1月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Blade is a tiny Java 8+ MVC framework, built from scratch with some clear goals in mind: to be self-contained, productive, elegant, intuitive, and super fast.

Blade是一个微小的Java 8+MVC框架,它从零开始构建,有一些明确的目标:自成一体、富有成效、优雅、直观和超级快速。

Many different frameworks inspired its design: Node’s Express, Python’s Flask, and Golang’s Macaron / Martini.

许多不同的框架启发了它的设计。Node的Express、Python的Flask和Golang的Macaron /Martini

Blade is also part of an ambitiously larger project, Let’s Blade. It includes a heterogeneous collection of other small libraries, from Captcha generation to JSON conversion, from templating to a simple database connection.

Blade也是一个雄心勃勃的大项目的一部分,Let’s Blade。它包括一个其他小库的异质集合,从验证码生成到JSON转换,从模板制作到简单的数据库连接。

However, in this tutorial, we’ll focus on the MVC only.

然而,在本教程中,我们将只关注MVC。

2. Getting Started

2.开始

First of all, let’s create an empty Maven project and add the latest Blade MVC dependency in the pom.xml:

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

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

2.1. Bundling a Blade Application

2.1.捆绑一个刀片应用程序

Since our app will be created as a JAR, it won’t have a /lib folder, like in a WAR. As a result, this leads us to the problem how to provide the blade-mvc JAR, along with any other JAR we might need, to our app.

由于我们的应用程序将被创建为JAR,它不会像WAR那样有一个/lib文件夹。因此,这就导致了如何向我们的应用程序提供blade-mvc JAR以及我们可能需要的任何其他JAR的问题。

The different ways of doing this, each one with pros and cons, are explained in the How to Create an Executable JAR with Maven tutorial.

如何用Maven创建可执行的JAR教程中解释了不同的方法,每种方法都有优点和缺点。

For simplicity, we’ll use the Maven Assembly Plugin technique, which explodes any JAR imported in the pom.xml and subsequently bundles all the classes together in a single uber-JAR.

为了简单起见,我们将使用Maven Assembly Plugin技术,该技术可以分解pom.xml中导入的任何JAR,随后将所有的类捆绑在一个超JAR中。

2.2. Running a Blade Application

2.2.运行一个刀片应用程序

Blade is based upon Netty, an amazing asynchronous event-driven network application framework. Therefore, to run our Blade-based application we don’t need any external Application Server nor Servlet Container; the JRE will be enough:

Blade是基于Netty,这是一个了不起的异步事件驱动的网络应用框架。因此,为了运行我们基于Blade的应用程序,我们不需要任何外部应用服务器或Servlet容器;JRE就足够了。

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

After that, the application will be accessible at the http://localhost:9000 URL.

之后,该应用程序将可在http://localhost:9000 URL上访问。

3. Understanding the Architecture

3.了解架构

The architecture of Blade is very straightforward:

刀锋战士的架构非常简单明了。

architecture

It always follows the same life cycle:

它总是遵循相同的生命周期。

  1. Netty receives a request
  2. Middlewares are executed (optional)
  3. WebHooks are executed (optional)
  4. Routing is performed
  5. The response is sent to the client
  6. Cleanup

We’ll explore the above functions in the next sections.

我们将在接下来的章节中探讨上述功能。

4. Routing

4.路线选择

In short, routing in MVC is the mechanism used to create a binding between an URL and a Controller.

简而言之,MVC中的路由是用来在URL和控制器之间创建一个绑定的机制。

Blade provides two types of routes: a basic one and an annotated one.

Blade提供两种类型的路线:基本路线和注释路线。

4.1. Basic Routes

4.1.基本路线

Basic routes are intended for very small software, like microservices or minimal web applications:

基本路线是为非常小的软件准备的,如微服务或最小的网络应用。

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);

The name of the method used to register a route corresponds to the HTTP verb that will be used to forward the request. As simple as that.

用于注册路由的方法的名称与将用于转发请求的HTTP动词相对应。就这么简单。

In this case, we’re returning a text, but we can also render pages, as we’ll see later in this tutorial.

在这种情况下,我们要返回一个文本,但我们也可以渲染页面,我们将在本教程的后面看到。

4.2. Annotated Routes

4.2.带注释的路线

Certainly, for more realistic use cases we can define all the routes we need using annotations. We should use separate classes for that.

当然,对于更现实的用例,我们可以使用注解来定义我们需要的所有路线。我们应该为此使用单独的类。

First of all, we need to create a Controller through the @Path annotation, which will be scanned by Blade during the startup.

首先,我们需要通过@Path注解创建一个控制器,它将在启动期间被Blade扫描。

We then need to use the route annotation related to the HTTP method we want to intercept:

然后我们需要使用与我们想要拦截的HTTP方法相关的路由注解。

@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"; 
    }
}

We can also use the simple @Route annotation and specify the HTTP method as a parameter:

我们也可以使用简单的@Route注解并指定HTTP方法作为参数。

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

On the other hand, if we don’t put any method parameter, the route will intercept every HTTP call to that URL, no matter the verb.

另一方面,如果我们不放任何方法参数,路由将拦截对该URL的每个HTTP调用,无论动词是什么。

4.3. Parameter Injection

4.3.参数注入

There are several ways to pass parameters to our routes. Let’s explore them with some examples from the documentation.

有几种方法可以向我们的路由传递参数。让我们通过一些例子从文档中探索它们。

  • Form parameter:
@GetRoute("/home")
public void formParam(@Param String name){
    System.out.println("name: " + name);
}
  • Restful parameter:
@GetRoute("/users/:uid")
public void restfulParam(@PathParam Integer uid){
    System.out.println("uid: " + uid);
}
  • File upload parameter:
@PostRoute("/upload")
public void fileParam(@MultipartParam FileItem fileItem){
    byte[] file = fileItem.getData();
}
  • Header parameter:
@GetRoute("/header")
public void headerParam(@HeaderParam String referer){
    System.out.println("Referer: " + referer);
}
  • Cookie parameter:
@GetRoute("/cookie")
public void cookieParam(@CookieParam String myCookie){
    System.out.println("myCookie: " + myCookie);
}
  • Body parameter:
@PostRoute("/bodyParam")
public void bodyParam(@BodyParam User user){
    System.out.println("user: " + user.toString());
}
  • Value Object parameter, called by sending its attributes to the route:
@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. Static Resources

5.静态资源

Blade can also serve static resources if needed, by simply putting them inside the /resources/static folder.

如果需要,Blade也可以提供静态资源,只需将它们放在/resources/static文件夹内。

For example, the src/main/resources/static/app.css will be available at http://localhost:9000/static/app.css.

例如,src/main/resources/static/app.css将可在http://localhost:9000/static/app.css

5.1. Customizing the Paths

5.1.定制路径

We can tune this behavior by adding one or more static paths programmatically:

我们可以通过以编程方式添加一个或多个静态路径来调整这一行为。

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

The same result is obtainable through configuration, by editing the file src/main/resources/application.properties:

通过配置,编辑src/main/resources/application.properties文件,可以得到同样的结果。

mvc.statics=/custom-static

5.2. Enabling the Resources Listing

5.2.启用资源列表

We can allow the listing of a static folder’s content, a feature turned off by default for a security reason:

我们可以允许列出一个静态文件夹的内容,出于安全原因,这个功能默认是关闭的。

blade.showFileList(true);

Or in the configuration:

或在配置中。

mvc.statics.show-list=true

We can now open the http://localhost:9000/custom-static/ to show the content of the folder.

我们现在可以打开http://localhost:9000/custom-static/来显示文件夹的内容。

5.3. Using WebJars

5.3.使用WebJars

As seen in the Introduction to WebJars tutorial, static resources packaged as JAR are also a viable option.

正如在WebJars入门教程中所看到的,将静态资源打包成JAR也是一种可行的选择。

Blade exposes them automatically under the /webjars/ path.

Blade在/webjars/路径下自动暴露它们。

For instance, let’s import Bootstrap in the pom.xml:

例如,让我们在pom.xml中导入Bootstrap>。

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

As a result, it’ll be available under http://localhost:9000/webjars/bootstrap/4.2.1/css/bootstrap.css

因此,它将在http://localhost:9000/webjars/bootstrap/4.2.1/css/bootstrap.css下提供。

6. HTTP Request

6.HTTP请求

Since Blade is not based on the Servlet Specification, objects like its interface Request and its class HttpRequest are slightly different than the ones we’re used to.

由于Blade不是基于Servlet规范,所以它的接口Request和它的类HttpRequest等对象与我们所熟悉的对象略有不同。

6.1. Form Parameters

6.1.表格参数

When reading form parameters, Blade makes great use of Java’s Optional in the results of the query methods (all methods below return an Optional object):

在读取表单参数时,Blade在查询方法的结果中大量使用了Java的Optional(以下所有方法都返回一个Optional对象)。

  • query(String name)
  • queryInt(String name)
  • queryLong(String name)
  • queryDouble(String name)

They’re also available with a fallback value:

它们也可以有一个回退值。

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

We can read a form parameter through the automapped property:

我们可以通过automapped属性读取一个表单参数。

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

Or from the Request object:

或来自Request对象。

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

6.2. JSON Data

6.2.JSON数据

Let’s now take a look at how a JSON object can be mapped to a POJO:

现在让我们来看看如何将JSON对象映射到POJO中。

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

POJO (annotated with Lombok for readability):

POJO(为了便于阅读,用Lombok注解)。

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

Again, the value is available as the injected property:

同样,该值可作为注入的属性。

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

And from the Request:

而从Request

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

6.3. RESTful Parameters

6.3.RESTful参数

RESTFul parameters in pretty URLs like localhost:9000/user/42 are also first-class citizens:

localhost:9000/user/42这样漂亮的URL中的RESTFul参数也是一流的公民。

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

As usual, we can rely on the Request object when needed:

像往常一样,我们可以在需要时依靠Request对象。

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

Obviously, the same method is available for Long and String types too.

很明显,同样的方法也适用于LongString类型。

6.4. Data Binding

6.4.数据绑定

Blade supports both JSON and Form binding parameters and attaches them to the model object automatically:

Blade支持JSON和Form绑定参数,并将它们自动附加到模型对象上。

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

6.5. Request and Session Attributes

6.5.请求和会话属性

The API for reading and writing objects in a Request and a Session are crystal clear.

RequestSession中读取和写入对象的API非常清晰。

The methods with two parameters, representing key and value, are the mutators we can use to store our values in the different contexts:

有两个参数的方法,代表key和value,是我们可以用来在不同上下文中存储我们的值的突变器。

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

On the other hand, the same methods accepting only the key parameter are the accessors:

另一方面,只接受关键参数的相同方法是访问器。

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

An interesting feature is their Generic return type <T> T, which saves us from the need of casting the result.

一个有趣的特点是它们的通用返回类型<T>T,这使我们无需对结果进行铸造。

6.6. Headers

6.6.页眉

Request headers, on the contrary, can only be read from the request:

相反,请求头信息只能从请求中读取。

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. Utilities

6.7.实用工具

The following utility methods are also available out of the box, and they’re so evident that don’t need further explanations:

以下实用方法也是开箱即用的,它们是如此明显,不需要进一步解释。

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

6.8. Reading Cookies

6.8.阅读Cookies

Let’s see how the Request object helps us deal with Cookies, specifically when reading the Optional<Cookie>:

让我们看看Request对象如何帮助我们处理Cookies,特别是在读取Optional<Cookie>时。

Optional<Cookie> cookieRaw(String name);

We can also get it as a String by specifying a default value to apply if a Cookie doesn’t exist:

我们也可以通过指定一个默认值,在Cookie不存在的情况下,将其作为一个String来获取。

String cookie(String name, String defaultValue);

Finally, this is how we can read all the Cookies at once (keys are Cookies’ names, values are Cookies’ values):

最后,这就是我们如何一次性读取所有的Cookies(keys是Cookies的名字,values是Cookies的值)。

Map<String, String> cookies = request.cookies();

7. HTTP Response

7.HTTP响应

Analogous to what was done with the Request, we can obtain a reference to the Response object by simply declaring it as a parameter of the routing method:

与处理Request的方法类似,我们可以通过简单地将Response对象声明为路由方法的一个参数来获得它的引用。

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

7.1. Simple Output

7.1.简单输出

We can easily send a simple output to the caller through one of the handy output methods, along with a 200 HTTP code and the appropriate Content-Type.

我们可以很容易地通过一个方便的输出方法向调用者发送一个简单的输出,以及一个200的HTTP代码和适当的内容类型。

Firstly, we can send a plain text:

首先,我们可以发送一个纯文本。

response.text("Hello World!");

Secondly, we can produce an HTML:

第二,我们可以制作一个HTML。

response.html("<h1>Hello World!</h1>");

Thirdly, we can likewise generate an XML:

第三,我们同样可以生成一个XML。

response.xml("<Msg>Hello World!</Msg>");

Finally, we can output JSON using a String:

最后,我们可以使用String来输出JSON。

response.json("{\"The Answer\":42}");

And even from a POJO, exploiting the automatic JSON conversion:

甚至从一个POJO,利用自动JSON转换。

User user = new User("Baeldung", "baeldung.com"); 
response.json(user);

7.2. File Output

7.2.文件输出

Downloading a file from the server couldn’t be leaner:

从服务器上下载文件是再简单不过的了。

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

The first parameter sets the name of the file that will be downloaded, while the second one (a File object, here constructed with a String) represents the path to the actual file on the server.

第一个参数设置将被下载的文件的名称,而第二个参数(一个文件对象,这里用一个字符串构建)代表服务器上实际文件的路径。

7.3. Template Rendering

7.3.模板的渲染

Blade can also render pages through a template engine:

刀片还可以通过模板引擎渲染页面。

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

The templates default directory is src/main/resources/templates/, hence the previous one-liner will look for the file src/main/resources/templates/admin/users.html.

模板的默认目录是src/main/resources/templates/,因此前面的单行代码将寻找文件src/main/resources/templates/admin/users.html

We’ll learn more about this later, in the Templating section.

我们将在后面的模板化部分中了解更多这方面的信息。

7.4. Redirect

7.4.重定向

A redirection means sending a 302 HTTP code to the browser, along with an URL to follow with a second GET.

重定向意味着向浏览器发送一个302HTTP代码,同时发送一个URL,以进行第二次GET。

We can redirect to another route, or to an external URL as well:

我们可以重定向到另一条路线,或者也可以重定向到一个外部URL。

response.redirect("/target-route");

7.5. Writing Cookies

7.5.写作Cookies

We should be used to the simplicity of Blade at this point. Let’s thus see how we can write an unexpiring Cookie in a single line of code:

在这一点上,我们应该已经习惯了Blade的简单性。因此,让我们看看如何用一行代码编写一个未到期的Cookie。

response.cookie("cookie-name", "Some value here");

Indeed, removing a Cookie is equally simple:

事实上,删除一个Cookie也同样简单。

response.removeCookie("cookie-name");

7.6. Other Operations

7.6.其他业务

Finally, the Response object provides us with several other methods to perform operations like writing Headers, setting the Content-Type, setting the Status code, and so on.

最后,Response对象为我们提供了其他几个方法来执行操作,如编写头文件、设置内容类型、设置状态代码等等。

Let’s take a quick look at some of them:

让我们快速看一下其中的一些情况。

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

8. WebHooks

8.WebHooks

A WebHook is an interceptor through which we can run code before and after the execution of a routing method.

WebHook是一个拦截器,通过它我们可以在执行路由方法之前和之后运行代码

We can create a WebHook by simply implementing the WebHook functional interface and overriding the before() method:

我们可以通过简单地实现WebHook功能接口并重写before()方法来创建一个WebHook。

@FunctionalInterface
public interface WebHook {

    boolean before(RouteContext ctx);

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

As we can see, after() is a default method, hence we’ll override it only when needed.

正如我们所看到的,after()是一个默认的方法,因此我们只有在需要时才会覆盖它。

8.1. Intercepting Every Request

8.1.拦截每个请求

The @Bean annotation tells the framework to scan the class with the IoC Container.

@Bean注解告诉框架用IoC容器扫描该类。

A WebHook annotated with it will consequently work globally, intercepting requests to every URL:

因此,用它来注释的WebHook将在全球范围内工作,拦截对每个URL的请求。

@Bean
public class BaeldungHook implements WebHook {

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

8.2. Narrowing to a URL

8.2.缩小到一个URL的范围

We can also intercept specific URLs, to execute code around those route methods only:

我们还可以拦截特定的URL,只围绕这些路由方法执行代码。

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

8.3. Middlewares

8.3.中间件

Middlewares are prioritized WebHooks, which get executed before any standard WebHook:

中间件是有优先权的WebHooks,它在任何标准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;
    }
}

They simply need to be defined without the @Bean annotation, and then registered declaratively through use():

它们只需要在没有@Bean注解的情况下被定义,然后通过use()声明性地注册。

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

In addition, Blade comes with the following security-related built-in Middlewares, whose names should be self-explanatory:

此外,Blade还配有以下与安全有关的内置中间件,其名称应该不言自明。

9. Configuration

9.配置

In Blade, the configuration is totally optional, because everything works out-of-the-box by convention. However, we can customize the default settings, and introduce new attributes, inside the src/main/resources/application.properties file.

在Blade中,配置是完全可选的,因为一切都按惯例开箱即用。然而,我们可以在src/main/resources/application.properties文件中定制默认设置,并引入新的属性。

9.1. Reading the Configuration

9.1.读取配置

We can read the configuration in different ways, with or without specifying a default value in case the setting is not available.

我们可以用不同的方式读取配置,在设置不可用的情况下,也可以不指定默认值。

  • During startup:
Blade.of()
  .on(EventType.SERVER_STARTED, e -> {
      Optional<String> version = WebContext.blade().env("app.version");
  })
  .start(App.class, args);
  • Inside a route:
@GetRoute("/some-route")
public void someRoute(){
    String authors = WebContext.blade().env("app.authors","Unknown authors");
}
  • In a custom loader, by implementing the BladeLoader interface, overriding the load() method, and annotating the class with @Bean:
@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. Configuration Attributes

9.2.配置属性

The several settings already configured, but ready to be customized, are grouped by type and listed at this address in three-column tables (name, description, default value). We can also refer to the translated page, paying attention to the fact that the translation erroneously capitalizes the settings’ names. The real settings are fully lowercase.

已经配置好的、但随时可以定制的几个设置按类型分组,并在三栏表格中列出在这个地址(名称、描述、默认值)。我们也可以参考翻译后的页面,注意翻译时错误地将设置的名称大写。真正的设置是完全小写的。

Grouping configuration settings by prefix makes them readable all at once into a map, which is useful when there are many of them:

按前缀对配置设置进行分组,可以使它们一下子读到一张地图上,这在有很多配置设置时很有用。

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. Handling Multiple Environments

9.3.处理多个环境

When deploying our app to a different environment, we might need to specify different settings, for example the ones related to the database connection. Instead of manually replacing the application.properties file, Blade offers us a way to configure the app for different environments. We can simply keep application.properties with all the development settings, and then create other files in the same folder, like application-prod.properties, containing only the settings that differ.

当把我们的应用部署到不同的环境时,我们可能需要指定不同的设置,例如与数据库连接有关的设置。Blade为我们提供了一种为不同环境配置应用程序的方法,而不是手动替换application.properties文件。我们可以简单地保留带有所有开发设置的application.properties,然后在同一文件夹中创建其他文件,如application-prod.properties,只包含不同的设置

During the startup, we can then specify the environment we want to use, and the framework will merge the files by using the most specific settings from application-prod.properties, and all the other settings from the default application.properties file:

在启动过程中,我们可以指定我们要使用的环境,框架将通过使用application-prod.properties中最具体的设置,以及默认application.properties文件中的所有其他设置来合并文件。

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

10. Templating

10.模板设计

Templating in Blade is a modular aspect. While it integrates a very basic template engine, for any professional use of the Views we should rely on an external template engine. We can then choose an engine from the ones available in the blade-template-engines repository on GitHub, which are FreeMarker, Jetbrick, Pebble, and Velocity, or even creating a wrapper to import another template we like.

刀片中的模板是一个模块化的方面。虽然它集成了一个非常基本的模板引擎,但对于任何专业的视图使用,我们应该依赖一个外部模板引擎。然后我们可以从GitHub上的Blade-template-engines资源库中选择一个引擎,它们是FreeMarkerJetbrickPebbleVelocity,甚至可以创建一个包装器来导入我们喜欢的其他模板。

Blade’s author suggests Jetbrick, another smart Chinese project.

Blade的作者建议Jetbrick,另一个聪明的中国项目。

10.1. Using the Default Engine

10.1.使用默认引擎

The default template works by parsing variables from different contexts through the ${} notation:

默认模板的工作方式是通过${}符号来解析来自不同语境的变量。

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

10.2. Plugging in an External Engine

10.2.插入一个外部引擎

Switching to a different template engine is a breeze! We simply import the dependency of (the Blade wrapper of) the engine:

切换到一个不同的模板引擎是一件很容易的事!我们只需导入(Blade包装器的)引擎的依赖关系即可。我们只需导入引擎的依赖关系(Blade包装器)。

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

At this point, it’s enough to write a simple configuration to instruct the framework to use that library:

在这一点上,只要写一个简单的配置来指示框架使用该库就足够了。

@Bean
public class TemplateConfig implements BladeLoader {

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

As a result, now every file under src/main/resources/templates/ will be parsed with the new engine, whose syntax is beyond the scope of this tutorial.

因此,现在src/main/resources/templates/下的每个文件都将被新引擎解析,其语法已经超出了本教程的范围。

10.3. Wrapping a New Engine

10.3.包裹一个新的引擎

Wrapping a new template engine requires creating a single class, which must implement the TemplateEngine interface and override the render() method:

封装一个新的模板引擎需要创建一个单一的类,它必须实现TemplateEngine接口并重写render()方法。

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

For this purpose, we can take a look at the code of the actual Jetbrick wrapper to get an idea of what that means.

为此,我们可以看一下实际的Jetbrick包装器的代码,以了解这意味着什么。

11. Logging

11.记录

Blade uses slf4j-api as logging interface.

Blade使用slf4j-api作为记录接口。

It also includes an already configured logging implementation, called blade-log. Therefore, we don’t need to import anything; it works as is, by simply defining a Logger:

它还包括一个已经配置好的日志实现,叫做blade-log。因此,我们不需要导入任何东西;只要定义一个Logger,就可以照常工作。

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

11.1. Customizing the Integrated Logger

11.1.定制集成式记录器

In case we want to modify the default configuration, we need to tune the following parameters as System Properties:

如果我们想修改默认配置,我们需要将以下参数调整为系统属性。

  • Logging levels (can be “trace”, “debug”, “info”, “warn”, or “error”):
# Root Logger
com.blade.logger.rootLevel=info

# Package Custom Logging Level
com.blade.logger.somepackage=debug

# Class Custom Logging Level
com.blade.logger.com.baeldung.sample.SomeClass=trace
  • Displayed information:
# Date and Time
com.blade.logger.showDate=false

# Date and Time Pattern
com.blade.logger.datePattern=yyyy-MM-dd HH:mm:ss:SSS Z

# Thread Name
com.blade.logger.showThread=true

# Logger Instance Name
com.blade.logger.showLogName=true

# Only the Last Part of FQCN
com.blade.logger.shortName=true
  • Logger:
# Path 
com.blade.logger.dir=./logs

# Name (it defaults to the current app.name)
com.blade.logger.name=sample

11.2. Excluding the Integrated Logger

11.2.不包括综合记录仪

Although having an integrated logger already configured is very handy to start our small project, we might easily end up in the case where other libraries import their own logging implementation. And, in that case, we’re able to remove the integrated one in order to avoid conflicts:

尽管已经配置了一个集成的日志记录器对于开始我们的小项目非常方便,但我们可能很容易就会遇到其他库导入他们自己的日志实现的情况。在这种情况下,我们就可以删除集成的日志记录器,以避免冲突。

<dependency>
    <groupId>com.bladejava</groupId>
    <artifactId>blade-mvc</artifactId>
    <version>${blade.version}</version>
    <exclusions>
        <exclusion>
            <groupId>com.bladejava</groupId>
            <artifactId>blade-log</artifactId>
        </exclusion>
    </exclusions>
</dependency>

12. Customizations

12.定制

12.1. Custom Exception Handling

12.1.自定义异常处理

An Exception Handler is also built-in by default in the framework. It prints the exception to the console, and if app.devMode is true, the stack trace is also visible on the webpage.

框架中还默认内置了一个异常处理程序。它将异常打印到控制台,如果app.devModetrue,堆栈跟踪也可以在网页上看到。

However, we can handle an Exception in a specific way by defining a @Bean extending the DefaultExceptionHandler class:

然而,我们可以通过定义一个扩展@BeanDefaultExceptionHandler类,以特定方式处理异常。

@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. Custom Error Pages

12.2.自定义错误页面

Similarly, the errors 404 – Not Found and 500 – Internal Server Error are handled through skinny default pages.

同样,错误404 – Not Found500 – Internal Server Error也是通过瘦的默认页面处理的。

We can force the framework to use our own pages by declaring them in the application.properties file with the following settings:

我们可以通过在application.properties文件中用以下设置来强制框架使用我们自己的页面。

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

Certainly, those HTML pages must be placed under the src/main/resources/templates folder.

当然,这些HTML页面必须放在src/main/resources/templates文件夹下。

Within the 500 one, we can moreover retrieve the exception message and the stackTrace through their special variables:

在500中,我们还可以通过它们的特殊变量检索到异常messagestackTrace

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>500 Internal Server Error</title>
    </head>
    <body>
        <h1> Custom Error 500 Page </h1>
        <p> The following error occurred: "<strong>${message}>"</p>
        <pre> ${stackTrace} </pre>
    </body>
</html>

13. Scheduled Tasks

13.预定的任务

Another interesting feature of the framework is the possibility of scheduling the execution of a method.

该框架的另一个有趣的特点是可以对一个方法的执行进行调度。

That’s possible by annotating the method of a @Bean class with the @Schedule annotation:

这可以通过用@Bean注解类的方法来实现,@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.");
    }
}

Indeed, it uses the classical cron expressions to specify the DateTime coordinates. We can read more about those in A Guide to Cron Expressions.

事实上,它使用经典的Cron表达式来指定DateTime坐标。我们可以在A Guide to Cron Expressions中阅读更多关于这些内容。

Later on, we might exploit the static methods of the TaskManager class to perform operations on the scheduled tasks.

后来,我们可能会利用TaskManager类的静态方法来执行对计划任务的操作。

  • Get all the scheduled tasks:
List<Task> allScheduledTasks = TaskManager.getTasks();
  • Get a task by name:
Task myTask = TaskManager.getTask("baeldungTask");
  • Stop a task by name:
boolean closed = TaskManager.stopTask("baeldungTask");

14. Events

14.大事记

As already seen in section 9.1, it’s possible to listen for a specified event before running some custom code.

正如在第9.1节中已经看到的,在运行一些自定义代码之前,可以监听一个指定的事件。

Blade provides the following events out of the box:

刀片提供了以下开箱即用的事件。

public enum EventType {
    SERVER_STARTING,
    SERVER_STARTED,
    SERVER_STOPPING,
    SERVER_STOPPED,
    SESSION_CREATED,
    SESSION_DESTROY,
    SOURCE_CHANGED,
    ENVIRONMENT_CHANGED
}

While the first six are easy to guess, the last two need some hints: ENVIRONMENT_CHANGED allows us to perform an action if a configuration file changes when the server is up. SOURCE_CHANGED, instead, is not yet implemented and is there for future use only.

虽然前六个很容易猜到,但最后两个需要一些提示。ENVIRONMENT_CHANGED允许我们在服务器启动时,如果配置文件发生变化,就执行一个动作。而SOURCE_CHANGED则还没有实现,只是为了将来使用。

Let’s see how we can put a value in the session whenever it’s created:

让我们看看我们如何在会话创建时在其中放入一个值。

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

15. Session Implementation

15.会议的实施

Talking about the session, its default implementation stores session values in-memory.

谈到会话,它的默认实现在内存中存储会话值。

We might, thus, want to switch to a different implementation to provide cache, persistence, or something else. Let’s take Redis, for example. We’d first need to create our RedisSession wrapper by implementing the Session interface, as shown in the docs for the HttpSession.

因此,我们可能想换一个不同的实现来提供缓存、持久性或其他东西。让我们以Redis为例。我们首先需要通过实现RedisSession包装器来创建Session接口,正如HttpSession的文档中所示。

Then, it’d be only a matter of letting the framework know we want to use it. We can do this in the same way we did for the custom template engine, with the only difference being that we call the sessionType() method:

然后,这只是一个让框架知道我们想使用它的问题。我们可以用与定制模板引擎相同的方式来做这件事,唯一的区别是我们调用sessionType()方法。

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

16. Command Line Arguments

16.命令行参数

When running Blade from the command line, there are three settings we can specify to alter its behavior.

当从命令行运行Blade时,我们可以指定三种设置来改变其行为。

Firstly, we can change the IP address, which by default is the local 0.0.0.0 loopback:

首先,我们可以改变IP地址,默认是本地0.0.0.0环回。

java -jar target/sample-blade-app.jar --server.address=192.168.1.100

Secondly, we can also change the port, which by default is 9000:

其次,我们还可以改变端口,默认情况下是9000

java -jar target/sample-blade-app.jar --server.port=8080

Finally, as seen in section 9.3, we can change the environment to let a different application-XXX.properties file to be read over the default one, which is application.properties:

最后,正如在第9.3节中所看到的,我们可以改变环境,让一个不同的application-XXX.properties文件被读取,而不是默认的,即application.properties

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

17. Running in the IDE

17.在IDE中运行

Any modern Java IDE is able to play a Blade project without even needing the Maven plugins. Running Blade in an IDE is especially useful when running the Blade Demos, examples written expressly to showcase the framework’s functionalities. They all inherit a parent pom, so it’s easier to let the IDE do the work, instead of manually tweaking them to be run as standalone apps.

任何现代Java集成开发环境都能播放Blade项目,甚至不需要Maven插件。在IDE中运行Blade在运行Blade演示时尤其有用,这些例子是专门为展示该框架的功能而编写的。它们都继承了一个父pom,所以让IDE来完成工作更容易,而不是手动调整它们以作为独立的应用程序运行。

17.1. Eclipse

17.1.Eclipse

In Eclipse, it’s enough to right-click on the project and launch Run as Java Application, select our App class, and press OK.

在Eclipse中,只需右键单击该项目并启动Run as Java Application,选择我们的App类,然后按OK

Eclipse’s console, however, will not display ANSI colors correctly, pouring out their codes instead:

然而,Eclipse的控制台不会正确显示ANSI颜色,而是倒出其代码。

eclipse workspace sample app - Eclipse IDE

Luckily, installing the ANSI Escape in Console extension fixes the problem for good:

幸运的是,安装控制台中的ANSI逃逸扩展可以永久地解决这个问题。

eclipse workspace - Eclipse IDE

17.2. IntelliJ IDEA

17.2.IntelliJ IDEA

IntelliJ IDEA works with ANSI colors out of the box. Therefore, it’s enough to create the project, right-click on the App file, and launch Run ‘App.main()’ (which is equivalent to pressing Ctrl+Shift+F10):

IntelliJ IDEA开箱就能使用ANSI颜色。因此,只需创建项目,右键单击App文件,并启动Run ‘App.main()’(相当于按下Ctrl+Shift+F10)。

sample blade app

17.3. Visual Studio Code

17.3.Visual Studio代码

It’s also possible to use VSCode, a popular non-Java-centric IDE, by previously installing the Java Extension Pack.

通过事先安装Java扩展包,也可以使用VSCode,这是一个流行的不以Java为中心的IDE。

Pressing Ctrl+F5 will then run the project:

然后按Ctrl+F5将运行该项目。

pom.xml sample app

18. Conclusion

18.结论

We’ve seen how to use Blade to create a small MVC application.

我们已经看到如何使用Blade来创建一个小型的MVC应用程序。

The entire documentation is available only in the Chinese language. Despite being widespread mainly in China, thanks to its Chinese origins, the author has recently translated the API and documented the core functionalities of the project in English on GitHub.

整个文档只有中文版本。尽管主要在中国广为流传,但由于其源自中国作者最近在GitHub上翻译了API并将项目的核心功能记录为英文。

As always, we can find the source code of the example over on GitHub.

一如既往,我们可以在GitHub上找到该示例的源代码。