Középhaladó Olvasási idő: ~9 perc

Bean kezelés

@Component, @Service, @Repository, @Configuration, @Bean, @Primary, @Qualifier

Bean kezelés

1. Definíció

A Spring Framework bean kezelés (bean management) a container által kezelt objektumok regisztrálásának, konfigurálásának és életciklusának szabályozására vonatkozik. A Spring kétféle megközelítést kínál: automatikus felfedezés sztereotíp annotációkkal (@Component, @Service, @Repository) és explicit deklaráció @Bean metódusokkal @Configuration osztályokban. A két megközelítés kombinálása adja a modern Spring alkalmazások konfigurációs alapját.

A bean-ek regisztrálásán túl a Spring mechanizmusokat biztosít az egyértelműsítésre (@Primary, @Qualifier), a bean definíciók módosítására (BeanFactoryPostProcessor), és a bean példányok testreszabására (BeanPostProcessor).


2. Alapfogalmak

Sztereotíp annotációk

A Spring négy fő sztereotíp annotációt definiál, mindegyik a @Component specializációja:

Annotáció Cél Extra viselkedés
@Component Általános bean Alap komponens jelölés
@Service Üzleti logika réteg Szemantikai jelölés (nincs extra logika)
@Repository Adatelérési réteg Exception translation (SQL → DataAccessException)
@Controller Web réteg HTTP request mapping (@RequestMapping társaival)

A @Service és @Component technikai szempontból azonosak — a különbség szemantikai: jelzi a fejlesztőnek és az eszközöknek, milyen szerepű az osztály.

@Configuration és @Bean

A @Configuration osztály egy konfigurációs forrás, ahol @Bean metódusok explicit bean definíciókat hoznak létre:

@Configuration
public class AppConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Mikor @Bean és mikor @Component?

  • @Component: saját osztályokhoz, amelyekre rátehetjük az annotációt
  • @Bean: harmadik féltől származó (third-party) osztályokhoz, amiket nem módosíthatunk, vagy ha komplexebb inicializáció kell

@Primary és @Qualifier

Ha egy interfésznek több implementációja van, a Spring nem tudja automatikusan feloldani:

public interface NotificationSender { void send(String msg); }

@Service
@Primary
public class EmailSender implements NotificationSender { ... }

@Service
public class SmsSender implements NotificationSender { ... }
  • @Primary — alapértelmezett implementáció, ha nincs explicit kiválasztás
  • @Qualifier("smsSender") — nevesített kiválasztás injection-nél
@Service
public class AlertService {
    // @Primary-t kapja (EmailSender)
    private final NotificationSender defaultSender;

    // Explicit @Qualifier-rel SmsSender-t kapja
    private final NotificationSender smsSender;

    public AlertService(
            NotificationSender defaultSender,
            @Qualifier("smsSender") NotificationSender smsSender) {
        this.defaultSender = defaultSender;
        this.smsSender = smsSender;
    }
}

Custom Qualifier annotáció

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Sms {}

@Service @Sms
public class SmsSender implements NotificationSender { ... }

// Használat
public AlertService(@Sms NotificationSender sender) { ... }

3. Gyakorlati használat

Component scanning

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Deprecated.class
    )
)
public class AppConfig {}

Conditional bean regisztráció

@Configuration
public class CacheConfig {
    @Bean
    @ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("items");
    }
}

Bean collection injection

@Service
public class NotificationRouter {
    private final List<NotificationSender> senders; // minden implementáció

    public NotificationRouter(List<NotificationSender> senders) {
        this.senders = senders;
    }

    public void notifyAll(String msg) {
        senders.forEach(s -> s.send(msg));
    }
}

@Order és @Priority

@Service @Order(1)
public class EmailSender implements NotificationSender { ... }

@Service @Order(2)
public class SmsSender implements NotificationSender { ... }
// A List<NotificationSender> EmailSender-rel kezdődik

4. Kód példák

Teljes rétegzett alkalmazás

// Repository réteg
@Repository
public class JpaUserRepository implements UserRepository {
    private final EntityManager em;

    public JpaUserRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public User findById(Long id) {
        return em.find(User.class, id);
    }
}

// Service réteg
@Service
public class UserService {
    private final UserRepository userRepo;

    public UserService(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    public User getUser(Long id) {
        return userRepo.findById(id);
    }
}

@Bean initMethod / destroyMethod

@Configuration
public class DataSourceConfig {
    @Bean(initMethod = "start", destroyMethod = "close")
    public HikariDataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:h2:mem:test");
        return ds;
    }
}

BeanPostProcessor példa

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof Auditable) {
            System.out.println("Initializing auditable bean: " + beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean; // AOP proxy-k itt készülnek
    }
}

