Lightning-mvc - 完整指南

/ 技术 / 暂无评论 /18次浏览

概述

Lightning是一个小巧的Java 8 + MVC框架,有一些明确的目标:自包含,高效,优雅,直观,超快。

入门

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

        <groupId>com.lightning</groupId>
            <artifactId>lightning-mvc</artifactId>
            <version>${lightning-mvc.version}</version>
        </dependency>

运行Lightning MVC 应用程序

Lightning基于Netty。运行基于Lightning的应用程序,不需要任何外部Application Server或Servlet Container; JRE就足够了:

java -jar target/lightning-app.jar

了解架构

alt
它始终遵循相同的生命周期:

  1. Netty收到请求
  2. 中间件被执行(可选)
  3. 执行路由
  4. 响应将发送给客户端
  5. 清理

路由

简而言之:MVC中的路由是用于在URL和Controller之间创建绑定的机制。

Lightning提供两种类型的路由:基本路由和注释路由

基本路由

基本路由适用于非常小的软件,如微服务或最小的Web应用程序:

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

在这种情况下,我们返回一个文本,但也可以呈现页面。

带注释的路由

当然,对于更实际的用例,我们可以使用注释定义所需的所有路径。我们应该为此使用单独的类。 首先,我们需要通过@Path注释创建一个Controller,它将在启动时由Lightning扫描 然后,我们需要使用与我们想要拦截的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"; 
    }
}

我们也可以使用简单的@Route注释并将HTTP方法指定为参数:

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

另一方面,如果我们不放任何参数,路由将对该URL的每个HTTP调用

参数注入

有几种方法可以将参数传递给我们的路线。让我们通过文档中的一些示例来探索它们。

@GetRoute("/home")
public void formParam(@Param String name){
    System.out.println("name: " + name);
}
@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();
}
@PostRoute("/upload")
public void fileParam(@MultipartParam FileItem fileItem){
    byte[] file = fileItem.getData();
}
@GetRoute("/cookie")
public void cookieParam(@CookieParam String myCookie){
    System.out.println("myCookie: " + myCookie);
}
@PostRoute("/bodyParam")
public void bodyParam(@BodyParam User user){
    System.out.println("user: " + user.toString());
}

Value Object parameter,通过将其属性发送到路由来调用:

@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>

静态资源

如果需要,Lightning 还可以通过简单地将它们放在/resources/static 文件夹中来提供静态资源。 例如,src/main/resources/static/app.css将在http://localhost:9000/static/app.css 中提供。

自定义路径

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

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

通过编辑文件 src/main/resources/application.properties 可以获得相同的结果

mvc.statics=/custom-static

启用资源列表

我们可以允许列出静态文件夹的内容,默认情况下会出于安全原因关闭该功能:

lightning.static.show-list=true

或者在配置中:

mvc.statics.show-list=true

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

使用WebJars

如 WebJars 简介教程中所示,打包为JAR的静态资源也是可行的选择。 Lightning会在/webjars/path 下自动公开它们。 例如,让我们在pom.xml中倒入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下可用

HTTP请求

由于 Lightning不是基于Servlet规范,因此其接口Request及其类HttpRequest等对象与我们习惯使用的对象略有不同。

表格参数

在读取表单参数时,Lightning在查询方法的结果中充分利用了Java的Optional(行哦啊,下面的所有方法都返回一个Optional对象):

它还具备默认值:

我们可以通过automapped属性读取表单参数:

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

或者从Request 对象:

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

##JSON Data 现在让我们看看如何将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();
}

RESTful 参数

漂亮的URL中的RESTFul参数,如localhost:9000/user/42 :

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

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

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

显然,Long和String类型也可以使用相同的方法

Data Binding

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

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

请求和会话属性

用于在请求和会话中读取和写入对象的API非常清楚。 具有两个参数(表示键和值)的方法是我们可以用来在不同的上下文中存储我们的值的突变:

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

一个有趣的特性是 它们返回的结果是 their Generic return type T,它是我们免于转换结果的需要

Headers

相反,请求标头只能从请求中读取:

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

Utilities

以下实用方法也可以直接使用,它们非常明显,不需要进一步解释:

Reading Cookies

让我们看看Request对象如何帮我们处理Cookie,特别是在阅读Optional 时:

