2017-01-30 26 views
0

我使用spring-spring和spring-amqp和RabbitMQ在本地運行的兩個JVM之間發送消息。根據我開始每個應用的順序,我有時會得到一個ClassNotFoundException。我有一個像這樣的多項目設置:Spring-rabbit中的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

下面是我使用的相關依存關係:

  • 春季啓動v1.3.8.RELEASE
  • 春AMQP v1.5.6.RELEASE
  • 春兔v1.5.6.RELEASE

回答

1

這很可能是某種Classloader問題 - 也許你在某種程度上有兩個版本的類。

我發現調試類似這樣的問題的最簡單方法是使用-verbose運行JVM並監視從何處加載類。

比較工作的運行和不運行的運行之間的日誌。

我不驚訝你得到與JSON相同的問題,因爲完全限定的類名稱傳遞在標題中。

另外,你有你的罐子獨特的包?如果您從不同的罐子提供相同的包裝,您可以獲得類似的問題。

+0

當它工作時,'-verbose'告訴我在我的工作區的Gradle構建輸出中的JAR中找到了該類:[從文件加載com.example.event.ClientAvailableEvent:/ Users/nick/dev/tmp /spring-amqp-project/common/build/libs/common-0.0.1-SNAPSHOT.jar。這對我來說似乎是合乎邏輯的,因爲班級也在我的工作空間中,但我不明白爲什麼在第一次運行時找不到它。當我引導運行客戶端時,可能Gradle覆蓋它,因爲它們在同一個項目中,或者類似的事情發生在JAR上。 – nickb

+0

這可能是它的原因 - 嘗試從引導超級jar而不是'bootRun'運行,'Spring Boot Gradle插件還包含一個bootRun任務,它可以用來以爆炸形式運行你的應用程序。 –

+0

從超級JAR運行很好。終於找到它了。我的Gradle構建中有一項任務在':common:jar'之前運行,導致JAR任務始終過時,因此每個構建都會導致重建common.jar。無論出於何種原因,一旦JAR被覆蓋,即使它具有相同的類,JVM也不會從它加載任何類。 – nickb