是否可以运行 Spring WebFlux和MVC(CXF,Shiro等)服务一起在Undertow?
Is it possible to run Spring WebFlux and MVC (CXF, Shiro, etc.) services together in Undertow?
我们正在考虑使用新的 Spring 5 "Reactive" API 实施一些服务。
我们目前在某种程度上依赖于 MVC、Apache CXF 和 Apache Shiro 来提供我们的 REST 服务和安全性。所有这些 运行 现在都在 Undertow 中。
我们可以让其中之一起作用,但不能同时起作用。当我们切换到反应式应用程序时,它似乎会淘汰 servlet、过滤器等。相反,当我们使用 MVC 样式的应用程序时,它看不到反应式处理程序。
是否可以 运行 Spring 5 Reactive 服务与 REST/servlet/filter 组件一起使用,或者自定义 Spring 启动到 运行 REST 和 Reactive 服务在不同的端口?
更新:
我 "seem" 能够让反应式处理程序工作,但我不知道这是否是正确的方法。
@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,执行我的 Reactive 路由显示直接使用 NIO 的 Undertow。当我尝试使用 ServletHttpHandler 时,它看起来只是将服务作为 Servlet 3 异步调用来调用。
处理程序 运行 彼此完全分开,允许我 运行 除了响应式服务之外还有我的 REST 服务。
1) 创建将用于将 RouterFunction 映射到 Undertow 处理程序的注释
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ReactiveHandler
{
String value();
}
2) 创建一个 UndertowReactiveHandler "Provider" 这样我就可以在配置 Undertow 时懒惰地获取注入的 RouterFunction 和 return 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) 如果 Undertow 在类路径上,则创建一个加载 post 处理器的 SpringBoot 自动配置。
@Configuration
@ConditionalOnClass(Undertow.class)
public class UndertowAutoConfiguration
{
@Bean
BeanFactoryPostProcessor nonBlockingHandlerFactoryPostProcessor()
{
return new NonBlockingHandlerFactoryPostProcessor();
}
}
7) 注释我想映射到 UndertowHandler 的任何 RouterFunctions。
@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 应用程序中调用我的 Reactive 服务(它们不使用 Shiro)。
在我的日志中,REST 调用显示使用阻塞 (task-#) 处理程序的 Undertow。 Reactive 调用使用非阻塞(I/O-# 和 nioEventLoopGroup)处理程序
显示 Undertow
我们正在考虑使用新的 Spring 5 "Reactive" API 实施一些服务。
我们目前在某种程度上依赖于 MVC、Apache CXF 和 Apache Shiro 来提供我们的 REST 服务和安全性。所有这些 运行 现在都在 Undertow 中。
我们可以让其中之一起作用,但不能同时起作用。当我们切换到反应式应用程序时,它似乎会淘汰 servlet、过滤器等。相反,当我们使用 MVC 样式的应用程序时,它看不到反应式处理程序。
是否可以 运行 Spring 5 Reactive 服务与 REST/servlet/filter 组件一起使用,或者自定义 Spring 启动到 运行 REST 和 Reactive 服务在不同的端口?
更新:
我 "seem" 能够让反应式处理程序工作,但我不知道这是否是正确的方法。
@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,执行我的 Reactive 路由显示直接使用 NIO 的 Undertow。当我尝试使用 ServletHttpHandler 时,它看起来只是将服务作为 Servlet 3 异步调用来调用。
处理程序 运行 彼此完全分开,允许我 运行 除了响应式服务之外还有我的 REST 服务。
1) 创建将用于将 RouterFunction 映射到 Undertow 处理程序的注释
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ReactiveHandler
{
String value();
}
2) 创建一个 UndertowReactiveHandler "Provider" 这样我就可以在配置 Undertow 时懒惰地获取注入的 RouterFunction 和 return 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) 如果 Undertow 在类路径上,则创建一个加载 post 处理器的 SpringBoot 自动配置。
@Configuration
@ConditionalOnClass(Undertow.class)
public class UndertowAutoConfiguration
{
@Bean
BeanFactoryPostProcessor nonBlockingHandlerFactoryPostProcessor()
{
return new NonBlockingHandlerFactoryPostProcessor();
}
}
7) 注释我想映射到 UndertowHandler 的任何 RouterFunctions。
@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 应用程序中调用我的 Reactive 服务(它们不使用 Shiro)。
在我的日志中,REST 调用显示使用阻塞 (task-#) 处理程序的 Undertow。 Reactive 调用使用非阻塞(I/O-# 和 nioEventLoopGroup)处理程序
显示 Undertow