spring-attic / spring-native

Spring Native is now superseded by Spring Boot 3 official native support
https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html
Apache License 2.0
2.74k stars 356 forks source link

Add support and sample for spring-boot-starter-activemq #438

Closed mlakshminara closed 1 year ago

mlakshminara commented 3 years ago

I took the Spring Pet Clinic JPA sample application and added enhancement to publish/subscribe JMS messages to ActiveMQ. I have used native-image-maven-plugin to build the native image. Image construction works properly and when I start the application, application crashes and gives the below error.

In fact I tried analysing using agent but it crashed too. I wanted to know do we have support for JMS in native image.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'healthEndpointGroupsBeanPostProcessor' defined in class path resource [org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.class]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': Initialization of bean failed; nested exception is org.springframework.core.annotation.AnnotationConfigurationException: Invalid declaration of container type [org.springframework.jms.annotation.JmsListeners] for repeatable annotation [org.springframework.jms.annotation.JmsListener]; nested exception is java.lang.NoSuchMethodException: No value method found at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:526) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[na:na] at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:244) ~[na:na] at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:767) ~[na:na] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:572) ~[na:na] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144) ~[na:na] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) [com.bestminds.petclinic.petcliniccustomernativeapp:2.4.1] at com.bestminds.petclinic.PetClinicCustomerNativeApp.main(PetClinicCustomerNativeApp.java:16) [com.bestminds.petclinic.petcliniccustomernativeapp:1.0.0] Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': Initialization of bean failed; nested exception is org.springframework.core.annotation.AnnotationConfigurationException: Invalid declaration of container type [org.springframework.jms.annotation.JmsListeners] for repeatable annotation [org.springframework.jms.annotation.JmsListener]; nested exception is java.lang.NoSuchMethodException: No value method found at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:617) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[na:na] at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:410) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1179) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:571) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[na:na] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[na:na] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[na:na] at org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans(BeanFactoryAdvisorRetrievalHelper.java:91) ~[na:na] at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findCandidateAdvisors(AbstractAdvisorAutoProxyCreator.java:111) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors(AnnotationAwareAspectJAutoProxyCreator.java:92) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(AspectJAwareAdvisorAutoProxyCreator.java:101) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessBeforeInstantiation(AbstractAutoProxyCreator.java:251) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1144) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1119) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[na:na] ... 15 common frames omitted Caused by: org.springframework.core.annotation.AnnotationConfigurationException: Invalid declaration of container type [org.springframework.jms.annotation.JmsListeners] for repeatable annotation [org.springframework.jms.annotation.JmsListener]; nested exception is java.lang.NoSuchMethodException: No value method found at org.springframework.core.annotation.RepeatableContainers$ExplicitRepeatableContainer.(RepeatableContainers.java:219) ~[na:na] at org.springframework.core.annotation.RepeatableContainers.of(RepeatableContainers.java:117) ~[na:na] at org.springframework.core.annotation.AnnotatedElementUtils.getRepeatableAnnotations(AnnotatedElementUtils.java:759) ~[na:na] at org.springframework.core.annotation.AnnotatedElementUtils.getMergedRepeatableAnnotations(AnnotatedElementUtils.java:455) ~[na:na] at org.springframework.jms.annotation.JmsListenerAnnotationBeanPostProcessor.lambda$postProcessAfterInitialization$0(JmsListenerAnnotationBeanPostProcessor.java:236) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.core.MethodIntrospector.lambda$selectMethods$0(MethodIntrospector.java:74) ~[na:na] at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:364) ~[na:na] at org.springframework.core.MethodIntrospector.selectMethods(MethodIntrospector.java:72) ~[na:na] at org.springframework.jms.annotation.JmsListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(JmsListenerAnnotationBeanPostProcessor.java:234) ~[com.bestminds.petclinic.petcliniccustomernativeapp:5.3.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:444) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:609) ~[na:na] ... 37 common frames omitted Caused by: java.lang.NoSuchMethodException: No value method found at org.springframework.core.annotation.RepeatableContainers$ExplicitRepeatableContainer.(RepeatableContainers.java:203) ~[na:na] ... 48 common frames omitted

