|
1 | 1 | /* |
2 | | - * Copyright 2016-2022 the original author or authors. |
| 2 | + * Copyright 2016-2023 the original author or authors. |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
|
16 | 16 |
|
17 | 17 | package org.springframework.kafka.core; |
18 | 18 |
|
| 19 | +import java.time.Duration; |
19 | 20 | import java.util.ArrayList; |
20 | 21 | import java.util.Collections; |
21 | 22 | import java.util.Enumeration; |
|
28 | 29 | import java.util.concurrent.ConcurrentHashMap; |
29 | 30 | import java.util.function.Supplier; |
30 | 31 |
|
31 | | -import org.aopalliance.aop.Advice; |
32 | | -import org.aopalliance.intercept.MethodInterceptor; |
33 | | -import org.aopalliance.intercept.MethodInvocation; |
34 | 32 | import org.apache.commons.logging.LogFactory; |
35 | 33 | import org.apache.kafka.clients.consumer.Consumer; |
36 | 34 | import org.apache.kafka.clients.consumer.ConsumerConfig; |
37 | 35 | import org.apache.kafka.clients.consumer.KafkaConsumer; |
38 | | -import org.apache.kafka.common.Metric; |
39 | 36 | import org.apache.kafka.common.MetricName; |
40 | 37 | import org.apache.kafka.common.serialization.Deserializer; |
41 | 38 |
|
42 | | -import org.springframework.aop.framework.ProxyFactory; |
43 | | -import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; |
44 | 39 | import org.springframework.beans.factory.BeanNameAware; |
45 | 40 | import org.springframework.core.log.LogAccessor; |
46 | 41 | import org.springframework.lang.Nullable; |
@@ -445,68 +440,73 @@ private void checkInaccessible(Properties properties, Map<String, Object> modifi |
445 | 440 | } |
446 | 441 | } |
447 | 442 |
|
448 | | - @SuppressWarnings("resource") |
449 | 443 | protected Consumer<K, V> createKafkaConsumer(Map<String, Object> configProps) { |
450 | 444 | checkBootstrap(configProps); |
451 | 445 | Consumer<K, V> kafkaConsumer = createRawConsumer(configProps); |
452 | | - |
453 | | - if (this.listeners.size() > 0) { |
454 | | - Map<MetricName, ? extends Metric> metrics = kafkaConsumer.metrics(); |
455 | | - Iterator<MetricName> metricIterator = metrics.keySet().iterator(); |
456 | | - String clientId; |
457 | | - if (metricIterator.hasNext()) { |
458 | | - clientId = metricIterator.next().tags().get("client-id"); |
459 | | - } |
460 | | - else { |
461 | | - clientId = "unknown"; |
462 | | - } |
463 | | - String id = this.beanName + "." + clientId; |
464 | | - kafkaConsumer = createProxy(kafkaConsumer, id); |
465 | | - for (Listener<K, V> listener : this.listeners) { |
466 | | - listener.consumerAdded(id, kafkaConsumer); |
467 | | - } |
| 446 | + if (!this.listeners.isEmpty() && !(kafkaConsumer instanceof ExtendedKafkaConsumer)) { |
| 447 | + LOGGER.warn("The 'ConsumerFactory.Listener' configuration is ignored " + |
| 448 | + "because the consumer is not an instance of 'ExtendedKafkaConsumer'." + |
| 449 | + "Consider extending 'ExtendedKafkaConsumer' or implement your own 'ConsumerFactory'."); |
468 | 450 | } |
| 451 | + |
469 | 452 | for (ConsumerPostProcessor<K, V> pp : this.postProcessors) { |
470 | 453 | kafkaConsumer = pp.apply(kafkaConsumer); |
471 | 454 | } |
472 | 455 | return kafkaConsumer; |
473 | 456 | } |
474 | 457 |
|
475 | 458 | /** |
476 | | - * Create a Consumer. |
| 459 | + * Create a {@link Consumer}. |
| 460 | + * By default, this method returns an internal {@link ExtendedKafkaConsumer} |
| 461 | + * which is aware of provided into this {@link #listeners}, therefore it is recommended |
| 462 | + * to extend that class if {@link #listeners} are still involved for a custom {@link Consumer}. |
477 | 463 | * @param configProps the configuration properties. |
478 | 464 | * @return the consumer. |
479 | 465 | * @since 2.5 |
480 | 466 | */ |
481 | 467 | protected Consumer<K, V> createRawConsumer(Map<String, Object> configProps) { |
482 | | - return new KafkaConsumer<>(configProps, this.keyDeserializerSupplier.get(), |
483 | | - this.valueDeserializerSupplier.get()); |
| 468 | + return new ExtendedKafkaConsumer(configProps); |
484 | 469 | } |
485 | 470 |
|
486 | | - @SuppressWarnings("unchecked") |
487 | | - private Consumer<K, V> createProxy(Consumer<K, V> kafkaConsumer, String id) { |
488 | | - ProxyFactory pf = new ProxyFactory(kafkaConsumer); |
489 | | - Advice advice = new MethodInterceptor() { |
| 471 | + @Override |
| 472 | + public boolean isAutoCommit() { |
| 473 | + Object auto = this.configs.get(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG); |
| 474 | + return auto instanceof Boolean |
| 475 | + ? (Boolean) auto |
| 476 | + : !(auto instanceof String) || Boolean.parseBoolean((String) auto); |
| 477 | + } |
| 478 | + |
| 479 | + protected class ExtendedKafkaConsumer extends KafkaConsumer<K, V> { |
| 480 | + |
| 481 | + private String idForListeners; |
490 | 482 |
|
491 | | - @Override |
492 | | - public Object invoke(MethodInvocation invocation) throws Throwable { |
493 | | - DefaultKafkaConsumerFactory.this.listeners.forEach(listener -> |
494 | | - listener.consumerRemoved(id, kafkaConsumer)); |
495 | | - return invocation.proceed(); |
| 483 | + protected ExtendedKafkaConsumer(Map<String, Object> configProps) { |
| 484 | + super(configProps, |
| 485 | + DefaultKafkaConsumerFactory.this.keyDeserializerSupplier.get(), |
| 486 | + DefaultKafkaConsumerFactory.this.valueDeserializerSupplier.get()); |
| 487 | + |
| 488 | + if (!DefaultKafkaConsumerFactory.this.listeners.isEmpty()) { |
| 489 | + Iterator<MetricName> metricIterator = metrics().keySet().iterator(); |
| 490 | + String clientId = "unknown"; |
| 491 | + if (metricIterator.hasNext()) { |
| 492 | + clientId = metricIterator.next().tags().get("client-id"); |
| 493 | + } |
| 494 | + this.idForListeners = DefaultKafkaConsumerFactory.this.beanName + "." + clientId; |
| 495 | + for (Listener<K, V> listener : DefaultKafkaConsumerFactory.this.listeners) { |
| 496 | + listener.consumerAdded(this.idForListeners, this); |
| 497 | + } |
496 | 498 | } |
| 499 | + } |
497 | 500 |
|
498 | | - }; |
499 | | - NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice); |
500 | | - advisor.addMethodName("close"); |
501 | | - pf.addAdvisor(advisor); |
502 | | - return (Consumer<K, V>) pf.getProxy(); |
503 | | - } |
| 501 | + @Override |
| 502 | + public void close(Duration timeout) { |
| 503 | + super.close(timeout); |
| 504 | + |
| 505 | + for (Listener<K, V> listener : DefaultKafkaConsumerFactory.this.listeners) { |
| 506 | + listener.consumerRemoved(this.idForListeners, this); |
| 507 | + } |
| 508 | + } |
504 | 509 |
|
505 | | - @Override |
506 | | - public boolean isAutoCommit() { |
507 | | - Object auto = this.configs.get(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG); |
508 | | - return auto instanceof Boolean ? (Boolean) auto |
509 | | - : auto instanceof String ? Boolean.valueOf((String) auto) : true; |
510 | 510 | } |
511 | 511 |
|
512 | 512 | } |
0 commit comments