Spring Boot Adnotări

Ghid complet pentru adnotările fundamentale (fără componenta web)

🚀

Adnotări Core

@SpringBootApplication Obligatoriu

Adnotarea principală care marchează clasa de pornire. Combină trei adnotări: @Configuration, @EnableAutoConfiguration și @ComponentScan.

Application.java
package com.example.demo;

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

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

Plasează această clasă în pachetul rădăcină pentru ca @ComponentScan să detecteze automat toate componentele din sub-pachete.

@Configuration

Marchează o clasă ca sursă de definiții pentru bean-uri. Înlocuiește fișierele XML de configurare tradiționale.

AppConfig.java
@Configuration
public class AppConfig {
    
    @Bean
    public EmailService emailService() {
        return new EmailServiceImpl();
    }
    
    @Bean
    public NotificationService notificationService() {
        // Putem injecta alte bean-uri
        return new NotificationService(emailService());
    }
}
@ComponentScan

Specifică pachetele pe care Spring le scanează pentru a găsi componente (@Component, @Service, etc.).

Scanare pachete
@Configuration
@ComponentScan(basePackages = {
    "com.example.services",
    "com.example.repositories"
})
public class AppConfig { }
⚙️

Configurare

@Value Foarte folosit

Injectează valori din application.properties sau application.yml direct în câmpuri.

application.properties
app.name=Aplicatia Mea
app.version=1.0.0
app.max-connections=100
AppSettings.java
@Component
public class AppSettings {
    
    @Value("${app.name}")
    private String appName;
    
    @Value("${app.max-connections:50}") // default: 50
    private int maxConnections;
}
@ConfigurationProperties Recomandat

Mapează un grup de proprietăți într-o clasă Java. Oferă type-safety și validare.

application.yml
database:
  host: localhost
  port: 5432
  credentials:
    username: admin
    password: secret
DatabaseProperties.java
@Configuration
@ConfigurationProperties(prefix = "database")
public class DatabaseProperties {
    
    private String host;
    private int port;
    private Credentials credentials;
    
    public static class Credentials {
        private String username;
        private String password;
        // getters & setters
    }
    // getters & setters
}
@Profile

Activează bean-uri sau configurări doar pentru anumite profiluri (dev, test, prod).

Configurări per mediu
@Configuration
@Profile("dev")
public class DevConfig {
    @Bean
    public DataSource dataSource() {
        // H2 in-memory pentru development
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2).build();
    }
}

@Configuration
@Profile("prod")
public class ProdConfig {
    @Bean
    public DataSource dataSource() {
        // PostgreSQL pentru producție
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://server:5432/db").build();
    }
}
📝

Activezi un profil cu spring.profiles.active=dev în properties sau -Dspring.profiles.active=prod la rulare.

@ConditionalOn...

Creează bean-uri condiționat, în funcție de prezența unor clase, proprietăți sau alte condiții.

Bean-uri condiționate
@Bean
@ConditionalOnProperty(name = "feature.email", havingValue = "true")
public EmailService emailService() {
    return new EmailServiceImpl();
}

@Bean
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public CacheService redisCacheService() {
    return new RedisCacheService();
}

@Bean
@ConditionalOnMissingBean(CacheService.class)
public CacheService defaultCacheService() {
    return new InMemoryCacheService();
}
📦

Beans & Componente

@Component Generic

Marchează o clasă ca fiind un component Spring, gestionat de container. Adnotarea de bază din care derivă celelalte stereotipuri.

Component generic
@Component
public class PdfGenerator {
    public byte[] generateReport(ReportData data) {
        // Logică de generare PDF
        return pdfBytes;
    }
}

@Component("customName") // nume custom pentru bean
public class AdvancedPdfGenerator { }
@Service Business Logic

Specializare a @Component pentru clasele din layer-ul de service (logică de business).

Service layer
@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User createUser(UserDto dto) {
        User user = new User();
        user.setEmail(dto.getEmail());
        return userRepository.save(user);
    }
}
@Repository Data Access

Specializare a @Component pentru layer-ul de persistență. Oferă traducerea automată a excepțiilor specifice bazei de date.

Repository layer
@Repository
public class UserRepositoryImpl implements UserRepository {
    
    private final JdbcTemplate jdbcTemplate;
    
    public UserRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    public Optional<User> findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class), id)
            .stream().findFirst();
    }
}
@Bean

Declară o metodă într-o clasă @Configuration al cărei rezultat va fi gestionat ca bean de container.

Definire bean-uri
@Configuration
public class AppConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean(name = "mainMapper")
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
    
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public ConnectionPool connectionPool() {
        return new ConnectionPool();
    }
}
@Scope & @Lazy

