使用 AOP 的动态 Kafka 消费者
Dynamic Kafka Consumer with AOP
我有几个动态 Kafka 消费者(基于部门 ID 等),您可以在下面找到代码。
基本上,我想记录每个 onMessage()
方法调用所花费的时间,因此我创建了一个 @LogExecutionTime
方法级别的自定义注释并将其添加到 onMessage()
方法。
但是我的 LogExecutionTimeAspect
的 logExecutionTime()
永远不会被调用,即使我的 onMessage()
在有关于该主题的消息时被调用并且其他一切正常。
能否请您帮助我缺少什么 LogExecutionTimeAspect
class 以便它开始工作?
日志执行时间:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
LogExecutionTimeAspect class:
@Aspect
@Component
public class LogExecutionTimeAspect {
@Around("within(com.myproject..*) && @annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(" Time taken by Listener ::"+(endTime-startTime)+"ms");
return object;
}
}
DepartmentsMessageConsumer class:
@Component
public class DepartmentsMessageConsumer implements MessageListener {
@Value(value = "${spring.kafka.bootstrap-servers}" )
private String bootstrapAddress;
@PostConstruct
public void init() {
Map<String, Object> consumerProperties = new HashMap<>();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "DEPT_ID_HERE");
ContainerProperties containerProperties =
new ContainerProperties("com.myproj.depts.topic");
containerProperties.setMessageListener(this);
DefaultKafkaConsumerFactory<String, Greeting> consumerFactory =
new DefaultKafkaConsumerFactory<>(consumerProperties,
new StringDeserializer(),
new JsonDeserializer<>(Department.class));
ConcurrentMessageListenerContainer container =
new ConcurrentMessageListenerContainer<>(consumerFactory,
containerProperties);
container.start();
}
@Override
@LogExecutionTime
public void onMessage(Object message) {
ConsumerRecord record = (ConsumerRecord) message;
Department department = (Department)record.value();
System.out.println(" department :: "+department);
}
}
ApplicationLauncher class:
@SpringBootApplication
@EnableKafka
@EnableAspectJAutoProxy
@ComponentScan(basePackages = { "com.myproject" })
public class ApplicationLauncher extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationLauncher.class, args);
}
}
编辑:
我试过@EnableAspectJAutoProxy(exposeProxy=true)
,但没有成功。
您应该考虑在 @EnableAspectJAutoProxy
:
上打开此选项
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
另一方面,有这样的东西,它会比 AOP 更好:
/**
* A plugin interface that allows you to intercept (and possibly mutate) records received by the consumer. A primary use-case
* is for third-party components to hook into the consumer applications for custom monitoring, logging, etc.
*
* <p>
* This class will get consumer config properties via <code>configure()</code> method, including clientId assigned
* by KafkaConsumer if not specified in the consumer config. The interceptor implementation needs to be aware that it will be
* sharing consumer config namespace with other interceptors and serializers, and ensure that there are no conflicts.
* <p>
* Exceptions thrown by ConsumerInterceptor methods will be caught, logged, but not propagated further. As a result, if
* the user configures the interceptor with the wrong key and value type parameters, the consumer will not throw an exception,
* just log the errors.
* <p>
* ConsumerInterceptor callbacks are called from the same thread that invokes {@link org.apache.kafka.clients.consumer.KafkaConsumer#poll(long)}.
* <p>
* Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
*/
public interface ConsumerInterceptor<K, V> extends Configurable {
更新
@EnableAspectJAutoProxy(exposeProxy=true)
did not work and I know that I could use interceptor, but I wanted to make it working with AOP.
那我建议你考虑分开一个DepartmentsMessageConsumer
和ConcurrentMessageListenerContainer
。我的意思是将 ConcurrentMessageListenerContainer
移到单独的 @Configuration
class 中。 ApplicationLauncher
是一个很好的候选者。将其设为 @Bean
并依赖于您的 DepartmentsMessageConsumer
进行注入。关键是你需要给 AOP 一个机会来检测你的 DepartmentsMessageConsumer
,但是对于 @PostConstruct
,从 Kafka 实例化和开始消费还为时过早。
我有几个动态 Kafka 消费者(基于部门 ID 等),您可以在下面找到代码。
基本上,我想记录每个 onMessage()
方法调用所花费的时间,因此我创建了一个 @LogExecutionTime
方法级别的自定义注释并将其添加到 onMessage()
方法。
但是我的 LogExecutionTimeAspect
的 logExecutionTime()
永远不会被调用,即使我的 onMessage()
在有关于该主题的消息时被调用并且其他一切正常。
能否请您帮助我缺少什么 LogExecutionTimeAspect
class 以便它开始工作?
日志执行时间:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
LogExecutionTimeAspect class:
@Aspect
@Component
public class LogExecutionTimeAspect {
@Around("within(com.myproject..*) && @annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object object = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(" Time taken by Listener ::"+(endTime-startTime)+"ms");
return object;
}
}
DepartmentsMessageConsumer class:
@Component
public class DepartmentsMessageConsumer implements MessageListener {
@Value(value = "${spring.kafka.bootstrap-servers}" )
private String bootstrapAddress;
@PostConstruct
public void init() {
Map<String, Object> consumerProperties = new HashMap<>();
consumerProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
bootstrapAddress);
consumerProperties.put(ConsumerConfig.GROUP_ID_CONFIG, "DEPT_ID_HERE");
ContainerProperties containerProperties =
new ContainerProperties("com.myproj.depts.topic");
containerProperties.setMessageListener(this);
DefaultKafkaConsumerFactory<String, Greeting> consumerFactory =
new DefaultKafkaConsumerFactory<>(consumerProperties,
new StringDeserializer(),
new JsonDeserializer<>(Department.class));
ConcurrentMessageListenerContainer container =
new ConcurrentMessageListenerContainer<>(consumerFactory,
containerProperties);
container.start();
}
@Override
@LogExecutionTime
public void onMessage(Object message) {
ConsumerRecord record = (ConsumerRecord) message;
Department department = (Department)record.value();
System.out.println(" department :: "+department);
}
}
ApplicationLauncher class:
@SpringBootApplication
@EnableKafka
@EnableAspectJAutoProxy
@ComponentScan(basePackages = { "com.myproject" })
public class ApplicationLauncher extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ApplicationLauncher.class, args);
}
}
编辑:
我试过@EnableAspectJAutoProxy(exposeProxy=true)
,但没有成功。
您应该考虑在 @EnableAspectJAutoProxy
:
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
另一方面,有这样的东西,它会比 AOP 更好:
/**
* A plugin interface that allows you to intercept (and possibly mutate) records received by the consumer. A primary use-case
* is for third-party components to hook into the consumer applications for custom monitoring, logging, etc.
*
* <p>
* This class will get consumer config properties via <code>configure()</code> method, including clientId assigned
* by KafkaConsumer if not specified in the consumer config. The interceptor implementation needs to be aware that it will be
* sharing consumer config namespace with other interceptors and serializers, and ensure that there are no conflicts.
* <p>
* Exceptions thrown by ConsumerInterceptor methods will be caught, logged, but not propagated further. As a result, if
* the user configures the interceptor with the wrong key and value type parameters, the consumer will not throw an exception,
* just log the errors.
* <p>
* ConsumerInterceptor callbacks are called from the same thread that invokes {@link org.apache.kafka.clients.consumer.KafkaConsumer#poll(long)}.
* <p>
* Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
*/
public interface ConsumerInterceptor<K, V> extends Configurable {
更新
@EnableAspectJAutoProxy(exposeProxy=true)
did not work and I know that I could use interceptor, but I wanted to make it working with AOP.
那我建议你考虑分开一个DepartmentsMessageConsumer
和ConcurrentMessageListenerContainer
。我的意思是将 ConcurrentMessageListenerContainer
移到单独的 @Configuration
class 中。 ApplicationLauncher
是一个很好的候选者。将其设为 @Bean
并依赖于您的 DepartmentsMessageConsumer
进行注入。关键是你需要给 AOP 一个机会来检测你的 DepartmentsMessageConsumer
,但是对于 @PostConstruct
,从 Kafka 实例化和开始消费还为时过早。