В предыдущей статье я сделал краткое введение о том, что такое Пиппо . В предложении 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 .
Полезные ссылки