Einführung in Ninja Framework

1. Übersicht

Heutzutage stehen viele JEE-basierte Frameworks wie Spring, Play und Grails für die Entwicklung von Webanwendungen zur Verfügung.

Wir haben vielleicht unsere Gründe, einen von ihnen den anderen vorzuziehen. Unsere Wahl hängt jedoch auch vom Anwendungsfall und dem Problem ab, das wir lösen möchten.

In diesem Einführungs-Tutorial werden wir das Ninja-Webframework untersuchen und eine einfache Webanwendung erstellen. Gleichzeitig werden wir einige der grundlegenden Funktionen untersuchen, die es bietet.

2. Ninja

Ninja ist ein Full-Stack-Webframework mit geringem Gewicht, das vorhandene Java-Bibliotheken verwendet, um die Arbeit zu erledigen.

Mit Funktionen von HTML bis JSON-Rendering und beständigem Testen ist dies eine Komplettlösung für die Erstellung skalierbarer Webanwendungen.

Es folgt dem Paradigma der Konvention über Konfiguration und kategorisiert den Code in Paketen wie Modellen , Controllern und Diensten .

Ninja verwendet beliebte Java-Bibliotheken für wichtige Funktionen wie Jackson für das JSON / XML-Rendering, Guice für das Abhängigkeitsmanagement, Hibernate für die Persistenz und Flyway für Datenbankmigrationen .

Für eine schnelle Entwicklung bietet es SuperDevMode zum Hot-Reload des Codes. So können wir die Änderungen sofort in der Entwicklungsumgebung sehen.

3. Setup

Ninja benötigt einen Standardsatz von Tools, um eine Webanwendung zu erstellen:

  • Java 1.8 oder höher
  • Maven 3 oder höher
  • IDE (Eclipse oder IntelliJ)

Wir werden einen Maven-Archetyp verwenden, um das Ninja-Projekt schnell einzurichten. Wir werden aufgefordert, eine Gruppen-ID, eine Artefakt-ID und eine Versionsnummer anzugeben, gefolgt von einem Projektnamen:

mvn archetype:generate -DarchetypeGroupId=org.ninjaframework \ -DarchetypeArtifactId=ninja-servlet-archetype-simple

Oder für ein vorhandenes Maven-Projekt können wir der pom.xml die neueste Ninja-Core-Abhängigkeit hinzufügen :

 org.ninjaframework ninja-core 6.5.0 

Dann führen wir den Befehl Maven aus, um die Dateien zum ersten Mal zu kompilieren:

mvn clean install

Zuletzt führen wir die App mit einem von Ninja bereitgestellten Maven-Befehl aus:

mvn ninja:run

Voila! Unsere Anwendung wird gestartet und ist unter localhost verfügbar: 8080 :

4. Projektstruktur

Werfen wir einen Blick auf die von Ninja erstellte Maven-ähnliche Projektstruktur:

Das Framework erstellt einige Pakete basierend auf Konventionen.

Die Java-Klassen sind in src / main / java in die Verzeichnisse conf , controller , models und services unterteilt .

Ebenso enthält src / test / java die entsprechenden Unit-Test-Klassen.

Das Verzeichnis views unter src / main / java enthält die HTML-Dateien. Das Verzeichnis src / main / java / assets enthält Ressourcen wie Bilder, Stylesheets und JavaScript-Dateien.

5. Controller

Wir sind alle bereit, einige grundlegende Funktionen des Frameworks zu diskutieren. Ein Controller ist eine Klasse, die eine Anforderung empfängt und die Antwort mit bestimmten Ergebnissen zurückgibt.

Lassen Sie uns zunächst einige Konventionen diskutieren, die zu befolgen sind:

  • Erstellen Sie eine Klasse im Controller- Paket und fügen Sie den Namen Controller hinzu
  • Eine Methode, die die Anforderung bedient, muss das Objekt der Result- Klasse zurückgeben

Erstellen wir die ApplicationController- Klasse mit einer einfachen Methode zum Rendern des HTML-Codes:

@Singleton public class ApplicationController { public Result index() { return Results.html(); } }

