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@Componenttechnikai 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:
- Bean példányosítás (konstruktor)
- Dependency injection (setter/field)
postProcessBeforeInitialization()— minden BPP@PostConstruct/InitializingBean.afterPropertiesSet()postProcessAfterInitialization()— minden BPP (itt készül az AOP proxy)- 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
Mi a különbség a @Component és a @Bean között?
@Componentclass-level, component scan regisztrálja.@Beanmethod-level,@Configurationosztályban, third-party osztályoknál is használható.Mi a @Service és a @Repository közötti különbség? Mindkettő
@Componentspecializáció. A@Repositoryextra: exception translation (SQL kivétel →DataAccessException). A@Servicepusztán szemantikai jelölés.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), vagyList<T>injection (mind).Mi a BeanPostProcessor? Hook a bean lifecycle-ba.
postProcessBeforeInitializationéspostProcessAfterInitialization. AOP proxy-k apostProcessAfterInitialization-ben készülnek.Mi a BeanFactoryPostProcessor? Bean definíciók módosítása példányosítás előtt. Pl.
PropertySourcesPlaceholderConfigurera@Valuefeloldásához.Miért fontos a @Configuration (vs @Component) a @Bean metódusoknál?
@ConfigurationCGLIB proxy-t kap → inter-bean referenciák singleton szemantikát tartanak.@Component-ben a@Beanmetódusok "lite mode"-ban futnak — nincs proxy, minden hívás új példányt ad.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