编写API
接口过程中,不可避免的会遇到一个问题,对于不同的接口,需要的字段不一样,但大多数情况下,使用的Service
层方法是相同的,也就是说,获取到的数据字段是一样的,但是往往不需要返回所有的字段。
常用的解决思路有两种,一种是针对每个接口定义VO
类,在数据返回时,将Service
层查到的数据复制到VO
类后再返回,这样的话就可以返回需要的字段,但这样也有缺点,不同的接口,需要定义专属的VO
类,这样会使类的数量增多,后期如果需要添加一个通用字段,那么需要在每个VO
类都添加字段,否则无法返回,后期维护工作量大,不好维护,其次是性能问题,数据返回到浏览器之前,都需要将数据复制到VO
类,这样会产生许多的中间实例,影响性能;
第二种方案,在数据序列化为JSON
字符串的时候,只序列化需要返回的字段,这种方法相对第一种方法,可以很好的避免第一种方法出现的缺点,对于Jackson
原生的注解,无法实现动态过滤需求,如果把注解加在实体字段上,无法实现动态过滤,于是有了改进方案,自定义注解,通过自定义注解获取需要返回或需要过滤的字段,在序列化时处理。
为了实现多注解,我们定义两个注解来实现,多注解可实现嵌套对象的字段过滤。
package com.jeeplus.common.annotation;
import java.lang.annotation.*;
/**
* JSON 返回字段过滤
*
* @author zhufeihong
* @since 2020/12/28 14:32
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(JsonFieldFilters.class)
public @interface JsonFieldFilter {
/**
* 对象类
*/
Class<?> type();
/**
* 只包含的字段
*/
String[] include() default {};
/**
* 不包含的字段,如果重新赋值那么默认值失效
*/
String[] exclude() default {"createBy", "updateBy"};
/**
* 不包含的字段,在exclude()默认值的条件下继续添加排除字段
*/
String[] addExclude() default {};
}
package com.jeeplus.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* JSON 返回字段过滤
*
* @author zhufeihong
* @since 2020/12/28 14:32
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonFieldFilters {
JsonFieldFilter[] value();
}
JSON
过滤器由于
Jackson
自带的过滤器com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter
不能满足需求,需要自定义过滤器,用于序列化时动态过滤。
package com.jeeplus.config.handler;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import java.util.*;
/**
* 自定义JSON序列化过滤器
*
* @author zhufeihong
* @since 2021/1/4 16:30
*/
@JsonFilter("JacksonJsonFilter")
public class SuberJacksonFilterProvider extends FilterProvider {
/**
* 包含字段 Map
*/
Map<Class<?>, Set<String>> includeMap = new HashMap<>();
/**
* 排除字段 Map
*/
Map<Class<?>, Set<String>> excludeMap = new HashMap<>();
/**
* 添加包含字段
*
* @param type 字段所属类
* @param fields 字段名数组
* @since 2021/1/4 17:03
*/
public void include(Class<?> type, String... fields) {
addToMap(includeMap, type, fields);
}
/**
* 添加排除字段
*
* @param type 字段所属类
* @param fields 字段名数组
* @since 2021/1/4 17:03
*/
public void exclude(Class<?> type, String... fields) {
addToMap(excludeMap, type, fields);
}
/**
* 实际执行添加包含/排除字段进对应Map的方法
*
* @param map 包含字段Map OR 排除字段Map
* @param type 字段所属类
* @param fields 字段名称数组
* @since 2021/1/4 17:04
*/
private void addToMap(Map<Class<?>, Set<String>> map, Class<?> type, String... fields) {
Set<String> fieldSet = map.getOrDefault(type, new HashSet<>());
fieldSet.addAll(Arrays.asList(fields));
map.put(type, fieldSet);
}
@Deprecated
@Override
public BeanPropertyFilter findFilter(Object filterId) {
throw new UnsupportedOperationException("Access to deprecated filters not supported");
}
@Override
public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
return new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider prov, PropertyWriter writer)
throws Exception {
if (apply(pojo.getClass(), writer.getName())) {
writer.serializeAsField(pojo, jgen, prov);
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, prov);
}
}
};
}
/**
* 判断是否序列化当前字段,在includeMap或不在excludeMap中的字段进行序列化
*
* @param type 字段所属类
* @param name 字段名称
* @return boolean 是否序列化
* @since 2021/1/4 17:09
*/
public boolean apply(Class<?> type, String name) {
Set<String> includeFields = includeMap.get(type);
Set<String> excludeFields = excludeMap.get(type);
if (includeFields != null && includeFields.contains(name)) {
return true;
} else if (excludeFields != null && !excludeFields.contains(name)) {
return true;
} else {
return includeFields == null && excludeFields == null;
}
}
}
JSON
序列化方法我们自定义了过滤器,要想实现字段过滤,需要自定义序列化方法。
package com.jeeplus.core.mapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ObjectArrays;
import com.jeeplus.common.annotation.JsonFieldFilter;
import com.jeeplus.common.annotation.JsonFieldFilters;
import com.jeeplus.config.handler.SuberJacksonFilterProvider;
/**
* JSON 序列化,用于API返回字段过滤
*
* @author zhufeihong
* @since 2020/12/28 15:11
*/
public class ResponseJsonFilterSerializer {
// JsonMapper 继承自com.fasterxml.jackson.databind.ObjectMapper
JsonMapper mapper = JsonMapper.getInstance();
SuberJacksonFilterProvider filterProvider = new SuberJacksonFilterProvider();
/**
* json数据返回时过滤字段
*
* @param clazz 需要设置规则的Class
* @param include 转换时包含哪些字段
* @param exclude 转换时过滤哪些字段
*/
public void filter(Class<?> clazz, String[] include, String[] exclude) {
if (clazz == null) {
return;
}
if (include != null && include.length > 0) {
filterProvider.include(clazz, include);
} else if (exclude != null && exclude.length > 0) {
filterProvider.exclude(clazz, exclude);
}
mapper.addMixIn(clazz, filterProvider.getClass());
}
/**
* json数据返回时过滤字段
*
* @param fieldFilters 注解数组进行过滤
* @since 2021/1/4 17:26
*/
public void filter(JsonFieldFilters fieldFilters) {
for (JsonFieldFilter json : fieldFilters.value()) {
this.filter(json.type(), json.include(),
ObjectArrays.concat(json.exclude(), json.addExclude(), String.class));
}
}
public String toJson(Object object) throws JsonProcessingException {
mapper.setFilterProvider(filterProvider);
return mapper.toJson(object);
}
}
ResponseJson
处理器定义以上准备就绪,我们需要定义一个处理器,用来调用我们的自定义序列化方法实现动态过滤字段。
package com.jeeplus.config.handler;
import com.google.common.collect.ObjectArrays;
import com.jeeplus.common.annotation.JsonFieldFilter;
import com.jeeplus.common.annotation.JsonFieldFilters;
import com.jeeplus.core.mapper.ResponseJsonFilterSerializer;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Objects;
/**
* JSON返回字段过滤处理器
*
* @author zhufeihong
* @since 2020/12/28 14:52
*/
public class JsonReturnFilterHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 如果有自定义的 @JsonFieldFilter 注解 就用我们这个Handler 来处理
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), JsonFieldFilters.class)
|| returnType.hasMethodAnnotation(JsonFieldFilters.class)
|| AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), JsonFieldFilter.class)
|| returnType.hasMethodAnnotation(JsonFieldFilter.class);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 设置这个就是最终的处理类了,处理完不再去找下一个类进行处理
mavContainer.setRequestHandled(true);
// 获得注解并执行filter方法 最后返回
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Annotation[] annotations = returnType.getMethodAnnotations();
ResponseJsonFilterSerializer jsonFilterSerializer = new ResponseJsonFilterSerializer();
Arrays.asList(annotations).forEach(a -> {
if (a instanceof JsonFieldFilter) {
JsonFieldFilter json = (JsonFieldFilter) a;
jsonFilterSerializer.filter(json.type(), json.include(),
ObjectArrays.concat(json.exclude(), json.addExclude(), String.class));
} else if (a instanceof JsonFieldFilters) {
jsonFilterSerializer.filter((JsonFieldFilters) a);
}
});
Objects.requireNonNull(response).setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
String json = jsonFilterSerializer.toJson(returnValue);
response.getWriter().write(json);
}
}
处理器定义好了,我们需要注册它,才能使用,在
Spring Boot
项目中,对于API
接口返回的JSON
数据,交由org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
处理后返回,我们需要在这个处理器前添加我们的自定义处理器,才能实现字段过滤,否则进行到这个过滤器后,就不会往下处理,直接返回数据了。
package com.jeeplus.config.handler;
import com.jeeplus.common.annotation.JsonFieldFilter;
import com.jeeplus.common.utils.collection.CollectionUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.util.ArrayList;
import java.util.List;
/**
* 配置 HandlerMethodReturnValueHandler
* 在此配置 API 接口返回的 JSON 字段过滤处理器
* 在处理器 {@link RequestResponseBodyMethodProcessor} 前添加 {@link JsonReturnFilterHandler}
* 默认的返回值处理配置 {@link RequestMappingHandlerAdapter#getDefaultReturnValueHandlers()}
*
* @author zhufeihong
* @see JsonFieldFilter
* @since 2020/12/30 10:10
*/
@Configuration
public class InitializingRequestMappingHandler implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() throws Exception {
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
if (CollectionUtil.isEmpty(returnValueHandlers)) {
return;
}
// 不能直接使用 returnValueHandlers集合,因为此集合被方法unmodifiableList设置为不可修改
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);
this.decorateHandlers(handlers);
adapter.setReturnValueHandlers(handlers);
}
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (int i = 0; i < handlers.size(); i++) {
// 在RequestResponseBodyMethodProcessor前添加自定义json数据返回处理器,用于返回字段过滤
if (handlers.get(i) instanceof RequestResponseBodyMethodProcessor) {
handlers.add(i, new JsonReturnFilterHandler());
break;
}
}
}
}
在方法上使用注解,填写只包含的字段或需要排除的字段,返回的
JSON
数据就可以实现动态过滤了,对于实体无侵入,不影响之前的方法。
/**
* 分页查询建筑物信息
*
* @param queryParam 查询参数
* @param page 分页参数
* @return java.util.Map
* @author zhufeihong
* @date 2020/11/4 16:48
*/
@Api
@RequestMapping("/findPage")
@JsonFieldFilter(type = BuildLocation.class, include = {"createDate", "updateDate", "id"})
@JsonFieldFilter(type = Page.class, include = {"pageSize", "pageNo", "count", "list"})
public Map<String, Object> findPage(@RequestAttribute BuildLocation queryParam,
@RequestAttribute Page<BuildLocation> page) {
Page<BuildLocation> pageList;
if (Strings.isBlank(queryParam.getAddressId())) {
pageList = new Page<>();
} else {
pageList = buildLocationService.findPage(page, queryParam);
}
return super.success(pageList);
}