Defining beans in plain Java with @Configuration and @Bean — and the CGLIB proxy trick that makes it behave.
@Bean goes, how a bean gets its name, how beans depend on each other, how to
combine several config classes — and the big one: why calling one @Bean method from
another still returns the singleton. That last point is a near-guaranteed question.
Spring reads bean definitions from three kinds of configuration metadata:
<bean> elements. Legacy, still examinable.@Component/@Service on your own classes (Lesson 3).@Bean methods inside a @Configuration class. This lesson.Use Java config when you don't own the class (e.g. a DataSource from a library) —
you can't put @Component on someone else's code, but you can write a @Bean method that
constructs it.
@Configuration
public class AppConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource());
}
@Bean
public TransferService transferService() {
// depend on another bean by calling its @Bean method
return new TransferService(accountRepository());
}
@Bean
public DataSource dataSource() { return ...; }
}
@Bean method name — here transferService,
accountRepository, dataSource. Override with @Bean("myName").
1. Call the other @Bean method (as above): new TransferService(accountRepository()).
2. Declare it as a method parameter — Spring injects the matching bean as the argument:
@Bean
public TransferService transferService(AccountRepository repo) {
return new TransferService(repo); // repo is injected by type
}
Both are correct. Parameter injection is cleaner when the dependency lives in a different config class.
Look again at method 1. transferService() calls accountRepository(), and
accountRepository() is also called on its own. In plain Java that's two
separate JdbcAccountRepository objects. But beans are singletons — so which is it?
@Configuration, Spring wraps it in a CGLIB proxy subclass.
Every call to a @Bean method is intercepted: if the singleton already exists, the proxy
returns the existing one instead of running the method again. So inter-bean calls honour singleton scope.
"Full" — @Configuration | "Lite" — @Bean on a plain @Component | |
|---|---|---|
| Class is proxied? | Yes (CGLIB) | No |
| Inter-bean method call returns… | the shared singleton | a new object each call |
| Use when | beans reference each other by method call | simple, independent factory methods |
@Configuration class is proxied by CGLIB, so it must not be final,
and neither can its @Bean methods — same CGLIB rule you met in the Container drill.
Real apps split config across several classes. Three ways to bring them together:
@Import on one config class: @Import({DataConfig.class, WebConfig.class}) — multiple values use the {} array syntax.new AnnotationConfigApplicationContext(DataConfig.class, WebConfig.class).@Configuration class is itself a @Component, so @ComponentScan picks it up.ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService a = ctx.getBean(TransferService.class); // by type
TransferService b = (TransferService) ctx.getBean("transferService"); // by name
TransferService c = ctx.getBean("transferService", TransferService.class); // by name + type
Read "Basic Concepts: @Bean and @Configuration" and "Using the @Configuration annotation" — the section on inter-bean dependencies is exactly the proxy point above.
Answer before you're sure. The proxy question is the one to nail.
@Import, @Configuration/@Bean, the @Profile+@Bean snippet).
Do those now, plus re-run the Container Drill.
@Autowired).