Introduzione a Ninja Framework

1. Panoramica

Al giorno d'oggi, ci sono molti framework basati su JEE come Spring, Play e Grails disponibili per lo sviluppo di applicazioni web.

Potremmo avere le nostre ragioni per scegliere uno di loro rispetto agli altri. Tuttavia, la nostra scelta dipende anche dal caso d'uso e dal problema che stiamo cercando di risolvere.

In questo tutorial introduttivo esploreremo il framework web Ninja e creeremo una semplice applicazione web. Allo stesso tempo, esamineremo alcune delle funzionalità di base che fornisce.

2. Ninja

Ninja è un framework web completo, ma leggero, che utilizza le librerie Java esistenti per portare a termine il lavoro.

Avendo funzionalità dal rendering HTML a JSON, dalla persistenza al test, è una soluzione completa per la creazione di applicazioni web scalabili.

Segue il paradigma della convenzione sulla configurazione e classifica il codice in pacchetti come modelli , controller e servizi .

Ninja utilizza le popolari librerie Java per funzionalità chiave come Jackson per il rendering JSON / XML, Guice per la gestione delle dipendenze, Hibernate per la persistenza e Flyway per le migrazioni di database .

Per uno sviluppo rapido, offre SuperDevMode per il ricaricamento a caldo del codice. Quindi, ci permette di vedere i cambiamenti istantaneamente nell'ambiente di sviluppo.

3. Configurazione

Ninja richiede un set standard di strumenti per creare un'applicazione web:

  • Java 1.8 o successivo
  • Maven 3 o versioni successive
  • IDE (Eclipse o IntelliJ)

Useremo un archetipo Maven per impostare rapidamente il progetto Ninja. Ci chiederà di fornire un ID gruppo, un ID artefatto e un numero di versione, seguito dal nome del progetto:

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

Oppure, per un progetto Maven esistente, possiamo aggiungere l'ultima dipendenza ninja-core al pom.xml :

 org.ninjaframework ninja-core 6.5.0 

Quindi, eseguiremo il comando Maven per compilare i file per la prima volta:

mvn clean install

Infine, eseguiamo l'app utilizzando un comando Maven fornito da Ninja:

mvn ninja:run

Ecco! La nostra applicazione viene avviata e sarà accessibile da localhost: 8080 :

4. Struttura del progetto

Diamo un'occhiata alla struttura del progetto simile a Maven creata da Ninja:

Il framework crea alcuni pacchetti basati su convenzioni.

Le classi Java sono classificate nelle directory conf , controller , models e services in src / main / java.

Allo stesso modo, src / test / java contiene le classi di unit test corrispondenti.

La directory views in src / main / java contiene i file HTML. Inoltre, la directory src / main / java / assets contiene risorse come immagini, fogli di stile e file JavaScript.

5. Controller

Siamo tutti pronti per discutere alcune funzionalità di base del framework. Un controller è una classe che riceve una richiesta e restituisce la risposta con risultati specifici.

Innanzitutto, discutiamo alcune convenzioni da seguire:

  • Crea una classe nel pacchetto controller e aggiungi al nome il suffisso Controller
  • Un metodo che serve la richiesta deve restituire l'oggetto della classe Result

Creiamo la classe ApplicationController con un metodo semplice per eseguire il rendering dell'HTML:

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

Qui, il metodo index eseguirà il rendering di un HTML chiamando il metodo html della classe Results . L' oggetto Result contiene tutto ciò che è necessario per eseguire il rendering del contenuto come codice di risposta, intestazioni e cookie.

Nota: l' annotazione @Singleton di Guice consente solo un'istanza del controller in tutta l'app .

6. Visualizza

Per il metodo index , Ninja cercherà il file HTML - index .ftl.html nella directory views / ApplicationController .

Ninja utilizza il motore di modelli Freemarker per il rendering HTML . Quindi, tutti i file sotto le visualizzazioni dovrebbero avere l' estensione .ftl.html .

Creiamo il file i ndex .ftl.html per il metodo index :

 Ninja: Index User Json 

Qui, abbiamo utilizzato il tag i18n fornito da Ninja per ottenere la proprietà helloMsg dal file message.properties . Ne discuteremo ulteriormente nella sezione sull'internazionalizzazione più avanti.

7. Itinerario

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.