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

时间:2017-01-30 17:55:20

标签: java spring-boot rabbitmq spring-amqp spring-rabbit

我正在使用带有spring-amqp和RabbitMQ的Spring Boot在本地运行的两个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$001(SimpleMessageListenerContainer.java:93)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.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.access$1100(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$1.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课程:

ServerConfiguration:

@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);
    }
}

我最初发现this question具有几乎相同的堆栈跟踪,但在这种情况下,解决方案是将所有常见事件/模型放在一个项目中,并将该项目包含在服务器和客户端项目中。但是,我已经这样做了。我还尝试使用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

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

  • Spring Boot v1.3.8.RELEASE
  • Spring AMQP v1.5.6.RELEASE
  • Spring Rabbit v1.5.6.RELEASE

1 个答案:

答案 0 :(得分:1)

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

我发现调试此类问题的最简单方法是使用-verbose运行JVM并监视加载类的位置。

比较运行的运行和不运行的运行之间的日志。

我对JSON遇到同样的问题并不感到惊讶,因为在标题中传递了完全限定的类名。

另外,你的罐子里有独特的包装吗?如果您从不同的罐子中提供相同的包装,您可能会遇到类似的问题。