Blade - Buku Panduan Lengkap

1. Ikhtisar

Blade adalah framework MVC Java 8+ yang mungil, dibangun dari awal dengan beberapa tujuan yang jelas: menjadi mandiri, produktif, elegan, intuitif, dan super cepat.

Banyak kerangka kerja berbeda yang menginspirasi desainnya: Node's Express, Python's Flask, dan Golang's Macaron / Martini.

Blade juga merupakan bagian dari proyek ambisius yang lebih besar, Let's Blade. Ini mencakup koleksi heterogen pustaka kecil lainnya, dari pembuatan Captcha hingga konversi JSON, dari pembuatan template ke koneksi database sederhana.

Namun, dalam tutorial ini, kami akan fokus hanya pada MVC.

2. Memulai

Pertama-tama, mari buat proyek Maven kosong dan tambahkan dependensi MVC Blade terbaru di pom.xml :

 com.bladejava blade-mvc 2.0.14.RELEASE  

2.1. Menggabungkan Aplikasi Blade

Karena aplikasi kita akan dibuat sebagai JAR, itu tidak akan memiliki folder / lib , seperti di WAR. Akibatnya, hal ini membawa kita pada masalah bagaimana menyediakan JAR blade-mvc , bersama dengan JAR lain yang mungkin kita perlukan, ke aplikasi kita.

Berbagai cara untuk melakukan ini, masing-masing dengan pro dan kontra, dijelaskan dalam tutorial Cara Membuat JAR yang Dapat Dieksekusi dengan Maven.

Untuk kesederhanaan, kita akan menggunakan teknik Maven Assembly Plugin , yang meledakkan JAR yang diimpor di pom.xml dan kemudian menggabungkan semua kelas menjadi satu dalam satu uber-JAR.

2.2. Menjalankan Aplikasi Blade

Blade didasarkan pada Netty , kerangka kerja aplikasi jaringan berbasis peristiwa asinkron yang menakjubkan. Oleh karena itu, untuk menjalankan aplikasi berbasis Blade, kami tidak memerlukan Server Aplikasi eksternal atau Penampung Servlet; JRE sudah cukup:

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

Setelah itu, aplikasi akan dapat diakses di URL // localhost: 9000 .

3. Memahami Arsitektur

Arsitektur Blade sangat mudah:

Itu selalu mengikuti siklus hidup yang sama:

  1. Netty menerima permintaan
  2. Middlewares dijalankan (opsional)
  3. WebHooks dijalankan (opsional)
  4. Perutean dilakukan
  5. Tanggapan dikirim ke klien
  6. Membersihkan

Kami akan menjelajahi fungsi di atas di bagian selanjutnya.

4. Perutean

Singkatnya, perutean di MVC adalah mekanisme yang digunakan untuk membuat pengikatan antara URL dan Pengontrol.

Blade menyediakan dua jenis rute: rute dasar dan beranotasi.

4.1. Rute Dasar

Rute dasar ditujukan untuk perangkat lunak yang sangat kecil, seperti layanan mikro atau aplikasi web minimal:

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

Nama metode yang digunakan untuk mendaftarkan rute sesuai dengan kata kerja HTTP yang akan digunakan untuk meneruskan permintaan. Sesimpel itu.

Dalam hal ini, kami mengembalikan teks, tetapi kami juga dapat merender halaman, seperti yang akan kita lihat nanti di tutorial ini.

4.2. Rute Beranotasi

Tentunya, untuk kasus penggunaan yang lebih realistis, kita dapat menentukan semua rute yang kita perlukan menggunakan anotasi. Kita harus menggunakan kelas terpisah untuk itu.

Pertama-tama, kita perlu membuat Controller melalui anotasi @Path , yang akan dipindai oleh Blade selama startup.

Kemudian kita perlu menggunakan anotasi rute yang terkait dengan metode HTTP yang ingin kita intersep:

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

Kita juga dapat menggunakan anotasi @Route sederhana dan menentukan metode HTTP sebagai parameter:

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

Di sisi lain, jika kita tidak meletakkan parameter metode apa pun, rute akan mencegat setiap panggilan HTTP ke URL itu , apa pun kata kerjanya.

4.3. Injeksi Parameter

