Search code examples
javaspring-bootwebsocketkotlinstomp

How to send from server arbitrarily to a STOMP end point in SpringBoot


I'm writing a bridge application in SpringBoot two bridge an internal messaging protocol over to a websocket based stomp setup. I'm operating in a mixed-mode Kotlin, java project (which "might" be making things difficult).

My goal is when I receive a message on our internal messaging queue, i wanted to send it over to the STOMP endpoints.

I can send from STOMP->STOMP and REST->STOMP but I seem unable to make the server send directly to a stomp endpoint correclty.

I've gone through just about every single stack post - and I see people have had similar issues, yet, I'm unable to actually get any of the listed solutions working.

From what I gather I need to add:

java

@Autowired
private SimpMessagingTemplate template;

kotlin

@Autowired
lateinit var template: SimpleMessagingTemplate

into one of my classes and either add @Controller or @Service to the top of the class, possibly making it open in kotlin

Attempts (java):

@Controller
public class STOMPDataListener implements SDDFDataListener {

    @Autowired
    private SimpMessagingTemplate template;


    public void handleData(String source, SDDFCommonData data) {
        System.out.println("Received: " + data);
    }
}

If I add a breakpoint on my handleData call I find out that:

template = null

Attempt (kotlin)

@Controller
class BridgeDataListener(val peerID: String = "default") : SDDFDataListener {

    @Autowired
    lateinit var template: SimpMessagingTemplate


    override fun handleData(source: String, data: SDDFCommonData) {
        println("Received: $data")
    }
}

lateinit property template has not been initialized enter image description here

If I mark it as

@Service
class BridgeDataListener(val peerID: String = "default") : SDDFDataListener {

I get the same error.

I know this is possible because in the Docs: https://docs.spring.io/spring/docs/4.0.1.RELEASE/spring-framework-reference/html/websocket.html they give an example, however, I'm not sure how I could use this example directly because I need to construct multiple versions of my DataListeners - and from what I understand an @Autowired class you get one copy of only? Could I make it a singleton or would that break something?

@Controller
public class GreetingController {

    private SimpMessagingTemplate template;

    @Autowired
    public GreetingController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @RequestMapping(value="/greetings", method=POST)
    public void greet(String greeting) {
        String text = "[" + getTimestamp() + "]:" + greeting;
        this.template.convertAndSend("/topic/greetings", text);
    }

}

Assistance is greatly appreciated.


Solution

  • So It looks like I ended up solving my issue.

    @Autowired does not work if you instantiate a class with new. As I was using some libraries I had to follow approach #3 from https://stackoverflow.com/a/19896871/2069812

    So I built a java class:

    /**
     * See: https://stackoverflow.com/a/19896871/2069812
     */
    @Component
    public class ApplicationContextHolder implements ApplicationContextAware {
        private static ApplicationContext context;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            context = applicationContext;
        }
    
        public static ApplicationContext getContext() {
            return context;
        }
    }
    

    And then in my data handler class I accessed the bean via:

    var template: SimpMessagingTemplate = ApplicationContextHolder.getContext().getBean(SimpMessagingTemplate::class.java)

    which allowed me to call:

     template.convertAndSend("/topic/sddf/$source", json.toString())
     template.convertAndSend("/topic/sddf/$source/$peerID", json.toString())