I tried analysing using agent but that also crashed as given below:

A fatal error has been detected by the Java Runtime Environment: SIGSEGV (0xb) at pc=0x00007f48def8b48a, pid=955940, tid=0x00007f48e42c1700 JRE version: OpenJDK Runtime Environment (8.0_272-b10) (build 1.8.0_272-b10) Java VM: OpenJDK 64-Bit Server VM GraalVM CE 20.3.0 (25.272-b10-jvmci-20.3-b06 mixed mode linux-amd64 compressed oops) Problematic frame: C [libnative-image-agent.so+0x5248a] Core dump written. Default location: /opt/petclinic-customers-native-service/customers-native-api/core or core.955940 An error report file with more information is saved as: /opt/petclinic-customers-native-service/customers-native-api/hs_err_pid955940.log If you would like to submit a bug report, please visit: https://github.com/oracle/graal/issues The crash happened outside the Java Virtual Machine in native code. See problematic frame for where to report the bug. Aborted (core dumped)

mlakshminara commented 3 years ago

The above error occurs when you include spring-boot-starter-activemq dependency in your POM, even without creating JMS listener.

aclement commented 3 years ago

There are no jms samples so no guarantees it will work without extra configuration. so attempting to collect any missing config with the agent is the right idea. I am most surprised to see the agent crash it. I would recommend trying with the Java 11 20.3 GraalVM rather than Java8. I will need to try a simpler sample, maybe https://spring.io/guides/gs/messaging-jms/ is a good starting point. Hope it represents your use case.

mlakshminara commented 3 years ago

Hi, I was able to start the agent with Java 11 & GraalVM v20.3 without error and see attached reflect-config.txt file for the list of java classes to add in reflect-config.json reflect-config.txt

During application load, I also found that @Autowired and @Value annotations doesn't work in @Configuration class. So if you have to read properties you need to implement EnvironmentAware. Sample JmsTemplate config is as given below

@Configuration
public class JmsTemplateConfig implements EnvironmentAware {
    private Environment env;

    @Bean
    public JmsTemplate jmsTemplate() {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(Include.NON_NULL);
        objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // create an Jackson Json Mapping Converter
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        converter.setTargetType(MessageType.TEXT);
        converter.setObjectMapper(objectMapper);

        //create an instance of ActiveMQ connection factory
        String brokerUrl = env.getProperty("broker-url");
        log.info("The ActiveMQ Url provided is: {}", brokerUrl);
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);

        // create instance of JmsTemplate
        JmsTemplate jmsTemplate = new JmsTemplate();
        jmsTemplate.setConnectionFactory(connectionFactory);
        jmsTemplate.setMessageConverter(converter);
        jmsTemplate.setExplicitQosEnabled(true);
        jmsTemplate.setDeliveryMode(2);
        return jmsTemplate;
    }
}

I also encountered errors in MappingJackson2MessageConverter and you need to have reflection for Jackson API's. Finally was able to start the application.

While publishing message to ActiveMQ at runtime, I have configured to convert POJO to JSON object using MappingJackson2MessageConverter and right now am encountering com.fasterxml.jackson.databind.exc.InvalidDefinitionException error when I invoke jmsTemplate.convertAndSend method during runtime. A sample publisher is as given below:

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void publish(String eventCode, String destinationName, Event payload) {
    jmsTemplate.convertAndSend(destinationName, event, new MessagePostProcessor() {
      @Override
      public Message postProcessMessage(Message message) throws JMSException
      {
      message.setJMSCorrelationID(event.getId());
      message.setStringProperty("event_code", event.getCode());
      return message;
       }
    });
log.info ("Successfully published message with correlation id: {}", event.getId());
}

