Spring Boot + Spring MVC + Ratpack 可以吗?

Is Spring Boot + Spring MVC + Ratpack possible?

我们是一家 Spring 新手店,我们的 REST 端点严重依赖 Spring MVC。我们使用 Boot 和嵌入式 Tomcat 创建一个自托管 JAR。是否可以用 Ratback 替换 Tomcat,同时仍然保留我所有的 Spring MVC 代码?恐怕 Spring MVC 以某种方式绑定到 servlet 规范中,如果没有 servlet 容器就不会 运行。我知道 dsyer/spring-boot-ratpack 的工作,但在略读代码后无法确定 Spring MVC 是否可以使用桥接器正常运行。有没有人知道有什么工作可以让我们保留对 Spring MVC 的投资并让 Spring Boot 使用 Ratpack 来管理 HTTP 流量?

SpringMVC 编程模型不是很依赖 Servlet API,但它不受任何其他容器的支持(即不在 Ratpack 中)。现在有一些异步的东西并且 Servlet 3.1 进一步增强了它,所以如果那是 Ratpack 吸引你的部分,也许只是使用它会是一个更好的方法。不过,您不会一路走到反应式和非阻塞 IO。

我怀疑您的问题的症结可以提炼为:"can we put our Spring controllers on top of Ratpack's non-blocking HTTP layer?" 而对该问题最简单的回答是否定的,因为 MVC 编程模型不太适合 reactive/NIO 模型很好

但是,如果您的应用程序遵循一些常见的模型-视图-控制器-(和服务)模式,那么您的控制器实际上应该只是执行数据绑定和解析并委托给服务层。如果是这种情况,那么您的控制器中的代码可能已经是非阻塞的,您可以轻松地将其转换为 Ratpack 代码。

例如,在 Spring 启动应用程序中考虑以下 @RestController

@RestController
@RequestMapping("/user")
class UserController {

  @Autowired
  UserService userService

  @RequestMapping(method = RequestMethod.POST)
  Long create(@RequestBody @Valid User user) {
    User savedUser = userService.save(user)
    return savedUser.id
  }
}

Spring 的数据绑定方面是一个计算过程(即没有 I/O 绑定),因此我们可以轻松地将其转换为 Ratpack 处理程序:

import app.SpringConfig
import app.User
import app.UserService
import org.springframework.boot.SpringApplication
import org.springframework.context.ApplicationContext
import ratpack.jackson.JacksonModule

import static ratpack.groovy.Groovy.ratpack
import static ratpack.jackson.Jackson.fromJson
import static ratpack.jackson.Jackson.json
import static ratpack.spring.Spring.spring

ratpack {
  bindings {
    add(new JacksonModule())
    bindInstance(ApplicationContext, SpringApplication.run(SpringConfig))
  }
  handlers { ApplicationContext ctx ->
    register(spring(ctx))

    prefix("user") {
      handler { UserService userService ->
        byMethod {
          post {
            def user = parse(fromJson(User))
            blocking {
              userService.save(user)
            } then { User savedUser ->
              render(json(savedUser))
            }
          }
        }
      }
    }
  }
}

其中 SpringConfig 看起来像这样:

package app

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class SpringConfig {
  @Bean
  UserService userService() {
    new UserService()
  }
}

这里有一个功能测试来证明这一点:

package app

import com.fasterxml.jackson.databind.ObjectMapper
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import ratpack.test.ApplicationUnderTest
import ratpack.test.http.TestHttpClient
import spock.lang.Shared
import spock.lang.Specification

import static groovy.json.JsonOutput.toJson

class FuncSpec extends Specification {
  @Shared ApplicationUnderTest aut = new GroovyRatpackMainApplicationUnderTest()
  @Shared ObjectMapper mapper = new ObjectMapper()
  @Delegate TestHttpClient client = aut.httpClient
  def "should parse and save user"() {
    given:
    def user = new User(username: "dan", email: "danielpwoods@gmail.com")

    when:
    requestSpec { spec ->
      spec.body { b ->
        b.type("application/json")
        b.text(toJson(user))
      }
    }
    post('user')

    then:
    def savedUser = mapper.readValue(response.body.text, User)

    and:
    savedUser.id
  }
}

希望对您有所帮助!