您现在的位置是:首页 > 文章详情

spring api接口返回数据优化 —— 只返回需要的字段数据

日期:2018-10-30点击:366

概述

spring/spring boot 返回的json数据,通常包含了对象所有的字段,有时候浪费流量。例如一个接口有10个字段,而前端只需要2个字段,都返回会浪费流量。
解决方案:前端在header中传递需要包含或需要排除的字段;后端在返回数据前进行统一拦截,只返回需要的字段。
具有有多种实现方式(这里只提供spring boot)。

首先约定返回的BaseResult对象格式如下,里面result属性就是实际各种数据对象。

{ "ret":0, "msg":null, "result":{ "id":1, "name":"后摄像头53" }, "time":1540972430498 }

实现方式一:通过AOP controller来实现

aop实现步骤说明:

  • 判断返回的是不是BaseResult对象
  • 判断request header或params是否有x-include-fields、x-exclude-fields属性(有则取出来放入set中)
  • 满足以上条件则对BaseResult.result 对象进行处理,用Map替换result对象,Map只返回需要的字段。如果是Array或Collection则每个Item替换成一个Map。
 import com.cehome.cloudbox.common.object.BaseResult; import com.cehome.cloudbox.common.object.ItemsResult; import com.cehome.cloudbox.common.object.PageResult; import com.cehome.cloudbox.common.page.Page; import com.cehomex.spring.feign.FeignRequestHolder; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.beans.PropertyDescriptor; import java.util.*; @Aspect public class ControllerAOP { private static String INCLUDE_FIELDS = "x-include-fields"; private static String EXCLUDE_FIELDS = "x-exclude-fields"; private static String P_INCLUDE_FIELDS = "x-include-fields"; private static String P_EXCLUDE_FIELDS = "x-exclude-fields"; private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class); @Pointcut("within(@org.springframework.stereotype.Controller *)") public void controller() { } @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)") public void restController() { } @Around("(controller() || restController()) && execution(public * *(..))") public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable { try { Object object=joinPoint.proceed(); ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); if (requestAttributes != null) { HttpServletRequest request = requestAttributes.getRequest(); handleReturnValue(object,request); } return object; } finally { FeignRequestHolder.removeAll(); } } /** * 返回前端需要的字段 * @param o * @param request * @throws Exception */ public void handleReturnValue(Object o,HttpServletRequest request) throws Exception { if(!isSuccess(o)) return; //HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class); //HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class); String fields1 = StringUtils.trimToEmpty(request.getHeader(INCLUDE_FIELDS)); if(fields1.length()==0) fields1 = StringUtils.trimToEmpty(request.getParameter(P_INCLUDE_FIELDS)); String fields2 = StringUtils.trimToEmpty(request.getHeader(EXCLUDE_FIELDS)); if(fields2.length()==0) fields2 = StringUtils.trimToEmpty(request.getParameter(P_EXCLUDE_FIELDS)); if (fields1.length() > 0 || fields2.length() > 0) { Set<String> includes = fields1.length() == 0 ? new HashSet<>() : new HashSet<>(Arrays.asList(fields1.split(","))); Set<String> excludes = fields2.length() == 0 ? new HashSet<>() : new HashSet<>(Arrays.asList(fields2.split(","))); if (o instanceof BaseResult) { BaseResult result = (BaseResult) o; Object object = result.getResult(); result.setResult(convertResult(object, includes, excludes)); } else if (o instanceof ItemsResult) { ItemsResult result = (ItemsResult) o; Object object = result.getItems(); result.setItems(convertResult(object, includes, excludes)); } else if (o instanceof PageResult) { PageResult result = (PageResult) o; Object object = result.getPage(); if (object instanceof Page) { Page page=(Page) object; List datas = page.getDatas(); page.setDatas((List)convertResult(datas, includes, excludes)); } } } } private boolean isSuccess(Object object){ if(object==null) return false; if (object instanceof BaseResult) return ( (BaseResult) object).isSuccess(); if (object instanceof ItemsResult) return ( (ItemsResult) object).isSuccess(); if (object instanceof PageResult) return ( (PageResult) object).isSuccess(); return false; } /*private void handleObject(Object object, Set<String> includes, Set<String> excludes) throws Exception { PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(object); for (PropertyDescriptor pd : pds) { String name = pd.getName(); if (name.equals("class")) { continue; } if (excludes.contains(name) || !includes.contains(name)) { PropertyUtils.setProperty(object, name, null); } } }*/ /** * convert objects to maps * @param object * @param includes * @param excludes * @return * @throws Exception */ private Object convertResult(Object object, Set<String> includes, Set<String> excludes) throws Exception{ if (object instanceof Object[]) { Object[] objects = (Object[]) object; return convertArray(objects,includes,excludes); } else if (object instanceof Collection) { Collection collection = (Collection) object; return convertCollection(collection,includes,excludes); }else{ return convertObject(object,includes,excludes); } } private Collection<Map> convertCollection(Collection collection, Set<String> includes, Set<String> excludes) throws Exception{ Collection<Map> result=new ArrayList<>(); for (Object item : collection) { result.add(convertObject(item,includes,excludes)); } return result; } private Map[] convertArray(Object[] objects, Set<String> includes, Set<String> excludes) throws Exception{ Map[] result=new HashMap[objects.length]; for(int i=0;i<objects.length;i++){ result[i]=convertObject(objects[i],includes,excludes); } return result; } /** * convert object to map * @param object input * @param includes include props * @param excludes exclude props * @return * @throws Exception */ private Map convertObject(Object object, Set<String> includes, Set<String> excludes) throws Exception { Map<Object,Object> result=new HashMap<>(); if(!(object instanceof Map)) { PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(object); for (PropertyDescriptor pd : pds) { String name = pd.getName(); if (name.equals("class")) { continue; } if(!excludes.isEmpty() && excludes.contains(name)){ continue; } if(!includes.isEmpty() && !includes.contains(name)){ continue; } result.put(name,PropertyUtils.getProperty(object, name)); } }else { Map<Object,Object> map=(Map<Object,Object>) object; for(Map.Entry<Object,Object> entry :map.entrySet()){ String name= entry.getKey()==null?"":entry.getKey().toString(); if(!excludes.isEmpty() && excludes.contains(name)){ continue; } if(!includes.isEmpty() && !includes.contains(name)){ continue; } result.put(entry.getKey(),entry.getValue()); } } return result; } } 

