Creare un web service REST in Java con JAX-WS
Per una delle aziende per cui lavoro, ho creato un REST web service in Java, che si connette al database Oracle, ed espone i dati in formato JSON.
Partendo da questo esempio reale, vi mostro come creare un web service REST in Java, usando JAX-WS!
Questo è quello che useremo nel progetto:
- Maven -> per gestire il progetto e le dipendenze
- Grizzly -> come HTTP server
- Jersey -> framework open source per la creazione di servizi RESTful in Java
- OJDBC -> per la connessione al db Oracle; se avete un altro tipo di db, potete usare JDBC o un altro driver appropriato
Quindi, una volta creato il progetto con Maven, aggiungete questo al vostro pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>codelds</id>
<url>https://code.lds.org/nexus/content/groups/main-repo</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<inherited>true</inherited>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.cimoda.rest.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<jersey.version>2.17</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Andiamo a descrivere la struttura del progetto (che voi potete ovviamente modificare come volete); io ho quattro package:
- com.cimoda.pojo -> qui tengo tutte le classi POJO
- com.cimoda.queries -> qui tengo le classi per la connessione al database e l'esecuzione delle query
- com.cimoda.service -> qui ci sono le varie classi che identificano gli endpoint da interrogare (ad esempio http://INDIRIZZO_IP/api/estrazione_1)
- com.cimoda.rest -> contiene il main e altre varie classi di utilità
Partiamo da una classe POJO, cioè dalla cosa più semplice:
package com.cimoda.pojo;
public class Zona {
private String zona;
private String descrizione;
public String getZona() {
return zona;
}
public void setZona(String zona) {
this.zona = zona;
}
public String getDescrizione() {
return descrizione;
}
public void setDescrizione(String descrizione) {
this.descrizione = descrizione;
}
}
Nulla di particolarmente complesso, quindi sorvolerei sulle spiegazioni.
Passiamo invece alla relativa query:
package com.cimoda.queries;
import com.cimoda.pojo.Zona;
import com.cimoda.rest.DBManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import org.glassfish.jersey.server.internal.process.MappableException;
public class ZonaQueries {
public ArrayList<Zona> getAll() throws ClassNotFoundException, SQLException, MappableException {
ArrayList<Zona> list = new ArrayList<>();
String query = "SELECT ZONA, DESCR32 FROM ZONA";
Connection conn = DBManager.getInstance().getConnection();
try (PreparedStatement pstmt = conn.prepareStatement(query)) {
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Zona zona = new Zona();
zona.setZona(rs.getString("ZONA"));
zona.setDescrizione(rs.getString("DESCR32"));
list.add(zona);
}
}
return list;
}
}
La classe DBManager la vedremo in seguito; sostanzialmente è una classe Singleton che si occupa della connessione al db.
Comunque, qui eseguiamo una query ed andiamo a riempire un ArrayList di oggetti Zona.
Il relativo endpoint sarà una cosa del genere:
package com.cimoda.service;
import com.cimoda.pojo.Zona;
import com.cimoda.queries.ZonaQueries;
import com.cimoda.rest.JsonError;
import com.cimoda.rest.NotFoundException;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("zona")
public class ZonaService {
@GET
@Path("/zone/")
@Produces(MediaType.APPLICATION_JSON)
public ArrayList getAll() {
ZonaQueries query = new ZonaQueries();
ArrayList<Zona> list = null;
try {
list = query.getAll();
} catch (ClassNotFoundException | SQLException ex) {
throw new NotFoundException(new JsonError("Errore", ex.getMessage()));
}
return list;
// http://192.168.1.30:8080/cr/zona/zone/ --> INDIRIZZO DA INTERROGARE
}
}
L'indirizzo da interrogare, commentato, dipenderà dalle impostazioni messe nel main (che vedremo dopo).
Anche JsonError è una classe che vedremo in seguito, insieme a NotFoundException.
Qui non passiamo parametri al path, ed indichiamo di produrre un JSON.
Se avessimo dovuto passare un parametro tramite query-string, avremmo fatto una cosa del genere:
package com.cimoda.service;
@Path("zona")
public class ZonaService {
@GET
@Path("/zone/{VALORE}")
@Produces(MediaType.APPLICATION_JSON)
public ArrayList getAll(@PathParam("VALORE") String from) {
ZonaQueries query = new ZonaQueries();
ArrayList<Zona> list = null;
try {
list = query.getAll();
} catch (ClassNotFoundException | SQLException ex) {
throw new NotFoundException(new JsonError("Errore", ex.getMessage()));
}
return list;
// http://192.168.1.30:8080/cr/zona/zone/VALORE
}
}
Vi ho modificato anche l'url commentato.
Bene, siamo quasi arrivati; ci mancano quattro classi usate precedentemente, e stanno tutte nel package com.cimoda.rest.
Cominciamo con la connessione al db:
package com.cimoda.rest;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBManager {
private static DBManager instance = null;
private static Connection conn = null;
private DBManager() {
}
public static DBManager getInstance() {
return (instance == null) ? (instance = new DBManager()) : instance;
}
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@INDIRIZZO:1521:DB_NAME", "USER", "PASSWORD");
return conn;
}
}
Ripeto, se usate un altro database, vi basta usare il driver e la stringa di connessione appropriate; le modifiche non sono tante.
Questa la classe relativa a NotFoundException:
package com.cimoda.rest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class NotFoundException extends WebApplicationException {
/**
* Create a HTTP 404 (Not Found) exception.
*/
public NotFoundException() {
super(Response.status(Response.Status.NOT_FOUND).
type(MediaType.TEXT_PLAIN).build());
}
/**
* Create a HTTP 404 (Not Found) exception.
*
* @param message the String that is the entity of the 404 response.
*/
public NotFoundException(String message) {
super(Response.status(Response.Status.NOT_FOUND).
entity(message).type(MediaType.TEXT_PLAIN).build());
}
public NotFoundException(JsonError jse) {
super(Response.status(Response.Status.NOT_FOUND).
entity(jse).type(MediaType.APPLICATION_JSON).build());
}
}
Classe generica per intercettare le eccezioni.
Questa invece la classe relativa agli errori JSON (che a volte possono indicare un'errata query):
package com.cimoda.rest;
public class JsonError {
private String type;
private String message;
public JsonError(String type, String message) {
this.type = type;
this.message = message;
}
public String getType() {
return this.type;
}
public String getMessage() {
return this.message;
}
}
Simile alle classi POJO in sostanza.
Infine il main:
package com.cimoda.rest;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;
public class Main {
public static final String BASE_URI_MIO = "http://192.168.1.30:8080/cr/";
public static HttpServer startServer() {
final ResourceConfig rc = new ResourceConfig().packages("com.cimoda.service");
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI_MIO), rc);
}
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
System.out.println(String.format("L'applicazione è in funzione sull'indirizzo %sapplication.wadlnDai invio per stopparla...", BASE_URI));
System.in.read();
server.shutdownNow();
}
}
Qui avviamo Grizzly, impostando l'indirizzo di ascolto, e il package dove ci sono gli endpoint.
Abbiamo visto parecchie cose, e non mi sono soffermato più di tanto su ogni argomento; mi ci sarebbe servito un libro.
Inoltre sono partito dalla cosa più facile, anche se come studio vi consiglio di partire dal main (che è la prima cosa che scriverete in questo caso).
Di spunti ne avete parecchi, quindi divertitevi.
Enjoy!
java rest jax-ws jersey maven grizzly restful oracle ojdbc
1 Commenti
Potrebbe gentilmente dire a cosa serve questo web service realizzato?
29/10/2018