Статьи

Предоставление AMF REST ресурсов в Java

Action Message Format (AMF) — это высокопроизводительный протокол веб-сервисов, разработанный и разработанный специально для приложений Flash от Adobe . AMF обычно считается протоколом RPC (т. Е. Оптимизированной альтернативой SOAP ), поскольку он определяет способ вызова удаленных методов из клиента на основе Flash.

Однако другой важной частью спецификации AMF является определение формата сериализации данных . Этот формат сериализации делает AMF жизнеспособным представлением (т. Е. Типом носителя) для ресурсов REST. Это дает значительный потенциал разработчикам Flash / Flex / ActionScript, поскольку доступность представлений AMF для ресурсов REST означает, что они могут работать со строго типизированными классами ActionScript без необходимости переводить их в / из XML (или JSON или любого другого формата). Вместо этого объекты могут быть записаны и / или прочитаны непосредственно в поток.

Это руководство предназначено, чтобы показать, как предоставлять ресурсы AMF REST в Java с помощью свободно доступных API. Как предоставить ресурсы AMF на других платформах (например, Ruby, .NET, PHP и т. Д.), Оставлено на другой день (и, вероятно, другого автора).

обзор

Вот что мы делаем:

  1. Напишите объекты данных на стороне сервера
  2. Напишите объекты данных на стороне клиента
  3. Напишите методы на стороне сервера
  4. Написать серверный AMF-провайдер данных
  5. Напишите код вызова на стороне клиента
  6. Компиляция, сборка и развертывание
Есть трудный путь, а потом есть …
Enunciate — это бесплатный инструмент времени сборки, разработанный, чтобы упростить разработку многофункционального API веб-сервиса. Начиная с версии 1.10, Enunciate будет автоматически развертывать конечные точки REST JAX-WS через AMF. Он также сгенерирует ваши объекты данных ActionScript на стороне клиента, которые можно использовать для использования этих конечных точек REST. С Enunciate вы просто пишете свои объекты данных и методы ресурсов на стороне сервера, а Enunciate позаботится обо всем остальном (без каламбура).

Тем не менее, поскольку важно знать, что работает под капотом, мы продолжаем учебник …

Для этого урока мы будем писать простое приложение адресной книги. Мы предоставим контактный ресурс для чтения списка контактов и предоставим контактный ресурс для обновления контактной информации. Мы будем использовать JAX-RS для определения наших методов и поставщиков ресурсов и BlazeDS для (де) сериализации наших объектов Java в / из AMF.

Напишите объекты данных на стороне сервера

Довольно простые POJO:

Contact.java

package net.java.ws.addressbook.domain;

/**
* A contact in the address book.
*/
public class Contact {

private int id;
private String name;
private String phone;
private String address1;
private String address2;
private String city;

/**
* The id of the contact.
*
* @return The id of the contact.
*/
public int getId() {
return id;
}

/**
* The id of the contact.
*
* @param id The id of the contact.
*/
public void setId(int id) {
this.id = id;
}

/**
* The name of the contact.
*
* @return The name of the contact.
*/
public String getName() {
return name;
}

/**
* The name of the contact.
*
* @param name The name of the contact.
*/
public void setName(String name) {
this.name = name;
}

/**
* The phone of the contact.
*
* @return The phone of the contact.
*/
public String getPhone() {
return phone;
}

/**
* TThe phone of the contact.
*
* @param phone The phone of the contact.
*/
public void setPhone(String phone) {
this.phone = phone;
}

/**
* The first address field of the contact.
*
* @return The first address field of the contact.
*/
public String getAddress1() {
return address1;
}

/**
* The first address field of the contact.
*
* @param address1 The first address field of the contact.
*/
public void setAddress1(String address1) {
this.address1 = address1;
}

/**
* The second address field of the contact.
*
* @return The second address field of the contact.
*/
public String getAddress2() {
return address2;
}

/**
* The second address field of the contact.
*
* @param address2 The second address field of the contact.
*/
public void setAddress2(String address2) {
this.address2 = address2;
}

/**
* The city of the contact.
*
* @return The city of the contact.
*/
public String getCity() {
return city;
}

/**
* The city of the contact.
*
* @param city The city of the contact.
*/
public void setCity(String city) {
this.city = city;
}
}

Contact.java

package net.java.ws.addressbook.domain;

import java.util.Collection;

/**
* A list of contacts.
*/
public class ContactList {

private Collection<Contact> contacts;

/**
* The contact list.
*
* @return The contact list.
*/
public Collection<Contact> getContacts() {
return contacts;
}

/**
* The contact list.
*
* @param contacts The contact list.
*/
public void setContacts(Collection<Contact> contacts) {
this.contacts = contacts;
}
}

Write the client-side data objects

The associated ActionScript classes are a reflection of the Java server-side classes. Of course, you have to do some translation, since not all Java types are ActionScript types and vice-versa. You can view some of the rules at Converting data from Java to ActionScript. Note also that if the name of your ActionScript object is different from that of your Java object, you need to register a «class alias» with the registerClassAlias method. (Here’s where a code generator, like Enunciate, comes in really handy.)

Contact.as

