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

  1. L'arbre de directoris generat
  2. src/main/java i src/main/resources
  3. src/test/java, target/ i el wrapper de Maven
  4. Anatomia del pom.xml, línia a línia
  5. Què és un starter i quins farem servir al curs
  6. El paquet arrel i l'escaneig de components
  7. Organitzar el codi: per capes o per funcionalitat
  8. L'estructura de paquets de CicloUrbana
  9. El cicle de vida de Maven
  10. Errors Habituals i Consells
  11. Exercicis

  1. 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 versiona

Aquesta 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).

  1. src/main/java i src/main/resources

src/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. Un static/logo.png és accessible a http://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=DEBUG

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

  1. src/test/java, target/ i el wrapper de Maven

src/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 proves

Quan 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.jar

Aquests tres fitxers sí que es versionen a Git: són els que garanteixen que qualsevol construeixi el projecte amb la mateixa versió de Maven.

  1. Anatomia del pom.xml, línia a línia

El 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:

  1. 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.
  2. Configuració de plugins. Deixa preconfigurats el compilador, el plugin de recursos, Surefire (proves) i el spring-boot-maven-plugin.
  3. Valors per defecte sensats. Codificació UTF-8 en fonts i recursos, i la versió de Java presa de la propietat java.version.
  4. Filtratge de recursos. Permet fer servir @propietat@ dins d'application.properties per injectar-hi valors del pom.xml.

Pots veure la llista completa de versions gestionades:

./mvnw help:effective-pom | less

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

<properties>
    <java.version>21</java.version>
</properties>

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 fase package, que reempaqueta el JAR amb BOOT-INF/ i el JarLauncher.
  • 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.

  1. 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:

./mvnw dependency:tree

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:compile

Una 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).

  1. 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:

  1. Col·loca sempre CicloUrbanaApplication al paquet arrel, per damunt de tots els paquets funcionals.
  2. 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ò.
  3. 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.

  1. 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.java

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

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

  1. 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 package

Sobre -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 provocar NoSuchMethodError en execució, un error especialment difícil de diagnosticar.
  • Versionar la carpeta target. Embruta el repositori amb megabytes d'artefactes regenerables. El .gitignore d'Initializr ja l'exclou; no el toquis.
  • Confondre src/main/resources amb src/main/java. Un application.properties col·locat a src/main/java no es copia al classpath i simplement s'ignora, sense cap avís.
  • No versionar mvnw i .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:tree respon 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-pom un 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 utils que 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

./mvnw dependency:tree -Dincludes=org.springframework.boot:spring-boot-starter-web

Respostes típiques amb Spring Boot 3.3.5:

  • Nombre d'artefactes: al voltant de 30 dependències transitives. Pots comptar-les amb:
./mvnw dependency:list | grep -c ":compile"
  • 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 noms jakarta.*, coherent amb Spring Boot 3.
  • Jackson: arriba a través de spring-boot-starter-json, que al seu torn és dependència de spring-boot-starter-web. La cadena és:
spring-boot-starter-web
  └─ spring-boot-starter-json
       └─ com.fasterxml.jackson.core:jackson-databind

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ò:

curl -i http://localhost:8080/api/v1/estacions
# HTTP/1.1 404

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.

package com.ciclourbana.estacions;

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

mkdir -p src/main/java/com/ciclourbana/{estacions,bicicletes,lloguers,usuaris,seguretat,comu}

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

Mòdul 2: Conceptes bàsics de Spring Boot

Mòdul 3: Construint serveis web RESTful

Mòdul 4: Accés a dades amb Spring Boot

Mòdul 5: Seguretat a Spring Boot

Mòdul 6: Proves a Spring Boot

Mòdul 7: Funcions avançades de Spring Boot

Mòdul 8: Desplegament d'aplicacions Spring Boot

Mòdul 9: Rendiment i monitoratge

Mòdul 10: Millors pràctiques i consells

© Copyright 2026. Tots els drets reservats