Está en la página 1de 26

danielme.

com

Programación Android, Spring y JEE

Introducción a Spring Boot: Aplicación Web


con servicios REST y Spring Data JPA

21/02/201822/03/2019
Última actualización: 22/03/2019

(http://www.springsource.org/)En el tutorial Persistencia en BD con


Spring: Integrando JPA, c3p0, Hibernate y EHCache
(https://danielme.com/2013/06/19/persistencia-bd-con-spring-
integrando-jpa-c3p0-hibernate-y-ehcache/), vimos de forma práctica
los pasos a seguir para con�gurar una aplicación Maven que integre
Spring con JPA, utilizando Hibernate como implementación. No es
difícil, pero resulta tedioso tener que replicar esta con�guración cada vez que
empecemos un proyecto nuevo y la con�guración puede complicarse si por ejemplo
vamos a desarrollar un proyecto web completo y necesitamos también utilizar otros
módulos de Spring tales como MVC, Data, Security, etc.

Numerosos programadores y empresas recurren a la creación de plantillas genéricas,


por ejemplo en la forma de arquetipos Maven, que eviten tener que invertir tiempo en
la creación y con�guración de los nuevos proyectos desde cero (aunque esto conlleva el
problema adicional de tener que mantener actualizadas dichas plantillas). Otra opción
es recurrir a plantillas de terceros disponibles en GitHub como por ejemplo las
indexadas en yeoman (http://yeoman.io/), o irnos directamente a frameworks basados
en Spring como JHipster (http://www.jhipster.tech/) aunque aquí ya entramos en el
terreno de los generadores de código.

Teniendo en cuanta esta problemática, allá por 2014 se publicó la primera versión
estable de Spring Boot (https://spring.io/projects/spring-boot), un módulo que
pretende solucionar estos problemas y acelerar el desarrollo de aplicaciones basadas en
Spring gracias a una gestión automatizada de la infraestructura software que
necesitamos en nuestra aplicación. En concreto: “Spring Boot proporciona una manera
rápida de construir aplicaciones. Inspecciona el classpath y los beans que tengas
con�gurado, hace asunciones razonables sobre lo que te falta y lo añade. Con Spring
Boot puedes centrarte más en la lógica de negocio y menos en la infraestructura”.

Hay que tener presente que Spring Boot no es un framework al uso ni un generador de
código, se centra en la con�guración e integración de las dependencias que solemos
necesitar de forma genérica para que sin ningún esfuerzo empecemos directamente a
desarrollar nuestra aplicación. Incluso con�gura de forma embebida un servidor
Tomcat o Jetty si así lo deseamos. Y lo mejor de todo es que no supone una limitación
ya que podemos seguir realizando cualquier con�guración de igual forma que si no
tuviéramos Spring Boot.

En este tutorial daremos los primeros pasos para empezar a utilizar Spring Boot en
nuestros proyectos. En concreto, crearemos una aplicación web con una pantalla que
muestre un listado de datos obtenidos desde una tabla de una base de datos MySQL, así
como una pequeña API REST de ejemplo. Asumo que el lector tiene conocimientos, a
nivel muy básico, de Maven, Spring Data JPA y Spring MVC porque voy a centrarme
sobre todo en Spring Boot.

En lo que respecta al testing de la aplicación (fundamental), he escrito otro tutorial


especí�co que puede verse como la segunda parte del presente.

Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos
embebidas (https://danielme.com/2018/11/26/testing-en-spring-boot-con-junit-45-
mockito-mockmvc-rest-assured-bases-de-datos-embebidas/)

Se usará Spring Boot 2.1 aunque se indican las diferencias más relevantes con respecto
a Spring Boot 1.5 de las funcionalidades que veamos.

Creando un proyecto con Spring Boot para Spring Data


JPA

Vamos a crear y con�gurar un proyecto Maven con Spring Boot similar al de los
tutoriales de Spring Data JPA (https://danielme.com/2014/02/08/persistencia-en-bd-
con-spring-data-jpa/) pero con una capa web (JSP y REST). Como es habitual,
empezamos con�gurando el pom.xml con las dependencias que necesitamos, y aquí
viene la primera ventaja de adoptar Spring Boot: no de�niremos como dependencias
directamente los módulos de Spring necesarios (web, orm, security) sino una suerte de
“metadependencias” (starters) de Spring Boot que incluyen todo lo necesario. Las
versiones de las dependencias son debidamente seleccionadas y testeadas por el equipo
de Spring Boot por lo que no tenemos que preocuparnos por posibles
incompatibilidades entre librerías. Para ello añadimos lo siguiente al pom.xml:

1. Utilizar Spring Boot como módulo padre de nuestro proyecto. Usaremos Spring Boot
1 para Spring 4 (el core de Spring es compatible con Java 6 pero dependencias
como Hibernate van a requerir Java 8) y Spring Boot 2 para Spring 5 (Java 8).

1 <p
parent>
2     <ggroupId>org.springframework.boot</g
groupId>
3     <aartifactId>spring-boot-starter-parent</a
artifactId>
4     <vversion>2.1.3.RELEASE</v
version>
5     <rrelativePath />
6 </pparent>

Nota: si no queremos o podemos utilizar Spring Boot como proyecto padre,


consultar la con�guración alternativa (https://docs.spring.io/spring-boot/docs
/current/reference/htmlsingle/#using-boot-maven-without-a-parent).

2. Añadir todos los metamódulos (starters) de Spring Boot que encapsulen las
funcionalidades que necesitemos. Los starters disponibles se pueden consultar
directamente en GitHub en este enlace (https://github.com/spring-projects/spring-
boot/tree/master/spring-boot-project/spring-boot-starters). En nuestro caso, para
utilizar Spring Data JPA con Hibernate necesitaremos el siguiente:

1 <d
dependency>
2   <ggroupId>org.springframework.boot</g
groupId>
3   <aartifactId>spring-boot-starter-data-jpa</a
artifactId>
4 </ddependency>

3. Adicionalmente también necesitamos el driver JDBC que en nuestro caso es MySQL.


1 <d
dependency>
2   <ggroupId>mysql</g
groupId>
3   <aartifactId>mysql-connector-java</a
artifactId>
4 </ddependency>

4. Opcionalmente se puede utilizar el plugin de Maven (https://docs.spring.io/spring-


boot/docs/current/maven-plugin/usage.html) de Spring Boot que proporciona
algunos “goals” interesantes para ejecutar y empaquetar nuestras aplicaciones.

1 <b
build>
2     <pplugins>
3         <pplugin>
4             <g groupId>org.springframework.boot</g
groupId>
5             <a artifactId>spring-boot-maven-plugin</a
artifactId
6         </pplugin>
7     </pplugins>
8 </bbuild>

(https://click.linksynergy.com/fs-bin/click?id=OZ02PWh8Q04&
o�erid=507388.1395&subid=0&type=4)

Spring Boot utiliza para el logging Logback (https://logback.qos.ch/) a través de la


abstracción slf4j (https://www.slf4j.org/), lo que permite cambiar de implementación
sin tener que modi�car el código tal y como veremos un poco más adelante. La
con�guración de Logback se realiza en el �chero /src/main/resources/logback.xml.

1 <?xxml version="1.0" encoding="UTF-8"?>


2 <c
configuration>
3     <aappender name="STDOUT"
4         class="ch.qos.logback.core.ConsoleAppender">
5         <eencoder>
6             <ppattern>%d{MM-dd-HH:mm:ss.SSS} [%thread] %-5level
7             </ppattern>
8         </eencoder>
9     </aappender>
10
11     <r
root level="DEBUG">
12         <aappender-ref ref="STDOUT" />
13     </rroot>
14 </c
configuration>

De�nimos una instancia del logger para una clase del siguiente modo.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...

private static final Logger logger = LoggerFactory.getLogger(CountryCo

En mis proyectos acostumbro a utilizar log4j2 (https://logging.apache.org/log4j/2.x/).


Podemos cambiar de logback a log4j2 fácilmente con los siguientes pasos.

1. Excluir el starter spring-boot-starter-logging de los starters que la tengan como


dependencia.
1 <d
dependency>

2. Añadir el starter para log4j2.

1 <d
dependency>
2     <ggroupId>org.springframework.boot</g
groupId>
3     <aartifactId>spring-boot-starter-log4j2</a
artifactId>
4 </ddependency>

3. Realizar la con�guración en el �chero /src/main/resources/log4j2.xml.

1 <?xxml version="1.0" encoding="UTF-8"?>


2 <C
Configuration>
3     <AAppenders>
4         <CConsole name="Console" target="SYSTEM_OUT">
5             <PPatternLayout
6                 pattern="%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %c
7         </CConsole>
8
9         <R
RollingFile name="RollingFile"
10             fileName="./logs/spring-boot.log"
11             filePattern="./logs/spring-boot-%d{-dd-MMMM-yyyy}
12             <PPatternLayout>
13                 <ppattern>%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %c
14                     %m%n</ppattern>
15             </PPatternLayout>
16             <PPolicies>
17                 <OOnStartupTriggeringPolicy />
18                 <SSizeBasedTriggeringPolicy
19                     size="5 MB" />
20                 <TTimeBasedTriggeringPolicy />
21             </PPolicies>
22         </RRollingFile>
23     </A
Appenders>
24
25     <L
Loggers>
26         <R
Root level="debug">
27             <AAppenderRef ref="Console" />
28             <!-- <AppenderRef ref="RollingFile" /> -->
29         </RRoot>
30
31     </L
Loggers>
32
33 </C
Configuration>

Puesto que seguimos utilizando slf4j como fachada para el sistema de logs no es
necesario cambiar el código de la aplicación.

Llegados a este punto el pom del proyecto inicial (será modi�cado a medida que
avancemos en el tutorial) queda así
1 <?x
xml version="1.0" encoding="UTF-8"?>
Con el comando mvn dependency:list podemos comprobar todas las librerías que
tenemos en nuestro proyecto.

Ahora tenemos que de�nir los parámetros de con�guración de nuestra aplicación (las
propiedades admitidas por las distintas librerías, al �nal de este tutorial explico cómo
añadir propiedades personalizadas). Spring Boot los lee por defecto del �chero
/src/main/resources/application.properties o /src/main/resources/application.yaml y en
este listado (https://docs.spring.io/spring-boot/docs/current/reference/html/common-
application-properties.html) encontramos los más habituales aunque no se incluyen los
de todas las librerías con las que Spring Boot se integra de forma automática.

Para nuestro ejemplo necesitamos como mínimo la url de conexión a la base de datos y
las credenciales. Adicionalmente establecemos también el modo DDL de Hibernate, que
por defecto es none, a update para que se sincronicen nuestras entidades JPA con las
tablas de la base de datos (¡No usar esto en producción si no tenemos claro lo que
estamos haciendo!).

spring.datasource.url=jdbc:mysql://localhost:3306/country
spring.datasource.username=demo
spring.datasource.password=demo
spring.jpa.hibernate.ddl-auto=update

Vamos a añadir una entidad de JPA y su repositorio de JPA.

La entidad será auditable siguiendo el mecanismo visto en el tutorial Persistencia en


BD con Spring Data JPA (III): Auditoría (https://danielme.com/2014/11
/02/persistencia-en-bd-con-spring-data-jpa-iii-auditoria/).
1 package com.danielme.springboot.entities;
2
3 import java.util.Calendar;
4
5 import javax.persistence.Column;
6 import javax.persistence.EntityListeners;
7 import javax.persistence.MappedSuperclass;
8 import javax.persistence.Temporal;
9 import javax.persistence.TemporalType;
10
11 import org.springframework.data.annotation.CreatedBy;
12 import org.springframework.data.annotation.CreatedDate;
13 import org.springframework.data.annotation.LastModifiedBy;
14 import org.springframework.data.annotation.LastModifiedDate;
15 import org.springframework.data.jpa.domain.support.AuditingEnti
16
17 @EntityListeners({ AuditingEntityListener.c
class })
18 @MappedSuperclass
19 public class AuditableEntity {
20
21     @CreatedDate
22     @Temporal(TemporalType.TIMESTAMP)
23     @Column(nullable = false)
24     p
private Calendar createdDate;
25
26     @LastModifiedDate
27     @Temporal(TemporalType.TIMESTAMP)
28     @Column(nullable = false)
29     p
private Calendar lastModifiedDate;
30
31     @CreatedBy

También vamos a necesitar la clase que establece el auditor de cada operación.

1 package com.danielme.springboot.repositories;
2
3 import java.util.Optional;
4
5 import org.springframework.data.domain.AuditorAware;
6 import org.springframework.stereotype.Component;
7
8 @Component
9 public class CustomAuditorAware implements AuditorAware<String>
10
11     @Override
12     p
public Optional<String> getCurrentAuditor() {
13         r
return Optional.of("test");
14     }
15
16 }

En la entidad tenemos una clave primaria (https://danielme.com/2013/01


/14/jpa_hibernate_p/) de tipo identidad y dos atributos que no pueden ser nulos, uno
de ellos además de valor único para todas las entidades. Además se ha usado Bean
Validation (https://danielme.com/2018/10/10/validaciones-con-hibernate-validator/).

1 package com.danielme.springboot.entities;
2
3 import javax.persistence.Column;
4 import javax.persistence.Entity;
5 import javax.persistence.GeneratedValue;
6 import javax.persistence.GenerationType;
7 import javax.persistence.Id;
8 import javax.persistence.Table;
9 import javax.validation.constraints.Max;
10 import javax.validation.constraints.Min;
11 import javax.validation.constraints.NotNull;
12
13 @Entity
14 @Table(name = "countries")
15 public class Country extends AuditableEntity {
16     @Id
17     @GeneratedValue(strategy = GenerationType.IDENTITY)
18     p
private Long id;
19
20     @Column(nullable = false, unique = true)
21     @NotNull
22     p
private String name;
23
24     @Column(nullable = false)
25     @NotNull
26     @Min(1)
27     @Max(2000000000)
28     p
private Integer population;
29
30     p
public Country() {
31         s
super();
32     }
33
34     p
public Country(String name, Integer population) {
35         s
super();
36         t
this.name = name;
37         t
this.population = population;
38     }
39
40 //getters y setters
41
42 }

Para el repositorio no se de�ne ninguna operación especí�ca ya que es su�ciente con


las operaciones que Spring Data JPA ofrece de serie.

1 package com.danielme.springboot.repositories;
2
3 import org.springframework.data.jpa.repository.JpaRepository;
4
5 import com.danielme.springboot.entities.Country;
6
7 public interface CountryRepository extends JpaRepository<Country
8
9 }

Vamos a seguir una arquitectura multicapa clásica basada en persistencia-servicios-


vista. Añadimos una clase de servicio para trabajar con los países y que simplemente
hace de intermediario con el repositorio.

1 package com.danielme.springboot.services;
2
3 import java.util.List;
4 import java.util.Optional;
5
6 import org.springframework.stereotype.Service;
7
8 import com.danielme.springboot.entities.Country;
9 import com.danielme.springboot.repositories.CountryRepository;
10
11 @Service
12 public class CountryService {
13
14     p
private final CountryRepository countryRepository;
15
16     p
public CountryService(CountryRepository countryRepository)
17         t
this.countryRepository = countryRepository;
18     }
19
20     ppublic List<Country> findAll() {
21         rreturn countryRepository.findAll();
22     }
23     
24     ppublic Optional<Country> findById(Long id) {
25         rreturn countryRepository.findById(id);
26     }
27     
28     ppublic Long insert(Country country) {
29         country.setId(nnull);
30         rreturn countryRepository.save(country).getId();
31     }
32 }

Echemos un vistazo a la estructura actual del proyecto.

Antes de continuar construyendo nuestra aplicación web, veamos cómo ejecutar el


proyecto utilizando una clase con un método main. Para hacer que Spring Boot
con�gure Spring anotamos esta clase con @SpringBootApplication
(https://docs.spring.io/spring-boot/docs/current/api/org/springframework
/boot/autocon�gure/SpringBootApplication.html) que aplica las funcionalidades
proporcionadas por @Con�guration (https://docs.spring.io/spring/docs/4.3.x/javadoc-
api/org/springframework/context/annotation/Con�guration.html),
@EnableAutoCon�guration, (https://docs.spring.io/autorepo/docs/spring-
boot/1.5.9.RELEASE/api/org/springframework/boot/autocon�gure
/EnableAutoCon�guration.html)@ComponentScan (https://docs.spring.io/spring
/docs/4.3.x/javadoc-api/org/springframework/context/annotation
/ComponentScan.html) y @EnableWebMvc (https://docs.spring.io/autorepo
/docs/spring/4.2.x/javadoc-api/org/springframework/web/servlet/con�g/annotation
/EnableWebMvc.html) (esta última si tenemos Spring MVC en el classpath). En nuestro
ejemplo necesitamos también activar la auditoria de entidades con
@EnableJpaAuditing (https://docs.spring.io/spring-data/jpa/docs/current/api/org
/springframework/data/jpa/repository/con�g/EnableJpaAuditing.html).

En el método main lanzamos la aplicación de forma estática. Para ejecutar código en el


arranque pero después de la iniciación de Spring, implementamos la interfaz
CommandLineRunner (https://docs.spring.io/spring-boot/docs/current/api/org
/springframework/boot/CommandLineRunner.html) con el método run
(https://docs.spring.io/spring-boot/docs/current/api/org/springframework
/boot/CommandLineRunner.html#run-java.lang.String...-).

1 package com.danielme.springboot;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.boot.CommandLineRunner;
7 import org.springframework.boot.SpringApplication;
8 import org.springframework.boot.autoconfigure.SpringBootApplica
9 import org.springframework.data.jpa.repository.config.EnableJpa
10
11 import com.danielme.springboot.services.CountryService;
12
13 @SpringBootApplication
14 @EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
15 public class DemoApp implements CommandLineRunner {
16
17     p
private static final Logger logger = LoggerFactory.getLogge
18
19     @Autowired
20     CountryService countryService;
21
22     p
public static void main(String[] args) {
23         SpringApplication.run(DemoApp.c
class, args);
24     }
25
26     @Override
27     p
public void run(String... arg0) throws Exception {
28         logger.info(String.valueOf(countryService.findAll().siz
29     }
30
31 }

Ahora ejecutamos el método main como cualquier proyecto Maven y comprobamos el


log.

También podemos utilizar el plugin de Maven de Spring Boot para empaquetar toda la
aplicación en un jar ejecutable sin tener que realizar ninguna acción adicional
utilizando el siguiente comando.

mvn package spring-boot:repackage

Este comando crea dos .jar en el directorio target.


El pequeño contiene sólo las clases propias del proyecto mientras que el grande incluye
todas las dependencias y puede ser ejecutado mediante java -jar.

Si queremos ejecutar la aplicación “al vuelo”, es más rápido ejecutar simplemente

mvn spring-boot:run

Para más información sobre el empaquetado de aplicaciones consultar Maven:


aplicaciones ejecutables (https://danielme.com/2015/05/13/maven-aplicaciones-
ejecutables/).

Pool de conexiones

Spring Boot proporciona de serie una implementación de DataSource


(https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html) que ofrece un
pool de conexiones. Esta implementación depende de la versión que utilicemos:

Spring 1.x: la implementación elegida es la de Tomcat(


org.apache.tomcat.jdbc.pool.DataSource (https://tomcat.apache.org/tomcat-7.0-
doc/api/org/apache/tomcat/jdbc/pool/DataSource.html))
Spring 2.x: proporciona HikariCP (http://brettwooldridge.github.io/HikariCP/).

HikariCP es bastante más potente que el pool de Tomcat y en los benchmark suele
ofrecer un mejor rendimiento, motivo por el cual el equipo de Spring lo ha adoptado
(https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-
Notes#hikaricp). Sin embargo, HikariCP estaba soportado en Spring Boot 1 y podemos
utilizarlo siguiendo estos dos pasos:

1. Incluir la dependencia en el pom. La versión a utilizar la delegamos en Spring Boot.

1 <d
dependency>
2   <ggroupId>com.zaxxer</g
groupId>
3   <aartifactId>HikariCP</aartifactId>
4 </ddependency>

2. Añadir la siguiente línea al application.properties.


spring.datasource.type: com.zaxxer.hikari.HikariDataSource

En cualquier caso, los parámetros de HikariCP que queramos con�gurar, siempre con
prudencia y en función de las necesidades de la aplicación, se de�nen en este mismo
�chero con el pre�jo spring.datasource.hikari, por ejemplo:

spring.datasource.hikari.maximumPoolSize=20: 10

Aplicación web con JSP

Convirtamos nuestro ejemplo en una aplicación web con Spring MVC que muestre el
listado de países que tengamos en la base de datos siguiendo los siguientes pasos.

1. Añadimos el starter spring-boot-starter-web. Vamos a utilizar JSP para renderizar la


vista de forma dinámica, pero si hiciéramos uso de alguno de los motores de
plantillas compatibles con Spring Boot (Thymelef, FreeMarker, Mustache, Velocity,
Groovy Templates) tendríamos que incluir el starter correspondiente, y en este caso
ya no es necesario el spring-boot-starter-web.

1 <d
dependency>
2     <ggroupId>org.springframework.boot</ggroupId>
3     <aartifactId>spring-boot-starter-web</aartifactId>
4     <eexclusions>
5         <eexclusion>
6             <g groupId>org.springframework.boot</ggroupId>
7             <a artifactId>spring-boot-starter-logging</aartifact
8             </e exclusion>
9         </eexclusions>
10 </ddependency>

2. Al utilizarse JSP, necesitamos la dependencia para el lenguaje JSTL.

1 <d
dependency>
2   <ggroupId>javax.servlet</g
groupId>
3   <aartifactId>jstl</a
artifactId>
4 </ddependency>

y el compilador para JSP

1 <d
dependency>
2   <ggroupId>org.apache.tomcat.embed</ggroupId>
3   <aartifactId>tomcat-embed-jasper</a
artifactId>
4   <sscope>provided</s
scope>
5 </ddependency>

3. Spring Boot utiliza un servidor Tomcat, Jetty o Undertow embebido para desplegar
la aplicación. Por defecto se utiliza Tomcat ya que spring-boot-starter-web incluye
spring-boot-starter-tomcat por lo que no tenemos que hacer nada.
4. Las herramientas de desarrollo (https://docs.spring.io/spring-boot/docs/current
/reference/html/using-boot-devtools.html) de Spring Boot permiten, entre otras
funcionalidades, la recarga automática de la aplicación web en el Tomcat al
detectarse cambios en los �cheros incluidos en el classpath.

1 <d
dependency>
2   <ggroupId>org.springframework.boot</ggroupId>
3   <aartifactId>spring-boot-devtools</a
artifactId>
4   <ooptional>true</o
optional>
5 </ddependency>

5. El valor del packaging debe ser war.

<p
packaging>war</p
packaging>

6. En el application.properties de�nimos el pre�jo y su�jo que utilizará el ViewResolver


de Spring MVC para encontrar los �cheros .jsp que renderizarán las respuestas de
las peticiones web.
spring.mvc.view.prefix= /WEB-INF/jsp/
spring.mvc.view.suffix= .jsp

Con estos pasos ya hemos convertido nuestra aplicación de ejemplo en una aplicación
web. Vamos a desarrollar un controlador que atienda la raíz de la dirección url para
obtener los países de la base de datos utilizando el repositorio de Spring Data que ya
tenemos pero siempre a través del servicio. Un script JSP será el responsable de
renderizar el html �nal que se enviará al navegador del usuario.

Marcamos la clase con la anotación @Controller (https://docs.spring.io/spring-


framework/docs/current/javadoc-api/org/springframework/stereotype
/Controller.html) (tener siempre presente que el scope de un controller es singleton
como cualquier otro bean de Spring) y en el método que procesa la petición indicamos
la url que “escucha” con la anotación @RequestMapping (https://docs.spring.io
/spring-framework/docs/current/javadoc-api/org/springframework/web/bind
/annotation/RequestMapping.html). Opcionalmente podemos de�nir el tipo de
petición (GET, POST, PUT…) que puede tratar el método, si no se indica se atenderán
todos. Dentro del método obtenemos la lista de países y se añade al Model
(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org
/springframework/ui/Model.html) de Spring MVC (en la práctica esto es equivalente a
poner la lista en un atributo del request). Por último, devolvemos una cadena con la
ruta del script JSP la cual consiste en todo lo que va entre los valores de los parámetros
spring.mvc.view.pre�x y spring.mvc.view.su�x, esto es, /WEB-INF/jsp/countriesList.jsp

1 package com.danielme.springboot.controllers;
2
3 import org.springframework.stereotype.Controller;
4 import org.springframework.ui.Model;
5 import org.springframework.web.bind.annotation.RequestMapping;
6
7 import com.danielme.springboot.services.CountryService;
8
9 @Controller
10 public class CountryController {
11
12     p
private final CountryService countryService;
13
14     p
public CountryController(CountryService countryService) {
15         t
this.countryService = countryService;      
16     }
17
18     @RequestMapping("/")
19     p
public String list(Model model) {
20         model.addAttribute("countriesList", countryService.find
21         r
return "countriesList";
22     }
23 }

Ubicamos countriesList en el directorio /src/main/WEB-INF/jsp tal y como hemos


indicado en el application.properties. Mostramos los datos iterando sobre el atributo
countriesList utilizando la librería de etiquetas estándar JSTL.
1 <%@ page contentType="text/html; charset=UTF-8"%>
2 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core
3 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt
4
5 <!DDOCTYPE html>
6 <h
html>
7
8 <h
head>
9     <m
meta charset="utf-8">
10     <m
meta name="viewport" content="width=device-width, initial-
11
12     <l
link rel="shortcut icon" type="image/png" href="/favicon.p
13
14     <l
link rel="stylesheet" href="https://stackpath.bootstrapcdn
15     integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm

Los �cheros con recursos estáticos tales como imágenes, css, JavaScript, etc, se ubican
por defecto en el directorio /src/main/resources/static o en subdirectorios del mismo.
Son accesibles a partir de la url raíz de la aplicación, por ejemplo /src/main/resources
/static/js/script.js estará en http://localhost:8080/js/script.js (http://localhost:8080
/js/script.js).

Para arrancar la aplicación web tenemos varias opciones:

Crear un .war ejecutable con el comando que vimos antes (mvn package spring-
boot:repackage).
Lanzar directamente la aplicación con mvn spring-boot:run.
Lanzar la aplicación, tanto en modo “normal” como en debug, en Eclipse utilizando
el plugin de Spring instalable desde el Marketplace. También podemos descargar
desde la web de Spring (https://spring.io/tools/sts/all) un Eclipse ya empaquetado
con este plugin. En cualquier caso, en el menu contextual tanto del proyecto como
de la clase con el Main tendremos la opción Spring Boot App dentro de Run As y
Debug As.

Una vez lanzado el servidor, se puede detener desde la propia consola de Eclipse.

La aplicación está disponible en la url http://localhost:8080 (http://localhost:8080)

Si fuera necesario el puerto se puede cambiar con la propiedad server.port en el


application.properties, al igual que el nombre con el que se despliega la aplicación
(server.servlet.context-path).

server.servlet.context-path=/spring-boot-demo
server.port=9090
Normalmente necesitaremos empaquetar las aplicaciones web en un war estándar que
pueda ser desplegado directamente en un contenedor o servidor de aplicaciones como
Tomcat. Conseguir esto es tan sencillo como hacer que la clase Main herede de
SpringBootServletInitializer, clase que depende de la versión de Spring Boot:

1 : org.springframework.boot.web.support.SpringBootServletInitializer
(https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/api/org/springframework
/boot/web/support/SpringBootServletInitializer.html)
2 : org.springframework.boot.web.servlet.support.SpringBootServletInitializer
(https://docs.spring.io/spring-boot/docs/current/api/org/springframework
/boot/web/servlet/support/SpringBootServletInitializer.html)

1 @SpringBootApplication
2 public class DemoApp extends SpringBootServletInitializer
3 {

NOTA: Si lanzamos el proyecto desde Eclipse, la url es http://localhost:8080/spring-


boot/ (http://localhost:8080/spring-boot/).

Con este pequeño cambio el war generado con Spring Boot sigue siendo autoejecutable
con el Tomcat embebido pero también puede desplegarse en cualquier Tomcat o
similar (renombrar y utilizar el �chero spring-boot-1.0.war.original). Si queremos
prescindir de la funcionalidad de autoejecución, hay que eliminar del pom las
dependencias spring-boot-starter-tomcat y tomcat-embed-jasper. Esto provoca como efecto
colateral que desde Eclipse la aplicación deba ser lanzada como cualquier proyecto web
Maven estándar en un servidor y no con las herramientas del plugin de Spring.

Servicios REST

Vamos a añadir un pequeño servicio REST para Country. Este servicio se implementa
sin utilizar ninguna funcionalidad especí�ca de Spring Boot y voy a utilizar algunas de
las anotaciones introducidas en Spring 4 tales como @RestController
(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org
/springframework/web/bind/annotation/RestController.html) o @GetMapping
(https://docs.spring.io/spring-framework/docs/current/javadoc-api/org
/springframework/web/bind/annotation/GetMapping.html) (Spring 4.3) que
simpli�can la de�nición de los servicios.

En primer lugar, creamos un controlador de tipo RestController con una url raíz para
todas las llamadas que contendrá

1 @RestController
2 @RequestMapping(CountryRestController.COUNTRY_RESOURCE)
3 public class CountryRestController {
4     
5     ppublic static final String COUNTRY_RESOURCE = "/api/country"

Tanto las respuestas y peticiones de nuestro servicio REST se basarán en JSON. Esta
funcionalidad ya la tenemos de serie en Spring y no es necesario con�gurar
absolutamente nada. La conversión entre objetos Java y JSON y viceversa (serialización
y deserialización) es realizada de forma automática por Spring si en el classpath se
encuentra la librería Jackson (https://github.com/FasterXML/jackson) la cual es una
dependencia de spring-boot-starter-web. La instancia del mapeador de Jackson
(ObjectMapper (https://fasterxml.github.io/jackson-databind/javadoc/2.2.0
/com/fasterxml/jackson/databind/ObjectMapper.html)) que Spring Boot crea de forma
automática puede personalizarse via application.properties siguiendo la documentación
o�cial (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-
mvc.html#howto-customize-the-jackson-objectmapper).

Para las respuestas se utiliza la clase ResponseEntity (https://docs.spring.io/spring-


framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html)
que representa la respuesta HTTP completa que se devuelve al cliente del servicio. Esta
clase, además de contener el objeto con los datos que el cliente recibirá en formato
JSON en el body de la respuesta, permite personalizar el código de estatus HTTP, algo
muy importante a la hora de hacer un buen diseño de una API REST.

Tenemos dos servicios REST para el recurso Country.

Obtención de un país a partir de su id. Es una llamada HTTP de tipo GET con el
identi�cador del país incluido en la propia url. Para recibir en el método este valor
utilizamos la anotación @PathVariable (https://docs.spring.io/spring-framework
/docs/current/javadoc-api/org/springframework/web/bind/annotation
/PathVariable.html). Si el país existe se devuelve junto con un status code 200, si
no simplemente se retorna un 404.

1 @GetMapping(value = "/{id}/")
2 public ResponseEntity<Country> getById(@PathVariable("id"
3     Optional<Country> country = countryService.findById(id);
4     i
if (country.isPresent()) {
5         r
return new ResponseEntity<>(country.get(), HttpStatus.
6     }
7     r
return new ResponseEntity<>(n
null, HttpStatus.NOT_FOUND);
8 }

Creación de un nuevo país. Es una llamada tipo POST directamente a la url raíz del
servicio y el cliente debe enviar un JSON con el nombre y población del país
{
"name": "Germany",
"population": 79778000
}

En el método del controller podemos recibir directamente un objeto de tipo Country


añadiéndolo como un parámetro con la anotación @RequestBody
(https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework
/web/bind/annotation/RequestBody.html). Esta anotación tiene la propiedad
required que por defecto es true. Si la inserción es correcta, se devuelve el
identi�cador que ha sido asignado al nuevo país en un JSON con el atributo id, y un
código 201 (creado). Si se produce una excepción, se devolverá el código 500
(Internal server error) y un JSON con el mensaje de la excepción. Además vamos a
imponer a los datos del país la veri�cación de las restricciones de�nidas en la clase
Country con la especi�cación Bean Validation (https://danielme.com/2018/10
/10/validaciones-con-hibernate-validator/), algo que tenemos “gratis” en Spring
Boot ya que el starter spring-boot-starter-web incluye Hibernate Validator
(http://hibernate.org/validator/) y sólo tenemos que anotar el parámetro con
@Valid.
1 @PostMapping
2 public ResponseEntity<Map<String, Object>> add(@RequestBody
3     t
try {
4         Long id = countryService.insert(country);

Si hay errores de validación se devuelve un 400 – Petición incorrecta y un JSON


con la traza completa del error que muestra demasiada información. Para
personalizar el JSON de respuesta consultar el �nal del tutorial Spring Boot: Gestión
de errores en aplicaciones web y REST (https://danielme.com/2019/03/02/spring-
boot-gestion-de-errores-en-aplicaciones-web-y-rest/).

La clase completa queda así


1 package com.danielme.springboot.controllers;
2
3 import java.util.Collections;
4 import java.util.Map;
5 import java.util.Optional;
6
7 import javax.validation.Valid;
8
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.springframework.http.HttpStatus;
12 import org.springframework.http.ResponseEntity;
13 import org.springframework.web.bind.annotation.GetMapping;
14 import org.springframework.web.bind.annotation.PathVariable;
15 import org.springframework.web.bind.annotation.PostMapping;
16 import org.springframework.web.bind.annotation.RequestBody;
17 import org.springframework.web.bind.annotation.RequestMapping
18 import org.springframework.web.bind.annotation.RestController
19
20 import com.danielme.springboot.entities.Country;
21 import com.danielme.springboot.services.CountryService;
22
23 @RestController
24 @RequestMapping(CountryRestController.COUNTRY_RESOURCE)
25 public class CountryRestController {
26
27     pprivate static final Logger logger = LoggerFactory.getLog
28     
29     ppublic static final String COUNTRY_RESOURCE = "/api/count
30
31     p
private final CountryService countryService;
32
33     p
public CountryRestController(CountryService countryServic
34         t
this.countryService = countryService;
35     }
36
37     @GetMapping(value = "/{id}/")
38     p
public ResponseEntity<Country> getById(@PathVariable
39         Optional<Country> country = countryService.findById(i
40         i
if (country.isPresent()) {
41             r
return new ResponseEntity<>(country.get(), HttpSt

La API REST se puede probar utilizando herramientas gratuitas y multiplataformas


tales como Insomnia REST Client (https://insomnia.rest/) o Postman
(https://www.getpostman.com/).
En el tutorial Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST
Assured, bases de datos embebidas (https://danielme.com/2018/11/26/testing-en-
spring-boot-con-junit-45-mockito-mockmvc-rest-assured-bases-de-datos-embebidas/)
testearemos esta API REST desde código en tests de JUnit utilizando MockMvc y
RestAssured.

Spring initializr

La creación inicial del proyecto se puede realizar de forma grá�ca mediante esta
herramienta vía web (https://start.spring.io/), o bien mediante un asistente en
Eclipse gracias al plugin Spring Tool Suite (https://spring.io/tools/sts) del que ya
hemos hablado. Para utilizarlo nos vamos a “File>New->Other” y seleccionamos
el asistente Spring Boot->Spring Starter Project.

El asistente consta de tres pantallas. En la segunda de ellas tenemos acceso a un


catálogo categorizado con todos los starters.
Los proyectos creados con esta herramienta incluyen un “wrapper” para Maven o
Gradle, según la opción elegida, por lo que no es necesario tener instalado como
prerrequisito Maven (o Gradle) para poder construir el proyecto. Además de esta
forma nos aseguramos que el proyecto se gestione siempre con la versión de Maven
o Gradle correcta. En mi opinión es una buena práctica utilizarlo aunque en el
proyecto de ejemplo no lo he incluido por simplicidad.

Disponemos de dos scripts, uno para Windows (cmd) y otro para Linux (bash), que
invocaremos con los mismos parámetros que usaríamos si llamáramos directamente
al ejecutable de Maven (mvn).

Propiedades personalizadas

Hemos visto que los parámetros de con�guración de Spring Boot y de las librerías
que integra se de�nen en el �chero application.properties. Para utilizar otros �cheros
de propiedades podemos seguir la praxis habitual de cualquier aplicación Spring.

1. Añadir el �chero al classpath dentro de /src/main/resources, siempre y cuando el


�chero vaya a ser empaquetado dentro de la aplicación.

song=ride like the wind


2. Importar el �chero desde una clase de con�guración de Spring con la anotación
@PropertySource (https://docs.spring.io/spring-framework/docs/current
/javadoc-api/org/springframework/context/annotation/PropertySource.html).

@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
@PropertySource("classpath:song.properties")
public class DemoApp extends SpringBootServletInitializer
{

3. Inyectar directamente las propiedades de forma individual…

@Value("${song}")
private String song;

o utilizar el Environment (https://docs.spring.io/spring-framework/docs/current


/javadoc-api/org/springframework/core/env/Environment.html) de Spring

@Value("${song}")
@Autowired
private Environment environment;

...

String song = environment.getProperty("song");

También podemos añadir nuestros propios parámetros al �chero


application.properties y obtenerlos del mismo modo, pero Spring Boot permite
modelar en clases esos parámetros para abstraer su utilización.

1. Creamos un clase que modele un grupo de propiedades de con�guración en


atributos con sus correspondientes getters y setters. Estos atributos pueden
validarse utilizando las anotaciones de Bean Validation (https://danielme.com
/2018/10/10/validaciones-con-hibernate-validator/) (en este caso la clase debe
ser anotada con @Validated (https://docs.spring.io/spring/docs/current
/javadoc-api/org/springframework/validation/annotation/Validated.html)).
Esta clase la anotamos con @Con�gurationProperties (https://docs.spring.io
/spring-boot/docs/current/api/org/springframework/boot/context/properties
/Con�gurationProperties.html) para de�nir el pre�jo que tendrán estas
propiedades.
1 package com.danielme.springboot;
2
3 import javax.validation.constraints.NotBlank;
4
5 import org.springframework.boot.context.properties.Configu
6 import org.springframework.validation.annotation.Validated
7
8 @Validated
9 @ConfigurationProperties("custom")
10 public class CustomProperties {
11
12     @NotBlank
13     p
private String value;
14
15     p
public String getValue() {
16         r
return value;
17     }
18
19     p
public void setValue(String value) {

La anotación @NotBlank (https://docs.jboss.org/hibernate/stable


/beanvalidation/api/javax/validation/constraints/NotBlank.html) impone que el
valor sea obligatorio y no una cadena en blanco, esto es, que no conste sólo de
espacios en blanco. Si no se satisface esta restricción se lanzará una excepción y
el inicio de Spring se aborta:

***************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target org.springframework.boot.context.properties.bin

Property: custom.value
Value:
Origin: class path resource [application.properties]:10:15
Reason: must not be blank

Action:

Update your application's configuration

2. Incluir esta clase en la con�guración de Spring Boot con la anotación


EnableCon�gurationProperties (https://docs.spring.io/spring-boot/docs/current
/api/org/springframework/boot/context/properties
/EnableCon�gurationProperties.html).

1 @SpringBootApplication
2 @EnableConfigurationProperties(CustomProperties.c
class)
3 public class DemoApp extends SpringBootServletInitializer
4 {
3. Le damos valor a la propiedad (custom.value).
spring.datasource.url=jdbc:mysql://localhost:3306/country
spring.datasource.username=demo
spring.datasource.password=demo
spring.jpa.hibernate.ddl-auto=update
spring.datasource.type= com.zaxxer.hikari.HikariDataSource

spring.mvc.view.prefix= /WEB-INF/jsp/
spring.mvc.view.suffix= .jsp

custom.value = Hello World!!!

4. Para leer la propiedad, inyectamos la clase que la modela. Como ejemplo, se


imprime la propiedad en el log en CountryController

1 package com.danielme.springboot.controllers;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.stereotype.Controller;
6 import org.springframework.ui.Model;
7 import org.springframework.web.bind.annotation.RequestMapp
8
9 import com.danielme.springboot.CustomProperties;
10 import com.danielme.springboot.services.CountryService;
11
12 @Controller
13 public class CountryController {
14
15     p
private static final Logger logger = LoggerFactory.get
16
17     p
private final CountryService countryService;
18     p
private final CustomProperties customProperties;
19
20     p
public CountryController(CountryService countryService
21             CustomProperties customProperties) {
22         t
this.countryService = countryService;
23         t
this.customProperties = customProperties;
24     }
25
26     @RequestMapping("/")
27     p
public String list(Model model) {
28         logger.info(customProperties.getValue());
29         model.addAttribute("countriesList", countryService
30         r
return "countriesList";
31     }
32 }

5. Si añadimos al pom la siguiente dependencia

1 <d
dependency>
2     <ggroupId>org.springframework.boot</g
groupId>
3     <aartifactId>spring-boot-configuration-processor</a
artifa
4     <ooptional>true</o
optional>
5 </ddependency>

al compilar el proyecto se creará el �chero /target/classes/META-INF/spring-


con�guration-metadata.json que detalla las propiedades personalizadas que
vayamos añadiendo
{
"groups": [
{
"name": "custom",
"type": "com.danielme.springboot.CustomProperties",
"sourceType": "com.danielme.springboot.CustomProperties"
}
],
"properties": [
{
"name": "custom.value",
"type": "java.lang.String",
"sourceType": "com.danielme.springboot.CustomProperties"
}
],
"hints": []
}

Código de ejemplo

El proyecto completo para el presente tutorial y también Testing en Spring Boot con
JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas
(https://danielme.com/2018/11/26/testing-en-spring-boot-con-junit-45-mockito-
mockmvc-rest-assured-bases-de-datos-embebidas/) se encuentra disponible en
GitHub (https://github.com/danielme-com/spring-boot-web-spring-data-jpa). Para
más información sobre cómo utilizar GitHub, consultar este artículo
(https://danielme.com/2013/08/07/importar-repositorios-de-github-con-git-
o-eclipse).

También se encuentran en GitHub los ejemplos o�ciales de Spring Boot


(https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples).

Otros tutoriales relacionados con Spring

Spring REST: Securización BASIC y JDBC con Spring Security(https://danielme.com


/2019/03/20/spring-rest-basic-spring-security/)

Spring Boot: Gestión de errores en aplicaciones web y REST (https://danielme.com


/2019/03/02/spring-boot-gestion-de-errores-en-aplicaciones-web-y-rest/)

Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de
datos embebidas (https://danielme.com/2018/11/26/testing-en-spring-boot-con-
junit-45-mockito-mockmvc-rest-assured-bases-de-datos-embebidas/)

Spring JDBC Template: simpli�cando el uso de SQL (https://danielme.com


/2017/10/02/spring-jdbc-template-simpli�cando-el-uso-de-sql/)

Persistencia en BD con Spring Data JPA (https://danielme.com/2014/02


/08/persistencia-en-bd-con-spring-data-jpa/)
Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache(https://danielm
/2013/06/19/persistencia-bd-con-spring-integrando-jpa-c3p0-hibernate-
y-ehcache/)

Testing Spring con JUnit 4 (https://danielme.com/2017/07/17/tutorial-spring-


testing-junit-4/)

Ficheros .properties en Spring IoC (https://danielme.com/2012/07/25/�cheros-


properties-en-spring-ioc/)

(https://click.linksynergy.com/fs-
bin/click?id=OZ02PWh8Q04&o�erid=467035.271&subid=0&type=4)

2 comentarios sobre “Introducción a Spring Boot:


Aplicación Web con servicios REST y Spring Data
JPA”

1. Miguel
DICE:
15/03/2019 DE 10:15
Hola Daniel, muy bueno el articulo.

¿como podriamos securizarlo?

Responder
1. danielme.com
DICE:
20/03/2019 DE 19:52
Acabo de publicar Spring REST: Securización BASIC y JDBC con Spring Security.

Responder

This site uses Akismet to reduce spam. Learn how your comment data is processed.

BLOG DE WORDPRESS.COM.

También podría gustarte