標籤(空格分隔): springboot springmvc chunkedjava
做者:王清培(Plen wang) 滬江Java資深架構師web
在以前的一次性能壓測的時候咱們發現一個細節問題,咱們使用 spring boot 建立的 web rest 項目,使用默認 spring mvc 做爲 web rest 框架。spring
這在使用上沒有太大問題,可是有一個影響性能的細節問題被發現了,說實話這個問題很難被發現。後端
咱們來看一個簡單的 demo,我使用 IDEA 建立一個 spring boot 項目,建立過程當中沒有什麼特別的選項須要調整,一路 next 。而後咱們建立一個簡單的 controller 。spring-mvc
package springboot.demo.controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import springboot.demo.model.User; /** * Created by plen on 2017/11/25. */ @RestController public class SpringMvcController { @RequestMapping("/user/{id}") public User hello(@PathVariable Long id) { User user = new User(); user.setID(id); user.setUserName("mvc."); return user; } }
再建立一個簡單的 model 。springboot
package springboot.demo.model; import lombok.Data; import lombok.EqualsAndHashCode; /** * Created by plen on 2017/11/25. */ @Data @EqualsAndHashCode public class User { private Long ID; private String userName; }
而後啓動訪問這個 controller ,注意看下返回的 http 信息裏多了一個 Transfer-Encoding:chunked 。Transfer-Encoding:chunked 在 HTTP 協議裏的意思是沒法計算 Content-Length 長度,須要分塊傳輸。架構
這是 spring mvc 的默認 complex object 傳輸方式,若是咱們返回的是一個簡單的對象就不會有這個問題。mvc
Transfer-Encoding:chunked 帶來的性能問題就是訪問一次數據在 __http__層面看確實是一次 http 請求,而經過 tcp 抓包工具查看會發現多了一次 tcp 傳輸。app
解決這個問題兩個層面均可以,一種是採用比較粗暴的方式在 servlet 容器層面解決,可是這個會帶來一個後果就是當咱們計算 complex object 大小的時候會比較複雜並且容易出錯,也會影響項目將來的分塊傳輸功能,效果不太好。框架
還有一種就是在應用層面解決,比較柔性也易於擴展,咱們能夠集成一個 rest 框架,最好是符合 JAX-RS 規範,本文咱們集成 Jersey 框架。
jersey 集成若是經過 @Component 方式那麼 jersey 會默認接管全部的 web servlet 請求處理,因此就須要咱們手動的配置專門用來處理 jersey servlet 的容器。
spring boot 解決了之前 spring 繁重的配置,提供了 auto config 功能,原來經過 web.xml 配置 servlet 的,如今須要用代碼來配置。spring boot 提供了讓咱們手動註冊 servlet bean 的方式。
org.springframework.boot.web.servlet.ServletRegistrationBean
ServletRegistrationBean 可讓咱們註冊servlet,咱們來看下完整代碼。
package springboot.demo.config; import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.servlet.ServletProperties; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * Created by plen on 2017/11/25. */ @Component public class JerseyServletBeanConfig { @Bean public ServletRegistrationBean jerseyServlet() { ServletRegistrationBean registrationBean = new ServletRegistrationBean(new ServletContainer(), "/rest/v1/*"); registrationBean.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyResourceConfig.class.getName()); return registrationBean; } }
這和原來在 web.xml 配置的是同樣的,設置 routing 地址,設置 Init 初始化參數,對應的 servlet class name 。
全部的 __"rest/v1/*"__ 請求都將被 ServletContainer jersey servlet 容器接管。
package springboot.demo.config; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.spring.scope.RequestContextFilter; import springboot.demo.controller.JerseyController; /** * Created by plen on 2017/11/25. */ public class JerseyResourceConfig extends ResourceConfig { public JerseyResourceConfig() { register(JerseyController.class); register(RequestContextFilter.class); } }
ResourceConfig 實際上是一個 jersey Application 類型。這是 __jersey 註冊 servlet 時規定的。
package springboot.demo.controller; import springboot.demo.model.User; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; /** * Created by plen on 2017/11/25. */ @Path("/user/") public class JerseyController { @Path("{id}") @GET @Produces(MediaType.APPLICATION_JSON) public User hello(@PathParam("id") Long id) { User user = new User(); user.setID(id); user.setUserName("jersey."); return user; } }
這是咱們應用代碼 Controller ,使用 JAX-RS 規範的註解進行設置便可。
這樣就解決了 sprng mvc 和 jersey rest 共同存在的問題,咱們也不須要將全部的返回 chunked 的接口都改爲 JAX-RS 的 rest 服務,只須要將有性能瓶頸的接口改造下便可。