BeanFactoryPostProcessor példa

@Component
public class CustomPropertyPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
        BeanDefinition bd = factory.getBeanDefinition("dataSource");
        bd.getPropertyValues().addPropertyValue("maxPoolSize", "20");
    }
}

5. Trade-offok

Szempont @Component (scan) @Bean (explicit)
Egyszerűség ✅ Kevés kód ❌ Több boilerplate
Átláthatóság ❌ Szétszórt ✅ Egy helyen látható
Third-party osztályok ❌ Nem módosíthatók ✅ Bármi regisztrálható
IDE navigáció ✅ Annotáció → osztály ✅ Metódus → visszatérési típus
Feltételes reg. @Conditional annotációk @ConditionalOnProperty stb.
Szempont @Primary @Qualifier
Alapértelmezett ✅ Globális default ❌ Minden injection-nél meg kell adni
Pontosság ❌ Csak egy lehet ✅ Bármelyik kiválasztható
Karbantarthatóság ✅ Egyszerű ❌ String-alapú = törékeny
Custom qualifier ✅ Típusbiztos annotáció

BeanPostProcessor vs BeanFactoryPostProcessor

Aspektus BeanPostProcessor BeanFactoryPostProcessor
Mikor fut? Bean példányosítás után Bean definíciók betöltése után
Mit módosít? Bean példányokat Bean definíciókat (metaadatok)
Tipikus cél AOP proxy, validation, logging Property feloldás, bean def módosítás

6. Gyakori hibák

❌ @Repository nélküli DAO osztály

// ROSSZ — nincs exception translation
@Component
public class UserDao { ... }

// JÓ — DataAccessException mapping automatikus
@Repository
public class UserDao { ... }

❌ @Bean metódus nem @Configuration osztályban

// ROSSZ — @Component-ben a @Bean metódusok "lite mode"-ban futnak
// Az inter-bean referenciák NEM tartanak singleton szemantikát!
@Component
public class AppConfig {
    @Bean
    public ServiceA serviceA() {
        return new ServiceA(commonDep()); // ÚJ példány minden hívásra!
    }
    @Bean
    public CommonDep commonDep() { return new CommonDep(); }
}

// JÓ — @Configuration CGLIB proxy biztosítja a singleton-t
@Configuration
public class AppConfig { ... }

❌ @Qualifier string elírás

// ROSSZ — runtime hiba, ha a név nem stimmel
@Qualifier("smsSennder") // Elírás!
NotificationSender sender;

// JÓ — custom qualifier annotáció = compile-time biztonság
@Sms NotificationSender sender;

❌ BeanPostProcessor-ban @Autowired dependency

// ROSSZ — a BPP korán példányosodik, dependency-k nem biztos hogy készek
@Component
public class MyBPP implements BeanPostProcessor {
    @Autowired
    private SomeService service; // Lehet null vagy nem proxyzott!
}

❌ Több @Primary ugyanarra az interfészre

// ROSSZ — NoUniqueBeanDefinitionException
@Service @Primary
public class EmailSender implements NotificationSender { ... }

@Service @Primary
public class PushSender implements NotificationSender { ... }

7. Mélyebb összefüggések

Sztereotíp annotáció örökölődés

A @Service, @Repository, @Controller mind @Component-et tartalmaznak meta-annotációként. A Spring component scanning a @Component jelenlétét keresi (akár közvetlenül, akár meta-annotáción keresztül). Ezért működnek az egyedi sztereotíp annotációk is:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface UseCase {}

@UseCase
public class PlaceOrderUseCase { ... } // regisztrálódik bean-ként

BeanPostProcessor belső működés

A BeanPostProcessor a bean pipeline része:

  1. Bean példányosítás (konstruktor)
  2. Dependency injection (setter/field)
  3. postProcessBeforeInitialization() — minden BPP
  4. @PostConstruct / InitializingBean.afterPropertiesSet()
  5. postProcessAfterInitialization() — minden BPP (itt készül az AOP proxy)
  6. Bean használatra kész

Fontos: Ha a BPP-ben wrappelod a bean-t (pl. proxy-t adsz vissza), az eredeti bean referencia elvész. A Spring AOP pontosan így működik.

BeanFactoryPostProcessor és a property placeholder

A PropertySourcesPlaceholderConfigurer (a @Value("${...}") feloldásáért felelős) maga is BeanFactoryPostProcessor. Ez magyarázza, miért nem használhatod a @Value-t BeanFactoryPostProcessor-ban — az még nem futott le, amikor a BFPP példányosodik.

Bean overriding

