首页 > 解决方案 > 是否可以设置 Undertow 来服务 Spring REST 端点?

问题描述

我的主要目标是在我的应用程序中嵌入Redhat 的 Undertowweb.xml ,而无需任何Spring Boot。Undertow 看起来与 servlet 容器非常接近,可以满足我的要求,同时又超高性能和精简。就其他微框架而言,我也查看了SparkJava,但我首先尝试了 Undertow,因为它的文档看起来更好。

所以 Undertow 听起来很棒,但我遇到的所有文档和教程在返回“ Hello World ”后都停止了/。也许我能找到的最好的是StubbornJava/RestServer.java,其中所有端点都是硬编码的,例如:

public static final RoutingHandler ROUTES = new RoutingHandler()
    .get("/users", timed("listUsers", UserRoutes::listUsers))

我找不到任何显示如何或是否可以将 Spring MVC / REST 控制器注释与基本的 Undertow 结构链接起来的东西。

我已经有一个应用程序,其中包含在 Spring 注释中定义的一组端点。

在我对 Spring 和 Undertow 的了解中,我缺少关于如何将两者结合起来的一大块内容,但我可以从Baeldung / 配置 Spring Boot中看到 Spring 提供了一种在 Boot 中使用 Undertow 的方法。我只是不需要 Spring Boot。而且我真的不热衷于深入研究 Spring 源代码以了解 Pivotal 是如何做到的,因为在我的情况下它可能无法复制。这是在 Boot 中实现它的方法:

@Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
    UndertowEmbeddedServletContainerFactory factory = 
      new UndertowEmbeddedServletContainerFactory();

    factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {
        @Override
        public void customize(io.undertow.Undertow.Builder builder) {
            builder.addHttpListener(8080, "0.0.0.0");
        }
    });

    return factory;
}

我的猜测是我必须以编程方式获取带注释的 Spring REST 控制器并为每个控制器创建所需的 Undertow 资源。

似乎 Undertow 邮件列表也无法搜索。

标签: javaspringrestundertow

解决方案


我不得不硬着头皮浏览 Spring 源代码,看看 Pivotal 是如何将它们联系在一起的。

在提取相关部分之后,我重构了我所得到的并将其归结为基本要素。这首先是集成测试类。

    private static Undertow server;

    @BeforeAll
    public static void startServer() throws ServletException {
        server = ServletUtils.buildUndertowServer(
                8080,
                "localhost",
                "",
                SpringServletContainerInitializer.class,
                Collections.singleton(
                        MySpringServletInitializer.class),
                MyTests.class.getClassLoader());
        server.start();
    }

    @AfterAll
    public static void stopServer() {
        try {
            if (server != null) {
                server.stop();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testIsAvailable() {
        Response response = get("/mystuff/isAvailable");
        response.then().statusCode(200);
        ResponseBody body = response.getBody();
        assertThat("Body", body.asString(), is(equalTo("ok")));
    }

我在公用事业课上做了 Undertow 管道。我将它与 Spring 完全分开——这就是我将 Spring 实现javax.servlet.ServletContainerInitializer作为参数发送的原因。

import io.undertow.servlet.api.ServletContainerInitializerInfo;
import javax.servlet.ServletContainerInitializer;

public static Undertow buildUndertowServer(
        int port,
        String address,
        String contextPath,
        Class<? extends ServletContainerInitializer>
                servletContainerInitializerClass,
        Set<Class<?>> initializers,
        ClassLoader classLoader
) throws ServletException {

    ServletContainerInitializerInfo servletContainerInitializerInfo =
            new ServletContainerInitializerInfo(
                    servletContainerInitializerClass,
                    initializers);
    DeploymentInfo deployment = Servlets.deployment();
    deployment.addServletContainerInitializer(
            servletContainerInitializerInfo);
    deployment.setClassLoader(classLoader);
    deployment.setContextPath(contextPath);
    deployment.setDisplayName("adam");
    deployment.setDeploymentName("int-test");
    deployment.setServletStackTraces(ServletStackTraces.ALL);
    DeploymentManager manager =
            Servlets.newContainer().addDeployment(deployment);
    manager.deploy();
    Undertow.Builder builder = Undertow.builder();
    builder.addHttpListener(port, address);
    HttpHandler httpHandler = manager.start();
    httpHandler = Handlers.path().addPrefixPath(contextPath, httpHandler);
    builder.setHandler(httpHandler);
    return builder.build();
}

您必须实现 SpringAbstractAnnotationConfigDispatcherServletInitializer并将其传递给 Undertow,以便ServletContainerInitializer在 servlet 容器启动阶段调用。

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

public class MySpringServletInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {
                MySpringWebApplicationContext.class
        };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        logger.info("starting up");
        super.onStartup(servletContext);
    }
}

这个 Spring servlet 初始化程序将通过您的实现调用 Spring 上下文初始化AnnotationConfigWebApplicationContext(在该getRootConfigClasses方法中):

@PropertySource("file:target/application-int.properties")
@Configuration
@ComponentScan(basePackages = { "org.adam.rest" })
@EnableWebMvc
public class MySpringWebApplicationContext
        extends AnnotationConfigWebApplicationContext {
}

以这种方式在 Undertow 中启动整个 Spring REST 服务器大约需要 1 秒。有了 RESTassured 进行测试,一切都很顺利。


推荐阅读