是否可以在Undertow中一起运行Spring WebFlux和MVC(CXF,Shiro等)服务?



我们正在考虑使用新的Spring 5"反应式"API实现一些服务。

我们目前使用,有点依赖MVC,Apache CXF和Apache Shiro来提供我们的REST服务和安全性。所有这些现在都在Undertow中运行。

我们可以让其中一个工作,但不能同时工作。 当我们切换到反应式应用程序时,它似乎会淘汰 servlet、过滤器等。 相反,当我们使用 MVC 风格的应用程序时,它看不到反应式处理程序。

是否可以将Spring5 Reactive服务与REST/servlet/filter组件一起运行,或者自定义SpringBoot启动以在不同的端口上运行REST和Reactive服务?

更新:

我"似乎"能够让反应式处理程序执行此操作,但我不知道这是否是正确的方法。

@Bean
RouterFunction<ServerResponse> routeGoodbye(TrackingHandler endpoint)
{
RouterFunction<ServerResponse> route = RouterFunctions
.route(GET("/api/rx/goodbye")
.and(accept(MediaType.TEXT_PLAIN)), endpoint::trackRedirect2);
return route;
}
@Bean
RouterFunction<ServerResponse> routeHello(TrackingHandler endpoint)
{
RouterFunction<ServerResponse> route = RouterFunctions
.route(GET("/api/rx/hello")
.and(accept(MediaType.TEXT_PLAIN)), endpoint::trackRedirect);
return route;
}
@Bean
ContextPathCompositeHandler servletReactiveRouteHandler(TrackingHandler handler)
{
final Map<String, HttpHandler> handlers = new HashMap<>();
handlers.put("/hello", toHttpHandler((this.routeHello(handler))));
handlers.put("/goodbye", toHttpHandler(this.routeGoodbye(handler)));
return new ContextPathCompositeHandler(handlers);
}
@Bean
public ServletRegistrationBean servletRegistrationBean(final ContextPathCompositeHandler handlers)
{
ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(
new ReactiveServlet(handlers),
"/api/rx/*");
registrationBean.setLoadOnStartup(1);
registrationBean.setAsyncSupported(true);
return registrationBean;
}
@Bean
TrackingHandler trackingEndpoint(final TrackingService trackingService)
{
return new TrackingHandler(trackingService,
null,
false);
}
public class ReactiveServlet extends ServletHttpHandlerAdapter
{
ReactiveServlet(final HttpHandler httpHandler)
{
super(httpHandler);
}
}

好的,在玩了太久之后,我似乎终于能够拼凑出一个适合我的解决方案。 希望这是做我需要做的事情的正确方法。

现在,执行正常的 CXF RESTful 路由向我显示使用阻塞任务的 Undertow,执行我的反应式路由显示我直接使用 NIO 进行欠拖。当我尝试使用 ServletHttpHandler 时,它看起来只是将服务作为 Servlet 3 异步调用调用。

处理程序彼此完全独立运行,并允许我在反应式服务旁边运行 REST 服务。

1) 创建一个注释,用于将 RouterFunction 映射到欠拖处理程序

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ReactiveHandler
{
String value();
}

2) 创建一个 UndertowReactiveHandler "Provider",以便我可以懒惰地获取注入的 RouterFunction 并在配置 Undertow 时返回 UndertowHttpHandler。

final class UndertowReactiveHandlerProvider implements Provider<UndertowHttpHandlerAdapter>
{
@Inject
private ApplicationContext context;
private String path;
private String beanName;
@Override
public UndertowHttpHandlerAdapter get()
{
final RouterFunction router = context.getBean(beanName, RouterFunction.class);
return new UndertowHttpHandlerAdapter(toHttpHandler(router));
}
public String getPath()
{
return path;
}
public void setPath(final String path)
{
this.path = path;
}
public void setBeanName(final String beanName)
{
this.beanName = beanName;
}
}

3) 创建 NonBLockingHandlerFactory(实现 BeanFactoryPostProcessor)。 这将查找我用"ReactiveHandler"注释的任何@Bean方法,然后为每个带注释的路由器函数动态创建一个"UndertowReactiveHandlerProvider"bean,稍后用于向Undertow提供处理程序。

