This talks describes how you can write your own CDI extension to make external APIs look like native CDI. We use Apache Kafka as showcase. It explains what the CDI container does under the hood to automatically load Kafka consumers and producers, make them injectable in your code, and hide all the library-specific boilerplate. You also learn the basic techniques for making your favorite framework portable across all the Java EE containers.
6. CDI primer
@ApplicationScoped
public class UserModificationKafkaProducer {
private KafkaProducer<String, String>
kafkaProducer;
public void userModified(User modifiedUser) {
String record =
7. CDI primer
@ApplicationScoped
public class UserModificationKafkaProducer {
@Inject
private KafkaProducer<String, String>
kafkaProducer;
public void userModified(User modifiedUser) {
String record =
8. CDI primer
@ApplicationScoped
public class UserModificationKafkaProducer {
@Inject
@KafkaProducerConfig(bootstrapServers =
"mykafkahost:9090")
private KafkaProducer<String, String>
kafkaProducer;
public void userModified(User modifiedUser) {
9. CDI primer
public @interface KafkaProducerConfig {
Class keyType() default String.class;
Class valueType() default String.class;
String bootstrapServers() default "localhost:9092";
}
10. CDI primer
@ApplicationScoped
public class KafkaProducerFactory {
@Produces
public <K,V> KafkaProducer<K, V> createProducer(
InjectionPoint injectionPoint) {
KafkaProducerConfig annotation = injectionPoint
.getAnnotated()
.getAnnotation(KafkaProducerConfig.class);
Properties producerProperties = new Properties();
producerProperties.setProperty(
"bootstrap.servers", annotation.bootstrapServers());
// Set other props
return new KafkaProducer<>(producerProperties);
}
}
11. CDI primer
@ApplicationScoped
public class UserModificationKafkaProducer {
@Inject
@KafkaProducerConfig(bootstrapServers =
"mykafkahost:9090")
private KafkaProducer<String, String>
kafkaProducer;
public void userModified(@Observes User
12. CDI primer
public class KafkaUpdatesListener implements Runnable {
@Inject
@KafkaConsumerConfig(subscriptions = "user")
private KafkaConsumer<String, String> consumer;
@Override
public void run() {
while (continuePolling) {
ConsumerRecords<String, String> records =
consumer.poll(100);
if (!records.isEmpty()) {
// Update microservice storage
13. But what if...
• Leave factories boilerplate to the framework
• Leave polling updates to the framework
• Do it this way:
public class UserModificationKafkaConsumer {
•
@Consumes(topic = "user")
public void listenForUsers(
ConsumerRecord<String, String> record) {
• String userJson = record.value();
• // Store the user in DB
}
17. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
18. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Running
19. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
20. Kafka CDI extension tasks
• Register the factories and listener thread
• Create Kafka Consumer for all @Consumes methods
• Start polling consumers for changes
• Invoke @Consumes methods when change happens
21. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
public void addKafkaSpecificTypes(
@Observes BeforeBeanDiscovery bbd,
BeanManager beanManager) {
bbd.addAnnotatedType(
beanManager.createAnnotatedType(KafkaProducerFactory.class));
bbd.addAnnotatedType(
beanManager.createAnnotatedType(KafkaUpdatesListener.class));
}
22. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
public void collectConsumerMethods(
@Observes @WithAnnotations(Consumes.class)
ProcessAnnotatedType<?> pat) {
pat.getAnnotatedType().getMethods().stream()
.filter(m -> m.isAnnotationPresent(Consumes.class))
.forEach(m -> handleConsumerMethod(pat.getAnnotatedType(), m));
}
23. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
private void handleConsumerMethod(AnnotatedType<?> clazz,
AnnotatedMethod<?> am) {
List<AnnotatedParameter<?>> parameters = am.getParameters();
if (parameters.size() != 1) {
errors.add(new IllegalArgumentException(
"@Consume methods should only have one parameter"));
} else {
consumerDescriptors.add(new KafkaConsumerDescriptor(
clazz.getJavaClass(), am,
am.getAnnotation(Consumes.class).topic()));
}
}
24. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
public void startMonitoring(@Observes AfterDeploymentValidation adv,
BeanManager bm) {
if (!errors.isEmpty()) {
errors.forEach(adv::addDeploymentProblem);
} else {
KafkaUpdatesListener kafkaUpdatesListener = initializeListener(bm);
startListener(kafkaUpdatesListener, adv);
this.kafkaUpdatesListener = kafkaUpdatesListener;
}
}
25. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
private KafkaUpdatesListener initializeListener(BeanManager bm) {
Class<KafkaUpdatesListener> clazz = KafkaUpdatesListener.class;
KafkaUpdatesListener reference = createBean(bm, clazz);
consumerDescriptors.forEach(m -> addTopicToListen(m, reference));
return reference;
}
26. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
static Object createBean(BeanManager bm, Class<?> clazz) {
Set<Bean<?>> listenerBeans = bm.getBeans(clazz);
Bean<?> listenerBean = bm.resolve(listenerBeans);
CreationalContext<?> creationalContext =
bm.createCreationalContext(listenerBean);
return bm.getReference(listenerBean, clazz, creationalContext);
}
27. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
private void startListener(KafkaUpdatesListener kafkaUpdatesListener) {
try {
ExecutorService executorService =
InitialContext.doLookup("java:comp/DefaultManagedExecutorService");
executorService.submit(kafkaUpdatesListener);
} catch (NamingException e) {
// Handle NamingException
}
}
28. Before Bean Discovery
Process Annotated Types
After Type Discovery
Process Injection Point
Process Injection Target
Process Bean Attributes
After Deployment Validation
Before shutdown
Process Producer Method / Field
Process Observer Method
After Bean Discovery
Process Bean
Type discovery
Bean
discovery
Before shutdown
Running
public void stopListener(@Observes BeforeShutdown bsh) {
kafkaUpdatesListener.stopPolling();
}
29.
30. Wrap up
• CDI is powerful
• Extensions are portable
• New features and improvements to add
• Create extensions for other libraries
• Evangelize