====== 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.