@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException
{
final BeanDefinitionRegistry registry = (BeanDefinitionRegistry)configurableListableBeanFactory;
final String[] beanDefinitions = registry.getBeanDefinitionNames();
for (String name : beanDefinitions)
{
final BeanDefinition beanDefinition = registry.getBeanDefinition(name);
if (beanDefinition instanceof AnnotatedBeanDefinition
&& beanDefinition.getSource() instanceof MethodMetadata)
{
final MethodMetadata beanMethod = (MethodMetadata)beanDefinition.getSource();
final String annotationType = ReactiveHandler.class.getName();
if (beanMethod.isAnnotated(annotationType))
{
//Get the current bean details
final String beanName = beanMethod.getMethodName();
final Map<String, Object> attributes = beanMethod.getAnnotationAttributes(annotationType);
//Create the new bean definition
final GenericBeanDefinition rxHandler = new GenericBeanDefinition();
rxHandler.setBeanClass(UndertowReactiveHandlerProvider.class);
//Set the new bean properties
MutablePropertyValues mpv = new MutablePropertyValues();
mpv.add("beanName", beanName);
mpv.add("path", attributes.get("value"));
rxHandler.setPropertyValues(mpv);
//Register the new bean (Undertow handler) with a matching route suffix
registry.registerBeanDefinition(beanName + "RxHandler", rxHandler);
}
}
}
}

4) 创建 undertow ServletExtension。 这将查找任何UndertowReactiveHandlerProviders,并将其添加为UndertowHttpHandler。

public class NonBlockingHandlerExtension implements ServletExtension
{
@Override
public void handleDeployment(DeploymentInfo deploymentInfo, final ServletContext servletContext)
{
deploymentInfo.addInitialHandlerChainWrapper(handler -> {
final WebApplicationContext ctx = getWebApplicationContext(servletContext);
//Get all of the reactive handler providers
final Map<String, UndertowReactiveHandlerProvider> providers =
ctx.getBeansOfType(UndertowReactiveHandlerProvider.class);
//Create the root handler
final PathHandler rootHandler = new PathHandler();
rootHandler.addPrefixPath("/", handler);
//Iterate the providers and add to the root handler
for (Map.Entry<String, UndertowReactiveHandlerProvider> p : providers.entrySet())
{
final UndertowReactiveHandlerProvider provider = p.getValue();
//Append the HttpHandler to the root
rootHandler.addPrefixPath(
provider.getPath(),
provider.get());
}
//Return the root handler
return rootHandler;
});
}
}

5) 在 META-INF/services 下创建一个 "io.undertow.servlet.ServletExtension" 文件。

com.mypackage.NonBlockingHandlerExtension

6) 创建一个 SpringBoot 自动配置,如果 Undertow 在类路径上,则加载后处理器。

@Configuration
@ConditionalOnClass(Undertow.class)
public class UndertowAutoConfiguration
{
@Bean
BeanFactoryPostProcessor nonBlockingHandlerFactoryPostProcessor()
{
return new NonBlockingHandlerFactoryPostProcessor();
}
}

7) 注释任何我想映射到 UndertowHandler 的路由器函数。

@Bean
@ReactiveHandler("/api/rx/service")
RouterFunction<ServerResponse> routeTracking(TrackingHandler handler)
{
RouterFunction<ServerResponse> route = RouterFunctions
.nest(path("/api/rx/service"), route(
GET("/{cid}.gif"), handler::trackGif).andRoute(
GET("/{cid}"), handler::trackAll));
return route;
}

有了这个,我可以调用我的REST服务(Shiro与它们一起工作),将Swagger2与我的REST服务一起使用,并在同一个SpringBoot应用程序中调用我的响应式服务(它们不使用Shiro)。

在我的日志中,REST 调用显示使用阻塞 (task-#) 处理程序的 Undertow。 反应式调用显示使用非阻塞(I/O-# 和 nioEventLoopGroup)处理程序的 Undertow

最新更新