The error stack-trace is

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.bestminds.petclinic.domain.Event and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
        at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1277)
        at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
        at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
        at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
        at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1516)
        at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1217)
        at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1059)
        at org.springframework.jms.support.converter.MappingJackson2MessageConverter.mapToTextMessage(MappingJackson2MessageConverter.java:279)
        at org.springframework.jms.support.converter.MappingJackson2MessageConverter.toMessage(MappingJackson2MessageConverter.java:184)
        at org.springframework.jms.core.JmsTemplate.lambda$convertAndSend$7(JmsTemplate.java:692)
        at org.springframework.jms.core.JmsTemplate.doSend(JmsTemplate.java:604)
        at org.springframework.jms.core.JmsTemplate.lambda$send$3(JmsTemplate.java:586)
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:504)
        at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:584)
        at org.springframework.jms.core.JmsTemplate.convertAndSend(JmsTemplate.java:691)
        at com.bestminds.petclinic.publisher.ActiveMqPublisher.publish(ActiveMqPublisher.java:49)
        at java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
        at com.sun.proxy.$Proxy347.publish(Unknown Source)
        at com.bestminds.petclinic.service.OwnerServiceImpl.save(OwnerServiceImpl.java:47)

Any clue to solve this error ?

mlakshminara commented 3 years ago

I was able to get the publishing working, solved the above error by adding the POJO class which one will pass as an argument to jmsTemplate.convertAndSend method in reflect-config.json

Micronaut has @Introspected annotation. Do Spring is planning to introduce to generate introspection classes at compile time

mlakshminara commented 3 years ago

The reflect-config configuration also works for consumers. sample DMLC configuration and Listener is as given below:

@Configuration
public class DmlcConfig implements EnvironmentAware {

    private Environment env;

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory()
    {
        //create an instance of ActiveMQ connection factory
    String brokerUrl = env.getProperty("broker-url");
    log.info("The ActiveMQ Url provided for DMLC configuration is: {}", brokerUrl);
    ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);

    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrency("1-20");
        factory.setSessionTransacted(true);
        factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
        return factory;
    }

    @Override
    public void setEnvironment(Environment environment) { 
        this.env = environment;
    }
}

Sample JMS Listener: Need to add the Java Class in reflect-config.json

@Component
public class SampleListener {

    @JmsListener(destination = "test_queue")
    public void onMessage (final Message jsonMessage) {
        try
        {
         if (jsonMessage instanceof TextMessage)
            {
        // Retrieves the message from ActiveMQ
        // Read the data attribute in event as Map of key/value pairs
                String messageData = ((TextMessage)jsonMessage).getText();

                // Reads the messages from ActiveMQ
                final Event event = objectMapper.readValue(messageData, Event.class);
                log.info ("The event object obtained is: {}", event.toString());
            }
        } 
    catch (final JMSException exception) {
            throw new RuntimeException (exception);
        }
    }
}
sdeleuze commented 3 years ago

@mlakshminara You can use native hints if you need to add native configuration is a Spring way.

ch4mpy commented 2 years ago

I'ma lso trying to have JmsTemplate work in a native image. Here are the hints I added to now:

@NativeHint(
jdkProxies = {
    @JdkProxyHint(types = { org.springframework.jms.annotation.JmsListeners.class }),
})
@TypeHint(types = { org.apache.activemq.ActiveMQConnectionFactory.class })

And here is the issue I'm stuck on: "org.springframework.jms.UncategorizedJmsException: Uncategorized exception occurred during JMS processing; nested exception is javax.jms.JMSException: Could not create Transport. Reason: java.io.IOException: Transport scheme NOT recognized: [tcp]"

sdeleuze commented 1 year ago

Spring Native is now superseded by Spring Boot 3 official native support, see the related reference documentation for more details.

As a consequence, I am closing this issue, and recommend trying your use case with latest Spring Boot 3 version. If you still experience the issue reported here, please open an issue directly on the related Spring project (Spring Framework, Data, Security, Boot, Cloud, etc.) with a reproducer.

Thanks for your contribution on the experimental Spring Native project, we hope you will enjoy the official native support introduced by Spring Boot 3.