Перейти к основному содержимому

Blade — полное руководство

· 20 мин. чтения

1. Обзор

Blade — это крошечная среда Java 8+ MVC, созданная с нуля с некоторыми четкими целями: быть автономным, продуктивным, элегантным, интуитивно понятным и сверхбыстрым.

На его разработку вдохновило множество различных фреймворков: Node Express , Python Flask и Macaron / Martini от Golang .

Blade также является частью амбициозного проекта Let's Blade . Он включает в себя разнородный набор других небольших библиотек, от генерации Captcha до преобразования JSON, от шаблонов до простого подключения к базе данных.

Однако в этом руководстве мы сосредоточимся только на MVC.

2. Начало работы

Прежде всего, давайте создадим пустой проект Maven и добавим последнюю зависимость Blade MVC в pom.xml :

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

2.1. Объединение блейд-приложения

Поскольку наше приложение будет создано как JAR, у него не будет папки /lib , как в WAR. В результате это приводит нас к проблеме, как предоставить JAR -файл blade-mvc вместе с любым другим JAR-файлом, который нам может понадобиться, для нашего приложения.

Различные способы сделать это, каждый из которых имеет свои плюсы и минусы, объясняются в учебнике « Как создать исполняемый JAR-файл с помощью Maven ».

Для простоты мы будем использовать технику Maven Assembly Plugin , которая разбивает любой JAR, импортированный в pom.xml, и впоследствии объединяет все классы в один uber-JAR.

2.2. Запуск блейд-приложения

Blade основан на Netty , удивительной среде асинхронных сетевых приложений, управляемых событиями. Следовательно, для запуска нашего блейд-приложения нам не нужен ни внешний сервер приложений, ни контейнер сервлетов; JRE будет достаточно:

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

После этого приложение будет доступно по адресу http://localhost:9000 .

3. Понимание архитектуры

Архитектура Blade очень проста:

./34469e5ed0e1263b8ca82c297293442d.png

Он всегда следует одному и тому же жизненному циклу:

  1. Нетти получает запрос
  2. Промежуточные программы выполняются (необязательно)
  3. WebHooks выполняются (необязательно)
  4. Маршрутизация выполняется
  5. Ответ отправляется клиенту
  6. Очистка

Мы рассмотрим вышеуказанные функции в следующих разделах.

4. Маршрутизация

Короче говоря, маршрутизация в MVC — это механизм, используемый для создания привязки между URL-адресом и контроллером.

Blade предоставляет два типа маршрутов: базовый и аннотированный.

4.1. Основные маршруты

Базовые маршруты предназначены для очень небольшого программного обеспечения, такого как микросервисы или минимальные веб-приложения:

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. Аннотированные маршруты

Конечно, для более реалистичных вариантов использования мы можем определить все нужные нам маршруты с помощью аннотаций. Для этого мы должны использовать отдельные классы.

В первую очередь нам нужно создать Controller через аннотацию @Path , который будет сканироваться Blade во время запуска.

Затем нам нужно использовать аннотацию маршрута, связанную с методом 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" ;
}

С другой стороны, если мы не поместим какой-либо параметр метода, маршрут будет перехватывать каждый HTTP-вызов к этому URL -адресу , независимо от глагола.

4.3. Внедрение параметров

Есть несколько способов передать параметры нашим маршрутам. Давайте рассмотрим их на нескольких примерах из документации .

  • Параметр формы:
@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();
}
  • Параметр заголовка:
@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);
}
  • Параметр тела:
@PostRoute("/bodyParam")
public void bodyParam(@BodyParam User user){
System.out.println("user: " + user.toString());
}
  • Параметр Value Object, вызываемый отправкой его атрибутов на маршрут:
@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 также может обслуживать статические ресурсы, если это необходимо, просто помещая их в папку /resources/static .

Например, src/main/resources/static/app.css будет доступен по адресу http://localhost:9000/static/app.css .

5.1. Настройка путей

Мы можем настроить это поведение, программно добавив один или несколько статических путей:

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

Тот же результат можно получить с помощью конфигурации, отредактировав файл src/main/resources/application.properties :

mvc.statics=/custom-static

5.2. Включение списка ресурсов

Мы можем разрешить перечисление содержимого статической папки, функция отключена по умолчанию из соображений безопасности:

blade.showFileList(true);

Или в конфигурации:

mvc.statics.show-list=true

Теперь мы можем открыть http://localhost:9000/custom-static/ , чтобы показать содержимое папки.

