Ce este Spring Boot?
Spring Boot este un strat peste Spring Framework care oferă:
auto-configurare, starter-e pentru dependențe și un server web încorporat
(Tomcat/Jetty/Undertow). Scopul este să lansezi aplicații rapid, cu configurație minimă.
- Rulare cu java -jar app.jar sau direct din IDE.
- Convention over configuration: defaults bune, peste care poți suprascrie setări.
- Integrare nativă cu Spring MVC, Spring Data JPA, Security, Actuator etc.
Anotarea-cheie
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan.
Activează auto-config și scanează componentele în pachetul curent și sub-pachete.
Containerul IoC și Beans
Containerul IoC (Inversion of Control), numit în Spring ApplicationContext, este
motorul care creează, configurează și gestionează ciclul de viață al obiectelor
tale numite beans. În loc să le creezi cu new în codul tău, îi spui containerului
„dă-mi un bean de tipul X”, iar el ți-l oferă cu dependențele deja injectate.
Ce este un „bean”?
- Un obiect gestionat de Spring (creat, configurat, distrus de container).
- Este înregistrat prin
@Component & co. sau prin metode @Bean.
- Are un scope: singleton (implicit),
prototype, request, session.
Injecția de dependențe (DI)
- Constructor DI (recomandat): imutabilitate & testabilitate.
- Field DI (
@Autowired pe câmp) – rapid, dar mai greu de testat.
- Setter DI – util pentru dependențe opționale.
Exemplu scurt DI prin constructor
@Service
public class EmailService {
private final MailClient client;
public EmailService(MailClient client) { this.client = client; }
}
@Component
class MailClient {}
Tip: Evită new în servicii. Cere dependențele prin constructor și lasă Spring să le ofere.
Ciclul de viață & hooks
@Component
class WarmupCache {
@PostConstruct void init(){ /* pornește cache-uri */ }
@PreDestroy void shutdown(){ /* curăță resurse */ }
}
Anotări principale — pe scurt
Bootstrapping
- @SpringBootApplication — pornește auto-config & scanning.
- @Configuration — clasă de config Java-based.
- @Bean — înregistrează manual un bean în container.
- @ComponentScan — definește pachetele pentru scanning.
Layering
- @Component — componentă generică.
- @Service — logică de business.
- @Repository — acces la DB; traduce excepțiile (Spring Data).
- @Controller / @RestController — MVC/REST.
Wiring & Mediu
- @Autowired — injecție dependențe.
- @Qualifier — selectează implementarea când sunt multiple.
- @Primary — marcați implementarea preferată.
- @Profile — activează bean-ul doar pentru un profil.
- @Value — injectează valori din proprietăți/ENV.
Web & REST (Spring MVC)
Anotări frecvente pentru API-uri:
- @RequestMapping (la nivel de clasă/metodă) — mapare generică
- @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
- @PathVariable, @RequestParam, @RequestBody
- @ResponseStatus, @CrossOrigin, @ControllerAdvice / @ExceptionHandler
@RestController
@RequestMapping("/api/products")
class ProductController {
private final ProductService service;
public ProductController(ProductService service) { this.service = service; }
@GetMapping
List<ProductDto> all(@RequestParam(defaultValue="0") int page,
@RequestParam(defaultValue="10") int size) {
return service.getPage(page, size);
}
@GetMapping("/{id}")
ProductDto byId(@PathVariable Long id){ return service.getById(id); }
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
ProductDto create(@Valid @RequestBody CreateProductDto in){ return service.create(in); }
}
Global error handling
@RestControllerAdvice
class ApiErrors {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
Map<String,String> notFound(RuntimeException ex) {
return Map.of("error", ex.getMessage());
}
}
Spring Data JPA & MySQL
Anotări uzuale în modelul de date:
- @Entity, @Table, @Id, @GeneratedValue
- @Column, @Enumerated, @Embedded / @Embeddable
- @OneToMany, @ManyToOne, @ManyToMany, @JoinColumn, @JoinTable
Entități & relații
@Entity @Table(name="products")
public class Product {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, length=120)
private String name;
private double price;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="category_id")
private Category category;
}
@Entity
public class Category {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(nullable=false, unique=true)
private String name;
@OneToMany(mappedBy="category", cascade=CascadeType.ALL)
private List<Product> products = new ArrayList<>();
}
Repository & interogări
public interface ProductRepository extends JpaRepository<Product,Long> {
// Query derivation
List<Product> findByNameContainingIgnoreCase(String name);
// JPQL
@Query("select p from Product p where p.price > :min")
List<Product> expensive(@Param("min") double min);
// Paginare
Page<Product> findAll(Pageable pageable);
}
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: parola
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate.format_sql: true
Atenție: Folosește ddl-auto: validate sau migrații (Flyway/Liquibase)
în producție; update/create e doar pentru dev/POC.
Cross-cutting: Validare, Tranzacții, Scheduling, Caching
Validare (Jakarta Validation)
public record CreateProductDto(
@NotBlank @Size(max=120) String name,
@Positive double price,
@NotNull Long categoryId
) {}
Folosește @Valid în controller pentru a valida inputul.
Tranzacții
@Service
public class ProductService {
private final ProductRepository repo; private final CategoryRepository catRepo;
public ProductService(ProductRepository r, CategoryRepository c){ this.repo=r; this.catRepo=c; }
@Transactional
public ProductDto create(CreateProductDto in){
var category = catRepo.findById(in.categoryId())
.orElseThrow(() -> new EntityNotFoundException("Category not found"));
var p = new Product();
p.setName(in.name()); p.setPrice(in.price()); p.setCategory(category);
return toDto(repo.save(p));
}
}
@Transactional asigură commit/rollback automat. Evită apelurile interne către metode tranzacționale (proxy-bypass).
Scheduling
@EnableScheduling
@SpringBootApplication
class App {}
@Component
class Housekeeping {
@Scheduled(cron = "0 0 * * * *") // la fiecare oră
void clean(){ /* cleanup */ }
}
Caching
@EnableCaching
@SpringBootApplication
class App {}
@Service
class CategoryService {
private final CategoryRepository repo;
public CategoryService(CategoryRepository repo){ this.repo=repo; }
@Cacheable("categoryById")
Category get(Long id){ return repo.findById(id).orElseThrow(); }
@CacheEvict(value="categoryById", key="#id") void evict(Long id){}
}
Config, proprietăți & profiluri
@ConfigurationProperties (binding tip-safe)
@ConfigurationProperties(prefix="storage")
public record StorageProps(Path root, int maxFiles, long maxSizeBytes) {}
@EnableConfigurationProperties(StorageProps.class)
@SpringBootApplication
class App {}
storage:
root: "/var/app/data"
max-files: 1000
max-size-bytes: 10485760
Injectezi StorageProps în servicii pentru a citi setări strongly-typed.
Profiluri
@Profile("dev")
@Configuration
class DevOnlyConfig {
@Bean DataSource h2(){ /* dataSource H2 pentru dev */ return null; }
}
Activezi profilul cu --spring.profiles.active=dev sau în application.yml.
Exemple „end-to-end” scurte
DTO + Mapare + Paginare
record ProductDto(Long id, String name, double price, String category) {}
@Service
class ProductService {
private final ProductRepository repo;
ProductService(ProductRepository repo){ this.repo=repo; }
List<ProductDto> getPage(int page, int size){
var p = repo.findAll(PageRequest.of(page, size, Sort.by("id").descending()));
return p.stream().map(this::toDto).toList();
}
ProductDto toDto(Product e){
return new ProductDto(e.getId(), e.getName(), e.getPrice(),
e.getCategory()!=null ? e.getCategory().getName() : null);
}
}
@Bean vs @Component
@Configuration
class HttpConfig {
@Bean RestTemplate restTemplate(){ return new RestTemplate(); }
}
@Bean — când creezi bean-uri din librării third-party sau ai nevoie de control fin.
@Component
class UuidGenerator {
String newId(){ return UUID.randomUUID().toString(); }
}
@Component — pentru clasele tale, descoperite prin scanning.
Scope-uri & @Lazy
@Component @Scope("prototype")
class Task { /* instanță nouă la fiecare injectare */ }
@Component
class HeavyService {
private final ReportEngine engine;
HeavyService(@Lazy ReportEngine engine){ this.engine = engine; } // creat la primul apel
}
Filtrare CORS simplă
@Configuration
class CorsConfig {
@Bean
WebMvcConfigurer cors(){
return new WebMvcConfigurer() {
@Override public void addCorsMappings(CorsRegistry r){
r.addMapping("/api/**").allowedOrigins("https://app.example.com")
.allowedMethods("GET","POST","PUT","DELETE");
}
};
}
}
Arhitectura N-Tier în practică
- Presentation: Controllers + DTOs
- Business: Services (reguli, tranzacții, orchestrare)
- Data Access: Repositories + Entities (JPA) + MySQL
Structură recomandată de pachete
com.example.app
├─ App.java // @SpringBootApplication
├─ config/ // @Configuration, @Bean, security, cors
├─ domain/ // @Entity, agregate, value objects
├─ repository/ // Spring Data repositories
├─ service/ // business services (@Service)
├─ web/ // controllers, DTOs, advice
└─ util/ // helpers, mappers
Tip: Pune clasa cu @SpringBootApplication în rădăcina pachetelor pentru component scanning corect.