5

I am trying out simple sender and receiving of messages using Spring AMQP with jackson2JsonMessageConverter. Also, what is the significance of _TypeId_ here why it is showing sender package with class name? I am facing issues in receiving the message.

Below is my configuration

org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [org.springframework.amqp.helloworld.User] at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:121) at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.toJavaType(DefaultJackson2JavaTypeMapper.java:90) at org.springframework.amqp.support.converter.Jackson2JsonMessageConverter.fromMessage(Jackson2JsonMessageConverter.java:145) at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:236) at org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter.onMessage(MessageListenerAdapter.java:288) at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:777) ... 10 common frames omitted Caused by: java.lang.ClassNotFoundException: org.springframework.amqp.helloworld.User at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1305) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139) at org.springframework.util.ClassUtils.forName(ClassUtils.java:250) at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:118) ... 15 common frames omitted

XML Configuration

            <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
            xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
            xmlns:rabbit="http://www.springframework.org/schema/rabbit"
            xsi:schemaLocation="http://www.springframework.org/schema/rabbit
             http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
             http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
             http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd  
             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
             http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">


            <rabbit:connection-factory id="connectionFactory"
                        channel-cache-size="25" host="10.165.18.29" username="BipUser"
                        password="bip" />

            <rabbit:queue name="Job Queue"></rabbit:queue>

            <rabbit:queue name="Input Queue"></rabbit:queue>

            <rabbit:queue name="More Info Queue"></rabbit:queue>

            <rabbit:queue name="Adaptor O/P Queue"></rabbit:queue>

            <rabbit:queue name="Command Queue"></rabbit:queue>

            <rabbit:queue name="Error Queue"></rabbit:queue>

            <bean id="simpleMessageConverter"
                        class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
            </bean>

            <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
                        message-converter="jsonConverterWithDefaultType" />

            <rabbit:listener-container
                        connection-factory="connectionFactory" auto-declare="true"
                        message-converter="simpleMessageConverter" auto-startup="true"
                        acknowledge="auto">
                        <rabbit:listener ref="rabbitMQJobListener"
                                    queue-names="Job Queue" priority="10" />

            </rabbit:listener-container>

            <rabbit:admin connection-factory="connectionFactory" id="amqpAdmin" />

            <bean id="rabbitMQJobListener" class="com.bosch.bip.rabbitmq.consumer.RabbitMQJobListener">
            </bean>

            <rabbit:annotation-driven container-factory="rabbitListenerContainerFactory" />

            <bean id="rabbitListenerContainerFactory"
                        class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
                        <property name="connectionFactory" ref="connectionFactory"></property>
                        <property name="messageConverter" ref="jsonConverterWithDefaultType"></property>
            </bean>

            <bean id="jsonConverterWithDefaultType"
                        class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
                        <property name="classMapper">
                                    <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
                                    </bean>
                        </property>
            </bean>
</beans>

Sender

package org.springframework.amqp.helloworld;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.DefaultClassMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class Sender {


            public static void main(String[] args) {

                        ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
                        User user=new User();
                        user.setPassword("welcome");
                        user.setUserName("welcome");
                        user.setXml("myxml");
                        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

                        Jackson2JsonMessageConverter converter = context.getBean(Jackson2JsonMessageConverter.class);
                        MessageProperties properties = new MessageProperties();
                        properties.setHeader("user", "user");
                        properties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
                        Message message = converter.toMessage(user, properties);

                        System.out.println(message);



                        rabbitTemplate.send(message);
            }

            /* @RabbitListener(queues = HelloWorldConfiguration.helloWorldQueueName)
              public void handleMessage(User user) {
               System.out.println("User Values::::::::"+user.getPassword());
              }*/
}

Consumer

package com.bip.rabbitmq.consumer;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import com.bip.entity.User;

@EnableRabbit
@Component
public class RabbitMQJobListener {


