问题描述
springboot websocket 连接时,如果设置为SpringConfigurator,如下所示:
@ServerEndpoint(value = “/websocket/test”,configurator = SpringConfigurator.class)
当接收数据的时候,就会报错:
Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?
百度半天,都是复制粘贴,没有一个回答的。。。。。。
问题跟踪解决
代码跟踪到:
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
String beanName;
if (wac == null) {
beanName = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?";
logger.error(beanName);
throw new IllegalStateException(beanName);
} else {
beanName = ClassUtils.getShortNameAsProperty(endpointClass);
if (wac.containsBean(beanName)) {
T endpoint = wac.getBean(beanName, endpointClass);
if (logger.isTraceEnabled()) {
logger.trace("Using @ServerEndpoint singleton " + endpoint);
}
return endpoint;
} else {
Component ann = (Component)AnnotationUtils.findAnnotation(endpointClass, Component.class);
if (ann != null && wac.containsBean(ann.value())) {
T endpoint = wac.getBean(ann.value(), endpointClass);
if (logger.isTraceEnabled()) {
logger.trace("Using @ServerEndpoint singleton " + endpoint);
}
return endpoint;
} else {
beanName = this.getBeanNameByType(wac, endpointClass);
if (beanName != null) {
return wac.getBean(beanName);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Creating new @ServerEndpoint instance of type " + endpointClass);
}
return wac.getAutowireCapableBeanFactory().createBean(endpointClass);
}
}
}
}
}
getEndpointInstance这个方法时,SpringConfigurator使用ContextLoader获取上下文。 Spring Boot确实设置了ServletContext,但它从不使用ContextLoaderListener初始化ContextLoader来保存spring上下文的静态。您可以尝试添加ContextLoaderListener或作为解决方法,您可以编写自己的上下文持有者和配置程序。按照google到的结果,尝试可以,设置如些:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.websocket.server.ServerEndpointConfig;
public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
/**
* Spring application context.
*/
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CustomSpringConfigurator.context = applicationContext;
}
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {
@Bean
public CustomSpringConfigurator customSpringConfigurator() {
return new CustomSpringConfigurator(); // This is just to get context
}
}
下面就配置你自己定义的适配器:
@Component
@ServerEndpoint(value = "/",configurator = CustomSpringConfigurator.class)
public class ServerWebSocket {
}
为什么要这样做呢? 按照之前的我的博客讲述的那种,是websocket默认的适配器,当我们请求一次接口的时候,open方法会重新new instance,虽然我们使用了@Component交给springboot去管理,但是还是多例的。数量少还好,但是当请求数量很多的时候,这个创建对象,销毁对象的开销就非常大。现在更改open模式,这样就是一个单例,只会存在一个instance。