>  기사  >  웹 프론트엔드  >  Spring 5의 새로운 기능: 기능적 웹 프레임워크

Spring 5의 새로운 기능: 기능적 웹 프레임워크

高洛峰
高洛峰원래의
2016-10-15 13:50:361155검색

시작할 샘플 프로그램을 선택합니다. 다음은 Person 객체를 노출하기 위한 반응형 리소스 라이브러리입니다. 이 반응형 리소스 라이브러리는 Flux가 기존 List에 해당하고 Mono가 기존 Person 개체에 해당한다는 점을 제외하면 기존의 비반응형 리소스 라이브러리와 유사합니다. Mono 완료 플래그: 저장 작업 완료를 나타내는 데 사용됩니다. Reactor 유형에 대한 자세한 내용은 Dave

public interface PersonRepository {
  Mono<Person> getPerson(int id);
  Flux<Person> allPeople();
  Mono<Void> savePerson(Mono<Person> person);}

사용 방법을 소개합니다. 리소스 라이브러리를 노출하는 새로운 기능적 웹 프레임워크:

RouterFunction<?> route = route(GET("/person/{id}"),
  request -> {
    Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id"))
      .map(Integer::valueOf)
      .then(repository::getPerson);
    return Response.ok().body(fromPublisher(person, Person.class));
  })
  .and(route(GET("/person"),
    request -> {
      Flux<Person> people = repository.allPeople();
      return Response.ok().body(fromPublisher(people, Person.class));
    }))
  .and(route(POST("/person"),
    request -> {
      Mono<Person> person = request.body(toMono(Person.class));
      return Response.ok().build(repository.savePerson(person));
    }));

여기서 실행 방법을 소개하고 Reactor Netty의 예를 소개합니다.

HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter =
  new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create("localhost", 8080);
server.startAndAwait(adapter);

마지막으로 할 일 체험 요청을 해보세요:

$ curl &#39; 
{"name":"John Doe","age":42}

위 소개에는 많은 내용이 포함되어 있으니 좀 더 자세히 살펴보겠습니다!

핵심 컴포넌트

HandlerFunction, RouterFunction, FilterFunction 등 핵심 컴포넌트를 차례로 소개하면서 전체 프레임워크를 소개하겠습니다. 이 기사의 다른 유형과 함께 이 세 가지 인터페이스는 org.springframework.web.reactive.function 패키지에서 찾을 수 있습니다.

Handling Function

새 프레임워크의 시작점은 HandlerFunction으로, 이는 본질적으로 Function>이며, 여기서 Request와 Response는 새로 정의된 불변 인터페이스입니다. JDK8에 최적화된 기본 HTTP 메시지 설명 DSL을 제공합니다. ResponseEntity의 것과 매우 유사한 Response 인스턴스를 생성하기 위한 편리한 생성자가 있습니다. HandlerFunction에 해당하는 Annotation 메소드는 @RequestMapping으로 Annotation된 메소드이다.

다음은 상태가 200이고 본문이 문자열로 포함된 메시지를 반환하는 "Hello World"의 처리 방법입니다.

HandlerFunction<String> helloWorld =
  request -> Response.ok().body(fromObject("Hello World"));

위와 같이 Reactor에 구축된 처리 방법은 Flux, Mono 또는 기타 해당 스트림(Reactive Streams)의 게시자를 반환 유형으로 받아들일 수 있습니다.

응답을 매개변수가 아닌 반환 값으로 사용하기 때문에 처리 방법 자체에는 부작용이 없다는 점에 유의해야 합니다(본질적으로 BiConsumer인 Servlet.service(ServletRequest, ServletResponse)와 비교)). 부작용 없는 접근 방식에는 많은 이점이 있습니다. 즉, 테스트, 구축 및 최적화가 더 쉽습니다.

라우팅 기능

인바운드 요청은 RouterFunction에 의해 HandlerFunction으로 라우팅됩니다(예: Function>). 조건이 일치하면 라우팅 방법은 처리 방법을 실행하고, 그렇지 않으면 빈 결과를 반환합니다. 라우팅 방법에는 @RequestMapping 주석과 유사한 기능이 있습니다. 그러나 또 다른 중요한 차이점이 있습니다. 주석을 사용할 때 라우팅은 주석 값이 표현할 수 있는 범위로 제한되며 라우팅 방법을 사용할 때 이러한 메서드의 적용 범위를 처리하기가 어렵습니다. 쉽게 덮어쓰거나 교체할 수 있습니다.

다음은 인라인 처리 방식을 포함한 라우팅 방식의 예시이다. 이것은 약간 중복되어 보이지만 나중에 간소화할 것이므로 걱정하지 마십시오.

RouterFunction<String> helloWorldRoute = 
  request -> {
    if (request.path().equals("/hello-world")) {
      return Optional.of(r -> Response.ok().body(fromObject("Hello World")));
    } else {
      return Optional.empty();
    }
  };

