WebSockets JSR 356 Spring integration #ServerEndpoint - spring

Problem: #Autowired beans in #ServerEndpoint class are null
How can I make sure that this WebSocketController class below will be injected with beans, that is how can I make it managed by Spring? I can connect to the websocket so it works but gameService is always null inside the WebSocketController class instance, so I think that it is created by tomcat somehow and not Spring.
I'm using Spring boot. I just need to figure out how to inject beans into this websocket controller class.
WebSocketController class
#Component
#ServerEndpoint("/sock")
public class WebSocketController {
#Autowired
private GameService gameService;
private static Set<Session> clients = Collections.synchronizedSet(new HashSet<Session>());
#OnMessage
public void handleMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(
"Reversed: " + new StringBuilder(message).reverse());
}
#OnOpen
public void onOpen(Session session) {
clients.add(session);
System.out.println("New client #"+session.getId());
if (gameService == null) System.out.println("game service null");
}
#OnClose
public void onClose(Session session) {
clients.remove(session);
System.out.println("Client disconnected #" + session.getId());
}
}
GameService interface and implementation
public interface GameService {
List<Character> getCharacters();
}
#Service
public class GameServiceMockImpl implements GameService {
#Override
public List<Character> getCharacters() {
List<Character> list = new ArrayList<>();
list.add(new Character("aaa","1.png",100));
list.add(new Character("aaa","2.jpg",100));
list.add(new Character("aaa","3.jpg",100));
return list;
}
}
Application class
#SpringBootApplication
public class App {
public static void main(String args[]){
SpringApplication.run(App.class,args);
}
#Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
EDIT:
Using Spring 4 WebSockets doesn't work at all, I can't even connect via a browser.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
#Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
public class MyHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
System.out.println(message.getPayload());
}
}

You are trying to integrate Spring and Java WebSocket API. A class annotated by #Component is registered to a spring bean and its instance is managed by spring but if a class is annotated by #ServerEndpoint it is registered to a server-side WebSocket endpoint and every time the corresponding endpoint's WebSocket is connected to the server, its instance is created and managed by JWA implementation. We you can't use both annotations together.
Either you can use CDI injection(your server should also support)
#ServerEndpoint("/sock")
public class WebSocketController {
#Inject
private GameService gameService;
Or have a look on this doc, Spring 4 has support for WebSocket

Maybe this article can help:
https://spring.io/blog/2013/05/23/spring-framework-4-0-m1-websocket-support
You can use dependency (with spring version > 4)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
And then simply
#ServerEndpoint(value = "/echo", configurator = SpringConfigurator.class)
public class WebSocketEndpoint {
#Inject
private BroadcastService broadcaster;

Related

Spring get open websocket connections

I am using Spring Boot Websocket to enable my Spring Boot 2 microservice to deal with websocket connections.
My config:
#Configuration
#EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
#Autowired
WebSocketHandler webSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler();
handshakeHandler.setSupportedProtocols(HANDSHAKE_PROTOCOL);
registry.addHandler(webSocketHandler, WS_HANDLER_PATH + WILDCARD)
.setAllowedOrigins("*")
.setHandshakeHandler(handshakeHandler);
}
}
My clients are able to connect to my Service via Websocket. I am implementing WebSocketHandler interface to handle messages and log connections.
Now my question: Is there a way to show all current websocket users/sessions?
I was trying to use the SimpUserRegistry:
#Configuration
public class UserConfig {
final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
#Bean
public SimpUserRegistry userRegistry() {
return userRegistry;
}
}
and to show the users via a REST endpoint
#RestController
public class WebSocketManager {
private final SimpUserRegistry userRegistry;
public WebSocketManager(SimpUserRegistry userRegistry) {
this.userRegistry = userRegistry;
}
#GetMapping(path = "/users")
public List<String> getConnectedUsers() {
userRegistry.getUsers().stream()
.map(SimpUser::getName)
.forEach(System.out::println);
System.out.println("Users " + userRegistry.getUsers());
System.out.println("UsersCount " + userRegistry.getUserCount());
return this.userRegistry
.getUsers()
.stream()
.map(SimpUser::getName)
.collect(Collectors.toList());
}
}
But this always gives my an empty list: [] even when obviously WS connections are established.
Is this SimpUserRegistry working with the Websocket system of Spring which is configured with the WebSocketConfigurer and #EnableWebSocket? What am I doing wrong? Any tips or alternatives?
Thank you in advance!

