ALGO25 / BLOG

Spring Boot: primul proiect și injecția de dependențe

Pornește un proiect Spring Boot cu Java 21 și Maven. Înțelege controllerul, serviciul, pachetele și injecția prin constructor într-un exemplu local.

Începem proiectul comun al lecțiilor Spring Boot: un API pentru sarcini de învățare. În această etapă vrem o aplicație care pornește și răspunde la o cerere simplă. Separăm controllerul de un serviciu ca să vedem cum Spring conectează obiectele prin constructor.

Pregătire și versiuni

Ai nevoie de clase și interfețe Java, de liste și excepții și de JDK 21. Exemplul fixează Spring Boot 4.1.1. Documentația oficială pentru această versiune acceptă Java 21; numele starterelor și unele importuri diferă față de tutoriale pentru Boot 3.

Deschide Spring Initializr, alege Maven, Java, Boot 4.1.1, Java 21, group ro.algo25, artifact task-api și package ro.algo25.tasks. Adaugă Spring Web, Validation, Spring Data JPA, H2 Database și Actuator. Aceste dependențe vor fi folosite treptat. Descarcă proiectul și păstrează Maven Wrapper, inclusiv directorul .mvn.

Pentru a reproduce exact seria, înlocuiește pom.xml cu varianta de mai jos. Dacă Initializr nu mai listează ulterior această versiune, proiectul generat oferă structura și wrapperul, iar POM-ul fixează versiunea verificată aici. Nu înlocui automat versiunea cu un milestone sau snapshot.

<?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>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
    <relativePath/>
  </parent>
  <groupId>ro.algo25</groupId>
  <artifactId>task-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties><java.version>21</java.version></properties>
  <dependencies>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
    <dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc-test</artifactId><scope>test</scope></dependency>
  </dependencies>
  <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

Punctul de pornire și pachetele

Creează src/main/java/ro/algo25/tasks/TaskApiApplication.java:

package ro.algo25.tasks;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TaskApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskApiApplication.class, args);
    }
}

Pune celelalte clase în același pachet sau în subpachetele lui. Configurația implicită pornește căutarea componentelor din această zonă. Un controller aflat accidental într-un pachet din afara ei poate să nu fie descoperit. Nu compensa imediat prin configurații suplimentare înainte să verifici structura directoarelor.

Două obiecte, două responsabilități

Creează GreetingService.java în același director:

package ro.algo25.tasks;

import org.springframework.stereotype.Service;

@Service
public class GreetingService {
    public String message() {
        return "Salut din Spring Boot!";
    }
}

Apoi creează GreetingController.java:

package ro.algo25.tasks;

import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {
    private final GreetingService service;

    public GreetingController(GreetingService service) {
        this.service = service;
    }

    @GetMapping("/api/hello")
    public Map<String, String> hello() {
        return Map.of("message", service.message());
    }
}

Spring creează obiectele gestionate, numite beans, și furnizează serviciul prin constructorul controllerului. Constructorul unic nu cere @Autowired. Controllerul se ocupă de cererea HTTP; serviciul produce mesajul. Este aceeași idee de colaborator primit în constructor pe care ai folosit-o în Java simplu.

Nu crea new GreetingService() în controller pentru a ocoli o eroare de configurare. Ai muta construcția în codul care consumă serviciul și ai pierde motivul pentru care exersăm această separare. Verifică mai întâi adnotarea și pachetul.

Pornește numai local

În src/main/resources/application.properties scrie:

spring.application.name=task-api
server.address=127.0.0.1
spring.datasource.url=jdbc:h2:mem:tasks
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.open-in-view=false

H2 este o bază în memorie. create-drop este o alegere didactică pentru date temporare, nu o strategie de migrare a unei baze reale. Adresa 127.0.0.1 ține serverul pe interfața locală. Seria nu implementează încă autentificare, deci aplicația nu se publică pe internet în această formă.

Din directorul proiectului rulează ./mvnw spring-boot:run; pe Windows folosește mvnw.cmd spring-boot:run. La prima rulare sunt descărcate dependențe. În alt terminal, curl -i http://127.0.0.1:8080/api/hello trebuie să întoarcă HTTP 200 și un obiect JSON cu cheia message și textul Salut din Spring Boot!.

Exersează

  1. Schimbă mesajul în serviciu și repornește aplicația. Verifică răspunsul fără să modifici controllerul.
  2. Cere /api/inexistent. Răspunsul trebuie să fie 404; serverul pornit nu înseamnă că orice rută există.
  3. Explică cine creează serviciul și cine îl folosește. Răspuns: Spring îl creează, iar controllerul îl primește în constructor.

Verifică înainte să continui

Trebuie să poți porni aplicația, să identifici ruta și să urmărești apelul controller → serviciu. Continuă cu API REST pentru sarcini. Păstrează aceleași pachete și același POM pe tot parcursul.

Întrebări frecvente

Spring și Spring Boot sunt același lucru?

Spring oferă infrastructura de bază. Boot adaugă configurare automată, dependențe coordonate și un mod convenabil de pornire a aplicației.

Am nevoie de Maven instalat global?

Nu dacă folosești wrapperul proiectului. Ai totuși nevoie de un JDK și de acces la dependențe la prima descărcare.

De ce folosim starter-webmvc?

Seria este pentru Boot 4.1.1 și modelul servlet Spring MVC. Numele dependenței este cel verificat pentru această versiune.

De ce am dependențe JPA înainte să scriu entități?

Pregătim același proiect pentru etapele următoare. H2 oferă baza locală temporară; persistența va fi explicată separat.

Aplicația are deja autentificare?

Nu. Este un exercițiu local. Autentificarea și autorizarea trebuie proiectate înainte de expunerea publică.

Surse

Cerințe Spring Boot, prima aplicație și injecție prin constructor, consultate la 13 octombrie 2026.