5

@ServerEndPoint アノテーション付きクラスでスプリングを使用するのに問題があります

私はSpringboot 1.2.3を使用しており、エンドポイントの単一のインスタンスを持つ方法を理解しようとしています

@SpringBootApplication
@EnableJpaRepositories
@EnableWebSocket
public class ApplicationServer {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationServer.class, args);
    }
}

春の構成:

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {

    @Bean
    public ServerEndPoint serverEndpoint() {
        return new ServerEndPoint();
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocket エンドポイント:

@ServerEndpoint(value = "/", decoders = MessageDecoder.class, 
encoders = MessageEncoder.class, configurator = SpringConfigurator.class)
public class ServerEndPoint {

    private static final Logger LOG = LoggerFactory.getLogger(ServerEndPoint.class);

    public static final Set<CommunicationObserver> OBSERVERS = Sets.newConcurrentHashSet();

    @OnMessage
    public void onMessage(Session session, Message msg) {
        LOG.debug("Received msg {} from {}", msg, session.getId());
        for (CommunicationObserver o : OBSERVERS) {
            o.packetReceived(session, msg);
        }
    }

これは、 Spring WebSocket JSR-356 チュートリアルに基づいていますが、次のエラーが発生しました。

java.lang.IllegalStateException: Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?
    at org.springframework.web.socket.server.standard.SpringConfigurator.getEndpointInstance(SpringConfigurator.java:68)
    at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:50)
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:138)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:687)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

組み込みモードと外部 tomcat 8 および jetty 9 (外部モードでは、de Spring 構成ファイルを削除します) の両方でテストしましたが、同じエラーが表示されます。

私が見つけた唯一の回避策は、カスタム コンフィギュレーターを作成することです。

public class SpringEndpointConfigurator extends ServerEndpointConfig.Configurator {

    private static WebApplicationContext wac;

    public SpringEndpointConfigurator() {
    }

    public SpringEndpointConfigurator(WebApplicationContext wac) {
        SpringEndpointConfigurator.wac = wac;
    }

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
        T endPoint = wac.getAutowireCapableBeanFactory().getBean(endpointClass);
        return (endPoint != null) ? endPoint : wac.getAutowireCapableBeanFactory().createBean(endpointClass);
    }

パラメータ化されたコンストラクタを使用して @Bean として作成されます。

SpringConfigurator クラスでそれを行うには、何かを見逃したに違いありませんが、何がわかりません。

4

3 に答える 3

9

SpringConfiguratorContextLoader春のコンテキストを取得するために使用します。Spring Boot は ServletContext をセットアップしますが、ContextLoaderListenerどの初期化を使用してContextLoaderスプリング コンテキストの静的状態を保持することはありません。追加を試みるContextLoaderListenerか、回避策として独自のコンテキスト ホルダーとコンフィギュレーターを作成できます。

次に例を示します。

最初のコンテキスト ホルダーとコンフィギュレーター:

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;
    }
}

コンテキストを取得するには、これを Bean として構成する必要があります。

@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {

...

    @Bean
    public CustomSpringConfigurator customSpringConfigurator() {
        return new CustomSpringConfigurator(); // This is just to get context
    }
}

次に、コンフィギュレーターを正しく設定する必要があります。

@ServerEndpoint(value = "/", decoders = MessageDecoder.class, 
encoders = MessageEncoder.class, configurator = CustomSpringConfigurator.class)
public class ServerEndPoint {
...
}

補足として、はいSpringConfigurator、アプリケーションを削除すると起動し、リクエストを処理できます。ただし、他の Bean を自動配線することはできません。

于 2016-08-13T18:58:27.020 に答える