SpringBoot Undertow: как отправить рабочий поток

В настоящее время я просматриваю отлив Springboot, и мне не совсем ясно (для меня), как отправить входящий http-запрос в рабочий поток для блокировки обработки операции.

Глядя на класс UndertowEmbeddedServletContainer.class, похоже, что такого поведения быть не может, поскольку единственный HttpHandler — это ServletHandler, который допускает конфигурации @Controller.

private Undertow createUndertowServer() {
    try {
        HttpHandler servletHandler = this.manager.start();
        this.builder.setHandler(getContextHandler(servletHandler));
        return this.builder.build();
    }
    catch (ServletException ex) {
        throw new EmbeddedServletContainerException(
                "Unable to start embdedded Undertow", ex);
    }
}

private HttpHandler getContextHandler(HttpHandler servletHandler) {
    if (StringUtils.isEmpty(this.contextPath)) {
        return servletHandler;
    }
    return Handlers.path().addPrefixPath(this.contextPath, servletHandler);

}

По умолчанию в undertow все запросы обрабатываются IO-Thread для неблокирующих операций. Означает ли это, что каждое выполнение @Controller будет обрабатываться неблокирующим потоком? или есть решение для выбора из IO-THREAD или WORKER-THREAD?

Я пытаюсь написать обходной путь, но этот код довольно уродлив, и, возможно, у кого-то есть лучшее решение:

BlockingHandler.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BlockingHandler {

    String contextPath() default "/";

}

UndertowInitializer.class

public class UndertowInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        configurableApplicationContext.addBeanFactoryPostProcessor(new UndertowHandlerPostProcessor());
    }

}

UndertowHandlerPostProcessor.class

public class UndertowHandlerPostProcessor implements BeanDefinitionRegistryPostProcessor {


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(BlockingHandler.class));
        for (BeanDefinition beanDefinition : scanner.findCandidateComponents("org.me.lah")){

            try{
                Class clazz = Class.forName(beanDefinition.getBeanClassName());
                beanDefinitionRegistry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
            } catch (ClassNotFoundException e) {
                throw new BeanCreationException(format("Unable to create bean %s", beanDefinition.getBeanClassName()), e);
            }
        }
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        //no need to post process defined bean
    }
}

переопределить UndertowEmbeddedServletContainerFactory.class

public class UndertowEmbeddedServletContainerFactory extends  AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {
        DeploymentManager manager = createDeploymentManager(initializers);
        int port = getPort();
        if (port == 0) {
            port = SocketUtils.findAvailableTcpPort(40000);
        }
        Undertow.Builder builder = createBuilder(port);

        Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(BlockingHandler.class);
        return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
            port, port >= 0, handlers);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

...

переопределить UndertowEmbeddedServletContainer.class

public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
                                        String contextPath, int port, boolean autoStart, Map<String, Object> handlers) {
    this.builder = builder;
    this.manager = manager;
    this.contextPath = contextPath;
    this.port = port;
    this.autoStart = autoStart;
    this.handlers = handlers;
}

private Undertow createUndertowServer() {
    try {
        HttpHandler servletHandler = this.manager.start();
        String path = this.contextPath.isEmpty() ? "/" : this.contextPath;
        PathHandler pathHandler = Handlers.path().addPrefixPath(path, servletHandler);
        for(Entry<String, Object> entry : handlers.entrySet()){
            Annotation annotation = entry.getValue().getClass().getDeclaredAnnotation(BlockingHandler.class);
            System.out.println(((BlockingHandler) annotation).contextPath());
            pathHandler.addPrefixPath(((BlockingHandler) annotation).contextPath(), (HttpHandler) entry.getValue());
        }

        this.builder.setHandler(pathHandler);
        return this.builder.build();
    }
    catch (ServletException ex) {
        throw new EmbeddedServletContainerException(
                "Unable to start embdedded Undertow", ex);
    }
}

установить инициализатор в контексте приложения

public static void main(String[] args) {
    new SpringApplicationBuilder(Application.class).initializers(new UndertowInitializer()).run(args);
}

наконец создайте HttpHandler, который отправляет рабочий поток

@BlockingHandler(contextPath = "/blocking/test")
public class DatabaseHandler implements HttpHandler {

    @Autowired
    private EchoService echoService;

    @Override
    public void handleRequest(HttpServerExchange httpServerExchange) throws Exception {
        if(httpServerExchange.isInIoThread()){
            httpServerExchange.dispatch();
        }

        echoService.getMessage("my message");
    }

}

Как видите, мое «решение» действительно тяжелое, и я был бы очень признателен за любую помощь, чтобы упростить его.

Спасибо


person zouroto    schedule 17.12.2014    source источник


Ответы (1)


Вам не нужно ничего делать.

Конфигурация Undertow по умолчанию для Spring Boot использует ServletInitialHandler перед Spring MVC DispatcherServlet. Этот обработчик выполняет exchange.isInIoThread() проверку и вызывает dispatch() при необходимости.

Если вы поместите точку останова в свой @Controller, вы увидите, что она вызывается в потоке с именем XNIO-1 task-n, который является рабочим потоком (потоки ввода-вывода называются XNIO-1 I/O-n).

person Andy Wilkinson    schedule 18.12.2014