如何使用Spring Cloud Feign发送form-url-encoded数据的POST请求

28

使用spring-mvc注解:

  • 如何定义一个可以进行POST form-url-encoded@FeignClient
9个回答

28

使用 FormEncoder 来处理 Feign 请求:

你的 Feign 配置可以像这样:

class CoreFeignConfiguration {
  @Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters

  @Bean
  @Primary
  @Scope(SCOPE_PROTOTYPE)
  Encoder feignFormEncoder() {
      new FormEncoder(new SpringEncoder(this.messageConverters))
  }
}

然后,可以像这样映射客户端:

@FeignClient(name = 'client', url = 'localhost:9080', path ='/rest',
    configuration = CoreFeignConfiguration)
interface CoreClient {
    @RequestMapping(value = '/business', method = POST, 
                 consumes = MediaType.APPLICATION_FORM_URLENCODED)
    @Headers('Content-Type: application/x-www-form-urlencoded')
    void activate(Map<String, ?> formParams)
}

9
请注意这行代码 Map<String, ?> formParams,问号是必须的。 - Max Peng
对于那些不认识Groovy的人 - 这是用Groovy编写的,因此不包括“return”、“;”等内容 :) - kazuar
并不是很有用,它要求我的POJO具有@FormProperty,但不检查超类,我无法将20个表单属性分别提供给客户端调用。 - Sercan Ozdemir
1
您不需要额外的头部注释,Feign会自动使用消费配置进行设置。 - Bahadir Tasdemir
我使用了这种方法,但 Content-Type 被 "application/json" 覆盖了。有什么建议吗? - Nicolas Mafra

25

具有 kazuar 解决方案简化版本的完整 Java 代码,与 Spring Boot 兼容:

import java.util.Map;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;

@FeignClient(name = "srv", url = "http://s.com")
public interface Client {

    @PostMapping(value = "/form", consumes = APPLICATION_FORM_URLENCODED_VALUE)
    void login(@RequestBody Map<String, ?> form);

    class Configuration {
        @Bean
        Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> converters) {
            return new SpringFormEncoder(new SpringEncoder(converters));
        }
    }
}

依赖:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

这可能与主题无关,但值得一提。如果您期望获得JSON响应,则可能需要为Decoder配置@Bean,例如return new GsonDecoder(); - Neeraj Singh
2
不需要,Spring Boot 中已经配置了 Jackson 解码器。 - MariuszS
1
是的,没错。默认的JSONDecoder已经配置好了。然而,我已经启用了gson作为默认转换器,并使用了自定义版本@Bean Gson upbeatGson() { return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();} - 因此我才提到。否则,如果JSONDecoder - 默认版本可以工作,那就没有必要了。 - Neeraj Singh
2
在您的@FeignClient注释中,只是缺少了configuration = yourClass.Configuration.class配置。 - Henrique Schmitt
2
正如@HenriqueSchmitt所说,我必须设置@FeignClient(configuration = Client.Configuration.class)才能使其工作。答案应该被编辑。 - thchp
显示剩余2条评论

10

仅为补充已接受的答案,我们还可以使用POJO而不是Map<String, ?>来传递表单参数给Feign客户端:

@FeignClient(configuration = CustomConfig.class)
interface Client {

    @PostMapping(
        path = "/some/path", 
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    void postComment(CommentFormDto formDto);
    ...
}
...
@Configuration
class CustomConfig {
    @Bean
    Encoder formEncoder() {
        return new feign.form.FormEncoder();
    }
}
...

class CommentFormDto {

    private static String willNotBeSerialized;

    private final Integer alsoWillNotBeSerialized;

    @feign.form.FormProperty("author_id")
    private Long authorId;

    private String message;

    @feign.form.FormProperty("ids[]")
    private List<Long> ids;
    
    /* getters and setters omitted for brevity */  
}

这将导致请求的主体看起来像这样:

author_id=42&message=somemessage&ids[]=1&ids[]=2

@FormProperty 注解允许设置自定义字段名称;请注意,POJO 的静态或 final 字段以及继承的字段将不会作为表单内容序列化。

Kotlin 版本:

import org.springframework.http.MediaType

@FeignClient(configuration = [CustomConfig::class])
interface Client {

