spring-rabbit 中的 ClassNotFoundException 取决于何时启动消费者或生产者

ClassNotFoundException in spring-rabbit depending on when consumer or producer is started

我正在使用 Spring 使用 spring-amqp 和 RabbitMQ 引导在我在本地 运行 的两个 JVM 之间发送消息。根据我启动每个应用程序的顺序,我有时会得到 ClassNotFoundException。我有一个像这样的多项目设置:

- Project root
   - common (contains all events / messages that are sent)
   - server
   - client

当服务器首先启动时,它等待来自客户端的消息。当客户端随后启动时,它会实现一个 ApplicationListener<ApplicationReadyEvent> 并向服务器发送一条消息以表明它已准备就绪。

服务器侦听器:

@Component
@RabbitListener(queues =  "server.${server.id}")
public class ServerListener {
    private static final Logger logger = LoggerFactory.getLogger(ServerListener.class);

    @RabbitHandler
    public void onMessageReceived(@Payload ClientAvailableEvent event) {
        logger.info("Server: Received request from client ID = {}", event.getClientId());
    }
}

客户制作人:

@Component
public class ClientReadyProducer implements ApplicationListener<ApplicationReadyEvent> {
    private static final Logger logger = LoggerFactory.getLogger(ClientReadyProducer.class);

    @Value("${client.id}")
    private String id;

    private final RabbitTemplate template;

    @Autowired
    public EventBasedModuleRegistration(RabbitTemplate template) {
        this.template = template;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {    
        logger.info("Client initialized.");
        ClientAvailableEvent event = ClientAvailableEvent.from(id);
        template.convertSendAndReceive("server.exchange.all", "", event);
    }
}

当服务器收到这条消息时,日志爆炸了无数堆栈跟踪,抱怨找不到 ClientAvailableEvent:

2017-01-30 09:30:22.610  WARN 63573 --- [cTaskExecutor-1] s.a.r.l.ConditionalRejectingErrorHandler :  [][] Execution of Rabbit message listener failed.

org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:865)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:760)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:680)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access[=14=]1(SimpleMessageListenerContainer.java:93)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:183)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1358)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:661)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1102)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:1086)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access00(SimpleMessageListenerContainer.java:93)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1203)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Could not deserialize object type
    at org.springframework.amqp.utils.SerializationUtils.deserialize(SerializationUtils.java:82)
    at org.springframework.amqp.support.converter.SimpleMessageConverter.fromMessage(SimpleMessageConverter.java:110)
    at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:185)
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter$MessagingMessageConverterAdapter.extractPayload(MessagingMessageListenerAdapter.java:173)
    at org.springframework.amqp.support.converter.MessagingMessageConverter.fromMessage(MessagingMessageConverter.java:118)
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.toMessagingMessage(MessagingMessageListenerAdapter.java:102)
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:88)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:757)
    ... 10 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.example.event.ClientAvailableEvent
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:250)
    at org.springframework.core.ConfigurableObjectInputStream.resolveClass(ConfigurableObjectInputStream.java:74)
    at org.springframework.amqp.support.converter.SimpleMessageConverter.resolveClass(SimpleMessageConverter.java:179)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at java.util.ArrayList.readObject(ArrayList.java:791)
    at sun.reflect.GeneratedMethodAccessor46.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1924)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at org.springframework.amqp.utils.SerializationUtils.deserialize(SerializationUtils.java:76)
    ... 17 common frames omitted

但是,我可以让这个异常消失。在客户端仍然 运行 的情况下,如果我重新启动服务器,一切都很好并且可以继续正常工作。我可以重新启动客户端,它会发送另一个 ClientAvailableEvent,服务器会愉快地反序列化它。

这是我的 Spring 类:

服务器配置:

@Configuration
@EnableRabbit
public class ServerConfiguration {
    @Value("${server.id}")
    public String id;

    @Bean
    public Queue serverQueue() {
        return new Queue("server." + id, false, true, true);
    }

    @Bean
    public TopicExchange serverExchange() {
        return new TopicExchange("server.exchange");
    }

    @Bean
    public Binding bindingById() {
        return BindingBuilder.bind(serverQueue()).to(serverExchange()).with(id);
    }

    @Bean
    public FanoutExchange allServersExchange() {
        return new FanoutExchange("server.exchange.all");
    }

    @Bean
    public Binding bindingToAll() {
        return BindingBuilder.bind(serverQueue()).to(allServersExchange());
    }

    @Bean
    public TopicExchange clientExchange() {
        return new TopicExchange("client.exchange");
    }

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory factory) {
        return new RabbitAdmin(factory);
    }
}

客户端配置:

@Configuration
@EnableRabbit
public class ClientConfiguration {
    @Value("${client.id}")
    private String id;

    @Bean
    public Queue clientQueue() {
        return new Queue("client." + id, false, true, true);
    }

    @Bean
    public TopicExchange clientExchange() {
        return new TopicExchange("client.exchange");
    }

    @Bean
    public Binding bindingById() {
        return BindingBuilder.bind(clientQueue()).to(clientExchange()).with(id);
    }

    @Bean
    public TopicExchange clientExchange() {
        return new TopicExchange("client.exchange");
    }

    @Bean
    public FanoutExchange allClientsExchange() {
        return new FanoutExchange("client.exchange.all");
    }

    @Bean
    public Binding bindingToAll() {
        return BindingBuilder.bind(clientQueue()).to(allClientsExchange());
    }

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory factory) {
        return new RabbitAdmin(factory);
    }
}

我最初发现 具有几乎相同的堆栈跟踪,但这种情况下的解决方案是将所有常见事件/模型放在一个项目中,并将该项目包含在服务器和客户端中项目。但是,我已经在这样做了。我还尝试使用 JSON 发送消息(通过将以下内容添加到两个配置中)而不是标准序列化:

@Bean
public MessageConverter producerJsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

@Bean
public MappingJackson2MessageConverter consumerJsonMessageConverter(){
    return new MappingJackson2MessageConverter();
}

@Bean
public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() {
    DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
    factory.setMessageConverter(consumerJsonMessageConverter());
    return factory;
}

@Bean
public RabbitTemplate configureRabbitTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate template = new RabbitTemplate(connectionFactory);
    template.setMessageConverter(producerJsonMessageConverter());
    return template;
}

@Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
    registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    factory.setMessageConverter(producerJsonMessageConverter());
    return factory;
}

使用 JSON 导致类似的堆栈跟踪抱怨 ClassNotFoundException

以下是我正在使用的相关依赖项:

这很可能是某种 Classloader 问题 - 也许您在 class 路径上有两个版本的 class。

我发现调试此类问题的最简单方法是 运行 带有 -verbose 的 JVM 并监视从何处加载 class。

比较有效的 运行 和无效的日志。

我对你遇到与 JSON 相同的问题并不感到惊讶,因为完全限定的 class 名称是在 header.

中传递的

此外,您的 jars 中是否有独特的包?如果您从不同的罐子中提供相同的包,您可能会遇到这样的问题。