Optional<Cookie> cookieRaw(String name);

如果Cookie不存在,我们也可以通过指定要应用的默认值来获取它:

String cookie(String name, String defaultValue);

最后,这就是我们如何一次读取所有Cookie(键是Cookies的名称,值是Cookie的值):

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

HTTP Response

类似于对Request所做的操作,我们可以通过简单地将其声明为路由方法的参数来获取对Response对象的引用:

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

Simple Output

我们可以通过一种方便的输出方法,一个200 HTTP代码和相应的Content-Type,轻松地向调用者发送一个简单的输出。 首先,我们可以发送一个纯文本:

response.text("Hello World!");

其次,我们可以生成一个HTML:

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

第三,我们同样可以生成XML:

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

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

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

甚至从POJO开始,利用自动JSON转换:

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

文件输出

从服务器下载文件不能更精简:

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

第一个参数设置要下载的文件的名称,而第二个参数(File对象,这里用String构造)表示服务器上实际文件的路径。

模板渲染

Lightning 还可以通过模板引擎呈现页面:

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

模板默认目录是src/main/resources/templates/,因此前一个单行将查找文件 src/main/ resources/templates/admin/users.html。

我们稍后会在模板部分详细了解这一点。

重定向

重定向意味着向浏览器发送302 HTTP代码,以及跟随第二个GET的URL。 我们可以重定向到另一条路线,也可以重定向到外部网址:

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

写Cookie

在这一点上我们应该习惯Blade的简单性。让我们看看我们如何在一行代码中编写一个未启动的Cookie:

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

实际上,删除Cookie同样简单:

response.removeCookie("cookie-name");

其他

最后,Response对象为我们提供了几个其他方法来执行诸如编写Headers,设置Content-Type,设置Status代码等操作。 让我们快速看看其中一些:

WebHooks

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

我们可以通过简单地实现WebHook功能接口并覆盖before() 方法来创建WebHook :

@FunctionalInterface
public interface WebHook {
 
    boolean before(RouteContext ctx);
 
    default boolean after(RouteContext ctx) {
        return true;
    }
}

我们可以看到,after() 是一个默认方法,因此我们只在需要时才覆盖它。

拦截每一个请求

该@Bean注解告诉扫描类的IOC容器的框架。

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

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

缩小到URL

我们还可以截取特定的URL,仅围绕这些路由方法执行代码:

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

中间件

中间件是优先级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;
    }
}

它们只需要在没有@Bean注释的情况下定义,然后通过use() 以声明方式注册:

Lightning.of()
  .use(new BaeldungMiddleware())
  .start(Application.class, args);

此外,Lightning 还附带了以下与安全相关的内置中间件,其名称应该是不言自明的:

配置

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

阅读配置

我们可以通过不同方式读取配置,无论是否设置默认值,如果设置不可用。

在启动期间:

Lightning.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接口,重写load()方法,并使用@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");
    }
}

配置属性

已配置但已准备好进行自定义的多个设置按类型分组,并在此列地址中列为三列表(名称,描述,默认值)。我们也可以参考翻译页面,注意翻译错误地将设置的名称大写。实际设置是完全小写的。

按前缀对配置设置进行分组可以将它们一次性读取到地图中,这在有很多时非常有用:

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

处理多个环境

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

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

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

模板

Lightning中的模板化是一个模块化方面。虽然它集成了一个非常基本的模板引擎,但对于视图的任何专业用途,我们都应该依赖外部模板引擎。然后我们可以从 GitHub上的lightning-template-engines存储库中选择一个引擎,它们是FreeMarker,Jetbrick,Pebble和Velocity,甚至可以创建一个包装器来导入我们喜欢的另一个模板。

使用默认引擎

默认模板通过$ {}表示法解析来自不同上下文的变量:

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

插入外部引擎

切换到不同的模板引擎是轻而易举的!我们只需导入引擎的(Lightning wrapper)依赖项:

<dependency>
    <groupId>com.lightning</groupId>
    <artifactId>lightning-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());
    }
}

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

包装新引擎

包装新模板引擎需要创建一个类,该类必须实现TemplateEngine接口并覆盖render()方法:

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

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

日志记录

Lightning使用slf4j-api作为日志记录界面。

