spring-bootjdbckotlinspring-amqpspring-remoting

Observing argument type mismatch Spring Amqp Remoting With Kotlin


Though there is exactly 1 parameter for Service i am always hitting argument mismatch when using spring with kotlin combination. i have also debugged org.springframework.remoting.support.RemoteInvocation.invoke method i could see it is passing exactly 1 argument and correct targetObject. But invoke(targetObject, this.arguments); resulted in parameter mismatch.

Source Code:

https://github.com/c-nnooka/RabbitMqRemotingKotlin

Exception

Caused by: **java.lang.Throwable: argument type mismatch**
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_161]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_161]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_161]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_161]
    at org.springframework.remoting.support.RemoteInvocation.invoke(RemoteInvocation.java:215) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.remoting.support.DefaultRemoteInvocationExecutor.invoke(DefaultRemoteInvocationExecutor.java:39) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.remoting.support.RemoteInvocationBasedExporter.invoke(RemoteInvocationBasedExporter.java:78) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.remoting.support.RemoteInvocationBasedExporter.invokeAndCreateResult(RemoteInvocationBasedExporter.java:114) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter.onMessage(AmqpInvokerServiceExporter.java:80) ~[spring-amqp-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1457) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1348) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1324) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1303) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:785) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:769) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:77) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1010) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_161]
    at org.springframework.remoting.support.RemoteInvocationUtils.fillInClientStackTraceIfPossible(RemoteInvocationUtils.java:45) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.remoting.support.RemoteInvocationResult.recreate(RemoteInvocationResult.java:156) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.amqp.remoting.client.AmqpClientInterceptor.invoke(AmqpClientInterceptor.java:78) ~[spring-amqp-2.0.2.RELEASE.jar:2.0.2.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at com.sun.proxy.$Proxy108.findJobById(Unknown Source) ~[?:?]

Interface Details:

interface IJobQueryService{

    fun findJobById(args : QueryJobArgs) : QueryJobResponse

}

interface IJobService : IJobQueryService

Service Implementation Details:

class JobController(val jobQueryService: IJobQueryService) : IJobService{


    override fun findJobById(args: QueryJobArgs): QueryJobResponse {
        return jobQueryService.findJobById(args)
    }


}

BeanConfiguration: (Note: Not specifying complete configuration for simplicity)

    @Bean
    fun rmsExporter(rabbitTemplate: RabbitTemplate):AmqpInvokerServiceExporter {
        val exporter = AmqpInvokerServiceExporter();
        exporter.amqpTemplate = rabbitTemplate;
        exporter.service = JobController(JobQueryProvider());
        exporter.serviceInterface = IJobService::class.java
        exporter.messageConverter = producerJackson2MessageConverter()
        return exporter;
    }

    @Bean
    fun rmxProxy(rabbitTemplate: RabbitTemplate) : AmqpProxyFactoryBean {
        val proxy = AmqpProxyFactoryBean();
        proxy.amqpTemplate = rabbitTemplate;
        proxy.serviceInterface = IJobService::class.java
        proxy.routingKey = "rms.webservice.api"
        return proxy;
    }

Invocation Details

val jobservice  = applicationContext.getBean(IJobService::class.java)

println("Remoting Is On => " +  jobservice.findJobById(QueryJobArgs().apply { job = Job().apply { id = 1 } } ))

Solution

  • The problem that you try to transport JSON over the network.

    Therefore an AmqpClientInterceptor wraps your QueryJobArgs into the RemoteInvocation object and already that one is serialized to the JSON like:

    (Body:'{"methodName":"findJobById","parameterTypes":["com.example.rabbiitmqremoting.args.job.QueryJobArgs"],"arguments":[{"job":{"id":1,"createdBy":null}}],"attributes":null}' MessageProperties [headers={__TypeId__=org.springframework.remoting.support.RemoteInvocation}, contentType=application/json, contentEncoding=UTF-8, contentLength=167, deliveryMode=PERSISTENT, priority=0, deliveryTag=0])
    

    Pay attention to the __TypeId__=org.springframework.remoting.support.RemoteInvocation header. This one is used on the consumer side to deserialize a content to the RemoteInvocation. But since there is no an (automatic) info for the arguments to be deseriailized to the QueryJobArgs they remains as a LinkedHashMap. Therefore Caused by: java.lang.Throwable: argument type mismatch.

    As a workaround I suggest you to come back to the SimpleMessageConverter which will use a standard Java serialization mechanism.

    UPDATE

    OK! You know I've hacked this with the JSON as well.

    So, I did this:

    @Autowired
    lateinit var objectMapper: ObjectMapper
    
    @PostConstruct
    fun init() {
        this.objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
    }
    

    And injected this objectMapper into all the Jackson converters presented in your RabbitConfig. Now it works as expected I assume:

    Hellow From RMS
    Remoting Is On => com.example.rabbiitmqremoting.response.QueryJobResponse@151bf776
    

    Also I removed all the modifications related to the messageHandlerMethodFactory. Doesn't look like it is involved in the RPC. However you may need it for some other stuff. Different story though...