Caching & Messaging Caching @Configuration
@EnableCaching
class CacheConfig {} @Service
public class BookService {
@Cacheable ( "books" )
public Book byId ( long id ) { … } // called once per id
@CachePut ( value = "books" , key = "#book.id" )
public Book update ( Book book ) { … } // always runs, refreshes the entry
@CacheEvict ( value = "books" , key = "#id" )
public void delete ( long id ) { … }
@CacheEvict ( value = "books" , allEntries = true )
public void reload () { … }
} Keys default to the method arguments; key = "#id" or a SpEL expression overrides that.
Without a cache library you get a simple ConcurrentHashMap. For anything real, pick one:
<dependency>
<groupId> com.github.ben-manes.caffeine</groupId>
<artifactId> caffeine</artifactId>
</dependency> spring.cache.cache-names = books
spring.cache.caffeine.spec = maximumSize=1000,expireAfterWrite=10m Shared across instances? Use Redis:
spring.cache.type = redis
spring.data.redis.host = localhost
spring.data.redis.time-to-live = 10m
WarningCache only what is expensive and stable. Every cache adds a staleness window and a new
class of bug: wrong data that looks right.
Messaging Messages decouple producer and consumer, absorb load spikes, and survive a restart of the
receiver.
Kafka <dependency>
<groupId> org.springframework.kafka</groupId>
<artifactId> spring-kafka</artifactId>
</dependency> spring.kafka.bootstrap-servers = localhost:9092
spring.kafka.consumer.group-id = demo
spring.kafka.consumer.auto-offset-reset = earliest @Service
class BookEvents {
private final KafkaTemplate < String , BookCreated > template ;
BookEvents ( KafkaTemplate < String , BookCreated > template ) { this . template = template ; }
void publish ( BookCreated event ) {
template . send ( "books" , String . valueOf ( event . id ()), event );
}
}
@Component
class BookConsumer {
@KafkaListener ( topics = "books" , groupId = "demo" )
void on ( BookCreated event ) { … }
} RabbitMQ spring.rabbitmq.host = localhost rabbitTemplate . convertAndSend ( "books.exchange" , "book.created" , event );
@RabbitListener ( queues = "books" )
void on ( BookCreated event ) { … } JmsClient Spring 7 adds a fluent JMS client next to JmsTemplate:
jmsClient . destination ( "notifications" ). send ( event ); Consumer rules idempotent handlers: the same message can arrive twiceacknowledge after the work succeeded, not beforea dead letter topic for messages that keep failing log the message key with every error, or debugging is guesswork Local infrastructure compose.yaml next to pom.xml plus:
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-docker-compose</artifactId>
<optional> true</optional>
</dependency> ./mvnw spring-boot:run now starts the containers and wires the connection details in.
★ Exercises Add @Cacheable to a slow lookup and measure the second call. Configure Caffeine with a 1-minute expiry and prove entries disappear. Evict the cache on update and show a stale read without it. Publish a BookCreated event to Kafka or RabbitMQ and consume it in the same app. Make the consumer idempotent — how do you detect a duplicate?