I am using spring 4.0.0.rc1 websocket endpoint and topic.
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
#ComponentScan(basePackages="org.springframework.samples")
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocketendpoint").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerConfigurer configurer) {
configurer.enableSimpleBroker("/queue/", "/topic/");
}
}
But JMS listener can not post a message on websocket-topic. In this case I am using camel-consume annotation to listen to queue and post message to topic.
#Service
public class WebsocketTopicService implements ApplicationListener<BrokerAvailabilityEvent>
{
private static final Logger log = Logger.getLogger(MessageChannelService.class);
private final MessageSendingOperations<String> messagingTemplate;
private AtomicBoolean brokerAvailable = new AtomicBoolean();
#Autowired
public MessageChannelService(MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#Override
public void onApplicationEvent(BrokerAvailabilityEvent event) {
this.brokerAvailable.set(event.isBrokerAvailable());
}
//#Scheduled(fixedDelay=2000)
#Consume(uri="activemq:queue.out")
public void processMessage(String msg){
if (this.brokerAvailable.get()) {
this.messagingTemplate.convertAndSend("/topic/message.status", msg);
log.info("Sending quote " + msg);
}
}
Any idea why ??
Related
I've a MainHandler class :
#Component
class MainHandler {
//inject this
private Handler handler;
#Autowired
public MainHandler(Handler handler){
this.handler = handler;
}
public void action(String message){
//watch photo
if (message.equals("photo")){
handler.handle();
}
if(message.equals("audio")){
//play music
handler.handle();
}
if(message.equals("video")){
//play video
handler.handle();
}
}
And following other handlers with interface.
Can I inject dependencies with Spring Boot by only interface type handler?
#Component
public interface Handler {
void handle();
}
#Component
class PhotoHandler implements Handler {
public void handle(){
System.out.println("Featuring photo...");
}
}
#Component
class VideoHandler implements Handler {
public void handle(){
System.out.println("Playing video...");
}
}
#Component
class AudioHandler implements Handler {
public void handle(){
System.out.println("Playing music...");
}
}
Or I want to try something like this below. Is it possible ?
class MainHandler {
private VideoHandler videoHandler;
private AudioHandler audioHandler;
private PhotoHandler photoHandler;
#Autowired
public MainHandler(VideoHandler videoHandler,
AudioHandler audioHandler,
PhotoHandler photoHandler) {
this.videoHandler = videoHandler;
this.audioHandler = audioHandler;
this.photoHandler = photoHandler;
}
public void action(String message){
//watch photo
if (message.equals("photo")){
photoHandler.handle();
}
if(message.equals("audio")){
//play music
audioHandler.handle();
}
if(message.equals("video")){
//play video
videoHandler.handle();
}
}
}
So, type of handler depends on user's message. I don't know how Spring can choose which handler gonna be used in this context. Any solution?
There can be multiple solution to this case.
Option #1
You can tweak a design of your handler a bit.
For instance you can introduce a method
boolean canHandle(String message);
so each handler can answer whether passed message can be handled or not.
Then you can inject a list of all handlers into your MainHandler.
private List<Handler> handlers;
Now having that list you can call each handler by message:
public void action(String message) {
handlers.stream()
.filter(h -> h.canHandle(message))
.forEach(handler -> handler.handle());
}
Full example:
#SpringBootApplication
public class SO62370917 {
public static void main(String[] args) {
SpringApplication.run(SO62370917.class, args);
}
#Component
static class MainHandler {
private final List<Handler> handlers;
MainHandler(List<Handler> handlers) {
this.handlers = handlers;
}
public void action(String message) {
handlers.stream()
.filter(h -> h.canHandle(message))
.forEach(Handler::handle);
}
}
#Bean
CommandLineRunner cmd(MainHandler mainHandler) {
return args -> {
mainHandler.action("video");
mainHandler.action("audio");
mainHandler.action("photo");
};
}
interface Handler {
void handle();
boolean canHandle(String message);
}
#Component
class PhotoHandler implements Handler {
public void handle(){
System.out.println("Featuring photo...");
}
#Override
public boolean canHandle(String message) {
return "photo".equals(message);
}
}
#Component
class VideoHandler implements Handler {
public void handle(){
System.out.println("Playing video...");
}
#Override
public boolean canHandle(String message) {
return "video".equals(message);
}
}
#Component
class AudioHandler implements Handler {
public void handle(){
System.out.println("Playing music...");
}
#Override
public boolean canHandle(String message) {
return "audio".equals(message);
}
}
}
Option #2
Use qualifiers.
You can name your handlers however you like and then inject a Map<String, Handler> into your mainHandler. The key would be a bean name and the value - the actual handler. Spring will automatically take care of this.
#SpringBootApplication
public class SO62370917 {
public static void main(String[] args) {
SpringApplication.run(SO62370917.class, args);
}
#Component
static class MainHandler {
private final Map<String, Handler> handlers;
MainHandler(Map<String, Handler> handlers) {
this.handlers = handlers;
}
public void action(String message) {
if (handlers.containsKey(message)) {
handlers.get(message).handle();
}
}
}
#Bean
CommandLineRunner cmd(MainHandler mainHandler) {
return args -> {
mainHandler.action("video");
mainHandler.action("audio");
mainHandler.action("photo");
};
}
interface Handler {
void handle();
}
#Component("photo")
class PhotoHandler implements Handler {
public void handle() {
System.out.println("Featuring photo...");
}
}
#Component("video")
class VideoHandler implements Handler {
public void handle() {
System.out.println("Playing video...");
}
}
#Component("audio")
class AudioHandler implements Handler {
public void handle() {
System.out.println("Playing music...");
}
}
}
The output:
2020-06-14 13:06:47.140 INFO 29447 --- [ main] com.example.demo.SO62370917 : Started SO62370917 in 1.356 seconds (JVM running for 1.795)
Playing video...
Playing music...
Featuring photo...
There are two simple ways in which you can approach :
Recommended : You can use #Qualifier to inject the desired particular bean.
For example
#Component
class MainHandler {
#Autowired
#Qualifier("videoHandler") // example
private Handler handler;
public void action(){
handler.message(); // this will print playing video...
}
}
You can inject the ApplicationContext.
For example :
#Component
class MainHandler {
#Autowired
private ApplicationContext context;
public void action(String message){
//watch photo
if (message.equals("photo")){
((PhotoHandler) context.getBean(PhotoHandler.class)).handle();
}
if(message.equals("audio")){
//play music
((AudioHandler) context.getBean(AudioHandler.class)).handle();
}
if(message.equals("video")){
//play video
((VideoHandler) context.getBean(VideoHandler.class)).handle();
}
}
}
The code below can only be invoked via JMS. Modify it so that it is a web service as well.
#MessageDriven(name = "testMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/test")
})
public class MessageOne implements MessageListener {
public void onMessage(Message message) {
for (int i = 0; i < 1000; i++) {
Process(i);
}
}
private void Process(int i) throws Exception {
Thread.sleep(5000); //This sleep represents really complex code that takes 5 seconds to run and cannot be further optimised
}
}
To send message from REST you can use a Simple message template for simple protocols like stomp or just JmsTemplate:
#Controller
public class MessageOneSender implements ApplicationListener<SessionDisconnectEvent> {
private static final Logger log = LoggerFactory.getLogger(MessageOneSender.class);
private final SimpMessageSendingOperations messagingTemplate;
public MessageOne(SimpMessageSendingOperations messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#MessageMapping("/queue/test")
#SendTo("queue/test")
public MyDTO sendMessage(#Payload MyDTO myDTO, Principal principal) {
// TODO
}
#Override
public void onApplicationEvent(SessionDisconnectEvent event) {
// or this way
messagingTemplate.convertAndSend("queue/test", "message");
}
}
The above code is for receiving a message via rest and passing It to the JMS. To reuse code from your message listener you should probably have to add #JmsListener method, move your code to the #Service and you can call it from rest & from listener method:
#Component
public class MyDTOReceiver {
#Autowired
private MyService service;
#JmsListener(destination = "queue/test", containerFactory = "myFactory")
public void receiveMessage(MyDTO dto) {
System.out.println("Received <" + dto + ">");
service.save(dto);
}
}
#RestController
#RequestMapping("/api")
public class Example {
#Autowired
private JmsTemplate jmsTemplate;
#Autowired
private MyService service;
#PostMapping("/save")
public void save(#RequestBody MyDTO dto) {
service.save(dto);
System.out.println("passing to another queue.");
jmsTemplate.convertAndSend("queue/test2", dto);
}
}
#Service
public class MyService {
public void save(MyDTO dto) {}
}
I have implemented a console application using Spring and WebSockets. The application works fine if one or more participants are connected to the base method which is anotated like this.
#MessageMapping("/chat")
#SendTo("/topic/messages")
I will copy the configuration and the implementation which i have made o be more clear.
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
#Controller
public class ChatController {
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(#Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
#MessageMapping("/chat/{room}")
#SendTo("/topic/messages/{room}")
public OutputMessage enableChatRooms(#DestinationVariable String room, #Payload Message message) {
return new OutputMessage(message.getFrom(), message.getText());
}
}
#Service
public class SessionHandlerService extends StompSessionHandlerAdapter {
private String nickName;
public SessionHandlerService() {
this.nickName = "user";
}
private void sendJsonMessage(StompSession session) {
ClientMessage msg = new ClientMessage(nickName, " new user has logged in.");
session.send("/app/chat", msg);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return ServerMessage.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.err.println(payload.toString());
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/messages", new SessionHandlerService());
sendJsonMessage(session);
}
}
The problem which i face is that when i subscribe to /topic/messages and session.send("/app/chat", msg);everything works fine. But if i choose something like session.send("/app/chat/room1", msg); and /topic/messages/room1 the participans can not see each other messages like they are in different chat rooms.
I'm using this tutorial and I'm trying to figure out how to get the number of current sessions.
My WebSocketConfig looks like this (copy and paste from the tutorial) :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
I'd like to know the number of sessions inside of this class (again copy and paste):
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
Is there an easy way to get the number of current sessions(users, connections) to the websocket?
Edit:
Here is my solution:
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
#EventListener
private void onSessionConnectedEvent(SessionConnectedEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.add(sha.getSessionId());
}
#EventListener
private void onSessionDisconnectEvent(SessionDisconnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
mySet.remove(sha.getSessionId());
}
I can now get the number of Sessions with mySet.size() .
You can use SimpUserRegistry and its getUserCount() method instead of handling connections manually.
Example:
#Autowired
private SimpUserRegistry simpUserRegistry;
public int getNumberOfSessions() {
return simpUserRegistry.getUserCount();
}
You can use ApplicationContext events. Every connection, subscription or other action will fire a special event: SessionConnectEvent, SessionConnectedEvent, SessionSubscribeEvent and so on.
Full doc is here. When one of these events fires, you can handle it with your own logic.
Sample code for reference:
#EventListener(SessionConnectEvent.class)
public void handleWebsocketConnectListner(SessionConnectEvent event) {
logger.info("Received a new web socket connection : " + now());
}
#EventListener(SessionDisconnectEvent.class)
public void handleWebsocketDisconnectListner(SessionDisconnectEvent event) {
logger.info("session closed : " + now());
}
.
.
.
#Autowired
private MessageSendingOperations<String> messagingTemplate;
#Scheduled(fixedDelay = 5000)
public void sendMessage(){
messagingTemplate.convertAndSend("/topic/data","hello");
}
}
The above works. but
sendMessage();
public void sendMessage(){
messagingTemplate.convertAndSend("/topic/data", "hello");
}
}
doesn't work, it got java.lang.NullPointerException
Does that mean I could only use messagingTemplate.convertAndSend(...) inside a function annotated with #Scheduled?
Please help...
#Deendayal Garg, thanks for your help, I did this this according to your advice:
public class Communicator implements ApplicationListener {
private final MessageSendingOperations<String> messagingTemplate;
#Autowired
public Communicator(final MessageSendingOperations<String> messagingTemplate){
this.messagingTemplate = messagingTemplate;
}
public void onApplicationEvent(BrokerAvailabilityEvent event) {
// TODO Auto-generated method stub
}
public void sendMessage(Message data) {
this.messagingTemplate.convertAndSend("/topic/data",data.getBody());
System.out.println(new Random().nextInt(100));
}
}
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(
final DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public Communicator communicator(){
Communicator comm = new Communicator();
return comm;
}
}
Then I injected the bean in the class below
public class StepExecListener implements StepExecutionListener, ApplicationListener<BrokerAvailabilityEvent>{
#Autowired
Communicator communicator;
Message msg = new Message();
msg.setBody("Twale!");
msg.setType("test");
communicator.sendDataUpdates(msg);
}
But I still got it got java.lang.NullPointerException after calling communicator.sendDataUpdates(msg);