标签(空格分隔): 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 服务,只须要将有性能瓶颈的接口改造下便可。