package net.java.ws.addressbook.domain {

import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
import flash.net.registerClassAlias;


/**
* A contact in the address book.
*/
[Bindable]
[RemoteClass(alias="net.java.ws.addressbook.domain.amf.Contact")]
public class Contact {

private var _id:int;
private var _name:String;
private var _phone:String;
private var _address1:String;
private var _address2:String;
private var _city:String;

public function Contact() {
}

/**
* The id of the contact.
*/
public function get id():int {
return _id;
}

/**
* The id of the contact.
*/
public function set id(id:int):void {
_id = id;
}

/**
* The name of the contact.
*/
public function get name():String {
return _name;
}

/**
* The name of the contact.
*/
public function set name(name:String):void {
_name = name;
}

/**
* The phone of the contact.
*/
public function get phone():String {
return _phone;
}

/**
* The phone of the contact.
*/
public function set phone(phone:String):void {
_phone = phone;
}

/**
* The first address field of the contact.
*/
public function get address1():String {
return _address1;
}

/**
* The first address field of the contact.
*/
public function set address1(address1:String):void {
_address1 = address1;
}

/**
* The second address field of the contact.
*/
public function get address2():String {
return _address2;
}

/**
* The second address field of the contact.
*/
public function set address2(address2:String):void {
_address2 = address2;
}

/**
* The city of the contact.
*/
public function get city():String {
return _city;
}

/**
* The city of the contact.
*/
public function set city(city:String):void {
_city = city;
}
}
}

ContactList.as

package net.java.ws.addressbook.domain {

import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
import flash.net.registerClassAlias;
import net.java.ws.addressbook.domain.Contact;
import mx.collections.ArrayCollection;

//register the item type of the collection for the "contacts" property.
registerClassAlias("net.java.ws.addressbook.domain.amf.Contact", Contact);

/**
* A list of contacts.
*/
[Bindable]
[RemoteClass(alias="net.java.ws.addressbook.domain.amf.ContactList")]
public class ContactList {

private var _contacts:ArrayCollection;

public function ContactList() {
}

/**
* The contact list.
*/
public function get contacts():ArrayCollection {
return _contacts;
}

/**
* The contact list.
*/
public function set contacts(contacts:ArrayCollection):void {
_contacts = contacts;
}
}
}

Write the server-side resource methods

We just write a pair of simple JAX-RS resource methods. We’ll refer you to other resources to learn more about JAX-RS. Note that the resource method produces and/or consumes media type «application/x-amf».

AddressBook.java

package net.java.ws.addressbook.services;

import net.java.ws.addressbook.domain.Contact;
import net.java.ws.addressbook.domain.ContactList;

import java.util.*;

import javax.ws.rs.*;

/**
* The address book resource.
*/
@Path ("/contacts")
public class AddressBook {

@GET
@Produces ( "application/x-amf" )
public ContactList getContacts() {
ContactList list = ...; //load the contacts
return list;
}

@POST
@Path ("/contact")
@Consumes ( "application/x-amf" )
public void postContact(Contact contact) throws AddressBookException {
//store the contact...
}

}

Write the server-side AMF data provider

Again, we use JAX-RS to supply a provider for AMF. Here’s what it might look like:

AMFProvider.java

package net.java.ws.providers;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;

/**
* A JAX-RS provider for data that is serialized/deserialized according to the AMF specification for serialization of objects.
* E.g. mime type "application/x-amf".
*/
@Provider
@Produces ("application/x-amf")
@Consumes ("application/x-amf")
public class AMFProvider implements MessageBodyReader, MessageBodyWriter {

public boolean isReadable(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true; //we'll assume everything is readable, for now.
}

public Object readFrom(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream stream)
throws IOException, WebApplicationException {
SerializationContext context = new SerializationContext();
Amf3Input input = new Amf3Input(context);
input.setInputStream(stream);
return input.readObject();
}

public boolean isWriteable(Class realType, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true; //we'll assume everything is writeable, for now.
}

public void writeTo(Object o, Class realType, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream stream) throws IOException, WebApplicationException {
SerializationContext context = new SerializationContext();
Amf3Output output = new Amf3Output(context);
output.setOutputStream(stream);
output.writeObject(obj);
}

public long getSize(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1; //we don't know the size
}

}

Write the client-side invocation code

What you do with the data is up to you. But here’s some sample code that shows how one might make a request to a REST resource and (de)serialize the data:

myapp.mxml

import net.java.ws.addressbook.domain.ContactList;
import net.java.ws.addressbook.domain.Contact;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.net.URLRequestMethod;
import flash.utils.ByteArray;
import flash.events.*;
import flash.errors.*;

private function getContact(id:String):void {
var request:URLRequest = new URLRequest("/contacts");
request.method = URLRequestMethod.GET;
var contactStream:URLStream = new URLStream();
contactStream.addEventListener("complete", handleContacts)
contactStream.addEventListener("ioError", handleError)
try {
contactStream.load(request);
}
catch (error:Error) {
Alert.show(error.toString());
}
}

private function handleContacts(event:Event):void {
var contactStream:URLStream = URLStream( event.target );
var contacts:ContactList = (contactStream.readObject() as ContactList);
//handle the contacts...
}

private function postContact():void {
var request:URLRequest = new URLRequest("/contacts/contact");
request.method = URLRequestMethod.POST;
var ba:ByteArray = new ByteArray();
var c:Contact = new Contact();
c.id = 12345;
c.name = "new name";
c.phone = "new phone";
c.address1 = "new address1";
c.address2 = "new address2";
c.city = "new city";
ba.writeObject(c);
request.data = ba;
request.contentType = "application/x-amf";
var contactStream:URLStream = new URLStream();
contactStream.addEventListener("complete", handleSuccessfulUpdate)
contactStream.addEventListener("ioError", handleError)
try {
contactStream.load(request);
}
catch (error:Error) {
Alert.show(error.toString());
}
}

private function handleSuccessfulUpdate(event:Event):void {
//do something to show the update was successful...
}

private function handleError(event:Event):void {
//handle the error...
}

Compile, build, and deploy

This one’s up to you. How you build and deploy is very environment-specific. It also depends on the JAX-RS implementation you’re using. And of course, you’ve got to compile your Flex app, too…

Again, if you’re using Enunciate, it’s easy—Enunciate will package up everything in a war file for you, which you can drop in your favorite servlet container.

Original post here. Used by permission.