poniedziałek, 23 lipca 2012

JEE6 i RESTful Web Services

Najnowsza specyfikacja Java EE 6 bardzo dobrze wspiera RESTful Web Services, czyli lżejszą wersję usług sieciowych. Poniżej demonstracja jak JEE radzi sobie z REST'ami.

Konfiguracja
Na początek warto sprawdzić czy mamy załączone odpowiednie liby. Jest to raczej formalność, ale nigdy nic nie wiadomo.

    org.jboss.spec.javax.ws.rs
    jboss-jaxrs-api_1.1_spec
    provided

Musimy również stworzyć klasę Activatora, która:
  • dziedziczy po javax.ws.rs.core.Application
  • posiada adnotację @javax.ws.rs.ApplicationPath z zadeklarowaną ogólną ścieżką do naszych REST'ów
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest")
public class JaxRsActivator extends Application {
   /* class body intentionally left blank */
}

Alternatywnie możemy umieścić kilka liniejek kodu w web.xml (opisane niżej).

RESTful Web Services
Klasa, która będzie wystawiać usługę typu REST powinna posiadać adnotację @javax.ws.rs.Path ze wskazaniem ścieżki pod jaką będzie widoczna usługa. Dodatkowo wskazujemy metodę, która będzie się wykonywać w momencie wywołania. Do tego celu wykorzystujemy adnotacje:
  • @GET (lub @POST, @HEAD, @PUT, itd.) z pakietu javax.ws.rs. Dzięki temu możemy określić różne metody dla różnych typów wywołań (GET/POST/PUT/HEAD/...) REST'a.
  • @javax.ws.rs.Produces z określeniem w jakim formacie będzie prezentowany wynik. Do dyspozycji mamy dwa popularne formaty: json ("application/json") i xml ("text/xml").
import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/hello")
public class HelloRestService {

 @Inject
 private HelloService helloService;

 @GET
 @Produces("application/json")
 public List<Person>> getPersons() {

  return helloService.getPersons();
 }

}
Tak stworzona usługa będzie widniała pod adresem http://127.0.0.1:8080/myApp/rest/hello.

Parametry
Jedno z podstawowych pytań, jakie warto zadać to: Jak przekazywać parametry do naszych usług? Odpowiedź jest bardzo prosta. Wystarczy stworzyć metodę z parametrami i wykorzystać kilka adnotacji. W kodzie wygląda to mniej więcej tak:
@GET
@Path("/{id}")
@Produces("application/json")
public Person getPerson(@PathParam("id") int id) {

 return helloService.getPerson(id);
}
W adnotacji @javax.ws.rs.Path wskazujemy jak w URL'u będą przekazywane parametry. Parametry oznaczamy w klamrowych nawiasach, np. {param1}. W ścieżce może pojawiać się statyczny tekst, np. "/personId/{id}". Kolejna przydatna adnotacja to @javax.ws.rs.PathParam, która wstawia parametry w odpowiednie miejsca w metodzie. Efekt możemy zobaczyć pod adresem: http://127.0.0.1:8080/myApp/rest/hello/1.

web.xml
Alternatywnie możemy umieścić całą konfigurację w web.xml. Jeśli ktoś ma potrzebę albo po porostu preferuje taki sposób konfiguracji, to wystarczy, że skopiuje poniższy fragment kodu. Oczywiście w takim wypadku nie potrzebujemy już klasy Activatora, możemy ją po prostu usunąć.
    
    Resteasy
    
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
     
     
            resteasy.servlet.mapping.prefix
            /rest
      
     1



       resteasy.scan
       true
   


        Resteasy
        /rest/*



XML
Wspomniałem już że Rest'y poza jsonem wspierają również XML. Niestety sprawa się trochę komplikuje, ponieważ obiekt, który ma zostać zamieniony na XML, wymaga małego tuningu :). Jeśli zwracane obiekty nie będą dostosowane, to w przeglądarce zobaczymy błąd 500, a w konsoli błąd:
Failed executing GET /hello/1: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: pl.mkorwel.sample.rest.app.rest.Person of media type: text/xml
Cały trick polega na dodaniu do klasy adnotacji @javax.xml.bind.annotation.XmlRootElement oraz stworzeniu bezargumentowego konstruktora. Klasa powinna wyglądać mniej więcej tak:
@XmlRootElement
public class Person {

 private String name;
 
 private String surname;

 public Person() {
 }

 public Person(String name, String surname) {
  super();
  this.name = name;
  this.surname = surname;
 }

 // GET i SET
}
Jeśli macie jakieś pytania/sugestie, zapraszam do umieszczania ich w komentarzach.