В предыдущей статье я сделал краткое введение о том, что такое Пиппо . В предложении Pippo — это микро- фреймворк с открытым исходным кодом (Apache License) micro ( около 100K) на Java, с минимальными зависимостями (только slf4j-api для регистрации) и кривой быстрого обучения.
Я думаю, что в Pippo есть много замечательных вещей (модульность, простота в использовании, встроенная поддержка для многих встроенных веб-серверов — jetty, undertow, tjws -, несколько шаблонизаторов — freemarker, jade, trimou, pebble, groovy -, многократный контент типы — обычный текст, json, xml, yaml -, интеграция с Spring и Guice, интернационализация и многое другое), но, на мой взгляд, Pippo хорошо сочетает создание страниц, созданных сервером, с API-интерфейсами RESTful. Чтобы доказать это, я хочу показать вам в этой статье, как создать минималистичный
CRUD (
C reate,
R etrieve,
U pdate,
D elete) с использованием Pippo и AngularJS.
Прежде всего, нужно создать новое приложение ( CrudNgApplication ) и добавить несколько маршрутов:
public class CrudNgApplication extends ControllerApplication { private ContactService contactService; public ContactService getContactService() { return contactService; } @Override protected void onInit() { /* * initiate the contact service */ contactService = new InMemoryContactService(); /* * add handlers for static resources */ GET(new WebjarsResourceHandler()); GET(new PublicResourceHandler()); /* * redirect “/” to “/contacts” */ GET("/", new RedirectHandler("/contacts")); /* * Server-generated HTML pages */ GET("/contacts", new TemplateHandler("contacts")); GET("/contact/.*", new TemplateHandler("contact")); /* * RESTful API */ GET("/api/contacts", CrudNgApiController.class, "getContacts"); GET("/api/contact/{id}", CrudNgApiController.class, "getContact"); DELETE("/api/contact/{id}", CrudNgApiController.class, "deleteContact"); POST("/api/contact", CrudNgApiController.class, "saveContact"); } }
Нам нужны контакты и шаблоны контактов для запросов «
/ contacts » и «
/contact/.* ». Мы будем использовать freemarker в качестве движка шаблонов для этой демонстрации, но вы можете использовать любой движок шаблонов, поддерживаемый из коробки.
Итак, мы должны поместить эти шаблоны в «
ресурсы / шаблоны ». В этой папке у нас есть 3 (три) файла шаблона:
- base.ftl
<#macro page title> <!DOCTYPE html> <html ng-app="crudNgApp"> <head> <meta charset="utf-8"> <meta content="IE=edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${title}</title> <link href="${webjarsAt('bootstrap/3.3.1/css/bootstrap.min.css')}" rel="stylesheet"> <link href="${webjarsAt('font-awesome/4.2.0/css/font-awesome.min.css')}" rel="stylesheet"> <link href="${publicAt('css/style.css')}" rel="stylesheet"> <base href="${appPath}/"> </head> <body> <div class="container"> <#nested/> <script src="${webjarsAt('jquery/1.11.1/jquery.min.js')}"></script> <script src="${webjarsAt('bootstrap/3.3.1/js/bootstrap.min.js')}"></script> <script src="${webjarsAt('angularjs/1.3.6/angular.min.js')}"></script> <script src="${publicAt('js/crudNgApp.js')}"></script> </div> </body> </html> </#macro>
- contacts.ftl
<#import "base.ftl" as base/> <@base.page title="Contacts"> <div class="page-header"> <h2>Contacts <small>RESTful JSON APIs + AngularJS</small></h2> </div> <div ng-controller="ContactsCtrl"> <div class="buttons pull-right"> <a id="addContactButton" type="button" class="btn btn-primary" href="contact/0"/"><i class="fa fa-plus"></i> Add Contact</a> </div> <table class="table table-striped table-bordered table-hover"> <thead> <tr> <th>#</th> <th>Name</th> <th>Phone</th> <th colspan='2'>Address</th> </tr> </thead> <tbody> <tr ng-repeat="contact in contacts"> <td>{{contact.id}}</td> <td>{{contact.name}}</td> <td>{{contact.phone}}</td> <td>{{contact.address}}</td> <td style="text-align: right;"> <div class="btn-group btn-group-xs"> <a class="btn btn-default" href="contact/{{contact.id}}"><i class="fa fa-pencil"></i> Edit</a> <button class="btn btn-default" ng-click="deleteContact(contact.id)"><i class="fa fa-trash"></i> Delete</button> </div> </td> </tr> </tbody> </table> </div> </@base.page>
- contact.ftl
<#import "base.ftl" as base/> <@base.page title="Contact"> <div class="page-header"> <h2>Contact <small>RESTful JSON APIs + AngularJS</small></h2> </div> <div ng-controller="ContactCtrl"> <form novalidate class="form-horizontal" role="form" name="contactForm" ng-submit="postContact(contactForm.$valid)"> <div class="form-group" ng-class="{ 'has-error' : contactForm.name.$invalid && !contactForm.name.$pristine }"> <label for="name" class="col-sm-2 control-label">Name</label> <div class="col-sm-9"> <input type="text" class="form-control" id="name" name="name" ng-model="name" required ng-minlength="3"> <p ng-show="contactForm.name.$error.minlength" class="help-block">The entered name is too short.</p> </div> </div> <div class="form-group" ng-class="{ 'has-error' : contactForm.phone.$invalid && !contactForm.phone.$pristine }"> <label for="phone" class="col-sm-2 control-label">Phone</label> <div class="col-sm-9"> <input type="text" class="form-control" id="phone" name="phone" ng-model="phone" required ng-minlength="7"> <p ng-show="contactForm.phone.$error.minlength" class="help-block">The entered phone number is too short.</p> </div> </div> <div class="form-group" ng-class="{ 'has-error' : contactForm.address.$invalid && !contactForm.address.$pristine }"> <label for="address" class="col-sm-2 control-label">Address</label> <div class="col-sm-9"> <input type="text" class="form-control" id="address" name="address" ng-model="address" required > <p ng-show="contactForm.address.$invalid && !contactForm.address.$pristine" class="help-block">The address is required.</p> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-9"> <button type="submit" class="btn btn-default btn-primary" ng-disabled="contactForm.$invalid">Submit</button> <a type="submit" class="btn" href="contacts">Cancel</a> </div> </div> </form> </div> </@base.page>
Мы должны написать логику для нашего приложения angularjs, поэтому я создал
файл crudNgApp.js в папке «
resources / public / js »:
var crudNgApp = angular.module("crudNgApp", []); var baseUrl = angular.element("base").attr("href"); // // Contacts Controller is responsible for listing and deleting a contact // see crudng/contacts.ftl // crudNgApp.controller("ContactsCtrl", [ '$scope', '$http', '$location', '$window', function($scope, $http, $location, $window) { // // Retrieve the contacts from Pippo // $http({ method : 'GET', url : baseUrl + 'api/contacts' }).success(function(data, status, headers, config) { // update the ng-models on success $scope.contacts = data; }).error(function(data, status, headers, config) { // alert on failure alert("Whoops!\n\n" + JSON.stringify({ message : data.statusMessage, code : data.statusCode, method : data.requestMethod, uri : data.requestUri }, null, 2)); }); // // Delete the specified contact $scope.deleteContact = function(id) { $http({ method : 'DELETE', url : baseUrl + 'api/contact/' + id }).success(function(data, status, headers, config) { // redirect on success $window.location.href = baseUrl; }).error(function(data, status, headers, config) { // alert on failure alert("Whoops!\n\n" + JSON.stringify({ message : data.statusMessage, code : data.statusCode, method : data.requestMethod, uri : data.requestUri }, null, 2)); }); }; } ]); // // Contact Controller is responsible for editing a contact // see crudng/contact.ftl // crudNgApp.controller("ContactCtrl", [ '$scope', '$http', '$location', '$window', function($scope, $http, $location, $window) { // Extract contact id from the url var contactUrl = $location.absUrl(); var id = contactUrl.substring(contactUrl.lastIndexOf('/') + 1); // // Retrieve the specified contact from Pippo // $http({ method : 'GET', url : baseUrl + 'api/contact/' + id }).success(function(data, status, headers, config) { // update the ng-models on success $scope.id = data.id; $scope.name = data.name; $scope.phone = data.phone; $scope.address = data.address; }).error(function(data, status, headers, config) { // alert on failure alert("Whoops!\n\n" + JSON.stringify({ message : data.statusMessage, code : data.statusCode, method : data.requestMethod, uri : data.requestUri }, null, 2)); }); // // Post the new/updated contact back to Pippo // $scope.postContact = function(isValid) { if (isValid) { // prepare the contact from the ng-models var dataObj = { id : $scope.id, name : $scope.name, phone : $scope.phone, address : $scope.address }; $http({ method : 'POST', url : baseUrl + 'api/contact', data : dataObj }).success(function(data, status, headers, config) { // redirect on success $window.location.href = baseUrl; }).error(function(data, status, headers, config) { // alert on failure alert("Whoops!\n\n" + JSON.stringify({ message : data.statusMessage, code : data.statusCode, method : data.requestMethod, uri : data.requestUri }, null, 2)); }); } }; } ]);
Конечно, мы должны создать контроллер (
CrudNgApiController ), который инкапсулирует наш бизнес:
public class CrudNgApiController extends Controller { private static final Logger log = LoggerFactory.getLogger(CrudNgApiController.class); ContactService getContactService() { return ((CrudNgApplication) getApplication()).getContactService(); } public void getContacts() { getResponse().xml().contentType(getRequest()).send(getContactService().getContacts()); log.info("Retrieved all contacts"); } public void getContact(@Param("id") int id) { Contact contact = (id > 0) ? getContactService().getContact(id) : new Contact(); getResponse().xml().contentType(getRequest()).send(contact); log.info("Retrieved contact #{} '{}'", contact.getId(), contact.getName()); } public void deleteContact(@Param("id") int id) { if (id <= 0) { getResponse().badRequest(); } else { Contact contact = getContactService().getContact(id); if (contact == null) { getResponse().badRequest(); } else { getContactService().delete(id); log.info("Deleted contact #{} '{}'", contact.getId(), contact.getName()); getResponse().ok(); } } } public void saveContact(@Body Contact contact) { getContactService().save(contact); getResponse().ok(); log.info("Saved contact #{} '{}'", contact.getId(), contact.getName()); } }
В вышеприведенном классе очень ясно представлены бизнес-методы CRUD:
getContacts ,
getContact ,
deleteContact и
saveContact . В этих методах мы читаем запрос, делаем некоторые операции в нашем хранилище через службу контактов и отправляем ответ обратно пользователю.
Код прост, поэтому я хотел бы только отметить, что мы использовали
аннотации @Param и
@Body, чтобы ввести некоторые детали запроса (например, параметры запроса) в качестве параметров метода (идентификатор контакта, контакт).
Теперь у нас есть приложение, которое объявляет маршруты, у нас есть контроллер, который делает вещи, поэтому нам нужен модуль запуска. Для запуска нашего приложения мы должны использовать внешний контейнер сервлета или встроенный контейнер сервлета. Я буду использовать второй подход в этой демонстрации, чтобы увидеть, насколько это просто.
public class CrudNgDemo { public static void main(String[] args) { Pippo pippo = new Pippo(new CrudNgApplication()); pippo.start(); } }
Итак, наш
CrudNgDemo — это простое Java-приложение с
основным методом. Вы можете запустить это приложение из командной строки или из любой Java IDE (Eclipse, Idea IntelliJ).
При сборке проекта не забудьте указать зависимости в вашем Maven
pom.xml:
<dependencies> <!-- Pippo --> <dependency> <groupId>ro.pippo</groupId> <artifactId>pippo-core</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>ro.pippo</groupId> <artifactId>pippo-controller</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>ro.pippo</groupId> <artifactId>pippo-freemarker</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>ro.pippo</groupId> <artifactId>pippo-fastjson</artifactId> <version>${project.version}</version> </dependency> <!-- Webjars --> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>angularjs</artifactId> <version>1.3.6</version> </dependency> </dependencies>
Из приведенного выше фрагмента видно, что мы добавили в качестве зависимостей только те модули pippo, которые нам нужны (ядро, контроллер, freemarker в качестве движка шаблонов и fastjson в качестве библиотеки json). Также мы добавили
самозагрузки ,
шрифт-удивительный и
angularjs как
webjars .
Я добавил 2 (два) скриншота из нашего приложения:
Весь код этой статьи доступен по адресу https://github.com/decebals/pippo/tree/master/pippo-demo/pippo-demo-crudng .
Полезные ссылки