Controlează durata de viață și momentul inițializării bean-urilor.

Scope și Lazy
// Singleton (default) - o singură instanță
@Component
@Scope("singleton")
public class SingletonBean { }

// Prototype - instanță nouă la fiecare injectare
@Component
@Scope("prototype")
public class PrototypeBean { }

// Lazy - inițializat la prima utilizare
@Service
@Lazy
public class HeavyService { }
ScopeDescriere
singletonO singură instanță per container (default)
prototypeInstanță nouă la fiecare cerere
requestPer HTTP request (web)
sessionPer HTTP session (web)
@Primary

Marchează un bean ca fiind preferențial când există mai multe implementări ale aceluiași tip.

Bean principal
public interface MessageSender {
    void send(String msg);
}

@Service
@Primary  // Va fi ales implicit
public class EmailSender implements MessageSender { }

@Service
public class SmsSender implements MessageSender { }
💉

Dependency Injection

@Autowired Fundamental

Marchează un câmp, constructor sau setter pentru injectarea automată a dependențelor.

Moduri de injectare
@Service
public class OrderService {
    
    // 1. CONSTRUCTOR INJECTION (RECOMANDAT)
    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    
    // @Autowired e opțional pentru un singur constructor
    public OrderService(OrderRepository repo, PaymentService pay) {
        this.orderRepository = repo;
        this.paymentService = pay;
    }
    
    // 2. FIELD INJECTION (simplu dar nu e recomandat)
    @Autowired
    private NotificationService notificationService;
    
    // 3. SETTER INJECTION
    private AuditService auditService;
    
    @Autowired
    public void setAuditService(AuditService audit) {
        this.auditService = audit;
    }
}
⚠️

Constructor injection este recomandat: permite câmpuri final, face dependențele explicite și ușurează testarea.

@Qualifier

Specifică exact care bean să fie injectat când există mai multe implementări ale aceluiași tip.

Selectare bean specific
@Service("emailChannel")
public class EmailChannel implements NotificationChannel { }

@Service("smsChannel")
public class SmsChannel implements NotificationChannel { }

@Service
public class AlertService {
    
    private final NotificationChannel email;
    private final NotificationChannel sms;
    
    public AlertService(
            @Qualifier("emailChannel") NotificationChannel email,
            @Qualifier("smsChannel") NotificationChannel sms) {
        this.email = email;
        this.sms = sms;
    }
}
@RequiredArgsConstructor Lombok

Generează automat constructorul cu toate câmpurile final. Simplifică mult codul.

Cu Lombok
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ProductService {
    
    // Lombok generează constructorul automat!
    private final ProductRepository productRepository;
    private final CategoryService categoryService;
    private final PriceCalculator priceCalculator;
    
    public Product create(ProductDto dto) {
        // folosim dependențele direct
    }
}
🗃️

JPA & Date

@Entity JPA

Marchează o clasă Java ca fiind o entitate JPA, mapată la o tabelă din baza de date.

Entitate JPA
import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true, length = 100)
    private String email;
    
    @Column(name = "full_name")
    private String fullName;
    
    @Enumerated(EnumType.STRING)
    private UserStatus status;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    // constructors, getters, setters
}
Relații JPA

Adnotări pentru definirea relațiilor între entități.

Relații între entități
@Entity
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // Many-to-One: Multe comenzi -> Un user
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    // One-to-Many: O comandă -> Multe items
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items = new ArrayList<>();
    
    // One-to-One
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "invoice_id")
    private Invoice invoice;
}

@Entity
public class Product {
    // Many-to-Many
    @ManyToMany
    @JoinTable(name = "product_categories",
        joinColumns = @JoinColumn(name = "product_id"),
        inverseJoinColumns = @JoinColumn(name = "category_id"))
    private Set<Category> categories;
}
@Transactional Important

Gestionează tranzacțiile. Dacă apare o excepție, toate operațiile sunt anulate (rollback).

Gestiune tranzacții
@Service
public class TransferService {
    
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepo.findById(fromId).orElseThrow();
        Account to = accountRepo.findById(toId).orElseThrow();
        
        from.withdraw(amount);
        to.deposit(amount);
        
        accountRepo.save(from);
        accountRepo.save(to);
        // Dacă orice eșuează -> totul e anulat
    }
    
    @Transactional(readOnly = true)
    public List<Account> getAll() {
        return accountRepo.findAll();
    }
}
📝

@Transactional funcționează doar pe metode publice apelate din exterior. Apelurile interne din aceeași clasă nu activează tranzacția!

Spring Data JPA Repository

Interfețe care oferă operații CRUD automate și query methods generate din numele metodelor.

