最近研究websocket是发现一个问题,自动注入失败,代码如下:
@ServerEndpoint(value = "/websocket/{authToken}")
@Component
public class EzgoWebSocket {
@Autowired
private TokenService tokenService;
}
其中的tokenService是null,然后导致用到这个对象的方法全部爆空指针异常。
研究了一波websocket源码,现在直接贴代码。
服务端与客户端建立连接时,
1.初始化连接
@Override
public void init(WebConnection connection) {
if (ep == null) {
throw new IllegalStateException(
sm.getString("wsHttpUpgradeHandler.noPreInit"));
}
String httpSessionId = null;
Object session = handshakeRequest.getHttpSession();
if (session != null ) {
httpSessionId = ((HttpSession) session).getId();
}
// Need to call onOpen using the web application's class loader
// Create the frame using the application's class loader so it can pick
// up application specific config from the ServerContainerImpl
Thread t = Thread.currentThread();
ClassLoader cl = t.getContextClassLoader();
t.setContextClassLoader(applicationClassLoader);
try {
wsRemoteEndpointServer = new WsRemoteEndpointImplServer(socketWrapper, webSocketContainer);
wsSession = new WsSession(ep, wsRemoteEndpointServer,
webSocketContainer, handshakeRequest.getRequestURI(),
handshakeRequest.getParameterMap(),
handshakeRequest.getQueryString(),
handshakeRequest.getUserPrincipal(), httpSessionId,
negotiatedExtensions, subProtocol, pathParameters, secure,
endpointConfig);
wsFrame = new WsFrameServer(socketWrapper, wsSession, transformation,
applicationClassLoader);
// WsFrame adds the necessary final transformations. Copy the
// completed transformation chain to the remote end point.
wsRemoteEndpointServer.setTransformation(wsFrame.getTransformation());
//@ServerEndpoint注解标注的websocket消息对象的类名等信息都在endpointConfig
ep.onOpen(wsSession, endpointConfig);
webSocketContainer.registerSession(ep, wsSession);
} catch (DeploymentException e) {
throw new IllegalArgumentException(e);
} finally {
t.setContextClassLoader(cl);
}
}
endpointConfig对象:
2.执行onOpen方法,在这里创建对象并执行消息对象中加了@onOpen注解的方法
@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
ServerEndpointConfig sec = (ServerEndpointConfig) endpointConfig;
Object pojo;
try {
//在这里创建我们自定义的消息对象
pojo = sec.getConfigurator().getEndpointInstance(
sec.getEndpointClass());
} catch (InstantiationException e) {
throw new IllegalArgumentException(sm.getString(
"pojoEndpointServer.getPojoInstanceFail",
sec.getEndpointClass().getName()), e);
}
setPojo(pojo);
//后面还有执行onOpen方法的代码,这里就不说了O(∩_∩)O哈哈~
}
3.我们进去看一下创建消息对象的过程
@Override
public <T> T getEndpointInstance(Class<T> clazz)
throws InstantiationException {
try {
return clazz.getConstructor().newInstance();
} catch (InstantiationException e) {
throw e;
} catch (ReflectiveOperationException e) {
InstantiationException ie = new InstantiationException();
ie.initCause(e);
throw ie;
}
}
这里方法很简单,就是反射new一个消息对象。
总结:
通过以上我们可以发现,websocket在连接一次就会new一个对象,由于是通过反射直接实例化对象,所有spring自动注入的bean并没什么卵用。O(∩_∩)O哈哈~
解决方法:
@ServerEndpoint(value = "/websocket/{authToken}")
@Component
public class EzgoWebSocket {
private static TokenService tokenService;
@Autowired
public EzgoWebSocket(TokenService tokenService){
this.tokenService = tokenService;
}
public EzgoWebSocket(){}
}
通过构造器注入,赋值给静态的tokenService,这样每次连接实例化消息对象时就不会是null了
注意,下面这个默认构造器一定要保留,上面的源码实例化消息对象时,调用的是默认的构造器,如果没有就会报错。