解决feign接口返回泛型设置属性为null的问题

简介

feign是一种声明式http请求调用方式,工作原理就是根据FeignClient注解生成新的接口(也就是传说中的动态代理),常见使用方式如下所示:

@FeignClient(name="UserFeignService",url="${auth.url}",
        fallbackFactory = OrgFeignServiceFallback.class,
        configuration = FeignErrorDecoderConfiguration.class)
public interface OrgFeignService {

    /**
     *
     * @param org
     * @return
     */
    @PostMapping(value="Tenant/AddTenantOrg", consumes="application/json; charset=UTF-8")
    APIResultTO<TenantOrg> addOrg(OrgDto org, @RequestHeader("token")String token);
}

应用场景

1、序列化以及反序列化采用jackson

2、调用第三方采用feign注解式接口

问题分析

APIResultTO是一个api通用接口返回泛型类,TenantOrg为传入的具体泛型类,咱们来看下出问题的类:

@Getter
@Setter
@NoArgsConstructor
public class TenantOrg {
    /**
     */
    @JsonProperty("Id")
    private String Id;
    /**
     * 父级Id
     */
    @JsonProperty("PId")
    private String PId;
    /**
     * 租户代码
     */
    @JsonProperty("Tenant")
    private String tenant;
    /**
     * 组织架构名字
     */
    @JsonProperty("Name")
    private String name;
}

必须要用@JsonProperty("Id")或者@JsonSetter("Id")注解来显示声明属性名字,尤其是首字母为大写的情况,否则反序列化后的数据就为空值。

为什么TenantOrg类中的Id等其他属性跟第三方服务返回的json数据字段完全一致,却没有成功设置对应的属性呢,这个就要看下BeanDeserializer类的deserializeFromObject方法,从其名字上我们可以看出这是将请求返回的数据反序列化成对应的类对象:

public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        /* 09-Dec-2014, tatu: As per [databind#622], we need to allow Object Id references
         *   to come in as JSON Objects as well; but for now assume they will
         *   be simple, single-property references, which means that we can
         *   recognize them without having to buffer anything.
         *   Once again, if we must, we can do more complex handling with buffering,
         *   but let's only do that if and when that becomes necessary.
         */
        if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
            if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
                    && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
                return deserializeFromObjectId(p, ctxt);
            }
        }
        if (_nonStandardCreation) {
            if (_unwrappedPropertyHandler != null) {
                return deserializeWithUnwrapped(p, ctxt);
            }
            if (_externalTypeIdHandler != null) {
                return deserializeWithExternalTypeId(p, ctxt);
            }
            Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
            if (_injectables != null) {
                injectValues(ctxt, bean);
            }
            /* 27-May-2014, tatu: I don't think view processing would work
             *   at this point, so commenting it out; but leaving in place
             *   just in case I forgot something fundamental...
             */
            /*
            if (_needViewProcesing) {
                Class<?> view = ctxt.getActiveView();
                if (view != null) {
                    return deserializeWithView(p, ctxt, bean, view);
                }
            }
            */
            return bean;
        }
        final Object bean = _valueInstantiator.createUsingDefault(ctxt);
        // [databind#631]: Assign current value, to be accessible by custom deserializers
        p.setCurrentValue(bean);
        if (p.canReadObjectId()) {
            Object id = p.getObjectId();
            if (id != null) {
                _handleTypedObjectId(p, ctxt, bean, id);
            }
        }
        if (_injectables != null) {
            injectValues(ctxt, bean);
        }
        if (_needViewProcesing) {
            Class<?> view = ctxt.getActiveView();
            if (view != null) {
                return deserializeWithView(p, ctxt, bean, view);
            }
        }
        if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
            String propName = p.getCurrentName();
            do {
                p.nextToken();
                //如果要跟踪测试的话,直接定位到该位置就可以,你就会发现如果没有
                //JSONProperty之类的注解定义属性名字的话,Id、PId属性在_beanProperties都成了小写的属性
                SettableBeanProperty prop = _beanProperties.find(propName);
                if (prop != null) { // normal case
                    try {
                        prop.deserializeAndSet(p, ctxt, bean);
                    } catch (Exception e) {
                        wrapAndThrow(e, bean, propName, ctxt);
                    }
                    continue;
                }
                handleUnknownVanilla(p, ctxt, bean, propName);
            } while ((propName = p.nextFieldName()) != null);
        }
        return bean;
    }

具体如下图所示:

正如上面所示,用@JsonProperty注解配置的属性,在反序列化时就按照@JsonProperty注解定义的属性名相同,至于为什么在TenantOrg中定义的PId属性在使用时怎么变成了pid,