5.3. Использование веб-банков

Как показано в руководстве Introduction to WebJars , статические ресурсы, упакованные в виде JAR, также являются приемлемым вариантом.

Blade автоматически предоставляет их по пути /webjars/ .

Например, давайте импортируем Bootstrap в pom.xml :

<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 Specification , такие объекты, как его интерфейс Request и его класс HttpRequest , немного отличаются от тех, к которым мы привыкли.

6.1. Параметры формы

При чтении параметров формы Blade широко использует необязательный элемент Java в результатах методов запроса (все приведенные ниже методы возвращают необязательный объект):

  • запрос (имя строки)
  • queryInt (имя строки)
  • queryLong (имя строки)
  • queryDouble (имя строки)

Они также доступны с запасным значением:

  • Строковый запрос (имя строки, строка по умолчанию)
  • int queryInt (имя строки, int defaultValue)
  • длинный запросLong (имя строки, длинное значение по умолчанию)
  • double queryDouble (имя строки, двойное значение по умолчанию)

Мы можем прочитать параметр формы через свойство automapped:

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

Или из объекта запроса :

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

6.2. JSON-данные

Давайте теперь посмотрим, как объект JSON может быть сопоставлен с POJO:

curl -X POST http://localhost:9000/users -H 'Content-Type: application/json' \ 
-d '{"name":"ForEach","site":"foreach.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. REST-параметры

Параметры RESTFul в красивых URL-адресах, таких как 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 .

6.4. Привязка данных

Blade поддерживает параметры привязки JSON и Form и автоматически прикрепляет их к объекту модели:

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

6.5. Атрибуты запроса и сеанса

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

Интересной особенностью является их универсальный возвращаемый тип <T> T , который избавляет нас от необходимости приводить результат.

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. Утилиты

Следующие служебные методы также доступны из коробки, и они настолько очевидны, что не нуждаются в дополнительных пояснениях:

  • логический isIE()
  • логическое значение isAjax()
  • Тип содержимого строки ()
  • Строка userAgent()

Давайте посмотрим, как объект Request помогает нам работать с файлами cookie, особенно при чтении Optional<Cookie> :

Optional<Cookie> cookieRaw(String name);

Мы также можем получить его как строку , указав значение по умолчанию, которое будет применяться, если cookie не существует:

String cookie(String name, String defaultValue);

Наконец, вот как мы можем прочитать все файлы cookie одновременно ( ключи — это имена файлов cookie, значения — значения файлов cookie):

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

7. HTTP-ответ

Аналогично тому, что было сделано с Request , мы можем получить ссылку на объект Response , просто объявив его как параметр метода маршрутизации:

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

7.1. Простой вывод

Мы можем легко отправить простой вывод вызывающей стороне с помощью одного из удобных методов вывода вместе с HTTP-кодом 200 и соответствующим Content-Type.

Во-первых, мы можем отправить обычный текст:

response.text("Hello World!");

Во-вторых, мы можем создать HTML:

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

В-третьих, мы также можем сгенерировать XML:

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

Наконец, мы можем вывести JSON с помощью String :

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

И даже из POJO, используя автоматическое преобразование JSON:

User user = new User("ForEach", "foreach.com"); 
response.json(user);

7.2. Выходной файл

Загрузка файла с сервера не может быть проще:

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

Первый параметр задает имя файла, который будет загружен, а второй ( объект File , здесь созданный с помощью String ) представляет собой путь к фактическому файлу на сервере.

7.3. Рендеринг шаблона

Blade также может отображать страницы через механизм шаблонов:

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

Каталог шаблонов по умолчанию — src/main/resources/templates/ , поэтому предыдущий однострочный файл будет искать файл src/main/resources/templates/admin/users.html .

Мы узнаем об этом больше позже, в разделе « Шаблон ».

7.4. перенаправить

Перенаправление означает отправку в браузер HTTP-кода 302 вместе с URL-адресом, за которым следует второй запрос GET.

Мы можем перенаправить на другой маршрут или на внешний URL:

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

На данный момент мы должны привыкнуть к простоте Blade. Итак, давайте посмотрим, как мы можем написать неистекающий файл cookie в одной строке кода:

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

Действительно, удалить Cookie так же просто:

response.removeCookie("cookie-name");

7.6. Другие операции

Наконец, объект Response предоставляет нам несколько других методов для выполнения таких операций, как запись заголовков, установка Content-Type, установка кода состояния и так далее.

