是否可以设置 Undertow 来服务 Spring REST 端点?
Posted
技术标签:
【中文标题】是否可以设置 Undertow 来服务 Spring REST 端点?【英文标题】:Is it possible to set up Undertow to serve Spring REST endpoints? 【发布时间】:2019-03-24 07:03:32 【问题描述】:我的主要目标是在我的应用程序中嵌入 Redhat's Undertow,而不使用任何 web.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 / Configuring Spring Boot 看到 Spring 提供了一种在 Boot 中使用 Undertow 的方法。我只是不需要 Spring Boot。而且我真的不热衷于挖掘into the Spring source 以了解 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 邮件列表也无法搜索。
【问题讨论】:
【参考方案1】:我创建了一个使用 Spring 和 Undertow 构建的示例项目。它还与 JSP 和 Open Api 3.0 集成。你可以在这里查看:https://github.com/essentialprogramming/undertow-spring-web
【讨论】:
【参考方案2】:我不得不硬着头皮浏览 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 完全分开 - 这就是为什么我将 javax.servlet.ServletContainerInitializer
的 Spring 实现作为参数发送。
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();
您必须实现 Spring 的 AbstractAnnotationConfigDispatcherServletInitializer
并将其传递给 Undertow,以便在 servlet 容器启动阶段由 ServletContainerInitializer
调用。
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 初始化程序将通过您的 AnnotationConfigWebApplicationContext
实现调用 Spring 上下文初始化(在 getRootConfigClasses
方法中):
@PropertySource("file:target/application-int.properties")
@Configuration
@ComponentScan(basePackages = "org.adam.rest" )
@EnableWebMvc
public class MySpringWebApplicationContext
extends AnnotationConfigWebApplicationContext
以这种方式在 Undertow 中启动整个 Spring REST 服务器大约需要 1 秒。 有了 RESTassured 进行测试,一切都很顺利。
【讨论】:
您的代码中可能存在拼写错误:'server = ServletUtils.buildUndertowServer' 应该是 'server = Undertow.buildUndertowServer'? 非常有用的代码,但在我的情况下,我必须将 MySpringWebApplicationContext 直接提供给 ServletContainerInitializerInfo,而不使用 MySpringServletInitializer。不知道原因,我只是尝试过 :-) 否则我在 MySpringWebApplicationContext 中的方法不会被调用(你没有任何方法)。以上是关于是否可以设置 Undertow 来服务 Spring REST 端点?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 undertow 服务器中将端口设置为 80 失败? (爪哇)
Undertow - 如何设置反向代理来监控我的 REST API 的 HTTP/HTTPS 流量