====== Práctica 1a ====== En esta práctica vamos a utilizar [[https://spring.io/projects/spring-boot|Spring Boot]], una herramienta que nos permite crear una aplicación [[https://spring.io/|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: [[https://marketplace.visualstudio.com/items?itemName=Pivotal.vscode-boot-dev-pack|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 [[https://start.spring.io/|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 [[https://start.spring.io/|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//: {{ :clase:daw:prog:2eval:practicas:spring01.png?400 |}} 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: * [[https://www.thymeleaf.org/|Thymeleaf]]: sistema de plantillas para entornos web * [[https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web|Spring web]]: iniciador de aplicaciones web. Utiliza [[https://tomcat.apache.org/|Tomcat]] como servidor web embebido * [[https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/using-boot-devtools.html|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 {{ :clase:daw:prog:2eval:practicas:spring02.png?400 |}} Una vez seleccionada la carpeta donde queramos instalar nuestro proyecto, tendrá el siguiente aspecto: {{ :clase:daw:prog:2eval:practicas:spring03.png?400 |}} ==== 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//: {{ :clase:daw:prog:2eval:practicas:spring04.png?400 |}} 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): {{ :clase:daw:prog:2eval:practicas:spring05.png?400 |}} === 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//: {{ :clase:daw:prog:2eval:practicas:spring06.png?400 |}} Si abres ahora el navegador, verás como podrás acceder a tu aplicación cargando la url http://localhost:8081/: {{ :clase:daw:prog:2eval:practicas:spring07.png?400 |}} ===== Beans ===== Dentro de la infraestructura de //Spring// tenemos algo llamado [[https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html|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 [[https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition|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: {{ :clase:daw:prog:2eval:practicas:spring08.png?400 |}} 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): {{ :clase:daw:prog:2eval:practicas:spring09.png?400 |}} ==== 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"); } } Ten en cuenta que no tenemos que juntar todas las rutas en un mismo controlador. En una aplicación web, normalmente tenemos varios controladores, cada uno con su propias rutas. Gracias a //Spring Boot//, para que todo funcione correctamente solo tenemos que añadir la anotación @Controller a cada uno de nuestros controladores y definir las rutas para que se haga cargo el framework. Si las rutas de nuestros controladores tienen todas la misma raíz (/categorias, /categorias/id_categoria, /categorias/id_categoria/productos...) podemos utilizar la anotación @RequestMapping delante de la clase para establecer esa ruta común: @RequestMapping("/categorias") @Controller public class CategoryController { ... Por ahora, solo estamos utilizando el método //HTTP// //GET// mediante la anotación @GetMapping, que sirve para recuperar información. Si queremos añadir, modificar o borrar datos, también tenemos otras anotaciones como @PostMapping, @PutMappint, @DeleteMapping... pero eso lo dejaremos para más adelante cuando trabajemos con bases de datos. ==== 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//): {{ :clase:daw:prog:2eval:practicas:spring16.png?400 |}} Si accedes a la ruta ///productos/14//, verás como cambia el //id// del producto de manera correcta: {{ :clase:daw:prog:2eval:practicas:spring17.png?400 |}} Si al parámetro lo llamas igual que la variable de entrada del método, te puedes ahorrar escribir su nombre entre paréntesis en la anotación @PathVariable: @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 //// del //HTML//): {{ :clase:daw:prog:2eval:practicas:spring10.png?400 |}} 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: {{ :clase:daw:prog:2eval:practicas:spring11.png?400 |}} ==== Modificando la página de error ==== Por defecto, //Spring Boot// muestra una página de error genérica llamada //Whitelabel//: {{ :clase:daw:prog:2eval:practicas:spring05.png?400 |}} Podemos deshabilitar dicha página añadiendo la siguiente línea a //application.properties//: server.error.whitelabel.enabled=false {{ :clase:daw:prog:2eval:practicas:spring13.png?400 |}} Para añadir nuestra propia página de error usando //Thymeleaf//, simplemente tenemos que crear el archivo //error.html// en la carpeta //templates//: {{ :clase:daw:prog:2eval:practicas:spring14.png?400 |}} A partir de ahora, cuando se introduzca una ruta que no existe //Spring// mostrará nuestra página de error personalizada: {{ :clase:daw:prog:2eval:practicas:spring15.png?400 |}} ==== 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. Si te fijas, el tipo de la propiedad //precio// es [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigDecimal.html|BigDecimal]]. Esta clase es una manera exacta de representar números, y permite trabajar con mayor precisión que, por ejemplo, el tipo de dato //double//. 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 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: Document Aquí es donde empezamos a utilizar la sintaxis propia de //Thymeleaf//. En su [[https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html|documentación]] puedes ver como usar este sistema de plantillas. Para recorrer los productos, usamos el bucle [[https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#using-theach|th:each]]: 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 :
  • Texto por defecto
  • 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...): {{ :clase:daw:prog:2eval:practicas:spring18.png?400 |}} 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.