I am trying to @Inject a Guice service into a @ServerEndpoint. I am using Tomcat 8.0.15 as the JSR-356 implementation. However, the dependency injection isn't working. Is there any additional configuration that needs to be done in order to enable Guice injection? Note that I am using all standard javax annotations only.
Tomcat8 WebSockets (JSR-356) with Guice 3.0
1.6k views Asked by Aritra AtThere are 3 answers
I figured this out. The Websocket endpoint needs to have a custom configurator, which creates and returns instances using the Guice injector instance.
Example:
Custom Guice servlet context listener:
public class CustomServletContextListener extends GuiceServletContextListener {
public static Injector injector;
@Override
protected Injector getInjector() {
injector = Guice.createInjector(...);
return injector;
}
}
Websockets custom configurator:
public class CustomConfigurator extends Configurator {
@Override
public <T> T getEndpointInstance(Class<T> clazz)
throws InstantiationException {
return CustomServletContextListener.injector.getInstance(clazz);
}
}
And then in the Websocket endpoint:
@ServerEndpoint(value = "/ws/sample_endpoint", configurator = CustomConfigurator.class)
public class SampleEndpoint {
private final SomeService service;
@Inject
public SampleEndpoint(SomeService service) {
this.service = service;
}
...
}
First, using annotations to do any "magic" behind the scene is a bad idea in general: it's much better to deploy Endpoints
programmatically in ServletContextListener.contextInitialized(event)
with ServerContainer.addEndpoint(config)
, so that you have the full control and can avoid storing injector on static vars.
Now regarding the injection, the solution is to define your custom ServerEndpointConfig.Configurator
as stated in other answers already, however it is much safer to use field/setter injections in Endpoint
classes and call super.getEndpointInstance(endpointClass)
followed by injector.injectMembers(endpointInstance)
. That's because super
(default Configurator
impl of the given container) may return instances of container-specific dynamic subclasses or decorators wrapping the newly created instance of endpointClass
. Furthermore, the spec requires Endpoint
classes to have a paramless constructor, so some containers may refuse to deploy Endpoint
classes that use constructor params for injections.
public class MyConfigurator extends ServerEndpointConfig.Configurator {
public <EndpointT> EndpointT getEndpointInstance(Class<EndpointT> endpointClass)
throws InstantiationException {
EndpointT endpointInstance = super.getEndpointInstance(endpointClass);
injector.injectMembers(endpointInstance);
return endpointInstance;
}
}
Now a SevletContextListener
that adds Endpoints
programmatically:
public class MyServletCtxListener implements SevletContextListener {
ServerEndpointConfig.Configurator endpointConfigurator;
ServerContainer endpointContainer;
void addEndpoint(Class<?> endpointClass, String path) throws DeploymentException {
endpointContainer.addEndpoint(
ServerEndpointConfig.Builder
.create(endpointClass, path)
.configurator(endpointConfigurator)
.build()
);
}
public void contextInitialized(ServletContextEvent initialization) {
final var ctx = initialization.getServletContext();
endpointContainer = (ServerContainer)
ctx.getAttribute("javax.websocket.server.ServerContainer");
final var injector = Guice.createInjector(
// put modules here...
);
endpointConfigurator = new MyConfigurator(injector); // NO STATIC :)
try {
addEndpoint(MyEndpoint.class, "/websocket/my");
addEndpoint(MyOtherEndpoint.class, "/websocket/myOther");
addEndpoint(MyYetAnotherEndpoint.class, "/websocket/myYetAnother");
// ...
} catch (DeploymentException e) {
e.printStackTrace();
System.exit(1); // fail fast
}
}
}
Note that if you go for programmatic adding, your Endpoint
classes should be extending javax.websocket.Endpoint
/ jakarta.websocket.Endpoint
according to the spec (although AFAIR Tomcat specifically used to be relaxed regarding this requirement).
Some related self-promotion: for anyone combining guice with websockets, you may find useful my lib that provides custom Scopes
for ws Endpoints
: https://github.com/morgwai/servlet-scopes
Building upon Aritra's own answer:
To be honest, I don't know for sure if this works with Guice 3.0, but it does work for 4.0, which is the current stable release.
I think a somewhat cleaner approach is to change your CustomConfigurator into something like this:
And then from your extended
ServletModule
class'configureServlets
method, callrequestStaticInjection(CustomConfigurator.class)
That way you won't expose the injector to everyone. I don't know about you, but it gives me a nice and fuzzy feeling inside to know that no one will be able to mess with my injector :-).