|
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