leftso 3348 0 2018-04-01 20:42:53

引言

在这篇文章中,我们将着眼于在Spring WebFlux中将多个路由器功能定义到不同的逻辑域。如果您创建“微服务”,这可能不会成为问题,因为您很可能只在每个服务的单个域中工作,但如果您不是,那么您可能需要在应用程序中包含多个域用户或您自己的服务可以与之交互。这样做的代码就像我希望的那样简单,可以用几句话来解释。为了使这篇文章更有趣一些,我们将看一些使这一切成为可能的Spring代码。

如果你是WebFlux的新手,我推荐看看我以前的博客,[Spring WebFlux 项目实战],在那里我写了一些关于这个主题的完整例子和解释。
 

所以我们先假设置场景。你的应用程序中有两个不同的域,例如人员和位置。你可能想要让它们不仅在逻辑上而且在你的代码中彼此分离。要做到这一点,您需要一种方法来定义与其他域相互隔离的路由。这是我们将在这篇文章中看到的内容。

如果你认为你已经知道这个问题的答案,那么你可能是对的。这真的很简单。尽管如此,让我们继续努力吧。要为人员域创建路由,请创建一个RouterFunction映射到相关处理函数的bean,如下所示。

@Configuration
public class MyRouter {
  // works for a single bean
  @Bean
  public RouterFunction<ServerResponse> routes(PersonHandler personHandler) {
    return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
        .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
        .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)
        .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)
        .andRoute(DELETE("/people/{id}"), personHandler::delete)
        .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry);
  }
}

这将创建到各种处理函数的路由PersonHandler
所以,现在我们要添加位置逻辑的路由。我们可以简单地将路由添加到此bean,如下所示。
@Configuration
public class MyRouter {
  // not ideal!
  @Bean
  public RouterFunction<ServerResponse> routes(PersonHandler personHandler, LocationHandler locationHandler) {
    return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
        .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
        .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)
        .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)
        .andRoute(DELETE("/people/{id}"), personHandler::delete)
        .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry)
        .andRoute(GET("/locations/{id}").and(accept(APPLICATION_JSON)), locationHandler::get);
  }
}

这个bean现在包含一个引用,LocationHandler以便可以设置位置路由。这个解决方案的问题是它需要将代码耦合在一起。而且,如果你需要添加更多的处理程序,你很快就会被注入到这个bean中的依赖关系的数量所淹没。

解决这个问题的方法是创建多个RouterFunctionbean。而已。因此,如果我们在人员域中创建一个,PersonRouter并且在位置域中指定一个LocationRouter,则每个人都可以定义他们需要的路由,而Spring将完成剩下的任务。这是有效的,因为Spring遍历应用程序上下文并找到或创建任何RouterFunctionbean并将它们合并为一个函数供以后使用。

使用这些信息,我们可以编写下面的代码。
 

@Configuration
public class PersonRouter {
  // solution
  @Bean
  public RouterFunction<ServerResponse> peopleRoutes(PersonHandler personHandler) {
    return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
        .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
        .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)
        .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)
        .andRoute(DELETE("/people/{id}"), personHandler::delete)
        .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry);
  }
}
@Configuration
public class LocationRouter {
  // solution
  @Bean
  public RouterFunction<ServerResponse> locationRoutes(LocationHandler locationHandler) {
    return RouterFunctions.route(GET("/locations/{id}").and(accept(APPLICATION_JSON)), locationHandler::get);
  }
}
PersonRouter可以与其他人/与人相关的代码保存,并LocationRouter可以做同样的事情。
 

为了让这个更有趣,为什么这个工作?

RouterFunctionMapping是检索RouterFunction应用程序上下文中创建的所有bean 的类。该RouterFunctionMapping豆创建中WebFluxConfigurationSupport这是春天WebFlux配置震中。通过@EnableWebFlux在配置类中包含注释或者依靠自动配置,一系列事件开始并收集我们所有的RouterFunctions就是其中之一。

下面是这RouterFunctionMapping堂课。我已经删除了它的构造函数和一些方法来使这里的代码片段更容易消化。

public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {

  @Nullable
  private RouterFunction<?> routerFunction;

  private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

  // constructors

  // getRouterFunction

  // setMessageReaders

  @Override
  public void afterPropertiesSet() throws Exception {
    if (CollectionUtils.isEmpty(this.messageReaders)) {
      ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
      this.messageReaders = codecConfigurer.getReaders();
    }

    if (this.routerFunction == null) {
      initRouterFunctions();
    }
  }

  /**
   * Initialized the router functions by detecting them in the application context.
   */
  protected void initRouterFunctions() {
    if (logger.isDebugEnabled()) {
      logger.debug("Looking for router functions in application context: " +
          getApplicationContext());
    }

    List<RouterFunction<?>> routerFunctions = routerFunctions();
    if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
      routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
    }
    this.routerFunction = routerFunctions.stream()
        .reduce(RouterFunction::andOther)
        .orElse(null);
  }

  private List<RouterFunction<?>> routerFunctions() {
    SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer();
    obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container);

    return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() :
        container.routerFunctions;
  }

  // getHandlerInternal

  private static class SortedRouterFunctionsContainer {

    @Nullable
    private List<RouterFunction<?>> routerFunctions;

    @Autowired(required = false)
    public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) {
      this.routerFunctions = routerFunctions;
    }
  }

}

检索所有路由的路径开始于创建bean afterPropertiesSet后调用RouterFunctionMapping。由于它是内部的RouterFunctionnull它会initRouterFunctions触发一系列导致执行的方法routerFunctions。一个新SortedRouterFunctionsContainer的构造(私有静态类)routerFunctions通过注入RouterFunction应用程序上下文中的所有s来设置它的字段。这工作,因为Spring会注入类型的所有豆类T一个当List<T>被注入。现在检索到的RouterFunctions被组合在一起以制作一个RouterFunction从现在开始用于将所有传入请求路由到适当处理程序的单个节点。

这里的所有都是它的。总而言之,RouterFunction为不同业务领域定义多个是非常简单的,因为您只需在最有意义的任何区域创建它们,Spring就会关闭并获取所有区域。为了使我们研究的一些魔法神秘化,RouterFunctionMapping以了解RouterFunction我们创建的s是如何收集和组合的,以便它们可以用来将请求路由到处理程序。作为结束语,我确实明白这篇文章在某些方面是相当微不足道的,但有时看起来很明显的信息会非常有帮助。

我建议看看我以前的帖子[Spring WebFlux 项目实战]