相信很多同学在项目开发,会遇到这种问题,就是某些字段如果为null,返回给前台,然后前端会各种null判断? 或者后端同学在返回之前对null之进行判空,然后返回。。。 这样做的后果是,随着系统的逐步升级,以及字段数量的递增,系统会出现大量无效代码(对null判断),并且这些代码会侵入系统,导致系统越来越臃肿。
今天,我分享一个方法,是springmvc提供的自定义接口,用来对返回值进行处理
在我们的系统里,会有很多大量的返回值要处理,做到这种可以自定义扩展的返回值null 用来”判空置字符串“是非常有必要的。 此前我已经写了一篇关于HandlerMethodReturnValueHandler接口的介绍,这篇主要用于应用,篇幅较长。 代码我先贴上,首先需要自定义空字段处理的handler,并实现接口,并利用jdk提供的注解的特点来实现,下来我贴上自定义的代码
package org.choviwu.movie.config.returnhandler;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
/**
* @author ChoviWu 2019年4月18日17:41:48
*/
@Slf4j
public class EmptyFieldReturnValueHandler implements HandlerMethodReturnValueHandler {
protected final HandlerMethodReturnValueHandler handlerMethodReturnValueHandler;
private final ReturnSelectorAdapter returnSelectorAdapter;
//自定义handler,并传入responsebody的handler做最终json转化处理
public EmptyFieldReturnValueHandler(HandlerMethodReturnValueHandler handlerMethodReturnValueHandler) {
this.handlerMethodReturnValueHandler = handlerMethodReturnValueHandler;
//自定义适配器(全局单例) 用来对所有自定义的返回值选择器进行适配
this.returnSelectorAdapter = NullToEmptyUtil.getInstance().getReturnSelectorAdapter();
}
//如果被@ResponseBody注解修饰的 返回true
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
long startTime = System.currentTimeMillis();
//反射调用返回值的所有字段,一般返回为(msg/data/code) 可根据需求进行编写
Field[] fields = returnValue.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
//获取字段 注解
if (field.isAnnotationPresent(NullToEmpty.class)) {
Object value = field.get(returnValue);
List<?> list = null;
//返回值是否是List
if (value instanceof List) {
list = (List<?>) value;
}
//如果是list 则list不为null 调用setProperties来进行递归操作
if (list != null) {
list.forEach(this::setProperties);
} else {
//被修饰的字段必须是包装类型 否则错误
Optional.of(value).ifPresent(this::setProperties);
}
}
} catch (Exception e) {
log.error("Exception :", e);
}
}
// true false 是否对responsebody注解做拦截 自定义返回?
// mavContainer.setRequestHandled();
log.info("实际处理时间:{} ms", (System.currentTimeMillis() - startTime));
//ResponseBody注解执行器
handlerMethodReturnValueHandler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
//对字段的处理,传入一个字段的对象
protected void setProperties(Object obj) {
//被修饰的字段必须是包装类型 否则错误
List<?> list = null;
//如果是分页对象 拿到分页的list
if (obj instanceof PageInfo) {
try {
Field field = obj.getClass().getDeclaredField("list");
field.setAccessible(true);
List<?> objRet = (List<?>) field.get(obj);
list = objRet;
} catch (Exception e) {
log.error("Error :", e);
}
}
//如果有分页 list即不为空,进行递归操作,获取list下的对象
if (list != null) {
list.forEach(this::setProperties);
}
//单例---->递归调用获取对象的所有字段(包含父类的)
List<Field> valueFields = NullToEmptyUtil.getInstance().getSuperFields(obj.getClass(), Lists.newArrayList());
valueFields.forEach(valueField -> {
//如果对象套对象
valueField.setAccessible(true);
try {
//字段上如果有该注解 处理
if (valueField.isAnnotationPresent(NullToEmpty.class)) {
NullToEmpty empty = valueField.getAnnotation(NullToEmpty.class);
boolean toTrim = empty.toTrim() ? true : empty.toTrim();
boolean toNull = empty.toNull() ? true : empty.toNull();
String text = empty.text();
//重载trimNull方法
toTrimOrNull(toTrim, valueField, obj, text);
toTrimOrNull(toNull, valueField, obj, text);
} else {
toTrimOrNull(true, valueField, obj, "");
}
} catch (IllegalAccessException e) {
log.info("Exception : {}", e);
}
});
}
protected void dealFieldValue(Field valueField, Object obj,
Object fieldValue, String text) {
//类型判断
try {
//实际的适配器调用匹配的字段class
returnSelectorAdapter.match(obj, fieldValue, valueField, text);
} catch (Exception e) {
log.error("Error : ", e);
}
}
/**
* 重载ToTrim Null 方法
*/
protected void toTrimOrNull(boolean flag, Field valueField, Object obj, String text) throws IllegalAccessException {
toTrimOrNull(flag, valueField, obj, valueField.get(obj), text);
}
protected void toTrimOrNull(Field valueField, Object obj, Object fieldValue) throws IllegalAccessException {
toTrimOrNull(true, valueField, obj, fieldValue, "");
}
private void toTrimOrNull(boolean flag, Field valueField, Object obj, Object fieldValue, String text) throws IllegalAccessException {
if (flag) {
dealFieldValue(valueField, obj, fieldValue, text);
}
}
}
贴上自定义注解的信息
package org.choviwu.movie.config.returnhandler;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface NullToEmpty {
//是否为”“ 或者null 进行处理 默认true
boolean toTrim() default true;
// 是否置为null false
boolean toNull() default false;
//自定义选择器 适配器做适配
Class<? extends ReturnSelector> clazz() default ObjectReturnSelector.class;
/**
* 是否包装类型
* @return
*/
boolean isObj() default false;
//类型转化的目标字符串 ------>即 如果为null,转化为text
String text() default "";
}
加入选择器接口,这里加入接口是用来作约束
package org.choviwu.movie.config.returnhandler;
import java.lang.reflect.Field;
//加入泛型 用来做类型约束
public interface ReturnSelector<T> {
//是否支持该字段类型 返回为true才可以执行match方法
boolean supportsFieldType(Class<T> tClass, Field field);
//执行匹配逻辑
public void match(Object obj, T t, Field field, String str);
}
下来贴上适配器:
package org.choviwu.movie.config.returnhandler;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
@Component
public class ReturnSelectorAdapter {
//初始化容器后,自定义添加多个选择器,用来做筛选
private List<ReturnSelector> returnSelectors;
public ReturnSelectorAdapter(ReturnSelector... returnSelector){
returnSelectors.addAll(Arrays.asList(returnSelector));
}
public ReturnSelectorAdapter( ){
}
public void setReturnSelectors(List<ReturnSelector> returnSelectors) {
this.returnSelectors = returnSelectors;
}
public List<ReturnSelector> getReturnSelectors() {
return returnSelectors;
}
public void match(Object obj, Object result, Field field, String str) {
//ignored filter
if (field.isAnnotationPresent(Ignored.class)) {
return;
}
//是否指定某个选择器
Class<? extends ReturnSelector> clazz = getReturnSelector(field);
if (clazz!=null && clazz != ObjectReturnSelector.class) {
try {
//指定选择器 并实例化 调用对应的选择器
ReturnSelector returnSelector = null;
returnSelector = clazz.getConstructor(null).newInstance();
returnSelector.match(obj, result, field, str);
return;
} catch (Exception e) {
e.printStackTrace();
}
}
//未指定选择器,调用初始化后的选择器列表,筛选
for (ReturnSelector returnSelector : getReturnSelectors()){
if(returnSelector.supportsFieldType(field.getType(),field)){
//adapter
returnSelector.match(obj,result,field,str);
break;
}
}
}
private Class<? extends ReturnSelector> getReturnSelector(Field field){
if(field.isAnnotationPresent(NullToEmpty.class)){
NullToEmpty empty = field.getDeclaredAnnotation(NullToEmpty.class);
return empty.clazz();
}
return null;
}
}
自定义具体的选择器,以基本类型和包装类型为例,分别列出 Integer,String,WxUser
package org.choviwu.movie.config.returnhandler;
import java.lang.reflect.Field;
import java.util.Objects;
public class IntegerReturnSelector implements ReturnSelector<Integer> {
@Override
public boolean supportsFieldType(Class<Integer> integerClass, Field field) {
return (integerClass==Integer.class || "int".equals(field.getName().toString()));
}
@Override
public void match(Object obj, Integer s, Field field,String str) {
if(Objects.isNull(s)){
try {
field.set(obj,Integer.parseInt(str));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
package org.choviwu.movie.config.returnhandler;
import java.lang.reflect.Field;
import java.util.Objects;
public class StringReturnSelector implements ReturnSelector<String> {
@Override
public boolean supportsFieldType(Class<String> stringClass, Field field) {
return (stringClass == String.class);
}
@Override
public void match(Object obj, String s, Field field,String str) {
if(Objects.isNull(s)){
try {
field.set(obj,str);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
package org.choviwu.movie.config.returnhandler;
import org.choviwu.movie.model.WxUser;
import java.lang.reflect.Field;
import java.util.Objects;
public class WxUserReturnSelector implements ReturnSelector<WxUser> {
@Override
public boolean supportsFieldType(Class<WxUser> wxUserClass, Field field) {
return wxUserClass == WxUser.class;
}
//WxUser是用户自定义类型,
@Override
public void match(Object obj, WxUser wxUser, Field field, String str) {
if(wxUser!=null){
for (Field f : wxUser.getClass().getDeclaredFields()){
NullToEmptyUtil.getInstance().getField(wxUser,f);
}
}else{
wxUser = new WxUser();
try {
field.set(obj,wxUser);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
//WxUser包装对象将这样展示
package org.choviwu.movie.model;
import org.choviwu.movie.config.returnhandler.DateReturnSelector;
import org.choviwu.movie.config.returnhandler.IntegerReturnSelector;
import org.choviwu.movie.config.returnhandler.NullToEmpty;
import org.choviwu.movie.config.returnhandler.StringReturnSelector;
import java.util.Date;
@Data
public class WxUser {
//调用IntegerReturnSelector选择器
@NullToEmpty(text = "1",clazz = IntegerReturnSelector.class)
private Integer id;
//调用StringReturnSelector选择器
@NullToEmpty(text = "暂无",clazz = StringReturnSelector.class)
private String openid;
@NullToEmpty(text = "yyyy-MM-dd",clazz = DateReturnSelector.class)
private Date addtime;
@NullToEmpty(text = "暂无",clazz = StringReturnSelector.class)
private String addip;
}
当对象内包装对象的时候,原理也是一样的,如下:
package org.choviwu.movie.model;
import lombok.Data;
import org.choviwu.movie.config.returnhandler.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
//未被注解扫描到的则不会对该值进行处理
public class UserInputVo {
@NullToEmpty(text = "12213",clazz = IntegerReturnSelector.class)
private Integer id;
@NullToEmpty(text = "我是内容",clazz = StringReturnSelector.class)
private String content;
@NullToEmpty(text = "我是响应",clazz = StringReturnSelector.class)
private String response;
@NullToEmpty(text = "openId",clazz = StringReturnSelector.class)
private String openid;
@NullToEmpty(text = "标记",clazz = StringReturnSelector.class)
private String remark;
@NullToEmpty(text = "yyyyMMdd",clazz = DateReturnSelector.class)
private Date addtime;
private String addip;
//WxUser对象会调用WxUser选择器
@NullToEmpty(isObj = true,clazz = WxUserReturnSelector.class)
private WxUser wxUser;
//这里会指定一个属性 isObj 表示是否是对象类型----》对象类型即用户自定义对象pojo,包含getter/setter等
@NullToEmpty(isObj = true)
private Movie movie;
}
接下来列出核心的单例转化工具类NullToEmptyUtil
package org.choviwu.movie.config.returnhandler;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
/**
* @author ChoviWu
*/
@Slf4j
public class NullToEmptyUtil {
private static final NullToEmptyUtil empty = new NullToEmptyUtil();
//默认初始化一个单例的适配器,用来调用
private ReturnSelectorAdapter returnSelectorAdapter;
//单例模式
public static NullToEmptyUtil getInstance() {
return empty;
}
public void setReturnSelectorAdapter(ReturnSelectorAdapter returnSelectorAdapter) {
this.returnSelectorAdapter = returnSelectorAdapter;
}
public ReturnSelectorAdapter getReturnSelectorAdapter() {
return returnSelectorAdapter;
}
//获取字段的值,并包装好,用适配器调用匹配
public void getField(Object object, Field field) {
//value
NullToEmpty returnNull = field.getDeclaredAnnotation(NullToEmpty.class);
StringBuilder text = new StringBuilder();
if (returnNull != null) {
text.append(returnNull.text());
}
field.setAccessible(true);
try {
Object result = field.get(object);
returnSelectorAdapter.match(object, result, field, text.toString());
}catch (Exception e){
log.error("get field exception :{} ,please checked field {}",e,field);
}
}
//获取对象的所有字段
public List<Field> getSuperFields(Class<?> obj, List<Field> list) {
//
if (obj == Object.class) {
return list;
}
Field[] fields = obj.getDeclaredFields();
Arrays.asList(fields).forEach(list::add);
//递归操作
Class clss = obj.getSuperclass();
return getSuperFields(clss, list);
}
}
最后,用java config配置上面的配置:
package org.choviwu.movie.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.choviwu.movie.config.returnhandler.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* @author Administrator
*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport implements InitializingBean{
@Autowired
RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Bean("jackson2HttpMessageConverter")
public HttpMessageConverter jackson2HttpMessageConverter(){
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDefaultVisibility(JsonAutoDetect.Value.defaultVisibility());
objectMapper.setDateFormat(DateFormat.getDateInstance());
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
@Override
public void afterPropertiesSet() throws Exception {
NullToEmptyUtil.getInstance().setReturnSelectorAdapter(selectorAdapter());
List<HandlerMethodReturnValueHandler> unmodifiableList = requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> list = new ArrayList<>(unmodifiableList.size());
for (HandlerMethodReturnValueHandler returnValueHandler : unmodifiableList) {
if (returnValueHandler instanceof RequestResponseBodyMethodProcessor) {
//将RequestResponseBodyMethodProcessor 实际返回值替换为自定义的,实际执行为RequestResponseBodyMethodProcessor
//重要
HandlerMethodReturnValueHandler handler = new EmptyFieldReturnValueHandler(returnValueHandler);
list.add(handler);
}
else {
list.add(returnValueHandler);
}
}
requestMappingHandlerAdapter.setReturnValueHandlers(list);
}
@Bean
public ReturnSelectorAdapter selectorAdapter(){
ReturnSelectorAdapter adapter = new ReturnSelectorAdapter();
List<ReturnSelector> list = Lists.newArrayList();
list.add(new IntegerReturnSelector());
list.add(new DoubleReturnSelector());
list.add(new LongReturnSelector());
list.add(new DateReturnSelector());
list.add(new StringReturnSelector());
list.add(new ObjectReturnSelector());
// list.add(new IntegerReturnSelector());
adapter.setReturnSelectors(list);
return adapter;
}
}
以上面的代码为例,我跑一个例子: 贴上test代码:
package org.choviwu.movie.service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.choviwu.movie.base.ApiResponse;
import org.choviwu.movie.mapper.MovieMapper;
import org.choviwu.movie.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* Created by ChoviWu on 2018/06/23
* Description:
*/
@Service
public class MovieService {
private final MovieMapper movieMapper;
@Autowired
MovieService(MovieMapper movieMapper){
this.movieMapper = movieMapper;
}
/**
*/
public Object getListByType2(String type){
List<Movie> list = movieMapper.getMovieListByType(type);
List<MovieVo> movieVos = Lists.newArrayList();
list.forEach(c->{
MovieVo movieVo = new MovieVo();
UserInputVo userInput = new UserInputVo();
userInput.setAddip(null);
WxUser user = new WxUser();
user.setAddtime(new Date());
userInput.setWxUser(user);
userInput.setMovie(c);
movieVo.setUserInputVo(userInput);
movieVos.add(movieVo);
});
return ApiResponse.builder().code(1).data(movieVos).msg("success").build();
}
}
package org.choviwu.movie.controller;
import org.choviwu.movie.annotation.Response;
import org.choviwu.movie.annotation.Validator;
import org.choviwu.movie.annotation.ValidatorBody;
import org.choviwu.movie.service.MovieService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@Autowired
MovieService movieService;
@ResponseBody
@RequestMapping(value = "abcde")
public Object abcde(){
return movieService.getListByType2("");
}
}
返回结果: