相信很多同学在项目开发,会遇到这种问题,就是某些字段如果为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("");
    }
}

返回结果: file

GitHub地址:SpringMVC的handlerMethodReturnValueHandler