Injecting dependency inside AbstractWebSocketHandler

How to inject a dependency inside a Web Socket handler:
public class WebsocketHandler extends AbstractWebSocketHandler {
#Autowired
GreetingMap greetingMap;
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
// NullPointerException here
String greeting = greetingMap.getSampleGreetings().get("hello") + " " + message.getPayload();
session.sendMessage(new TextMessage(greeting));
}
}
The code above throws NullPointerException
What could be missing here?
Try using dependency injection with constructor instead #Autowired:
private GreetingMap greetingMap;
public WebsocketHandler(GreetingMap greetingMap){
this.greetingMap = greetingMap
}
I think the problem is that SocketHanler is not a spring bean, but is created by "new" operator:
#Configuration
#EnableWebSocket
public class WebSocketsConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(), "/socket")
.setAllowedOrigins("*");
}
}
What you need to do in this case, is to inject your dependency into WebSocketConfiguration and pass it manually to SocketHandler constructor:
#Configuration
#EnableWebSocket
public class WebSocketsConfiguration implements WebSocketConfigurer {
#Autowired
MyDependency myDependency;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(myDependency), "/socket")
.setAllowedOrigins("*");
}
}
And in the handler, you need to add constructor that receives the dependency
public class SocketHandler extends AbstractWebSocketHandler {
private MyDependency myDependency;
public SocketHandler(MyDependency myDependency) {
this.myDependency = myDependency;
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
System.out.println(String.format("Message from client: %s", message));
}
}

spring boot how to unregister guava eventbus listener?

there's a spring boot application. and I create an event like UserCreateEvent and have a listener UserCreateListener
event:
public class UserCreateEvent {
private Long userId;
}
listener:
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
#Subscribe
public void onUserCreate(UserCreateEvent event) {
Long userId = event.getUserId();
// todo something necessary
}
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
now, I want to unregister the UserCreateListener after spring boot application startup. how can I make Eventbus unregister this event and listener??
Introduce an unregister() method in the UserCreateListener
#Component
public class UserCreateListener {
#Autowired
private Eventbus eventbus;
#PostConstruct
public void init() {
this.eventbus.register(this)
}
public void unregister() {
this.eventbus.unregister(this)
}
}
Then if you want to unregister get autowired UserCreateListener (or retrieve the bean from application context) and call listenerInstance.unregister()
UPDATE
Create your own component and call unregister there. Guess the listener and eventbus are singletons.
#Component
public class MyUnregisterService {
#Autowired
private Eventbus eventbus;
#Autowired
private UserCreateListener listener;
public void unregister() {
eventbus.unregister(listener)
}
}

unable to use #AspectJ with Spring-Apache CXF services

I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}

How to use guice-servlet with Jersey 2.0?