用Map替换的方式改变了原来的对象,还有一种效率更好的不改变对象的方式,
就是把不需要返回的字段设为null,然后配置一个JSON处理bean,统一过滤null的字段。这种方式null的字段就不再返回前端,需要前端做些兼容。

 @Bean @Primary @ConditionalOnMissingBean(ObjectMapper.class) public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; }

实现方式二:自定义HttpMessageConverter来实现

spring boot缺省包含了好几个消息转换器,根据返回媒体类型进行匹配,第一个匹配上就忽略掉其它的了。
MappingJackson2HttpMessageConverter 是其处理JSON的消息转换器。

  • 所以,需要先删除缺省的MappingJackson2HttpMessageConverter
  • 继承MappingJackson2HttpMessageConverter,实现自定义的消息转换。
 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonValue; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { //-- 移除缺省的JSON处理器 for (int i = converters.size() - 1; i >= 0; i--) { HttpMessageConverter<?> messageConverter = converters.get(i); if (messageConverter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) converters.remove(i); } // -- 添加自己得JSON处理器 MappingJackson2HttpMessageConverter c = new MappingJackson2HttpMessageConverter() { @Override protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //-- 例子一: 转成fastjson对象,然后替换name字段 JSONObject json = (JSONObject) JSON.toJSON(object); json.getJSONObject("result").put("name", "coolma"); super.writeInternal(json, type, outputMessage); //-- 例子二: 用过滤器只保留name字段,其它字段不要。 //注意,例子二需要给BaseResult对象的result属性加上com.fasterxml.jackson.annotation.JsonFilter注解: // @JsonFilter("result") // private T result; MappingJacksonValue value = new MappingJacksonValue(object); value.setFilters(new SimpleFilterProvider().addFilter("result", SimpleBeanPropertyFilter.filterOutAllExcept("name"))); super.writeInternal(value, type, outputMessage); } }; c.setDefaultCharset(Charset.forName("UTF-8")); List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); c.setSupportedMediaTypes(mediaTypes); converters.add(c); } } 
原文链接:https://yq.aliyun.com/articles/662099
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章