它还包括一个已配置的日志记录实现,称为lightning-log。因此,我们不需要进口任何东西; 通过简单地定义一个Logger,它可以原样工作:

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

自定义日志记录器

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

# 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
# Date and Time
com.blade.logger.showDate=false
 
# Date and Time Pattern
com.blade.logger.datePattern=yyyy-MM-dd HH🇲🇲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
# Path 
com.lightning.logger.dir=./logs
 
# Name (it defaults to the current app.name)
com.lightning.logger.name=sample

排除集成记录器

虽然已经配置了集成记录器非常方便启动我们的小项目,但我们可能很容易在其他库导入自己的日志记录实现的情况下结束。而且,在这种情况下,我们可以删除集成的,以避免冲突:

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

自定义

自定义异常处理 默认情况下,框架中还内置了一个异常处理程序。它将异常打印到控制台,如果app.devMode为true,则堆栈跟踪也会显示在网页上。

但是,我们可以通过定义扩展DefaultExceptionHandler类的@Bean以特定方式处理异常:

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

自定义错误页面

同样,错误404 - 未找到和500 - 内部服务器错误通过瘦的默认页面处理。

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

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

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

在500之内,我们还可以通过它们的特殊变量检索异常消息和stackTrace:

<!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}</strong>"</p>
        <pre> ${stackTrace} </pre>
    </body>
</html>

定时任务

该框架的另一个有趣特性是可以安排方法的执行。

通过使用@Schedule注释来注释@Bean类的方法是可能的:

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

实际上,它使用经典的cron表达式来指定DateTime坐标。我们可以在“Cron表达式指南”中阅读更多相关内容。

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

获取所有计划任务:

List<Task> allScheduledTasks = TaskManager.getTasks();

按名称获取任务:

Task myTask = TaskManager.getTask("baeldungTask");

按名称停止任务:

boolean closed = TaskManager.stopTask("baeldungTask");

事件

正如9.1节中所见,可以在运行某些自定义代码之前侦听指定的事件。

Lightning提供开箱即用的以下活动:

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

虽然前六个很容易猜到,但最后两个需要一些提示:ENVIRONMENT_CHANGED允许我们在服务器启动时配置文件发生变化时执行操作。相反,SOURCE_CHANGED尚未实现,仅供将来使用。

让我们看看如何在会话中创建一个值:

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

会议实施

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

因此,我们可能希望切换到不同的实现来提供缓存,持久性或其他内容。我们以Redis为例。我们首先需要通过实现Session接口来创建我们的RedisSession包装器,如HttpSession的文档中所示。

然后,这只是让框架知道我们想要使用它的问题。我们可以像对自定义模板引擎一样执行此操作,唯一的区别是我们调用sessionType()方法:

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

命令行参数

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

首先,我们可以更改IP地址,默认情况下是本地0.0.0.0环回:

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

其次,我们也可以更改端口,默认为9000:

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

最后,如9.3节所示,我们可以更改环境,让不同的application-XXX.properties文件通过默认文件读取,即application.properties:

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

在IDE中运行

任何现代Java IDE都可以在不需要Maven插件的情况下播放Blade项目。在运行Blade Demos时,在IDE中运行Blade特别有用,这些示例是为了展示框架的功能而明确编写的。它们都继承了父pom,因此让IDE更容易完成工作,而不是手动调整它们作为独立应用程序运行。

日食

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

但是,Eclipse的控制台无法正确显示ANSI颜色,而是倾注了他们的代码:

幸运的是,在Console扩展中安装ANSI Escape可以很好地解决问题:

IntelliJ IDEA

IntelliJ IDEA可与开箱即用的ANSI颜色配合使用。因此,创建项目就足够了,右键单击App文件,然后启动Run'App.main()'(相当于按Ctrl + Shift + F10):

Visual Studio代码

也可以通过以前安装Java Extension Pack来使用VSCode,这是一种流行的非Java中心IDE 。

然后按Ctrl + F5将运行项目:

结论

我们已经了解了如何使用Blade创建一个小型MVC应用程序。

整个文档仅以中文提供。尽管主要在中国广泛传播,但由于其中国的起源,作者最近翻译了API并在GitHub上用英语记录了该项目的核心功能。

与往常一样,我们可以在GitHub上找到示例的源代码。

登录后再评论.