Is there any sample code demonstrating how to use guice-servlet with Jersey 2.0?
https://github.com/Squarespace/jersey2-guice seems to be the first genuine Guice integration for Jersey 2 but it requires version 2.11+.
NOTE: I haven't tested this, but the idea is sound.
Yes, I've adapted an example and it's available here - https://github.com/piersy/jersey2-guice-example-with-test
I've updated the example code now, its got a test using jetty and another using tomcat.
There is a page at HK2 official about correct guice implementation: https://javaee.github.io/hk2/guice-bridge.html
You should create your Injector something like this:
public class GuiceConfig extends ResourceConfig {
#Inject
public GuiceConfig(ServiceLocator serviceLocator) {
this();
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(GuiceListener.createBiDirectionalGuiceBridge(serviceLocator));
}
public GuiceConfig() {
packages(Injections.packages);
addProperties(Injections.propertiesMap);
}
}
And code from the doc should be upgraded like:
#WebListener
public class GuiceListener extends GuiceServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
Locale.setDefault(Locale.ENGLISH);
super.contextInitialized(servletContextEvent);
}
public static volatile Injector injector = null;
#Override
protected Injector getInjector() {
return injector;
}
#SuppressWarnings("unchecked")
private static Module getModule() {
return binder -> {
Injections.singletonInterfaces.forEach((i, c) -> binder.bind(i).to(c).in(Scopes.SINGLETON));
Injections.singletonClasses.forEach(c -> binder.bind(c).in(Scopes.SINGLETON));
};
}
static synchronized Injector createBiDirectionalGuiceBridge(ServiceLocator serviceLocator) {
return GuiceListener.injector = createBiDirectionalGuiceBridge(serviceLocator, getModule());
}
}
Using the maven dependency at your pom.xml
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>guice-bridge</artifactId>
<version>2.3.0</version>
</dependency>
https://github.com/phxql/jersey2-guice doesn't work with jersey 2.22 and guice 4.0.
This is a minimum working PoC which wires Jersey 2 and Guice together:
https://github.com/phxql/jersey2-guice
I've already done in this sample:
https://github.com/jbescos/tododev
You have to register the class https://github.com/jbescos/tododev/blob/master/jersey2-guice/src/main/java/es/tododev/rest/ApplyGuiceContextFilter.java in your ResourceConfig, and the guice injector binded in an AbstractModule.
#Provider
#PreMatching
public class ApplyGuiceContextFilter implements ContainerRequestFilter, ContainerResponseFilter {
#Inject
public ApplyGuiceContextFilter(ServiceLocator serviceLocator, Injector injector) {
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(injector);
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
}
#Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) throws IOException {}
}
This is the ResouceConfig:
public class RestConfig extends ResourceConfig {
#Inject
public RestConfig() {
this(Guice.createInjector(new Module(){
#Override
public void configure(Binder arg0) {
// TODO Auto-generated method stub
}
}));
}
// Test
public RestConfig(Injector injector) {
packages(ResourceSample.class.getPackage().getName());
register(ApplyGuiceContextFilter.class);
register(new LoggingFilter(Logger.getLogger(LoggingFilter.class.getName()), true));
property(ServerProperties.TRACING, "ALL");
register(new RestBinder(injector));
}
private static class RestBinder extends AbstractBinder{
private final Injector injector;
private RestBinder(Injector injector){
this.injector = injector;
}
#Override
protected void configure() {
bind(injector).to(Injector.class);
}
}
}
GWizard includes a module that gives you out-of-the-box integration between Jersey2 and Guice. Here's an example of a complete JAX-RS service:
public class Main {
#Path("/hello")
public static class HelloResource {
#GET
public String hello() {
return "hello, world";
}
}
public static class MyModule extends AbstractModule {
#Override
protected void configure() {
bind(HelloResource.class);
}
}
public static void main(String[] args) throws Exception {
Guice.createInjector(new MyModule(), new JerseyModule()).getInstance(WebServer.class).startJoin();
}
}
Note that this is based on the Squarespace jersey2-guice adapter, which may not function properly with future point releases of Jersey. GWizard also offers a RESTEasy JAX-RS module, which is preferred.
Here is a blog entry about this that might help: http://blorn.com/post/107397841765/guice-and-jersey-2-the-easy-way
For those interested, there is a sample of guice/jersey integration available at https://github.com/mycom-int/jersey-guice-aop.
Here is an example using Embedded Jetty (it should probably work for Jetty server too)
jetty-jersey-HK2-Guice-boilerplate
If you are planning to use Guice for your application, all Guice components injected into Jersey need to be declared as a binding in the Guice config.
If you don't want to declare every binding in Guice config, there is an adapter here:
guice-bridge-jit-injector

Resources