            @RabbitListener(queues="Job Queue")
            public void onMessage(User message) {
                        System.out.println(new String(message.getPassword()));

            }
}

RabbitMQ

Exchange    (AMQP default)
Routing Key Job Queue
Redelivered ○
Properties  
priority:   0
delivery_mode:  2
headers:    
user:   user
__TypeId__: org.springframework.amqp.helloworld.User
content_encoding:   UTF-8
content_type:   application/json
Payload
57 bytes
Encoding: string
{"userName":"welcome","password":"welcome","xml":"myxml"}

2 Answers 2

10

The _TypeID_ header is set on outbound to tell the inbound what class to convert the JSON to. If you want to convert to a different class that is type-compatible with the JSON, you have to configure the converter.

If it'a always the same class, use a custom ClassMapper (not the default one).

Or, see this test, its listener and its configuration to see how to configure a different typeid mapping.

Sign up to request clarification or add additional context in comments.

3 Comments

Regarding the deleted "answer" below - the configuration link in my answer shows how to map the type id to a different class - of course, the target class must be compatible with the source class (field names etc). idClassMapping.put("com.xyz.Foo", com.abc.Foo.class);
Why is the exception not thrown if I remove @Valid? In this case the _TypeID_ is not used.
Coincidentally, we just fixed that today and it will be available in the next releases December 9th. As of now, if you add other annotations, such as @Valid, you need to explicitly add @Payload as well. github.com/spring-projects/spring-amqp/pull/1275
5

This might happen when the package name of the serialized instance is different than the consumer's model, represented by the headers: TypeId.

I believe following example will make things lot clearer.

Schema: Exchange x.invoice of type fanout is bound to queue q.invoice.

Producer: We are sending JSON message with type Id com.example.produceronequeuemultipletypes.model.InvoiceCreatedMessage. Class ParseConfig is to help us avoid manual serialization of the instance to String.

public void sendInvoiceMessages() {
    invoiceCreatedMessage.setId(0);
    invoiceCreatedMessage.setType("Invoice Created");
    rabbitTemplate.convertAndSend("x.invoice", "", invoiceCreatedMessage);   
}

class InvoiceCreatedMessage {
    private String type;
    private int id;
}

@Configuration
class ParseConfig {
    @Bean
    public ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public Jackson2JsonMessageConverter getConverter(
            @Autowired ObjectMapper objectMapper) {
        return new Jackson2JsonMessageConverter(objectMapper);
    }
}

Consumer: Create a class mapper bean with mapping from "com.example.produceronequeuemultipletypes.model.InvoiceCreated" to InvoiceCreated.class.

@Slf4j
@Service
public class InvoiceConsumer {
    @RabbitListener(queues = "q.invoice")
    public void handleInvoiceCreated(
            InvoiceCreatedMessage invoiceCreatedMessage) {
        log.info("[Created] Invoice " + invoiceCreatedMessage);
    }
}

@Configuration
class ParseConfig {
    @Bean
    public ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public Jackson2JsonMessageConverter getConverter(
            @Autowired ObjectMapper objectMapper) {
        Jackson2JsonMessageConverter messageConverter =
        new Jackson2JsonMessageConverter(objectMapper);
        messageConverter.setClassMapper(getClassMapper());
        return messageConverter;
    }

    @Bean
    public DefaultClassMapper getClassMapper() {
        DefaultClassMapper classMapper = new DefaultClassMapper();
        Map<String, Class<?>> map = new HashMap<>();
        map.put(
        "com.example.produceronequeuemultipletypes.model." + 
        "InvoiceCreatedMessage",
        InvoiceCreatedMessage.class)
        classMapper.setIdClassMapping(idClassMapping);
        return classMapper;
    }
}

class InvoiceCreatedMessage {
    private String type;
    private int id;
}

Reference:

  1. https://docs.spring.io/spring-amqp/reference/html/#json-message-converter
  2. https://www.udemy.com/course/rabbitmq-java-spring-boot-for-system-integration/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.