0

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.

Jeef
  • 26,861
  • 21
  • 78
  • 156
  • How is the `handleData` called i.e. how is the instance of `STOMPDataListener`/`BridgeDataListener` created? "Normally" when Spring cannot bind to a field an exception is thrown. Since it's not the case for you I suspect the instance is not created by Spring. – miensol Mar 19 '18 at 19:48

1 Answers1

1

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())
Jeef
  • 26,861
  • 21
  • 78
  • 156