Hier rendert die Indexmethode einen HTML-Code, indem sie die HTML- Methode der Results- Klasse aufruft . Das Ergebnisobjekt enthält alles, was zum Rendern des Inhalts erforderlich ist, z. B. Antwortcode, Header und Cookies.

Hinweis: Die @ Singleton- Annotation von Guice erlaubt nur eine Instanz des Controllers in der gesamten App .

6. Anzeigen

Für die Indexmethode sucht Ninja nach der HTML-Datei - index .ftl.html im Verzeichnis views / ApplicationController .

Ninja verwendet die Freemarker-Vorlagen-Engine für das HTML-Rendering . Daher sollten alle Dateien unter Ansichten die Erweiterung .ftl.html haben .

Lassen Sie uns erstellen die i ndex .ftl.html - Datei für den Index - Methode:

 Ninja: Index User Json 

Hier haben wir das von Ninja bereitgestellte i18n- Tag verwendet, um die helloMsg- Eigenschaft aus der Datei message.properties abzurufen . Wir werden dies später im Abschnitt zur Internationalisierung weiter diskutieren.

7. Route

Next, we'll define the route for the request to reach the index method.

Ninja uses the Routes class in the conf package to map a URL to a particular method of the controller.

Let's add a route to access the index method of the ApplicationController:

public class Routes implements ApplicationRoutes { @Override public void init(Router router) { router.GET().route("/index").with(ApplicationController::index); } }

That's it! We're all set to access the index page at localhost:8080/index:

8. JSON Rendering

As already discussed, Ninja uses Jackson for JSON rendering. To render JSON content, we can use the json method of the Results class.

Let's add the userJson method in the ApplicationController class and render the content of a simple HashMap in JSON:

public Result userJson() { HashMap userMap = new HashMap(); userMap.put("name", "Norman Lewis"); userMap.put("email", "[email protected]"); return Results.json().render(user); }

Then, we'll add the required routing to access the userJson:

router.GET().route("/userJson").with(ApplicationController::userJson);

Now, we can render JSON using localhost:8080/userJson:

9. Service

We can create a service to keep the business logic separate from the controller and inject our service wherever required.

First, let's create a simple UserService interface to define the abstraction:

public interface UserService { HashMap getUserMap(); }

Then, we'll implement the UserService interface in the UserServiceImpl class and override the getUserMap method:

public class UserServiceImpl implements UserService { @Override public HashMap getUserMap() { HashMap userMap = new HashMap(); userMap.put("name", "Norman Lewis"); userMap.put("email", "[email protected]"); return userMap; } }

Then, we'll bind the UserService interface with the UserServiceImpl class using Ninja's dependency injection feature provided by Guice.

Let's add the binding in the Module class available in the conf package:

@Singleton public class Module extends AbstractModule { protected void configure() { bind(UserService.class).to(UserServiceImpl.class); } }

Last, we'll inject the UserService dependency in the ApplicationController class using the @Inject annotation:

public class ApplicationController { @Inject UserService userService; // ... }

Thus, we're all set to use the UserService‘s getUserMap method in the ApplicationController:

public Result userJson() { HashMap userMap = userService.getUserMap(); return Results.json().render(userMap); }

10. Flash Scope

Ninja provides a simple yet efficient way to handle success and error messages from requests through its feature called Flash Scope.

To use it in the controller, we'll add the FlashScope argument to the method:

public Result showFlashMsg(FlashScope flashScope) { flashScope.success("Success message"); flashScope.error("Error message"); return Results.redirect("/home"); }

Note: The redirect method of the Results class redirects the target to the provided URL.

Then, we'll add a routing /flash to the showFlashMsg method and modify the view to show the flash messages:

 ${flash.error} ${flash.success} 

Now, we can see the FlashScope in action at localhost:8080/flash:

11. Internationalization

Ninja provides a built-in internationalization feature that is easy to configure.

First, we'll define the list of supported languages in the application.conf file:

application.languages=fr,en

Then, we'll create the default properties file – messages.properties for English – with key-value pairs for messages:

header.home=Home! helloMsg=Hello, welcome to Ninja Framework!

Similarly, we can add the language code in the file name for a language-specific properties file — for instance, message_fr.properties file for French:

header.home=Accueil! helloMsg=Bonjour, bienvenue dans Ninja Framework!

Once the configurations are ready, we can easily enable internationalization in the ApplicationController class.

We've got two ways, either by using the Lang class or the Messages class:

@Singleton public class ApplicationController { @Inject Lang lang; @Inject Messages msg; // ... }

Then, using the Lang class, we can set the language of the result:

Result result = Results.html(); lang.setLanguage("fr", result);

Similarly, using the Messages class, we can get a language-specific message:

Optional language = Optional.of("fr"); String helloMsg = msg.get("helloMsg", language).get();

12. Persistence

Ninja supports JPA 2.0 and utilizes Hibernate to enable persistence in the web application. Also, it offers built-in H2 database support for rapid development.

12.1. Model

We require an Entity class to connect with a table in the database. For this, Ninja follows the convention of looking for the entity classes in the models package. So, we'll create the User entity class there:

@Entity public class User { @Id @GeneratedValue(strategy=GenerationType.AUTO) Long id; public String firstName; public String email; }

Then, we'll configure Hibernate and set the details for the database connection.

12.2. Configuration

For Hibernate configuration, Ninja expects the persistence.xml file to be in the src/main/java/META-INF directory:

    org.hibernate.jpa.HibernatePersistenceProvider          

Then, we'll add the database connection details to application.conf:

ninja.jpa.persistence_unit_name=dev_unit db.connection.url=jdbc:h2:./devDb db.connection.username=sa db.connection.password=

12.3. EntityManager

Last, we'll inject the instance of the EntityManager in the ApplicationController using Guice's Provider class:

public class ApplicationController { @Inject Provider entityManagerProvider; // ... }

So, we're ready to use the EntityManager to persist the User object:

@Transactional public Result insertUser(User user) { EntityManager entityManager = entityManagerProvider.get(); entityManager.persist(user); entityManager.flush(); return Results.redirect("/home"); }

Similarly, we can use the EntityManager to read the User object from the DB:

@UnitOfWork public Result fetchUsers() { EntityManager entityManager = entityManagerProvider.get(); Query q = entityManager.createQuery("SELECT x FROM User x"); List users = (List) q.getResultList(); return Results.json().render(users); }

Here, Ninja's @UnitOfWork annotation will handle everything about the database connections without dealing with transactions. Hence, it can prove handy for read-only queries, where we usually don't require transactions.

13. Validation

Ninja provides built-in support for bean validations by following the JSR303 specifications.

Let's examine the feature by annotating a property in the User entity with the @NotNull annotation:

public class User { // ... @NotNull public String firstName; }

Then, we'll modify the already discussed insertUser method in the ApplicationController to enable the validation:

@Transactional public Result insertUser(FlashScope flashScope, @JSR303Validation User user, Validation validation) { if (validation.getViolations().size() > 0) { flashScope.error("Validation Error: User can't be created"); } else { EntityManager entityManager = entitiyManagerProvider.get(); entityManager.persist(user); entityManager.flush(); flashScope.success("User '" + user + "' is created successfully"); } return Results.redirect("/home"); }

We've used Ninja's @JSR303Validation annotation to enable the validation of the User object. Then, we've added the Validation argument to work with validations through methods like hasViolations, getViolations, and addViolation.

Last, the FlashScope object is used to show the validation error on the screen.

Note: Ninja follows the JSR303 specifications for bean validations. However, the JSR380 specification (Bean Validation 2.0) is the new standard.

14. Conclusion

In this article, we explored the Ninja web framework — a full-stack framework that provides handy features using popular Java libraries.

To begin with, we created a simple web application using controllers, models, and services. Then, we enabled JPA support in the app for persistence.

At the same time, we saw a few basic features like Routes, JSON rendering, Internationalization, and Flash Scopes.

Last, we explored the validation support provided by the framework.

As usual, all the code implementations are available over on GitHub.