Spring Initializr t'ha lliurat una carpeta amb fitxers que segurament encara no has obert. Entendre què fa cadascun no és cap tràmit: la majoria dels problemes desconcertants dels primers dies —un controlador que retorna 404, una propietat que no es llegeix, una dependència que no apareix— s'expliquen per l'estructura del projecte i pel lloc on has posat una classe. En aquesta lliçó recorrerem l'arbre de directoris, llegirem el pom.xml línia a línia, veurem què és realment un starter, entendrem per què el paquet arrel és crític i decidirem l'organització de paquets definitiva de CicloUrbana.
Contingut
- L'arbre de directoris generat
src/main/javaisrc/main/resourcessrc/test/java,target/i el wrapper de Maven- Anatomia del
pom.xml, línia a línia - Què és un starter i quins farem servir al curs
- El paquet arrel i l'escaneig de components
- Organitzar el codi: per capes o per funcionalitat
- L'estructura de paquets de CicloUrbana
- El cicle de vida de Maven
- Errors Habituals i Consells
- Exercicis
- L'arbre de directoris generat
Aquest és el projecte tal com ha quedat després de la lliçó anterior:
ciclourbana/
├── .mvn/
│ └── wrapper/
│ └── maven-wrapper.properties
├── mvnw ← wrapper per a Linux i macOS
├── mvnw.cmd ← wrapper per a Windows
├── pom.xml ← definició del projecte Maven
├── .gitignore
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/ciclourbana/
│ │ │ ├── CicloUrbanaApplication.java
│ │ │ └── estacions/
│ │ │ ├── Estacio.java
│ │ │ └── EstacioController.java
│ │ └── resources/
│ │ ├── application.properties
│ │ ├── static/
│ │ └── templates/
│ └── test/
│ └── java/
│ └── com/ciclourbana/
│ └── CicloUrbanaApplicationTests.java
└── target/ ← generat per Maven, no es versionaAquesta disposició no se la inventa Spring Boot: és el layout estàndard de Maven, respectat per tot l'ecosistema Java. La conseqüència pràctica és que qualsevol desenvolupador Java sap orientar-se al teu projecte sense explicacions.
La separació fonamental és entre main (allò que s'empaqueta i es desplega) i test (allò que s'executa en construir però que mai no arriba al JAR).
src/main/java i src/main/resources
src/main/java i src/main/resourcessrc/main/java
Conté tot el codi font de producció. L'estructura de carpetes ha de reflectir exactament l'estructura de paquets: la classe com.ciclourbana.estacions.EstacioController ha d'estar a src/main/java/com/ciclourbana/estacions/EstacioController.java. No és una convenció opcional; el compilador de Java ho exigeix.
src/main/resources
Conté els fitxers no compilables que han d'acabar dins del JAR. Maven els copia tal qual a target/classes, cosa que significa que en temps d'execució són a l'arrel del classpath.
| Carpeta o fitxer | Què conté | Mòdul del curs |
|---|---|---|
application.properties |
Configuració de l'aplicació | 02-04, 02-05 |
application-dev.properties |
Configuració específica d'un perfil | 07-02 |
static/ |
Recursos servits tal qual: HTML, CSS, JS, imatges | — |
templates/ |
Plantilles de servidor (Thymeleaf) | — |
banner.txt |
Bàner ASCII d'arrencada | 01-05 |
db/migration/ |
Scripts SQL de Flyway | 04-08 |
Dos detalls importants:
static/: qualsevol fitxer que hi posis se serveix automàticament des de l'arrel. Unstatic/logo.pngés accessible ahttp://localhost:8080/logo.png. Això ho fa una autoconfiguració de Spring Web.templates/: només té sentit si afegeixes un motor de plantilles. CicloUrbana és una API REST pura, així que aquesta carpeta quedarà buida. Pots esborrar-la sense conseqüències.
application.properties
Neix buit. Li donarem contingut útil des d'ara mateix:
# src/main/resources/application.properties
# Nom de l'aplicació: apareix als logs i a Actuator
spring.application.name=ciclourbana
# Port del servidor incrustat (8080 és el valor per defecte)
server.port=8080
# Nivell de log del projecte mateix: DEBUG durant el desenvolupament
logging.level.com.ciclourbana=DEBUGCada línia és un parell clau=valor. Spring Boot defineix centenars de claus amb valors per defecte sensats; aquí només declares allò en què vols apartar-te d'aquests valors. La lliçó 02-05 aprofundeix en les propietats, inclosa l'alternativa YAML.
src/test/java, target/ i el wrapper de Maven
src/test/java, target/ i el wrapper de Mavensrc/test/java
Conté les proves. Initializr en genera una:
package com.ciclourbana;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CicloUrbanaApplicationTests {
@Test
void contextLoads() {
}
}Encara que el cos estigui buit, aquesta prova no és inútil: @SpringBootTest arrenca el context complet de Spring. Si falta una dependència, un bean no es pot construir o una propietat obligatòria no existeix, la prova falla. És una comprovació de fum molt barata que detecta errors de configuració abans de desplegar. El mòdul 6 es dedica sencer a les proves.
Les classes de test no s'inclouen al JAR final.
target/
És la carpeta de sortida de Maven. Es regenera sencera a cada construcció, per això és al .gitignore i no s'ha de versionar mai.
target/
├── classes/ ← els teus .class + els recursos copiats
├── test-classes/ ← proves compilades
├── ciclourbana-0.0.1-SNAPSHOT.jar ← el fat jar
├── ciclourbana-0.0.1-SNAPSHOT.jar.original ← el JAR "normal"
└── surefire-reports/ ← informes d'execució de provesQuan alguna cosa es comporta de manera inexplicable, ./mvnw clean esborra target i elimina les restes de compilacions anteriors. És el primer remei que cal provar.
mvnw, mvnw.cmd i .mvn/wrapper
Ja els coneixes de la lliçó 01-02. El contingut rellevant:
# .mvn/wrapper/maven-wrapper.properties
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jarAquests tres fitxers sí que es versionen a Git: són els que garanteixen que qualsevol construeixi el projecte amb la mateixa versió de Maven.
- Anatomia del
pom.xml, línia a línia
pom.xml, línia a líniaEl pom.xml (Project Object Model) és el cor del projecte. El llegirem sencer.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- (1) HERÈNCIA: d'on vénen les versions i la configuració base -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/> <!-- buscar el parent al repositori, no al disc -->
</parent>
<!-- (2) IDENTITAT del projecte: les "coordenades" Maven -->
<groupId>com.ciclourbana</groupId>
<artifactId>ciclourbana</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ciclourbana</name>
<description>Gestió de la xarxa de bicicletes elèctriques de Ribalta</description>
<!-- (3) PROPIETATS: variables reutilitzables -->
<properties>
<java.version>21</java.version>
</properties>
<!-- (4) DEPENDÈNCIES: quines llibreries necessita el projecte -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- (5) CONSTRUCCIÓ: plugins que participen en l'empaquetat -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>(1) El parent spring-boot-starter-parent
És, amb diferència, el bloc més important i el pitjor entès. Aporta quatre coses:
- Gestió de versions (
dependencyManagement). Declara la versió adequada de més de 250 llibreries —Spring, Jackson, Hibernate, Tomcat, JUnit, Mockito, Log4j...— provades i validades per funcionar juntes en aquella versió de Boot. Per això les teves dependències no porten<version>: l'hereten d'aquí. Això elimina d'un cop els conflictes de versions que turmentaven els projectes Java. - Configuració de plugins. Deixa preconfigurats el compilador, el plugin de recursos, Surefire (proves) i el
spring-boot-maven-plugin. - Valors per defecte sensats. Codificació UTF-8 en fonts i recursos, i la versió de Java presa de la propietat
java.version. - Filtratge de recursos. Permet fer servir
@propietat@dins d'application.propertiesper injectar-hi valors delpom.xml.
Pots veure la llista completa de versions gestionades:
Aquesta ordre mostra el POM "efectiu": el teu fusionat amb tot el que hereta del parent. La primera vegada impressiona veure quanta feina t'estan estalviant aquestes cinc línies.
Alternativa sense parent: si la teva organització ja fa servir el seu propi POM pare corporatiu, pots importar només la gestió de versions mitjançant el BOM (Bill of Materials):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>(2) Les coordenades del projecte
Tota llibreria Java s'identifica per tres valors:
| Coordenada | Valor a CicloUrbana | Significat |
|---|---|---|
groupId |
com.ciclourbana |
L'organització, en notació de domini invertit |
artifactId |
ciclourbana |
El nom de l'artefacte |
version |
0.0.1-SNAPSHOT |
La versió |
El sufix -SNAPSHOT significa "en desenvolupament, pot canviar". Maven tracta els snapshots de manera especial: els torna a descarregar periòdicament en lloc de guardar-los per sempre a la memòria cau. En publicar una versió estable se'n treu el sufix (1.0.0).
Les tres coordenades determinen el nom del JAR: ciclourbana-0.0.1-SNAPSHOT.jar.
(3) Les propietats
java.version la llegeix el parent per configurar el compilador amb -source 21 -target 21. Aquí pots definir les teves pròpies variables i fer-les servir amb la sintaxi ${nom}:
<properties>
<java.version>21</java.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>Aquesta dependència porta <version> perquè no està gestionada pel parent de Spring Boot: és una llibreria de tercers. És l'excepció que confirma la regla.
(4) Els àmbits de les dependències
L'element <scope> decideix quan està disponible una dependència:
| Scope | Compilació | Proves | Execució | Al JAR? | Exemple |
|---|---|---|---|---|---|
compile (per defecte) |
Sí | Sí | Sí | Sí | spring-boot-starter-web |
runtime |
No | Sí | Sí | Sí | Driver JDBC de PostgreSQL |
test |
No | Sí | No | No | spring-boot-starter-test |
provided |
Sí | Sí | No | No | API de Servlet en un WAR |
spring-boot-devtools combina runtime amb <optional>true</optional>: no s'hi compila contra, no es propaga a projectes que depenguin del teu i Spring Boot la desactiva en detectar que s'executa des d'un fat jar.
(5) El spring-boot-maven-plugin
És el plugin que converteix un JAR normal en el fat jar executable. Aporta:
- L'objectiu
repackage, enllaçat a la fasepackage, que reempaqueta el JAR ambBOOT-INF/i elJarLauncher. - L'objectiu
spring-boot:run, que arrenca l'aplicació sense empaquetar. - L'objectiu
build-image, que construeix una imatge Docker sense escriure cap Dockerfile (mòdul 7).
Sense aquest plugin, ./mvnw package produiria un JAR de 12 KB inservible per si sol.
- Què és un starter i quins farem servir al curs
Un starter és una dependència Maven sense codi propi: només un pom.xml que declara un conjunt coherent de dependències. El seu valor és que algú ja ha decidit per tu quines llibreries necessites i en quines versions.
Comprova-ho:
Sortida abreujada:
[INFO] com.ciclourbana:ciclourbana:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.3.5:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.3.5:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.3.5:compile
[INFO] | | \- org.yaml:snakeyaml:jar:2.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:3.3.5:compile
[INFO] | | \- com.fasterxml.jackson.core:jackson-databind:jar:2.17.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.5:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.31:compile
[INFO] | +- org.springframework:spring-web:jar:6.1.14:compile
[INFO] | \- org.springframework:spring-webmvc:jar:6.1.14:compileUna línia del teu pom.xml s'ha convertit en més de trenta artefactes coordinats.
Fixa't en spring-boot-starter: és l'starter base del qual depenen tots els altres. Aporta el nucli, l'autoconfiguració i el sistema de logs. Sempre hi és encara que no el declaris.
Aquests són els starters que aniran apareixent a CicloUrbana:
| Starter | Què aporta | Mòdul |
|---|---|---|
spring-boot-starter-web |
Spring MVC, Jackson, Tomcat incrustat, validació | 1 i 3 |
spring-boot-devtools |
Reinici automàtic, LiveReload | 1 |
spring-boot-starter-test |
JUnit 5, Mockito, AssertJ, Spring Test | 1 i 6 |
spring-boot-starter-validation |
Bean Validation amb Hibernate Validator | 3 |
spring-boot-starter-data-jpa |
Spring Data JPA, Hibernate, HikariCP | 4 |
spring-boot-starter-security |
Spring Security, filtres, xifratge de contrasenyes | 5 |
spring-boot-starter-actuator |
Salut, mètriques, endpoints de gestió | 7 i 9 |
spring-boot-starter-aop |
Programació orientada a aspectes | 9 |
Existeixen també starters de tercers, que per convenció inverteixen l'ordre del nom: els oficials són spring-boot-starter-* i els de tercers <nom>-spring-boot-starter (per exemple mybatis-spring-boot-starter).
- El paquet arrel i l'escaneig de components
Aquí hi ha la causa d'un dels errors més frustrants del principiant.
L'anotació @SpringBootApplication inclou @ComponentScan, que li diu a Spring: "busca classes anotades amb @Component, @Service, @Repository, @Controller o @RestController a partir del paquet d'aquesta classe i cap avall".
CicloUrbanaApplication és a com.ciclourbana, així que Spring escaneja com.ciclourbana i tots els seus subpaquets. El que quedi fora és invisible.
flowchart TD
A["com.ciclourbana<br/>CicloUrbanaApplication"] --> B["com.ciclourbana.estacions ✅"]
A --> C["com.ciclourbana.bicicletes ✅"]
A --> D["com.ciclourbana.lloguers ✅"]
A --> E["com.ciclourbana.comu ✅"]
F["com.empresa.utilitats ❌<br/>fora del paquet arrel:<br/>Spring NO l'escaneja"]
style F fill:#ffe0e0,stroke:#c00
El símptoma típic és un 404 en un endpoint el codi del qual sembla impecable: el controlador existeix però Spring no el va registrar mai, perquè era fora de l'arbre escanejat.
Regles pràctiques:
- Col·loca sempre
CicloUrbanaApplicational paquet arrel, per damunt de tots els paquets funcionals. - No la posis al paquet per defecte (sense
package). Spring escanejaria el classpath sencer, cosa que dispara el temps d'arrencada i provoca errors imprevisibles. Spring Boot avisa explícitament d'això. - Si necessites escanejar un paquet extern —una llibreria compartida de la teva empresa—, amplia l'escaneig:
@SpringBootApplication(scanBasePackages = {"com.ciclourbana", "com.ribalta.comu"})
public class CicloUrbanaApplication {
public static void main(String[] args) {
SpringApplication.run(CicloUrbanaApplication.class, args);
}
}Aquesta és la raó concreta per la qual "la classe principal va al paquet arrel" no és una mania estètica, sinó un requisit de funcionament.
- Organitzar el codi: per capes o per funcionalitat
Hi ha dues maneres d'estructurar els paquets, i l'elecció condiciona el manteniment del projecte durant anys.
Per capes (layer-based)
com.ciclourbana
├── controller/
│ ├── EstacioController.java
│ ├── BicicletaController.java
│ └── LloguerController.java
├── service/
│ ├── EstacioService.java
│ └── LloguerService.java
├── repository/
│ ├── EstacioRepository.java
│ └── LloguerRepository.java
└── model/
├── Estacio.java
└── Lloguer.javaPer funcionalitat (feature-based o package by feature)
com.ciclourbana
├── estacions/
│ ├── EstacioController.java
│ ├── EstacioService.java
│ ├── EstacioRepository.java
│ └── Estacio.java
├── lloguers/
│ ├── LloguerController.java
│ ├── LloguerService.java
│ ├── LloguerRepository.java
│ └── Lloguer.java
└── comu/Comparativa
| Criteri | Per capes | Per funcionalitat |
|---|---|---|
| Trobar tot el de "lloguers" | Cal obrir 4 paquets | És en un sol paquet |
| Cohesió | Baixa: el paquet service barreja dominis sense relació |
Alta: cada paquet és un tema |
| Encapsulació | Nul·la: tot ha de ser public per travessar capes |
Es pot fer servir visibilitat de paquet |
| Escalabilitat | Els paquets creixen sense límit | Creix el nombre de paquets, cadascun acotat |
| Extreure un microservei | Difícil: cal remenar en totes les capes | Fàcil: t'emportes el paquet sencer |
| Familiaritat | Molt estesa als tutorials | Recomanada per la comunitat per a projectes reals |
CicloUrbana farà servir organització per funcionalitat. La raó decisiva és l'última fila de la taula: al mòdul 7 parlarem de microserveis, i amb aquesta estructura extreure "lloguers" a un servei independent és gairebé copiar una carpeta.
- L'estructura de paquets de CicloUrbana
Aquesta és l'organització definitiva que el projecte anirà completant mòdul a mòdul:
flowchart TD
R["com.ciclourbana<br/>CicloUrbanaApplication"]
R --> EST["estacions<br/>Estacio, EstacioController<br/>EstacioService, EstacioRepository"]
R --> BIC["bicicletes<br/>Bicicleta, EstatBicicleta<br/>BicicletaController, BicicletaService"]
R --> ALQ["lloguers<br/>Lloguer, Tarifa, Incidencia<br/>LloguerController, LloguerService"]
R --> USU["usuaris<br/>Usuari, Rol<br/>UsuariController, UsuariService"]
R --> SEG["seguretat<br/>ConfiguracioSeguretat<br/>FiltreJwt, ServeiTokens"]
R --> COM["comu<br/>Excepcions, gestor global<br/>utilitats compartides"]
ALQ -.usa.-> BIC
ALQ -.usa.-> EST
ALQ -.usa.-> USU
SEG -.usa.-> USU
Descripció de cada paquet i en quin mòdul s'omplirà:
| Paquet | Contingut previst | Es construeix a |
|---|---|---|
estacions |
Estacions d'ancoratge, capacitat, ubicació | Mòduls 1, 3 i 4 |
bicicletes |
Bicicletes elèctriques, estat i bateria | Mòduls 3 i 4 |
lloguers |
Lloguers, tarifes i incidències | Mòduls 3, 4 i 9 |
usuaris |
Usuaris de la plataforma i els seus rols | Mòduls 4 i 5 |
seguretat |
Configuració de Spring Security, filtres JWT | Mòdul 5 |
comu |
Excepcions pròpies, gestor global d'errors, utilitats | Mòdul 3 en endavant |
Les fletxes puntejades del diagrama mostren les dependències legítimes entre paquets. Una regla que convé respectar des del principi: el paquet comu no ha de dependre de cap paquet funcional. Si comu importa alguna cosa de lloguers, deixa de ser comú i apareixen dependències circulars difícils de desfer.
De moment només existeix estacions, amb Estacio i EstacioController. És el correcte: els paquets es creen quan hi ha alguna cosa per ficar-hi a dins, no abans.
- El cicle de vida de Maven
Maven organitza la construcció en fases ordenades. En invocar una fase s'executen totes les anteriors.
| Ordre | Què fa | Quan fer-la servir |
|---|---|---|
./mvnw clean |
Esborra la carpeta target |
Quan sospites de restes de compilacions prèvies |
./mvnw compile |
Compila src/main/java a target/classes |
Comprovar ràpidament que compila |
./mvnw test |
Compila i executa les proves de src/test/java |
Abans de cada commit |
./mvnw package |
Tot l'anterior + genera el fat jar a target |
Per obtenir l'artefacte desplegable |
./mvnw install |
Tot l'anterior + copia el JAR a ~/.m2/repository |
Quan un altre projecte local depèn d'aquest |
./mvnw verify |
Tot l'anterior + comprovacions de qualitat | En integració contínua |
flowchart LR
A["validate"] --> B["compile"] --> C["test"] --> D["package"] --> E["verify"] --> F["install"] --> G["deploy"]
H["clean"] -.independent.-> A
clean pertany a un cicle diferent, per això es combina explícitament:
# La combinació més habitual: construcció neta i completa
./mvnw clean package
# Saltar-se les proves (útil puntualment, perillós com a costum)
./mvnw clean package -DskipTests
# Executar només una classe de prova
./mvnw test -Dtest=CicloUrbanaApplicationTests
# Mode fora de línia: fer servir només la memòria cau local
./mvnw -o clean packageSobre -DskipTests: compila les proves però no les executa. Existeix també -Dmaven.test.skip=true, que ni tan sols les compila i per tant amaga errors de compilació al codi de test. Prefereix el primer.
Errors Habituals i Consells
- Posar el controlador fora del paquet arrel. És la causa número u de "el meu endpoint retorna 404 i no entenc per què". Comprova sempre que el paquet de la classe comença per
com.ciclourbana. - Afegir
<version>a dependències que gestiona el parent. Trenques la coherència del conjunt i pots provocarNoSuchMethodErroren execució, un error especialment difícil de diagnosticar. - Versionar la carpeta
target. Embruta el repositori amb megabytes d'artefactes regenerables. El.gitignored'Initializr ja l'exclou; no el toquis. - Confondre
src/main/resourcesambsrc/main/java. Unapplication.propertiescol·locat asrc/main/javano es copia al classpath i simplement s'ignora, sense cap avís. - No versionar
mvnwi.mvn/. En clonar, ningú no podria construir sense instal·lar Maven. Han de ser a Git. - Consell — inspecciona l'arbre de dependències.
./mvnw dependency:treerespon a "d'on surt aquesta llibreria?" i als conflictes de versions. És l'eina de diagnòstic més útil de Maven. - Consell — fes servir
help:effective-pomun cop. Veure el POM efectiu aclareix de cop què està fent el parent per tu. - Consell — un paquet per concepte de negoci, no per tecnologia. Si et trobes creant un paquet
utilsque creix sense control, és senyal que falta identificar un concepte de domini.
Exercicis
Exercici 1
Executa ./mvnw dependency:tree al teu projecte i respon: quants artefactes arrossega spring-boot-starter-web? Quina versió de Tomcat incrustat s'està fent servir? De quin starter ve Jackson?
Exercici 2
Crea deliberadament l'error del paquet arrel: mou EstacioController al paquet com.altraempresa.web, arrenca l'aplicació i comprova què passa. Després arregla-ho de dues maneres diferents i explica quina és preferible.
Exercici 3
Prepara l'estructura de paquets de CicloUrbana creant els paquets buits previstos i un fitxer package-info.java a cadascun que en documenti la responsabilitat. Justifica per què comu no ha de dependre de cap altre paquet.
Solucions
Solució 1
Respostes típiques amb Spring Boot 3.3.5:
- Nombre d'artefactes: al voltant de 30 dependències transitives. Pots comptar-les amb:
- Tomcat incrustat:
org.apache.tomcat.embed:tomcat-embed-core:10.1.31. És Tomcat 10.1, la primera branca que fa servir l'espai de nomsjakarta.*, coherent amb Spring Boot 3. - Jackson: arriba a través de
spring-boot-starter-json, que al seu torn és dependència despring-boot-starter-web. La cadena és:
Això explica que l'endpoint de la lliçó anterior retornés JSON sense que hi afegissis cap dependència: ja hi venia inclosa.
Solució 2
En moure la classe:
package com.altraempresa.web; // fora de l'arbre de com.ciclourbana
@RestController
@RequestMapping("/api/v1/estacions")
public class EstacioController { /* ... */ }L'aplicació arrenca sense cap error —això és el desconcertant— però:
El controlador no s'ha registrat perquè l'escaneig de components no va visitar mai com.altraempresa.web.
Arranjament 1: ampliar l'escaneig.
@SpringBootApplication(scanBasePackages = {"com.ciclourbana", "com.altraempresa.web"})
public class CicloUrbanaApplication { /* ... */ }Arranjament 2: tornar la classe al seu lloc.
El segon és clarament preferible. El primer funciona, però introdueix una excepció a la convenció que cal recordar i documentar; amb el temps apareixen més paquets solts i l'escaneig es torna imprevisible. L'ampliació de scanBasePackages només està justificada quan s'integra una llibreria externa el paquet de la qual no pots canviar.
Solució 3
Un exemple de package-info.java, un fitxer especial de Java l'única funció del qual és documentar un paquet:
/**
* Gestió de les estacions d'ancoratge de la xarxa de Ribalta.
*
* <p>Conté el model d'estació, el seu controlador REST sota
* {@code /api/v1/estacions}, la lògica de negoci associada i,
* a partir del mòdul 4, el seu repositori de persistència.</p>
*
* <p>Aquest paquet pot dependre de {@code comu}, però no de
* {@code lloguers} ni de {@code seguretat}.</p>
*/
package com.ciclourbana.estacions;I el del paquet comú:
/**
* Codi transversal compartit per la resta de paquets:
* excepcions de negoci, gestor global d'errors,
* utilitats de data i constants de l'API.
*
* <p>REGLA: aquest paquet NO ha d'importar res de
* {@code estacions}, {@code bicicletes}, {@code lloguers},
* {@code usuaris} ni {@code seguretat}.</p>
*/
package com.ciclourbana.comu;Justificació de la regla: comu és la base sobre la qual s'apuntalen els altres paquets. Si depengués de lloguers, es crearia un cicle (lloguers → comu → lloguers) amb tres conseqüències greus: seria impossible raonar sobre l'ordre d'inicialització, no es podria extreure comu a una llibreria reutilitzable, i qualsevol canvi a lloguers obligaria a recompilar i tornar a provar pràcticament tot el projecte. Les dependències han de fluir sempre de l'específic al general, mai a l'inrevés.
Conclusió
Ja no hi ha fitxers misteriosos al projecte. Saps què hi ha a src/main/java, src/main/resources, src/test/java i target; has llegit el pom.xml sencer i entens que el parent és qui gestiona les versions de més de dues-centes llibreries, que un starter és una llista curada de dependències sense codi propi, i que el spring-boot-maven-plugin és qui fabrica el fat jar. Sobretot, saps per què CicloUrbanaApplication ha de viure al paquet arrel: l'escaneig de components parteix d'allà, i el que quedi fora serà invisible per a Spring. I has fixat l'estructura per funcionalitat —estacions, bicicletes, lloguers, usuaris, seguretat, comu— que acompanyarà el projecte fins al mòdul 10.
A la lliçó següent, L'Arrencada i el Cicle de Vida de l'Aplicació, entrarem dins de SpringApplication.run(...) per veure pas a pas què passa entre que prems Run i apareix "Started CicloUrbanaApplication": la creació del context, l'escaneig, l'autoconfiguració, l'arrencada de Tomcat i els esdeveniments que pots aprofitar per executar el teu propi codi en el moment just.
Curs de Spring Boot
Mòdul 1: Introducció a Spring Boot
- Què és Spring Boot?
- Configuració del teu entorn de desenvolupament
- Creant la teva primera aplicació Spring Boot
- Entenent l'estructura del projecte
- L'arrencada i el cicle de vida de l'aplicació
Mòdul 2: Conceptes bàsics de Spring Boot
- Anotacions de Spring Boot
- Injecció de dependències a Spring Boot
- Àmbit i cicle de vida dels beans
- Configuració de Spring Boot
- Propietats de Spring Boot
- Autoconfiguració i starters per dins
Mòdul 3: Construint serveis web RESTful
- Introducció als serveis web RESTful
- Creant controladors REST
- Gestió dels mètodes HTTP
- Validació de dades d'entrada
- DTOs i mapatge entre capes
- Gestió d'excepcions a REST
- Documentar l'API amb OpenAPI
Mòdul 4: Accés a dades amb Spring Boot
- Introducció a Spring Data JPA
- Configuració de fonts de dades
- Creació d'entitats JPA
- Relacions entre entitats
- Ús de repositoris de Spring Data
- Mètodes de consulta a Spring Data JPA
- Transaccions i gestió de la persistència
- Migracions d'esquema amb Flyway
Mòdul 5: Seguretat a Spring Boot
- Introducció a Spring Security
- Configuració de Spring Security
- Autenticació i autorització d'usuaris
- Implementació d'autenticació JWT
- Seguretat a nivell de mètode i enduriment de l'API
Mòdul 6: Proves a Spring Boot
- Introducció a les proves
- Proves unitàries amb JUnit
- Simulació amb Mockito
- Proves d'integració
- Proves amb Testcontainers
Mòdul 7: Funcions avançades de Spring Boot
- Spring Boot Actuator
- Perfils de Spring Boot
- Tasques programades i execució asíncrona
- Spring Boot amb Docker
- Spring Boot i microserveis
- Comunicació entre serveis i tolerància a fallades
Mòdul 8: Desplegament d'aplicacions Spring Boot
- Introducció al desplegament
- Desplegant a Heroku
- Desplegant a AWS
- Desplegant a Kubernetes
- Integració i lliurament continus
Mòdul 9: Rendiment i monitoratge
- Ajust de rendiment
- Memòria cau amb Spring Cache
- Monitoratge amb Spring Boot Actuator
- Ús de Prometheus i Grafana
- Gestió de registres i logs
- Traçabilitat distribuïda