具体可以看下POJOPropertiesCollector类的_removeUnwantedProperties方法以及_renameProperties方法:

    protected void _removeUnwantedProperties(Map<String, POJOPropertyBuilder> props)
    {
        Iterator<POJOPropertyBuilder> it = props.values().iterator();
        while (it.hasNext()) {
            POJOPropertyBuilder prop = it.next();

            // 去除private属性,PId属性会在这里移除
            if (!prop.anyVisible()) {
                it.remove();
                continue;
            }
            // Otherwise, check ignorals
            if (prop.anyIgnorals()) {
                // first: if one or more ignorals, and no explicit markers, remove the whole thing
                if (!prop.isExplicitlyIncluded()) {
                    it.remove();
                    _collectIgnorals(prop.getName());
                    continue;
                }
                // otherwise just remove ones marked to be ignored
                prop.removeIgnored();
                if (!prop.couldDeserialize()) {
                    _collectIgnorals(prop.getName());
                }
            }
        }
    }
protected void _renameProperties(Map<String, POJOPropertyBuilder> props)
    {
        // With renaming need to do in phases: first, find properties to rename
        Iterator<Map.Entry<String,POJOPropertyBuilder>> it = props.entrySet().iterator();
        LinkedList<POJOPropertyBuilder> renamed = null;
        while (it.hasNext()) {
            Map.Entry<String, POJOPropertyBuilder> entry = it.next();
            POJOPropertyBuilder prop = entry.getValue();

            //被@JsonProperty注解的属性会找到对应的属性名
            Collection<PropertyName> l = prop.findExplicitNames();

            // no explicit names? Implicit one is fine as is
            if (l.isEmpty()) {
                continue;
            }
            it.remove(); // need to replace with one or more renamed
            if (renamed == null) {
                renamed = new LinkedList<POJOPropertyBuilder>();
            }
            // simple renaming? Just do it
            //在这里使用@JsonProperty注解里面定义的属性名,比如PId、Id等
            //所以使用了@JsonProperty注解后,我们就无需关注类里面属性的大小写,设置不用关注属性名
            if (l.size() == 1) {
                PropertyName n = l.iterator().next();
                renamed.add(prop.withName(n));
                continue;
            }
            // but this may be problematic...
            renamed.addAll(prop.explode(l));

            /*
            String newName = prop.findNewName();
            if (newName != null) {
                if (renamed == null) {
                    renamed = new LinkedList<POJOPropertyBuilder>();
                }
                prop = prop.withSimpleName(newName);
                renamed.add(prop);
                it.remove();
            }
            */
        }

        // and if any were renamed, merge back in...
        if (renamed != null) {
            for (POJOPropertyBuilder prop : renamed) {
                String name = prop.getName();
                POJOPropertyBuilder old = props.get(name);
                if (old == null) {
                    props.put(name, prop);
                } else {
                    old.addAll(prop);
                }
                // replace the creatorProperty too, if there is one
                _updateCreatorProperty(prop, _creatorProperties);
                // [databind#2001]: New name of property was ignored previously? Remove from ignored
                // 01-May-2018, tatu: I have a feeling this will need to be revisited at some point,
                //   to avoid removing some types of removals, possibly. But will do for now.
                if (_ignoredPropertyNames != null) {
                    _ignoredPropertyNames.remove(name);
                }
            }
        }
    }

springcloud feign请求:数据返回null

问题描述

调用方调用服务,DEBUG被调用方服务得到正确数据,但调用方返回的数据对象属性全为null

原因及解决方法:

在feign调用接口中与被调用方接口返回类型不一致

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Cloud中关于Feign的常见问题总结

    一.FeignClient接口,不能使用@GettingMapping 之类的组合注解 代码示例: @FeignClient("microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET) public User findById(@PathVariable(&quo

  • 详解spring cloud feign踩坑记录

    1:多客户端时,feign接口抽取到公共jar中,此时,客户端的启动类上需要对该jar中feign所在的包进行扫描,要在spring和feign中同时注册,否则启动时会报:"Consider defining a bean of type '******Feign' in your configuration." @SpringBootApplication @EnableTransactionManagement @EnableDiscoveryClient @ComponentSc

  • 详解spring cloud Feign使用中遇到的问题总结

    本文介绍了spring cloud Feign使用中遇到的问题总结,分享给大家,具体如下: 问题一: 在前面的示例中,我们讲过 @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) @GetMapping("/user/{id}") 这两个注解的效果是等价的,但是在Feign使用中,只能用上面的那种方式,不能直接用@GetMapping,下面我们将前面的那个示例中,改成@GetMappin

  • Spring Cloud Feign接口返回流的实现

    服务提供者 @GetMapping("/{id}") public void queryJobInfoLogDetail(@PathVariable("id") Long id, HttpServletResponse response) { File file = new File("xxxxx"); InputStream fileInputStream = new FileInputStream(file); OutputStream ou

  • SpringCloud Feign参数问题及解决方法

    这篇文章主要介绍了SpringCloud Feign参数问题及解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天遇到使用Feign调用微服务,传递参数时遇到几个问题 1.无参数 以GET方式请求 服务提供者 @RequestMapping("/hello") public String Hello(){ return "hello,provider"; } 服务消费者 @GetMapping("

  • 解决feign接口返回泛型设置属性为null的问题

    简介 feign是一种声明式http请求调用方式,工作原理就是根据FeignClient注解生成新的接口(也就是传说中的动态代理),常见使用方式如下所示: @FeignClient(name="UserFeignService",url="${auth.url}", fallbackFactory = OrgFeignServiceFallback.class, configuration = FeignErrorDecoderConfiguration.class

  • 解决flask接口返回的内容中文乱码的问题

    写一个简单的例子程序: # coding:utf-8 import flask from flask import json, jsonify, request, render_template app = flask.Flask(__name__) @app.route("/api", methods=["GET", "POST"]) def api(): if request.method == 'GET': return jsonify({

  • Feign接口方法返回值设置方式

    一.介绍 随着微服务的广泛应用,越来越多的企业都会使用微服务进行项目开发,在各个服务之间需要通过feign来进行通信,所以在feign调用接口中方法会接受其他服务接口不同类型返回值. 二.返回值设置 1.依据被调用服务接口设置相同返回类型 介绍:微服务A接口getUser 返回List<User>类型,微服务B通过feign调用方法也返回相同的结果类型. 特点:返回类型一一对应,在调用时不需要进行转化直接拿来就可以用. 缺点:扩展性不好,维护性不加. 解释:在目前springboot开发中,接

  • MyBatis在DAO层定义接口返回类型泛型无效的解决

    MyBatis DAO层定义接口返回类型泛型无效 今天很偶然的因为一次粗心而发现的一个mybatis问题,这里就写出来与大家分享一下. DAO层定义了一个接口,返回String集合,用于获取最热门的搜索信息. mapper.xml文件接口返回的类型却是search对象. 调用接口,返回的是search对象集合,没有报错,泛型没起到作用. 仔细一想,泛型是在编译阶段将我们的返回值类型匹配到一具体类型,而DAO层的接口却没有具体的返回值信息,所以在编译阶段它是可以通过的,这也就是说我们在DAO层定义

  • 解决feign调用接口不稳定的问题

    我就废话不多说了,大家还是直接看代码吧~ Caused by: java.net.SocketException: Software caused connection abort: recv failed at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketIn

  • Feign 集成 Hystrix实现不同的调用接口不同的设置方式

    问题描述 小编在写项目时遇到一个需求: 假设现在有三个项目A.B.C,其中A.B作为服务提供方,C作为调用方,需要实现C在调用A.B时实现不同的超时时间,比如C调用A时超时是2s,调用B时超时是3s.... 本来以为是很简单的事,但是面向百度编程时发现没有搜索到,官网也没有,这就难受了,小编属于那种不会主动研究源码的,都是项目有需要或者说看到别人改造了啥玩意特别有意思,否则都不去喵一眼,现在没办法只能研究一波源码,手动改造. 正文 正文分为三个部分描述 源码研究 提出方案 方案实现 源码研究 先

  • mybatis中方法返回泛型与resultType不一致的解决

    mybatis方法返回泛型与resultType不一致 当xxxMaaper.java的方法返回值类型是List<A>,而xxxMappper.xml中对应的sql的resultType指定为B对象,这样是不会包错的(即使A与B不存在关系) 原因分析: 1.集合对象原本就是存储对象,可以是不同的对象List 2.直接处理List类型常常会出现类型转换异常,jdk5出现泛型,使得程序员向list中存放相同类型对象 3.泛型作用于编译阶段,仅为了防止类型混乱而出现,类型转换异常 4.mybatis

  • feign调用返回object类型转换方式

    feign调用返回object类型转换 引入依赖 <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> /** * @Description: 将数据转换到相应的容器 * @param b

  • 利用feign调用返回object类型转换成实体

    目录 feign调用返回object转成实体 feign调用报类型转换错误 问题现象 排查过程 问题原因 解决办法 feign调用返回object转成实体 <dependency>     <groupId>com.fasterxml.jackson.core</groupId>     <artifactId>jackson-databind</artifactId>     <version>2.9.8</version&g

随机推荐