    @PostMapping(
        path = "/some/path", 
        consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
    postComment(CommentFormDto formDto): responseDto
    ...
}
...
import feign.form.FormEncoder

@Configuration
class CustomConfig {
    @Bean
    fun formEncoder(): FormEncoder {
        return FormEncoder()
    }
}
...
import feign.form.FormProperty

data class CommentFormDto (

    @FormProperty("author_id")
    var authorId: Long

    @FormProperty("ids[]")
    var ids: List<Long>

)

1
非常感谢!我也遇到了同样的问题,@FormProperty 确实是我在寻找的!+1 - Yves Calaci
3
答案中有一点错误。应该是@FormProperty而不是@FeignProperty,但我无法编辑它。 - august0490

2

额外补充一点... 可以使用Spring抽象类org.springframework.util.MultiValueMap,该类没有其他依赖项(只需spring-cloud-starter-openfeign)。

@PostMapping(value = "/your/path/here", 
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
YourDtoResponse formUrlEncodedEndpoint(MultiValueMap<String, Object> params);

这个有用的数据结构的语法非常简单,如下所示:
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("param1", "Value 1");
params.add("param2", 2);

1

在 Kotlin 中测试: 对我有效的方法如下:

  1. 创建 Feign 配置:
@Configuration
class FeignFormConfiguration {
    @Bean
    fun multipartFormEncoder(): Encoder {
        return SpringFormEncoder(SpringEncoder {
            HttpMessageConverters(
                RestTemplate().messageConverters
            )
        })
    }
}
  1. 在你的Feign客户端中:
@FeignClient(
    value = "client",
    url = "localhost:9091",
    configuration = [FeignFormConfiguration::class]
)
interface CoreClient {
    @RequestMapping(
        method = [RequestMethod.POST],
        value = ["/"],
        consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
        produces = ["application/json"]
    )
    fun callback(@RequestBody form: Map<String, *>): AnyDTO?
}
  1. 使用Feign客户端进行消费:
// ----- other code --------
@Autowired
private lateinit var coreClient: CoreClient

fun methodName() {
  coreClient.callback(form)
}

// ----- other code --------


1
这对我有用。
@FeignClient(name = "${feign.repository.name}", url = "${feign.repository.url}")
public interface LoginRepository {

     @PostMapping(value = "${feign.repository.endpoint}", consumes = APPLICATION_FORM_URLENCODED_VALUE)
     LoginResponse signIn(@RequestBody Map<String, ?> form);
}

表单是Map<String, Object> loginCredentials = new HashMap<>();

该代码表示创建了一个名为loginCredentials的Map对象,其中键值对的键为字符串类型,值为Object类型。这个Map对象是用来存储用户登录凭证信息的。

引用自MariuszS的回答。 - saurabh
更新:使用POJO LoginResponse signIn(@RequestBody Request obj); 感谢@ghost28147 - saurabh

1

在Feign编码器中,如果要使用url-form-encoded数据进行POST请求,则必须使用FormEncoder

将依赖项包含到您的应用程序中:

Maven:

<dependency>
  <groupId>io.github.openfeign.form</groupId>
  <artifactId>feign-form</artifactId>
  <version>3.8.0</version>
</dependency>

将FormEncoder添加到您的Feign.Builder中,如下所示:

SomeFeign sample  = Feign.builder()
                      .encoder(new FormEncoder(new JacksonEncoder()))
                      .target(SomeFeign.class, "http://sample.test.org");

在Feign接口中
@RequestLine("POST /submit/form")
@Headers("Content-Type: application/x-www-form-urlencoded")
void from (@Param("field1") String field1, @Param("field2") String field2);

更多信息请参考: https://github.com/OpenFeign/feign-form


该方法会导致“Java.lang.NoSuchMethodError”错误。 - tf245yay

0

对于Feign.Builder,我的工作没有使用JacksonEncoder,只用了Feign FormEncoder:

将FormEncoder添加到您的Feign.Builder中:

SomeFeign sample  = Feign.builder()
                  .encoder(new FormEncoder())     <==difference here
                  .target(SomeFeign.class, "http://sample.test.org");

我在pom.xml中添加的Feign依赖项:

<dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
        <version>11.8</version>
    </dependency>

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-jackson</artifactId>
        <version>11.8</version>
    </dependency>
    
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form</artifactId>
        <version>3.8.0</version>
    </dependency>

pom.xml 中的父级是:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

根据Ramanan提供的,Feign接口如下:

@RequestLine("POST /submit/form")
@Headers("Content-Type: application/x-www-form-urlencoded")
void from (@Param("field1") String field1, @Param("field2") String field2);

0
public class CoreFeignConfiguration {

@Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters;

  @Bean
  @Primary
  @Scope("prototype")
  Encoder feignFormEncoder() {
      return new FormEncoder(new SpringEncoder(this.messageConverters));
  }
  @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
  
  @Bean
  Logger logger() {
    return  new MyLogger();
  }
  private static class MyLogger extends Logger {
        @Override
        protected void log(String s, String s1, Object... objects) {
            System.out.println(String.format(s + s1, objects)); // Change me!
        }
  }

}

在接口中 @FeignClient(name = "resource-service1", url = "NOT_USED", configuration = CoreFeignConfiguration.class)

public interface TestFc {

@RequestMapping( method=RequestMethod.POST,consumes = "application/x-www-form-urlencoded")
//@Headers("Content-Type: application/x-www-form-urlencoded")
ResponseEntity<String>  test(URI baseUrl,
        Map<String,?> request);

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接