The Spring container assembles an application at startup. It scans annotated classes, adds discovered components to an application context, creates required objects, and injects their dependencies. This separates a class’s business logic from the choice and construction of its collaborators—for example, a payment processor can depend on a NetworkClient interface and receive either a real client or a mock in tests. section 12.1
Listing 12.2 Annotations on classes specifying the relationship between them
@Component #1
public class PaymentProcessor implements Processor {
private NetworkClient networkClient;
@Autowired #2
public PaymentProcessor(NetworkClient networkClient) {
this.networkClient = networkClient;
}
...
}
...
@Component #3
public class SecureNetworkClient implements NetworkClient {
public SecureNetworkClient(...) {
...
}
....
}
@Component makes both classes available to the container; @Autowired tells it to supply the constructor’s NetworkClient dependency when creating the payment processor. section 12.1
In a Kafka application, the container manages application components as well as Kafka-facing infrastructure. A class marked @Component can receive a KafkaTemplate through its constructor, while @Value injects configured topic names. This lets a listener method concentrate on processing a loan application and producing its result rather than constructing its Kafka client or reading configuration directly. section 12.2.1
Wiring a loan processor
A NewLoanApplicationProcessor is a component whose constructor receives a KafkaTemplate created at container startup. Its @KafkaListener method consumes loan applications, selects an accepted or rejected topic, and sends the processed record; a separate component can listen to each resulting topic. section 12.2.1
@KafkaListener extends the container’s role from dependency wiring to listener setup. For each annotated listener method, it uses a ConcurrentKafkaListenerContainerFactory to create a KafkaListener that wraps a KafkaConsumer for the declared topic or topics. Listener annotations may also sit on a component class, with @KafkaHandler methods dispatching received values by type. section 12.2.1
Listing 12.11 Increasing the concurrency level for a container listener factory
@Bean
public ConcurrentKafkaListenerContainerFactory<String, LoanApplication>
kafkaListenerContainerFactory() { #1
ConcurrentKafkaListenerContainerFactory<String, LoanApplication>
kafkaListenerContainerFactory =
new ConcurrentKafkaListenerContainerFactory<>();
kafkaListenerContainerFactory.setConsumerFactory(consumerFactory());
kafkaListenerContainerFactory.setConcurrency(partitions); #2
return kafkaListenerContainerFactory;
}
A named @Bean can replace the default listener-container factory. Setting concurrency to the partition count creates a KafkaConsumer per partition for maximum throughput. section 12.2.2
Configure the container deliberately when Kafka workload requirements exceed the defaults:
- Use Spring Boot and @EnableKafka to obtain a ConcurrentKafkaListenerContainerFactory automatically. section 12.2.2
- Define a kafkaListenerContainerFactory bean with the expected name to have the container use a customized factory instead of its default. section 12.2.2
- Set listener concurrency with care: concurrency above one creates multiple threads running KafkaConsumer instances, so the @KafkaListener method must be thread safe. section 12.4
- Name a custom factory explicitly in @KafkaListener when a particular listener needs nondefault container behavior. section 12.2.2
Spring Boot reduces configuration through convention over configuration. An application can create its ApplicationContext with SpringApplicationBuilder, restrict itself to non-web execution when no web server is needed, and scan specified base packages for components. When more direct stream-application control is wanted, Spring can be retained solely for dependency injection. section 12.2 section 12.4
