代码之家  ›  专栏  ›  技术社区  ›  KJQ

是否可以在底层同时运行Spring WebFlux和MVC(CXF、Shiro等)服务?

  •  0
  • KJQ  · 技术社区  · 6 年前

    我们正在考虑使用新的Spring5“反应式”API实现一些服务。

    我们目前使用的REST服务和安全性在一定程度上依赖于MVC、Apache CXF和Apache Shiro。所有这一切现在都在暗中进行。

    我们可以让其中一个或另一个发挥作用,但不能同时让两者发挥作用。当我们切换到反应式应用程序时,它会删除servlet、过滤器等。相反,当我们使用MVC风格的应用程序时,它不会看到反应式处理程序。

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

    更新时间:

    我“似乎”能够让反应式处理程序这样做,但我不知道这是否是正确的方法。

    @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);
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  0
  •   KJQ    6 年前

    好吧,在玩了太久之后,我终于找到了一个适合我的解决方案。希望这是做我需要做的事情的正确方式。

    现在,执行正常的CXF RESTful路由会显示使用阻塞任务的拖航,执行反应式路由会显示直接使用NIO的拖航。当我尝试使用ServletHttpHandler时,它似乎只是作为Servlet3异步调用调用服务。

    处理程序完全独立运行,允许我在响应式服务之外运行REST服务。

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

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

    2) 创建一个UndertowReactiveHandler“Provider”,以便在配置Undertow时,可以懒洋洋地获取注入的RouterFunction并返回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,该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的任何RouterFunction。

    @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