Aprende

Aprende sobre la última tecnología.

Construye

Da rienda suelta a tus conocimientos y construye!

Comparte

Que más gente lo aproveche para mejorar!
 

RestFul con Spring

lunes, 13 de julio de 2015



El objetivo final de esta entrada será que el lector implementar servicios REST usando Spring como transporte de dicho servicio. Estamos dando por supuesto que el lector sabe lo que es un servicio web así como que también sabe lo que es un servicio REST, en caso contrario tendrá que documentarse al menos de la teoría para poder entender lo que sigue a continuación. Existen diferentes vías para implementar servicios REST, en mi caso tengo experiencia en 2 de ellas:
  1. Jersey: Ligera y sencilla implementación de Oracle,es una implementación de su propio standard (https://jax-rs-spec.java.net).Como referencia tenemos los siguientes  proyectos en github(RESTfulJersey,RESTfulJsonhttps://github.com/ldipotetjob/Spring-Examples
  2. Spring MVC: Nuestra explicación estará basada en Spring 3.2.x aunque es justo decir que la version de Spring 4 ha salido con algunos cambios en el tratamiento de servicios REST, aunque la base y gran parte de la api sigue siendo la  misma.
Lo que ocupará nuestro artículo será  el .2, la implementación de servicios REST a través Spring; a pesar de que en el .1 he dejado referencia a dos proyectos muy simples ubicados en mi github,tan solo para que entiendan como va la implementación de Jersey. De cualquier manera mi recomendación es que entren en  https://spring.io/projects y echen una mirada a los proyectos que allí hay. Para mi merece especial atención el proyecto http://projects.spring.io/spring-hateoas/ basado en el principio de HATEOAS, en el cual entraremos a fondo en futuras entradas en este Blog.
Como cultura no es necesario leer ninguna especificación de ningún lenguaje pero si vamos a hacer un proyecto serio creo que puede ser muy buena idea. Aquí os dejo la especificación de RESTFUL.
Cualquier  tema referente a inyección de código y autowire sino lo tiene claro puede ir a la anterior entrada de Spring que tenemos en este blog http://mojitoverde.blogspot.com.es/2015/07/spring-de-base.html.

Aunque no profundizaremos respecto al scope de los beans, en esta entrada podemos resumirlo en la figura que sigue, importante destacar que Spring crea una instancia por cada bean que crea y a la vez esta también puede ser compartida la misma.Es un detalle importante, pero en el que no nos detendremos.Queda al lector investigar o esperar posibles entregas.

Scope de un bean





Spring MVC encapsula datos de la aplicación para mostrarlos a una vista, no nos detendremos en
explicar SpringMVC en su conjunto ya que hoy nuestro objetivo es simplemente como trata la implementación de servicios web de tipo Restful, de momento la estructura a elegir como respuesta a la request del servicio serán mensajes en formato JSON.

Para ir introduciendo el tema veamos el ejemplo mas simple de código que podemos tener :

1
2
3
4
5
6
7
8
9
@Controller 
@RequestMapping("/hello") 
public class HelloWorldController {
    @RequestMapping(method = RequestMethod.GET)
    public String helloworld() {
      //.....
        return "hello world!";
 } 
}

Nuestro análisis basándonos en el código anterior es el siguiente: la anotación @Controller indica que una clase es un controlador, recordemos que un controlador en Spring representa un componente que recibe instancias HttpServletRequest y HttpServletResponse, como HttpServlet y que además esta en el workflow de MVC. Como vemos, una clase que este anotada con @Controller no tiene necesariamente que implementar ninguna interface o extender determinada clase.Otro aspecto a destacar es que en una clase controladora podrán existir uno o más métodos handlers(métodos que realizarán la dinámica del servicio en cuestión).

La anotación @RequestMapping puede ser aplicada tanto a nivel de clase como a nivel de método. Tenemos varios patrones de diseño o trabajo, uno de ellos es el "helloworld" anterior en el que se mapea un particular patrón de URL a la clase controladora y luego un particular método HTTP en cada método handler. Nuestro @RequestMapping contiene propiedades interesantes de destacar tanto a nivel de clase como a nivel de método.El uso de @RequestMapping("/hello")  anotando la clase es usado para especificar la URL sobre la cual es activado el controlador ó clase controladora, que contiene los métodos handlers.Significando lo anterior que cualquier request que se reciba sobre la URL /hello será atendida por la clase controladora HelloWorldController.

Cuando llega la petición(request); se delega la llamada en el método HTTP GET que tenemos por defecto declarado en la clase controladora. La request inicial hecha sobre una url es siempre de tipo HTTP GET, por lo que basándonos en el ejemplo anterior, cuando la clase controladora atienda una request que venga de una URL /hello se delegará en el método HTTP GET. En nuestro caso será "helloworld" que aparece a su vez decorado con la anotación @RequestMapping(method = RequestMethod.GET).

La anterior porción de código solo contempla un método GET bajo la URL /hello de la clase controladora, pero imaginemos que queremos hacer el mapping a nivel de clase como en la anterior sección de código y que además necesitamos varios métodos GET sobre la misma clase controladora:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Controller
@RequestMapping("/hello/*")
public class HelloWorldController {
private HelloService helloService;
@Autowired
public HelloWorldController(HelloService helloService) {
this.helloService = helloService;
}
@RequestMapping("greeting")
public String greetingWorld() {
//....
return "Hello world";
}
@RequestMapping("display/{user}")
public String displayUser(@RequestParam("season") String season,@PathVariable("user") String user) {
//....
}

Basándonos en el código anterior nuestra clase controladora atenderá una petición que venga de la URL /hello y luego la petición siguiente URL /hello/greeting será atendida en la linea 5 de manera equivalente sucederá lo mismo en la linea 14 ,que además incorpora otras anotaciones de interés como RequestParam y PathVariable.Vemos como se usa la anotación especial {path_variable} en linea 14 , para especificar su valor en el correspondiente @RequestMapping.Vea que se declara @PathVariable("user") String user; de este modo si se recibe una request con el siguiente formato /display/luis el método tendrá acceso a la variable "user", que tomará el valor "luis". De la misma manera @RequestParam lee el parámetro de season pasado en la request y lo asigna a una variable.

Otra opción es usar la anotación @RequestMapping a nivel de método tal y como indicamos en la siguiente porción de código:

1
2
3
4
5
6
7
8
@Controller 
public class HelloWorldController {
    @RequestMapping(value ="/hello",method = RequestMethod.GET)
    public String helloworld() {
      //.....
        return "hello world!";
 } 
}

Proponemos a continuación otro ejemplo del uso de la anotación @RequestMapping:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class MemberController {
    private MemberService memberService;
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
    @RequestMapping("/member/add")
    public String addMember(Model model) {
        model.addAttribute("member", new Member());
        model.addAttribute("guests", memberService.list());
        return "memberList";
    }
    @RequestMapping(value={"/member/remove","/member/delete"},method=RequestMethod.GET)
    public String removeMember(
            @RequestParam("memberName") String memberName) {
           memberService.remove(memberName);
           return "redirect:";
    }
}

En el código anterior tanto la linea  8 como la linea 14 muestran distintos patrones de uso de la anotación @RequestMapping.En ambos casos son llamadas a métodos HTTP GET. En la linea 14 establece que cualquier request que se reciba sobre la URL /member/remove/member/delete será atendida por el método removeMember,linea 15.

Hemos visto la anotación @RequestMapping anotando tanto a clases como a métodos y en ambos casos el objetivo es siempre el mismo, tratar una determinada request de una URL específica.

Existen aspectos que pueden resultar interesantes pero que escapan al ámbito de esta
entrada como puede ser atender una petición(request) teniendo en cuenta el tipo de la misma(HTTP POST,PUT,DELETE), GET esta dada por defecto.

Para cualquier tema referente a la configuración de los applicationContext ir a Spring Base en este mismo blog ó a la especificación de Spring https://spring.io/projects. En conjunto con las anotaciones será necesario configurar el fichero .xml para el IoC Container y el descriptor de despliegue(web.xml).

De cualquier manera vamos a destripar una pequeña aplicación de SpringMVC que devuelve un JSON que contienen una lista de elementos. Esta aplicación podrás encontrarla en mi GitHub Springmvc.
Analizaremos 2 de los ficheros fundamentales :
El descriptor de despliegue(web.xml)
El fichero de configuración del IoC Container

web.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    <!--.............-->
    <!-- spring DispatcherServlet servlet -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/mvc/*</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/springContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   <!--.............-->

En la linea 4 tenemos el servlet de Spring encargado de gestionar las peticiones,DispatcherServlet. Es org.springframework.web.servlet.DispatcherServlet el servlet que recibe una petición(request) e intenta enviarla a las diferentes clases que han sido declaradas con la anotacion @Controller. Este proceso de envío dependerá de las diferentes anotaciones @RequestMapping declaradas en la clase controladora. Es importante destacar que en la linea 24 registramos el ContextLoaderListener listener de Spring que será el encargado de cargar el ApplicationContext. En la linea 18 indicamos el fichero de configuración del Ioc Container  que queremos cargar. Las lineas que hacen referencia al código y que hemos destacado son las principales a tener en cuenta en nuestro descriptor de despliegue, pero no las únicas. A partir de aquí podemos agregar cada elemento que necesitemos y que se acoja al standard de especificación para el descriptor web.xml, algo que también escapa al ámbito de este documento.

Hasta ahora solo hemos visto ejemplos en los que los servicios implementados solo devuelven un boleano o incluso un nombre que puede ser el de una vista para realizar cualquier otra operación. Pero en el verdadero uso de un servicio REST solemos retornar, en el caso de un servicio de tipo HTTP GET, un JSON ó también un XML. En nuestro caso vamos a abordar la problemática de un servicio REST que genere un JSON como respuesta a una petición HTTP GET.
Para ello nuestro servicio contará por una parte con la anterior porción de código correspondiente al descriptor web.xml  y por otra parte tal como anteriormente mencionamos, con el fichero de configuración del IoC Container.

fichero de configuración del IoC Container.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- .........................-->   
 <mvc:annotation-driven/>

    <context:component-scan base-package="com.ldg.spring.rest.controller" use-default-filters="false">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>

    <!-- json handler -->
    <bean id="jsonHttpMessageConverter"
          class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
        <property name="supportedMediaTypes" value="application/json"/>
    </bean>

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>
<!-- .........................-->

Hemos de destacar algunos detalles no tenidos en cuenta en anteriores entradas de Spring, en lo referente al código anterior,la linea 1 nos muestra un tag ,mvc:annotation-driven, el cual suministra una funcionalidad adicional en los módulos de Spring. No confundir su utilidad con la de context:annotation-config ya que mvc:annotation-driven es mucho más que la declaración de soporte para las anotaciones de manera general, en nuestro caso lo usamos a aquí para una la conversión de mensajes HTTP con @RequestBody/@ResponseBody, específicamente para el soporte de las librerías de Jackson para Spring. Queremos que el mensaje de respuesta en cuestión sea de formato tipo JSON y para ellos estamos usando Jackson con lo que necesitaremos mvc:annotation-driven para que nos brinde el soporte al mismo. De cualquier manera tiene una mas utilidad esta anotación, dejamos aquí un enlace referente al documento de la especificación que explica la misma (mvc:annotation-driven). En combinación con la anotación anterior tenemos la declaración de la linea 15,en la que agregamos un HttpMessageConverter.

Por qué necesitamos el HttpMessageConverter,hablando de la manera mas llana posible tanto las peticiones como las respuestas HTTP están basadas en texto con lo que el browser y el servidor se comunican intercambiando texto pero los métodos del  controlador retornan Objetos.El modo o la vía que Spring usa para serializar/de-serializar estos objetos a texto es gestionado a través del HttpMessageConverter.

En la linea 14 creamos un bean que sea será implementación de HttpMessageConverter que pueda tanto escribir y leer JSON usando el Mapper de Jackson 1.x .Este es el bean que usaremos para setear los HttpMessageConverter en AnnotationMethodHandlerAdapter y que permitirá convertir a JSON tanto peticiones y respuestas HTTP.

Recomiendo al lector que eche un ojo a las librerías de Jackson,proyecto bastante interesante y mas util de lo que solemos creer.

El código que tenemos en está deprecado en lo referente a el HttpMessageConverter.
Básicamente porque todo el conjunto de pruebas han sido hechas con la version 1.X de las librerias de JACKSON,debido a unas pruebas de rendimiento que me daban mejor tiempo con la version 1.x de Jackson para JSONs de mas pesados.Tenemos pendiente cambiar tanto esta entrada como el código de GitHub para versiones 2.x de Jackson.

Si aún así estuviera interesado el lector en una versión actualizada ,la siguiente porción sería la que necesitaríamos cambiar dado que quisiéramos cambiar a una versión no deprecada:

fichero de configuración del IoC Container.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

    <mvc:annotation-driven/>

    <context:component-scan base-package="com.ldg.spring.rest.controller" use-default-filters="false">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>

    <!-- json handler -->
    <bean id="jsonHttpMessageConverter"
          class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="supportedMediaTypes" value="application/json"/>
    </bean>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>

</beans>

Mirar los cambios en linea 20 y en linea 25. Además habrá que cambiar en el pom.xml del proyecto las dependencias a las nuevas librerias de jackson (2.x).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
       <!-- Jackson dependency -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.12</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-core-asl</artifactId>
            <version>1.9.12</version>
        </dependency>

Como referencia el pom.xml a cambiar esta ubicado en mi github.

A continuación tenemos una porción de la clase controladora que hace además uso de  los ficheros de configuración anteriormente expuestos(web.xml y fichero de configuración del IoC container):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//..............................
@Controller
public class StatusController {

    private StatusService statusService;
    private TrackJsonService trackService;
    
    @Autowired
    public StatusController(StatusService statusService,TrackJsonService trackService) {
        this.statusService = statusService;
  this.trackService = trackService;
    }

    @ResponseBody
    @RequestMapping(value = "/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public StatusJson findStatus() {
        return new StatusJson(statusService.getStatus());
    }

    @ResponseBody
    @RequestMapping(value = "/track", method = RequestMethod.GET/*, produces = MediaType.APPLICATION_JSON_VALUE*/)
    public List<Track> findTrack(@RequestParam(value = "usr", required = false) String varjson) {
 
        return trackService.getTrackInJSON(varjson);
    }

}

