发现websocket引入springbean是无效的.如下:
@Component
@ServerEndpoint(value = "/websocket/{token}")
public class WebSocketAction {
@Autowired
private UserService userService;
使用userService方法时报了空指针,于是在构造方法里打印排查下,主要是通过打印对象的hashcode判断对象:
@Component
@ServerEndpoint(value = "/websocket/{token}")
public class WebSocketAction {
@Autowired
private UserService userService;
public ApiWebSocketAction() {
super();
WebSocketAction bean = null;
try {
bean = SpringUtil.getBean(ApiWebSocketAction.class);
} catch (Exception e) {
}
int code = 0;
if (bean != null) {
code = bean.hashCode();
}
System.out.println("==============="+this.hashCode());
System.out.println("==============="+code);
System.out.println("===============");
}
项目启动中打印日志
===============1861083998
===============0
===============
每次建立连接时打印日志
===============1171159013
===============1861083998
===============
===============1171844554===============1861083998
===============
可以发现spring初始化时启动了一个bean,但每次请求时并没有使用那个bean,而是建立了一个新的Object.而且这个Object并没有注入成为springbean.因为如果是springbean的话,getbean获取到的hashcode是会变的.如果还不放心,可以在onMessage里打印一下springbean,会发现其hash也没有变化.
这个初始创建的bean没有一点用啊看来.那是什么在起作用呢?
追查创建Object的栈,可以发现最终是这里执行的:
package org.apache.tomcat.websocket.server;
public class DefaultServerEndpointConfigurator
extends ServerEndpointConfig.Configurator {
@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;
}
}
而这个东西是tomcat的实现,可以发现最终调用websocket的并不是Spring的getBean方法,而是获取到了clazz直接反射获取.
那么它本身就不是bean,自然无从引入spring的bean了.
同时由于每次都是一个新的对象,那么局部变量将不再共享.
扩展一下:
怎么生成websocket对象是依据ServerEndpointConfig.Configurator的,一般来说servlert容器都会有实现,比如这个例子里就是由Tomcat去实现的,我们查下会发现spring也实现了.
如下:
public class SpringConfigurator extends Configurator {
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
if (wac == null) {
String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?";
logger.error(message);
throw new IllegalStateException(message);
}
//通过WebApplicationContext获取该endpointClass 对应的bean
...
...
那么我们可以在类上注解指定Configurator
@ServerEndpoint(value = "/websocket/{token}",configurator = SpringConfigurator.class)
那么推测这样的话, websocket对象就会是springbean对象,就可以外部引入spring的bean了.
但这个会报错,在上述方法里,会wac = null, ApplicationContext不存在.
原因在于currentContextPerThread; 因为这个map里什么都没有,查看下这个map的put方法都被谁调用了,则发现最终调用者是org.springframework.web.context.ContextLoaderListener的init调用的,这个类在springboot之前是非常重要的,因为web.xml都需要配置:
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
但springboot之后不再采取这样加载了,于是乎context就没有放进去过.
所以在取出的时候报错了.