Repository interface
public interface UserRepository 
        extends JpaRepository<User, Long> {
    
    // Query methods - generate automat din nume
    Optional<User> findByEmail(String email);
    List<User> findByStatusAndCreatedAtAfter(UserStatus s, LocalDateTime d);
    boolean existsByEmail(String email);
    long countByStatus(UserStatus status);
    
    // JPQL query custom
    @Query("SELECT u FROM User u WHERE u.status = :status")
    List<User> findByStatusCustom(@Param("status") UserStatus status);
    
    // Native SQL
    @Query(value = "SELECT * FROM users WHERE email LIKE %:d", nativeQuery = true)
    List<User> findByDomain(@Param("d") String domain);
    
    // Update/Delete
    @Modifying
    @Query("UPDATE User u SET u.status = :s WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("s") UserStatus s);
}
🔄

Ciclul de Viață

@PostConstruct & @PreDestroy

Metode executate după crearea bean-ului și înainte de distrugerea lui.

Lifecycle hooks
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

@Service
public class CacheService {
    
    private Map<String, Object> cache;
    
    @PostConstruct
    public void init() {
        System.out.println("Inițializez cache-ul...");
        this.cache = new ConcurrentHashMap<>();
        loadInitialData();
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("Curăț cache-ul...");
        cache.clear();
    }
}
@EventListener

Ascultă evenimente din aplicație sau evenimente custom.

Event listeners
@Component
public class AppEventListener {
    
    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        System.out.println("✅ Aplicația e gata!");
    }
    
    @EventListener(ContextClosedEvent.class)
    public void onShutdown() {
        System.out.println("👋 Se oprește...");
    }
}

// Event custom
public class UserCreatedEvent {
    private final User user;
    // constructor, getter
}

@Component
public class UserEventHandler {
    @EventListener
    public void handle(UserCreatedEvent event) {
        sendWelcomeEmail(event.getUser());
    }
}
@Scheduled

Programează execuția periodică a unei metode (cron jobs).

Task-uri programate
// Activează în configurare
@Configuration
@EnableScheduling
public class SchedulingConfig { }

@Service
public class ScheduledTasks {
    
    @Scheduled(fixedRate = 300000) // la 5 minute
    public void syncData() {
        System.out.println("Sincronizez...");
    }
    
    @Scheduled(fixedDelay = 10000) // 10s după terminare
    public void processQueue() { }
    
    @Scheduled(cron = "0 0 2 * * ?") // zilnic la 2:00
    public void nightlyBackup() { }
    
    @Scheduled(cron = "0 0 9 * * MON") // luni la 9:00
    public void weeklyReport() { }
}
💡

Format cron: secunde minute ore ziua_lunii luna ziua_saptamanii

@Async

Execută metoda asincron, într-un thread separat.

Execuție asincronă
@Configuration
@EnableAsync
public class AsyncConfig { }

@Service
public class EmailService {
    
    @Async
    public void sendBulkEmails(List<String> recipients) {
        // Nu blochează apelantul
        for (String email : recipients) {
            sendEmail(email);
        }
    }
    
    @Async
    public CompletableFuture<Report> generateReport(Long id) {
        Report report = buildReport(id);
        return CompletableFuture.completedFuture(report);
    }
}
📋

Sumar Rapid

🎯 Toate Adnotările

@SpringBootApplicationPunct de start
@ConfigurationClasă de config
@BeanDefinire bean
@ComponentComponent generic
@ServiceBusiness logic
@RepositoryData access
@AutowiredInjectare DI
@QualifierSelectare bean
@ValueInjectare valori
@ConfigurationPropertiesMapare props
@ProfileConfig per mediu
@ConditionalOn...Bean condiționat
@EntityEntitate JPA
@TransactionalTranzacții
@PostConstructInit bean
@PreDestroyCleanup bean
@ScheduledTask programat
@AsyncExecuție async
@ScopeDurată viață
@LazyInit întârziat

📁 Structură Proiect

Structură recomandată
com.example.myapp/
├── Application.java              // @SpringBootApplication
├── config/
│   ├── AppConfig.java             // @Configuration
│   └── DatabaseProperties.java    // @ConfigurationProperties
├── model/
│   └── User.java                  // @Entity
├── repository/
│   └── UserRepository.java        // extends JpaRepository
├── service/
│   └── UserService.java           // @Service
└── util/
    └── PdfGenerator.java          // @Component

⚡ Best Practices

Constructor Injection - Folosește pentru dependențe obligatorii

@ConfigurationProperties - Preferă în loc de @Value pentru grupuri de proprietăți

@Transactional(readOnly=true) - Pentru metode read-only

Field Injection - Evită @Autowired pe câmpuri