Práctica 1a
En esta práctica vamos a utilizar Spring Boot, una herramienta que nos permite crear una aplicación Spring de manera rápida y fácil.
Spring y Spring Boot
Un framework es un marco o esquema de trabajo generalmente utilizado por programadores para desarrollar aplicaciones. Los frameworks proporcionan una serie de módulos (ORM, sistema de enrutamiento, filtros…) que ayudan a organizar y desarrollar un producto software.
En Java, el framework más utilizado es Spring. Aunque no usaremos todas sus herramientas (en segundo ya aprenderás a sacarle todo el partido), vamos a utilizar un par de ellas que nos facilitarán mucho la vida a la hora de crear una web: el sistema de enrutamiento y los filtros.
Crear un proyecto web en Java consta de 3 pasos:
- Crear un proyecto Maven (o Gradle) y descargar las dependencias necesarias
- Desarrollar la aplicación
- Desplegar en un servidor
Para simplificar el proceso podemos utilizar alguna herramienta que nos permita centrarnos en el paso 2 (desarrollo de la aplicación). Spring Boot es una de esas herramientas creadas por el equipo de desarrollo de Spring. Pero antes de utilizarla, vamos a instalar una extensión que nos serán de mucha utilidad en VSC: Spring Boot Extension Pack. Esta extensión es una colección de 3 extensiones muy útiles para desarrollar aplicaciones con Spring Boot en VSC. En concreto está formada por:
- Spring Boot Tools: proporciona herramientas para desarrollar y solucionar problemas de aplicaciones Spring Boot
- Spring Initializr Java: brinda soporte para generar proyectos Spring Boot Java de inicio rápido con la API Spring Initializr
- Spring Boot Dashboard: proporciona un explorador en la barra lateral donde podemos ver todos los proyectos Spring Boot de un espacio de trabajo. También podemos iniciar, detener o depurar rápidamente un proyecto
Crear un proyecto Spring Boot
Para crear un nuevo proyecto Spring Boot contamos con una herramienta muy útil que nos automatiza el proceso: Spring Initializr. Podemos crear el proyecto directamente desde su web, configurándolo a nuestra medida y añadiendo las dependencias necesarias. Una vez terminado el proceso, generamos el proyecto, lo que nos descargará un zip con el proyecto ya configurado.
En nuestro caso, vamos a utilizar la extensión de VSC Spring Initializr. Para crear el proyecto abrimos la barra de comandos (Ctrl + Shift + p) y seleccionando Spring Initializr: Create a Mave Project:
A continuación nos pedirá la versión (seleccionamos la última), el lenguaje (Java), el group id(com.fpmislata) y artifact id (daw-prog-eval2-prac1), el tipo de compresión (Jar) y la versión de Java que vamos a utilizar (17).
En la siguiente opción podemos añadir las dependencias que queramos añadir a nuestro proyecto (después, una vez creado el proyecto, podemos añadir, modificar o eliminar las que queramos). En nuestro caso vamos a instalar:
- Thymeleaf: sistema de plantillas para entornos web
- Spring web: iniciador de aplicaciones web. Utiliza Tomcat como servidor web embebido
- Spring Boot DevTools: conjunto de herramientas para facilitar el desarrollo de aplicaciones. Entre otras cosas, nos permitirá no tener que reiniciar el servidor para aplicar los cambios que vayamos haciendo
Una vez seleccionada la carpeta donde queramos instalar nuestro proyecto, tendrá el siguiente aspecto:
Iniciar el servidor
Al crear el proyecto, Spring crea una clase que será la encargada de iniciarlo:
package com.fpmislata.dawprogeval2prac1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DawProgEval2Prac1Application { public static void main(String[] args) { SpringApplication.run(DawProgEval2Prac1Application.class, args); } }
Si queremos arrancar el servidor para ver que todo funciona correctamente, nos vamos a la pestaña de Spring Boot Dashboard en el lateral izquierdo de VSC y dentro de la sección APPS veremos nuestro proyecto. Lo único que tenemos que hacer es ejecutarlo con el botón play:
Por defecto, Spring monta el servidor en el puerto 8080 (veremos después como podemos cambiarlo). Si accedes desde un navegador a http://localhost:8080/, deberías ver una página genérica de error (tranquilo, esto quiere decir que todo ha funcionado correctamente):
Cambiar puerto por defecto en Spring Boot
Para cambiar el puerto en el que escucha el servidor embebido de Spring Boot, la manera más rápida y fácil es añadiendo la siguiente línea al archivo application.properties de la carpeta resources:
Si abres ahora el navegador, verás como podrás acceder a tu aplicación cargando la url http://localhost:8081/:
Beans
Dentro de la infraestructura de Spring tenemos algo llamado Spring Container, que vendría siendo el núcleo del framework. El contenedor crea objetos, los une ,los configura y administra su ciclo de vida completamente. Los Beans son objetos que puede manejar el contendor de Spring.
Para crear un Bean Spring utiliza anotaciones mediante el símbolo arroba (@). Por ejemplo, vamos a crear un controlador para responder a las rutas de nuestra aplicación. Para ello, crea dentro del package controller la clase MainController:
package com.fpmislata.dawprogeval2prac1.controller; import org.springframework.stereotype.Controller; @Controller public class MainController { public void index(){ System.out.println("Método index de MainController ejecutándose"); } }
Fíjate que en la línea anterior a la definición de la clase hemos añadido la anotación @Controller (asegúrate de importar la librería org.springframework.stereotype.Controller para que funcione). Con esa notación, le indicamos a Spring que queremos crear un Bean de tipo controlador. Existen varias anotaciones diferentes para crear Beans de Spring, pero nosotros, por ahora, vamos a utilizar controladores y poco más. Si miramos el Dashboard de Spring Boot en VSC veremos el Bean recién creado:
Para que Spring sepa qué método ejecutar según la ruta que introduzca el usario en el navegador, vamos a utilizar otro tipo de anotaciones delante de cada método: @GetMapping:
package com.fpmislata.dawprogeval2prac1.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class MainController { @GetMapping("/") public void index(){ System.out.println("Método index de MainController ejecutándose"); } }
Con ésto le estamos indicando a Spring que cada vez que se acceda a la ruta raíz de nuestra web (/) se ejecute el método MainController de nuestra aplicación. Si abrimos el navegador y accedemos a la ruta raíz (http://localhost:8081/) deberíamos ver en la terminal de VSC la frase “Método index de MainController ejecutándose” (además de una serie de errores que solucionaremos a continuación):
Añádiendo más rutas
Vamos a añadir la ruta /about con la información de nuestra organización. Lo único que tenemos que hacer es crear un nuevo método en nuestro controlador e indicarle la ruta a la que tiene que reaccionar:
package com.fpmislata.dawprogeval2prac1.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class MainController { @GetMapping("/") public void index(){ System.out.println("Método index de MainController ejecutándose"); } @GetMapping("/about") public void about(){ System.out.println("Método about de MainController ejecutándose"); } }
@RequestMapping("/categorias") @Controller public class CategoryController { ...
Rutas con parámetros variables
¿Qué pasa si nuestras rutas son del estilo /productos/id_product? Obviamente, hacer una ruta por cada producto es inviable. Para eso, tenemos que definir una ruta con un parámetro variable (id_product). La manera de hacerlo mediante Spring es utilizando las llaves para definir parámetros variables en nuestras rutas y usando la notación @PathVariable en la cabecera de nuestro método para recoger ese parámetro.
Por ejemplo, crea el controlador ProductController y añade lo siguiente:
package com.fpmislata.dawprogeval2prac1.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Controller public class ProductController { @GetMapping("/productos/{id}") public void getById(@PathVariable("id") int id){ System.out.println("Ruta: /productos/" + id); } }
Si abrimos la ruta, por ejemplo, /productos/25, veremos como nuestro método recoge el valor de dicho id (de nuevo, olvídate por ahora de los errores que muestra Spring):
Si accedes a la ruta /productos/14, verás como cambia el id del producto de manera correcta:
@GetMapping("/productos/{id}") public void getById(@PathVariable int id){ System.out.println("Ruta: /productos/" + id); }
Thymeleaf
Thymeleaf es el sistema de plantillas que vamos a utilizar en nuestro proyecto. Simplificándolo, un sistema de plantillas HTM nos permite crear archivos .html añadiéndole funcionalidad propia de un lenguaje de programación (muy reducida, eso sí) como bucles, acceso a variables, condicionales…
Antes, al crear nuestro controlador y acceder a la ruta raíz de nuestra web, has visto como Spring nos muestra una serie de errores. Eso es debido a que no encuentra la plantilla (el archivo .html) que debe cargar. Vamos a solucionar el problema.
Para cargar una plantilla desde nuestros controladores, primero vamos a crear el archivo index.html en la carpeta resources/templates (puedes mostrar lo que quieras en el <body> del HTML):
El siguiente paso, será decirle a nuestro método index() del controlador MainController qué plantilla tiene que cargar. Para eso, lo primero es cambiar el tipo devuelto por el método a String y después, devolver el nombre de la plantilla recién creada (sin el .html):
package com.fpmislata.dawprogeval2prac1.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class MainController { @GetMapping("/") public String index(){ System.out.println("Método index de MainController ejecutándose"); return "index"; } }
Ahora ya deberías ver la página web correctamente:
Modificando la página de error
Por defecto, Spring Boot muestra una página de error genérica llamada Whitelabel:
Podemos deshabilitar dicha página añadiendo la siguiente línea a application.properties:
server.error.whitelabel.enabled=false
Para añadir nuestra propia página de error usando Thymeleaf, simplemente tenemos que crear el archivo error.html en la carpeta templates:
A partir de ahora, cuando se introduzca una ruta que no existe Spring mostrará nuestra página de error personalizada:
Pasando parámetros a nuestras vistas
Ahora que ya sabemos como cargar nuestras vistas (plantillas .html), vamos a añadir funcionalidad a nuestra web pasándoles parámetros. Lo primero será crear una clase llamada Product dentro del package business/entity:
package com.fpmislata.dawprogeval2prac1.business.entity; import java.math.BigDecimal; public class Product { private int id; private String name; private String brand; private BigDecimal price; public Product(int id, String name, String brand, BigDecimal price) { this.id = id; this.name = name; this.brand = brand; this.price = price; } public int getId() { return id; } public String getName() { return name; } public String getBrand() { return brand; } public BigDecimal getPrice() { return price; } }
La clase es muy sencilla. Tiene como atributos el id, el nombre, la marca y el precio del producto. Creamos su constructor y los getters correspondientes.
Vamos a modificar el controlador de productos de la siguiente manera:
package com.fpmislata.dawprogeval2prac1.controller; import java.math.BigDecimal; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import com.fpmislata.dawprogeval2prac1.business.entity.Product; @RequestMapping("/productos") @Controller public class ProductController { List<Product> products = List.of( new Product(1, "Producto A", "Marca A", new BigDecimal(23.99)), new Product(2, "Producto B", "Marca A", new BigDecimal(14.99)), new Product(3, "Producto C", "Marca B", new BigDecimal(68.99)) ); @GetMapping public String getAll(Model model){ model.addAttribute("products", this.products); return "products"; } }
Hay varias cosas a tener en cuenta. Primero, hemos utilizado @RequestMapping para no tener que añadir el prefijo /productos a todas nuestra rutas. Además, hemos añadido un listado de productos como propiedad de la clase para simplificar las cosas.
Fíjate en el método getAll(). Ahora le estamos pasando un parámetro de tipo org.springframework.ui.Model. Esto es lo que nos va a permitir pasar parámetros a nuestras vistas usando su método addAttribute(). A este método le pasamos el nombre que tendrá el parámetro en nuestras vistas y su valor (en este caso, el listado de productos).
Ahora creamos la vista products.html con el siguiente código:
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <a th:each="product : ${products}" th:href="@{${'/productos/' + product.id}}"> <li th:text="|${product.brand} - ${product.name}|">Texto por defecto</li> </a> </ul> </body> </html>
Aquí es donde empezamos a utilizar la sintaxis propia de Thymeleaf. En su documentación puedes ver como usar este sistema de plantillas.
Para recorrer los productos, usamos el bucle th:each:
<a th:each="product : ${products}"....
Funciona de manera similar al bucle foreach de Java. En cada iteración, accedermos a cada producto con la variable product.
Para montar los links a cada página de producto, usamos th:href (en general, cualquier cosa que empiece con th: será propia de Thymeleaf):
... th:href="@{${'/productos/' + product.id}}"
Ésta es una de las maneras de concatenar texto con variables (usando @ y $).
Para mostrar el texto de cada producto, lo hacemos juntando la marca y el nombre mediante las tuberías en el atributo th:text. Si no existiera el producto, se mostraría el texto por defecto :
<li th:text="|${product.brand} - ${product.name}|">Texto por defecto</li>
Si abrimos ahora la página http://localhost:8081/productos, tendremos que ver un listado de productos, cada uno de los cuales es un link a su página de detalles (http://localhost:8081/productos/1, http://localhost:8081/productos/2…):
A partir de aquí, sólo nos quedaría crear el método findById() en el controlador, asociarlo con la ruta /productos/{id} y crear la página HTML del detalle del producto.