일반적으로 완전한 라우팅 방법을 작성할 필요는 없지만 RouterFunctions.route()를 정적으로 도입하면 요청 조건자(RequestPredicate)(예: Predicate)를 사용할 수 있고 처리 방법(HandlerFunction) 라우팅 방법을 생성합니다. 판정이 성공하면 처리 방법이 반환되고, 그렇지 않으면 빈 결과가 반환됩니다. 다음은 위의 예를 라우트 메소드를 사용하여 다시 작성한 것입니다.

RouterFunction<String> helloWorldRoute =
  RouterFunctions.route(request -> request.path().equals("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

RequestPredicates.*를 정적으로 도입한 후 일치하는 경로, HTTP 메소드, 컨텐츠 유형 등 일반적으로 사용되는 판단 표현식을 사용할 수 있습니다. 등. 이렇게 하면 위의 예가 더욱 간소화됩니다.

RouterFunction<String> helloWorldRoute =
  RouterFunctions.route(RequestPredicates.path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

组合功能

两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用RouterFunction.and()方法实现,如下:

RouterFunction<?> route =
  route(path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")))
  .and(route(path("/the-answer"),
    request -> Response.ok().body(fromObject("42"))));

上面的例子如果路径匹配/hello-world会返回“Hello World”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的Optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。

请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:

RouterFunction<?> route =
  route(method(HttpMethod.GET).and(path("/hello-world")), 
    request -> Response.ok().body(fromObject("Hello World")))
  .and(route(method(HttpMethod.GET).and(path("/the-answer")), 
    request -> Response.ok().body(fromObject("42"))));

实际上,RequestPredicates中的大部分判断式都是组合的!比如RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的组合。所以上面的例子可以重写为:

RouterFunction<?> route =
  route(GET("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")))
  .and(route(GET("/the-answer"),
    request -> Response.ok().body(fromObject(42))));

方法引用

此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:

class DemoHandler {
  public Response<String> helloWorld(Request request) {
    return Response.ok().body(fromObject("Hello World"));
  }
  public Response<String> theAnswer(Request request) {
    return Response.ok().body(fromObject("42"));
  }}

注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:

DemoHandler handler = new DemoHandler(); // or obtain via DI
RouterFunction<?> route =
  route(GET("/hello-world"), handler::helloWorld)
  .and(route(GET("/the-answer"), handler::theAnswer));

过滤功能

由路由器函数进行映射的路由可以通过调用 RouterFunction.filter(FilterFunction) 来进行过滤, 这里的 FilterFunction 其实就是一个 BiFunction, Response>。函数的处理器(handler)参数代表的就是整个链条中的下一项: 这是一个典型的 HandlerFunction, 但如果附加了多个过滤器的话,它也能够是另外的一个 FilterFunction。让我们向路由添加一个日志过滤器:

RouterFunction<?> route =
  route(GET("/hello-world"), handler::helloWorld)
  .and(route(GET("/the-answer"), handler::theAnswer))
  .filter((request, next) -> {
    System.out.println("Before handler invocation: " + request.path());
    Response<?> response = next.handle(request);
    Object body = response.body();
    System.out.println("After handler invocation: " + body);
    return response;
  });

注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。

因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 Response> 结束, 那样它就会可能有一个 String 类型的响应消息体。我们可以通过使用 RouterFunction.andSame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:

RouterFunction<String> route =
  route(GET("/hello-world"), handler::helloWorld)
  .andSame(route(GET("/the-answer"), handler::theAnswer))
  .filter((request, next) -> {
    Response<String> response = next.handle(request);
    String newBody = response.body().toUpperCase();
    return Response.from(response).body(fromObject(newBody));
  });

使用注解的话,类似的功能可以使用 @ControllerAdvice 或者是一个 ServletFilter 来实现。

运行一个服务端

所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 HTTP 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 RouterFunctions.toHttpHandler() 来将一个路由器函数转换成 HttpHandler。HttpHandler 是 Spring 5.0 M1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: Reactor Netty, RxNetty, Servlet 3.1+, 以及 Undertow。在本示例中,我们已经展示了在 Reactor Netty 中运行一个路由会是什么样子的。对于 Tomcat 来说则是像下面这个样子:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("",
  System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);  
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();

需要注意的意见事情就是上面的东西并不依赖于一个 Spring 应用程序上下文。就跟 JdbcTemplate 以及其它 Spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。
还要注意的就是你也可以将一个路由器函数转换到一个 HandlerMapping中去,那样就它可以在一个 DispatcherHandler (可能是跟响应式的 @Controllers 并行)中运行了。

结论

이것으로 Spring의 새로운 기능적 웹 프레임워크에 대한 소개를 마칩니다. 간단히 요약하자면

처리 기능은 응답을 하여 요청을 처리합니다.

라우터 기능은 처리 기능과 연결되어 다른 라우터 기능과 공유할 수 있습니다.

라우터 기능은 필터 기능으로 필터링할 수 있으며,

라우터 기능은 반응형 네트워크 운영 메커니즘에서 실행할 수 있습니다.


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.