Spring Boot 2.1+ óta a bean overriding alapértelmezetten tiltott (spring.main.allow-bean-definition-overriding=false). Ha két @Bean azonos nevet kap, BeanDefinitionOverrideException-t dob. Ez megakadályozza a véletlen felüldefiniálást.

@Lazy

@Service
@Lazy
public class HeavyService { ... }

// Csak akkor példányosodik, amikor először inject-álják/lekérik

A @Lazy hasznos indulásidő optimalizálásra, de elrejtheti a konfigurációs hibákat (nem fail-fast).

Kapcsolódó téma: A Spring Design Patterns fejezetben (→ 16. fejezet) részletesen tárgyaljuk, hogyan kapcsolódnak a Bean kezelés mintázatok a GoF mintákhoz (Factory, Singleton, Template Method).


8. Interjúkérdések

  1. Mi a különbség a @Component és a @Bean között? @Component class-level, component scan regisztrálja. @Bean method-level, @Configuration osztályban, third-party osztályoknál is használható.

  2. Mi a @Service és a @Repository közötti különbség? Mindkettő @Component specializáció. A @Repository extra: exception translation (SQL kivétel → DataAccessException). A @Service pusztán szemantikai jelölés.

  3. Hogyan oldod fel, ha egy interfésznek több implementációja van? @Primary (globális default), @Qualifier("name") (explicit név), custom qualifier annotáció (típusbiztos), vagy List<T> injection (mind).

  4. Mi a BeanPostProcessor? Hook a bean lifecycle-ba. postProcessBeforeInitialization és postProcessAfterInitialization. AOP proxy-k a postProcessAfterInitialization-ben készülnek.

  5. Mi a BeanFactoryPostProcessor? Bean definíciók módosítása példányosítás előtt. Pl. PropertySourcesPlaceholderConfigurer a @Value feloldásához.

  6. Miért fontos a @Configuration (vs @Component) a @Bean metódusoknál? @Configuration CGLIB proxy-t kap → inter-bean referenciák singleton szemantikát tartanak. @Component-ben a @Bean metódusok "lite mode"-ban futnak — nincs proxy, minden hívás új példányt ad.

  7. Mi a bean overriding és hogyan kezeli a Spring Boot? Ha két bean azonos nevet kap. Spring Boot 2.1+ tiltja alapértelmezetten (BeanDefinitionOverrideException). Engedélyezés: spring.main.allow-bean-definition-overriding=true.


9. Szószedet

Fogalom Jelentés
@Component Általános bean jelölő annotáció, component scan regisztrálja
@Service @Component specializáció, üzleti logika réteg
@Repository @Component specializáció, adatelérés + exception translation
@Configuration Konfigurációs osztály CGLIB proxy-val
@Bean Metódus-szintű bean definíció @Configuration osztályban
@Primary Alapértelmezett implementáció kijelölése
@Qualifier Bean kiválasztás név alapján injection-nél
BeanPostProcessor Bean példányok módosítása a lifecycle-ban
BeanFactoryPostProcessor Bean definíciók módosítása példányosítás előtt
Component scanning Automatikus bean felfedezés csomagok szkennelésével
Exception translation SQL kivételek DataAccessException-re konvertálása (@Repository)
Lite mode @Bean metódus @Component-ben, CGLIB proxy nélkül
Bean overriding Azonos nevű bean felüldefiniálása (Spring Boot-ban tiltott)

10. Gyorsreferencia

Sztereotíp annotációk:
  @Component    →  Általános bean
  @Service      →  Üzleti logika (szemantikai)
  @Repository   →  Adatelérés + exception translation
  @Controller   →  Web réteg + request mapping

@Bean vs @Component:
  @Component = class-level, component scan
  @Bean      = method-level, @Configuration, third-party

Egyértelműsítés:
  @Primary              →  globális default
  @Qualifier("name")    →  explicit kiválasztás
  Custom @Qualifier     →  típusbiztos kiválasztás
  List<Interface>       →  összes implementáció

Lifecycle hook-ok:
  BeanFactoryPostProcessor  →  bean definíciók módosítása
  BeanPostProcessor         →  bean példányok módosítása
  @PostConstruct            →  inicializáció DI után
  @PreDestroy               →  takarítás (csak singleton)

Bean pipeline:
  Constructor → DI → BPP.before → @PostConstruct → BPP.after → Ready

Tipikus hibák:
  ✗ @Bean metódus @Component-ben (lite mode)
  ✗ @Qualifier string elírás
  ✗ Több @Primary azonos interfészre
  ✗ @Autowired BeanPostProcessor-ban
  ✗ @Repository nélküli DAO

Spring Boot defaults:
  Bean overriding = tiltott (2.1+)
  @Lazy = nem default (eager init)

🎮 Játékok

10 kérdés