前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >项目中HandlerMethodReturnValueHandler的应用

项目中HandlerMethodReturnValueHandler的应用

作者头像
简单的程序员
发布2020-04-20 12:02:24
2K0
发布2020-04-20 12:02:24
举报
文章被收录于专栏:奕仁专栏奕仁专栏

相信很多同学在项目开发,会遇到这种问题,就是某些字段如果为null,返回给前台,然后前端会各种null判断? 或者后端同学在返回之前对null之进行判空,然后返回。。。 这样做的后果是,随着系统的逐步升级,以及字段数量的递增,系统会出现大量无效代码(对null判断),并且这些代码会侵入系统,导致系统越来越臃肿。

今天,我分享一个方法,是springmvc提供的自定义接口,用来对返回值进行处理

在我们的系统里,会有很多大量的返回值要处理,做到这种可以自定义扩展的返回值null 用来”判空置字符串“是非常有必要的。 此前我已经写了一篇关于HandlerMethodReturnValueHandler接口的介绍,这篇主要用于应用,篇幅较长。 代码我先贴上,首先需要自定义空字段处理的handler,并实现接口,并利用jdk提供的注解的特点来实现,下来我贴上自定义的代码

代码语言:javascript
复制
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);
        }
    }
}

贴上自定义注解的信息

代码语言:javascript
复制
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 "";
}

加入选择器接口,这里加入接口是用来作约束

代码语言:javascript
复制
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);
}

下来贴上适配器:

代码语言:javascript
复制
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

代码语言:javascript
复制
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;
	
}

当对象内包装对象的时候,原理也是一样的,如下:

代码语言:javascript
复制
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

代码语言:javascript
复制
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配置上面的配置:

代码语言:javascript
复制
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代码:

代码语言:javascript
复制
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();
    } 
}
代码语言:javascript
复制
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
file

GitHub地址:SpringMVC的handlerMethodReturnValueHandler

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档