Beans & Dependency Injection

What a bean is

A bean is an object the container creates, configures and hands out. You never call new on it yourself.

@Service
public class GreetingService {
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}
StereotypeMeaning
@Componentany bean
@Servicebusiness logic
@Repositorydata access (adds exception translation)
@RestControllerweb endpoint returning data
@Configurationclass that declares @Bean methods

All four of the first are @Component underneath — the name documents intent.

Constructor injection

@RestController
public class GreetingController {

    private final GreetingService service;

    public GreetingController(GreetingService service) {   // no @Autowired needed
        this.service = service;
    }

    @GetMapping("/greet/{name}")
    String greet(@PathVariable String name) {
        return service.greet(name);
    }
}

One constructor, final fields, no framework annotation in sight — that class is testable with plain new.

Avoid field injection

@Autowired private Foo foo; hides dependencies, breaks final, and forces reflection in tests. Use the constructor.

Beans from a configuration class

Use @Bean for types you do not own:

@Configuration
public class ClientConfig {

    @Bean
    RestClient restClient(RestClient.Builder builder) {
        return builder.baseUrl("https://api.example.com").build();
    }
}

The method name is the bean name; parameters are injected.

Choosing between candidates

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

@Component @Primary  class EmailNotifier implements Notifier {  }
@Component("sms")    class SmsNotifier   implements Notifier {  }
Alerts(Notifier defaultOne,                       // EmailNotifier, it is @Primary
       @Qualifier("sms") Notifier urgent) {  }

Inject List<Notifier> or Map<String, Notifier> to get all of them.

Conditional beans

@Bean
@ConditionalOnMissingBean          // only if the user did not define one
@ConditionalOnProperty(name = "features.cache", havingValue = "true")
CacheManager cacheManager() {  }

This is exactly how auto-configuration backs off in favour of your own beans.

Scope and lifecycle

Beans are singletons by default — one instance per context, shared by every thread. Keep them stateless.

@Component
@Scope("prototype")        // new instance per injection point
class Ticket { }
@Component
class Warmup {
    @PostConstruct  void start() {  }   // after injection
    @PreDestroy     void stop()  {  }   // on shutdown
}

For startup work with access to arguments:

@Bean
ApplicationRunner seed(BookRepository repo) {
    return args -> repo.save(new Book("Dune"));
}

★ Exercises

  1. Write a RandomQuoteService and inject it into a controller.
  2. Add a second Notifier implementation and select one with @Qualifier.
  3. Inject List<Notifier> and call every implementation.
  4. Add a @PostConstruct log line and watch the order of construction on startup.
  5. Why does a singleton bean holding a mutable field cause bugs?