//............

Mirar tanto la linea 14 como la linea 21 y en ambas el atributo produce de @RequestMappping,en uno de los casos está comentados y nos devuelve un JSON. No necesitamos este atributo, ya tenemos un messageconverter registrado en el fichero de configuración del Ioc Container con lo que todas las respuestas serán convertidas a JSON, esta es una de las grandes ventajas de los conversores pero que solo tienes su lógica en los casos que siempre tengamos respuestas en determinados formatos.

Las siguientes lineas de código son un porción de la implementación del servicio:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//.....
@Service
public class TrackJsonServiceImpl implements TrackJsonService {

public List<Track> getTrackInJSON(String varjson) {
 // TODO Auto-generated method stub
 
 List<Track> listtrack = new ArrayList<Track>();
 Track track = new Track();
 track.setTitle("Enter Sandman _" +varjson);
 track.setSinger("Metallica");
 
 Track track1 = new Track();
 track1.setTitle("Fragile _" +varjson);
 track1.setSinger("Sting");

 
 Track track2 = new Track();
 track2.setTitle("Imagine");
 track2.setSinger("Beattles _" +varjson);

 listtrack.add(track);
 listtrack.add(track1);
 listtrack.add(track2);
 
 return listtrack;
 
} 
//.....

Interesante ver la anotación de la linea 2, @Service,una especialización de @Component y que ayudará entre otras cosas a que pueda ser encontrado e inyectado el componente cuando se realice un scan indicando en el fichero de configuración del Ioc Container.


Hacemos un resumen de las principales anotaciones usadas aquí:
@Controller: Anota la clase que será  el controlador en MVC y manejara las peticiones HTTP.
@RequestMapping: Anota funciones que deberán manejar metodos HTTP, URIs ó HTTP Headers
@PathVariable: Una variable en la URI que puede ser inyectada usándola anotación @PathVariable.
@RequestParam: Usamos esta anotación para inyectar un parámetro de una URL en un método.
@RequestHeader: Usamos esta anotación para inyectar un HTTP HEADER en un método.
@RequestBody: Usamos esta anotación para inyectar el body de una request HTTP en un método.
@ResponseBody: Retornar un contenido u objeto como un HTTP  response body.
@Service:Es una especialización de @Component.


Nuestra próxima entrega tendremos un ejemplo muy simple a ir introduciendo de Scala con Spring.



1 comentarios:

Tony dijo...

Muy interesante y muy bien explicado¡¡¡

Publicar un comentario