Давайте кратко рассмотрим некоторые из них:

  • Статус ответа (статус int)
  • Заголовки карт()
  • Ответ не найден()
  • Файлы cookie карты ()
  • Тип содержимого ответа (тип содержимого строки)
  • пустое тело(данные @NonNull byte[])
  • Заголовок ответа (имя строки, значение строки)

8. Веб-хуки

WebHook — это перехватчик, через который мы можем запускать код до и после выполнения метода маршрутизации .

Мы можем создать WebHook, просто реализовав функциональный интерфейс WebHook и переопределив метод before() :

@FunctionalInterface
public interface WebHook {

boolean before(RouteContext ctx);

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

Как мы видим, after() — это метод по умолчанию, поэтому мы будем переопределять его только при необходимости.

8.1. Перехват каждого запроса

Аннотация @Bean указывает платформе сканировать класс с помощью контейнера IoC.

Следовательно, WebHook с его аннотацией будет работать глобально, перехватывая запросы к каждому URL-адресу:

@Bean
public class ForEachHook implements WebHook {

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

8.2. Сужение до URL

Мы также можем перехватывать определенные URL-адреса, чтобы выполнять код только вокруг этих методов маршрута:

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

8.3. ПО промежуточного слоя

Промежуточные программы — это приоритетные веб-перехватчики, которые выполняются перед любым стандартным веб-перехватчиком:

public class ForEachMiddleware implements WebHook {

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

Их просто нужно определить без аннотации @Bean , а затем зарегистрировать декларативно через use() :

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

Кроме того, Blade поставляется со следующими встроенными промежуточными программами, связанными с безопасностью, названия которых говорят сами за себя:

9. Конфигурация

В Blade конфигурация совершенно необязательна, потому что по соглашению все работает «из коробки». Однако мы можем настроить параметры по умолчанию и ввести новые атрибуты в файле src/main/resources/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 , переопределив метод 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");
}
}

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. Работа с несколькими средами

При развертывании нашего приложения в другой среде нам может потребоваться указать другие параметры, например, связанные с подключением к базе данных. Вместо ручной замены файла application.properties Blade предлагает нам способ настроить приложение для разных сред. Мы можем просто сохранить application.properties со всеми настройками разработки, а затем создать в той же папке другие файлы, например application-prod.properties , содержащие только отличающиеся настройки .

Затем во время запуска мы можем указать среду, которую хотим использовать, и платформа объединит файлы, используя наиболее специфические настройки из application-prod.properties и все остальные настройки из файла application.properties по умолчанию:

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

10. Шаблоны

Шаблоны в Blade — это модульный аспект. Хотя он интегрирует очень простой механизм шаблонов, для любого профессионального использования представлений мы должны полагаться на внешний механизм шаблонов. Затем мы можем выбрать движок из доступных в репозитории blade-template-engines на GitHub, таких как FreeMarker , Jetbrick , Pebble и Velocity , или даже создать оболочку для импорта другого понравившегося шаблона.

Автор Blade предлагает Jetbrick , еще один умный китайский проект.

10.1. Использование механизма по умолчанию

Шаблон по умолчанию работает, анализируя переменные из разных контекстов через нотацию ${} :

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

10.2. Подключение внешнего движка

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

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

10.3. Wrapping a New Engine

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

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.

11. Logging

Blade uses slf4j-api as logging interface.

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 :

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

11.1. Customizing the Integrated Logger

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

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.1. Custom Exception Handling

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.

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

@Bean
public class GlobalExceptionHandler extends DefaultExceptionHandler {

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

12.2. Custom Error Pages

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

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

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.

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

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

13. Scheduled Tasks

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
public class ScheduleExample {

@Schedule(name = "foreachTask", 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 .

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

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

14. Events

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

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.

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", "ForEach");
})
.start(App.class, args);

15. Session Implementation

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 .

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:

@Bean
public class SessionConfig implements BladeLoader {

@Override
public void load(Blade blade) {
blade.sessionType(new RedisSession());
}
}

16. Command Line Arguments

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

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

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 :

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 :

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

17. Running in the 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.

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's console, however, will not display ANSI colors correctly, pouring out their codes instead:

./7322d3c1562dd441bda5d310c9dff18a.png

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

./4d10d179601d7833443015e6b5a80470.png

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

./701e750c32309e336f70784dbd019b86.png

17.3. Visual Studio Code

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

Pressing Ctrl+F5 will then run the project:

./643c9790572eb9a90aec5692557f4e65.png

18. Conclusion

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

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 .

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