业余时间将kafka与spring-boot框架集成造轮子的过程中遇到@Configuration注解的问题,与大家分享一下。
问题重现
=========
TopicConsumer 类定义了一个Kafka消息的消费者,通过@Configuration的方式将类的初始化交给spring
@Configuration
@KafkaMessageListener(topics = "topic_a")
public class TopicConsumer implements MessageListener<String,String> {
@Override
public void onMessage(ConsumerRecord<String, String> consumerRecord) {
System.out.println(consumerRecord.value());
}
}
@KafkaMessageListener 注解中包含了一个topic 元数据,定义了消费者topic
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface KafkaMessageListener {
String[] topics() default {};
}
定义consumerFactory实现InitializingBean,在afterPropertiesSet()方法中,扫描代码包中所有声明了@KafkaMessageListener注解的消费者,取得注解中的topic信息, 并根据这些信息创建kafka消费者,代码如下:
public void afterPropertiesSet(){
for (String listenerName : applicationContext.getBeanNamesForAnnotation(KafkaMessageListener.class)) {
MessageListener listener = (MessageListener) applicationContext.getBean(listenerName);
Annotation[] annotations = listener.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof KafkaMessageListener) {
for (String topic : ((KafkaMessageListener) annotation).topics()) {
dosomething(topic, listener);
}
}
}
}
}
运行过程中发现并没有按照预先设想的运行,debug之。发现一个非常猎奇的现象。
首先
====
在applicationContext.getBeanNamesForAnnotation方法确实返回了含有@KafkaMessageListener注解的beanName:topicConsumer;
但是topicConsumer.getClass()返回的类中居然一个注解都没有。
仔细观察applicationContext.getBean(listenerName)返回的类,类名为“topicConsumer$$EnhancerBySpringCGLIB$$**”,是一个由cglib生产的动态代理。与@Component的实现方式不同。
由于我声明的注解@KafkaMessageListener 不是@Inherited的,所以Cglib生成的类自然无法获得其父类的注解。
在臧老板的帮助下,在spring reference doc中找到了理论依据:
All @Configuration classes are subclassed at startup-time with CGLIB
对对对!藏在referenceDoc就好了,千万别写在javaDoc里让人发现了!!
其次
applicationContext.getBeanNamesForAnnotation(@Annotation.class)底层的实现使用了Spring的AnnotationUtils.findAnnotation(clazz, annotationType)方法,这个方法并不是简单的反射拿注解,而是递归的扫描每个类的所有父类中的注解。所以即使是父类包含此注解的动态代理也可以被找到。
解决办法
老老实实把@Configuration注解换成@Component就解决了这个问题。还是要加强学习