Ada beberapa cara untuk meneruskan parameter ke rute kita. Mari kita jelajahi dengan beberapa contoh dari dokumentasi.

  • Parameter formulir:
@GetRoute("/home") public void formParam(@Param String name){ System.out.println("name: " + name); } 
  • Parameter tenang:
@GetRoute("/users/:uid") public void restfulParam(@PathParam Integer uid){ System.out.println("uid: " + uid); } 
  • Parameter unggahan file:
@PostRoute("/upload") public void fileParam(@MultipartParam FileItem fileItem){ byte[] file = fileItem.getData(); } 
  • Parameter header:
@GetRoute("/header") public void headerParam(@HeaderParam String referer){ System.out.println("Referer: " + referer); } 
  • Parameter cookie:
@GetRoute("/cookie") public void cookieParam(@CookieParam String myCookie){ System.out.println("myCookie: " + myCookie); } 
  • Parameter tubuh:
@PostRoute("/bodyParam") public void bodyParam(@BodyParam User user){ System.out.println("user: " + user.toString()); } 
  • Parameter Value Object, dipanggil dengan mengirimkan atributnya ke rute:
@PostRoute("/voParam") public void voParam(@Param User user){ System.out.println("user: " + user.toString()); } 

5. Sumber Daya Statis

Blade juga dapat menyajikan sumber daya statis jika diperlukan, cukup dengan meletakkannya di dalam folder / resources / static .

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

5.1. Customizing the Paths

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:

mvc.statics=/custom-static 

5.2. Enabling the Resources Listing

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 //localhost:9000/custom-static/ to show the content of the folder.

5.3. Using WebJars

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

Blade exposes them automatically under the /webjars/ path.

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

 org.webjars bootstrap 4.2.1  

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

6. HTTP Request

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.

6.1. Form Parameters

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

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

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

Or from the Request object:

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

6.2. JSON Data

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

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

POJO (annotated with Lombok for readability):

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:

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

6.3. RESTful Parameters

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

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

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

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

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

6.4. Data Binding

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

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

6.5. Request and Session Attributes

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

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

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, which saves us from the need of casting the result.

6.6. Headers

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 allHeaders = request.headers(); 

6.7. Utilities

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

Let's see how the Request object helps us deal with Cookies, specifically when reading the Optional:

Optional cookieRaw(String name); 

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

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

Map cookies = request.cookies(); 

7. HTTP Response

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:

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

7.1. Simple Output

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.

Firstly, we can send a plain text:

response.text("Hello World!");

Secondly, we can produce an HTML:

response.html("");

Thirdly, we can likewise generate an XML:

response.xml("Hello World!");

Finally, we can output JSON using a String:

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

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

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

7.2. File Output

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

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.

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

7.4. Redirect

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

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

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

7.5. Writing 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:

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

Indeed, removing a Cookie is equally simple:

response.removeCookie("cookie-name"); 

7.6. Other Operations

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.

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

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

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

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

8.1. Intercepting Every Request

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

A WebHook annotated with it will consequently work globally, intercepting requests to every 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

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

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

8.3. Middlewares

Middlewares are prioritized WebHooks, which get executed before any standard 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():

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:

  • BasicAuthMiddleware
  • CorsMiddleware
  • XssMiddleware
  • CsrfMiddleware

9. Configuration

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.

9.1. Reading the Configuration

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 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 version = WebContext.blade().env("app.version"); String authors = WebContext.blade().env("app.authors","Unknown authors"); } } 

9.2. Configuration Attributes

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 map = environment.getPrefix("app"); String version = map.get("version").toString(); String authors = map.get("authors","Unknown authors").toString(); 

9.3. Handling Multiple Environments

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.

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:

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

10. Templating

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.

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

10.1. Using the Default Engine

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

10.2. Plugging in an External Engine

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

 com.bladejava blade-template-jetbrick 0.1.3  

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

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:

 com.bladejava blade-mvc ${blade.version}   com.bladejava blade-log    

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 BaeldungException) { BaeldungException baeldungException = (BaeldungException) e; String msg = baeldungException.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:

    500 Internal Server Error   

The following error occurred: "${message}"

 ${stackTrace} 

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

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

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", "Baeldung"); }) .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:

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

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

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:

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.

Seperti biasa, kami dapat menemukan kode sumber dari contoh tersebut di GitHub.