在 Spring Boot 微服务中使用 FeignClient 报错 302
Posted
技术标签:
【中文标题】在 Spring Boot 微服务中使用 FeignClient 报错 302【英文标题】:Error 302 Using FeignClient in Spring Boot Microservices 【发布时间】:2021-12-30 10:04:47 【问题描述】:我遇到了 FeignClient 的问题。我部署了 Spring Boot 应用程序,在调用特定 feign 客户端时出现错误,当我想与用户微服务的特定方法通信时使用注册微服务时出现错误,而使用其他方法时问题不会发生,我还有一个用于发现的 Eureka 服务器和一个带有 Spring Cloud Gateway 的网关,配置了权限配置。我在应用程序中有@EnableEurekaClient 和@EnableFeignClients,它们可以在Eureka 服务器上看到,并使用resilience4j 实现CircuitBreaker。 对于测试,我使用邮递员。
请求:
没有CircuitBreaker我得到这个错误
feign.FeignException: [302] during [GET] to [http://app-usuarios/users/usuarioExisteDatos/?username=admin&email=admin%40udea.edu.co&cellPhone=3128211358] [UsersFeignClient#preguntarUsuarioExiste(String,String,String)]: true
at feign.FeignException.errorStatus(FeignException.java:182) ~[feign-core-10.12.jar:na]
at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.12.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.12.jar:na]
at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96) ~[feign-core-10.12.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.12.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.12.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.12.jar:na]
at jdk.proxy11/jdk.proxy11.$Proxy250.preguntarUsuarioExiste(Unknown Source) ~[na:na]
at com.app.registro.controllers.RegistroController.crearNuevo(RegistroController.java:28) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.13.jar:5.3.13]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.13.jar:5.3.13]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.13.jar:5.3.13]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.55.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.13.jar:5.3.13]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.55.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.13.jar:5.3.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.13.jar:5.3.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.13.jar:5.3.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.13.jar:5.3.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.13.jar:5.3.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.13.jar:5.3.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1722) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.55.jar:9.0.55]
at java.base/java.lang.Thread.run(Thread.java:831) ~[na:na]
使用断路器:
[302] during [GET] to [http://app-usuarios/users/usuarioExisteDatos/?username=admin&email=admin%40udea.edu.co&cellPhone=3128211358] [UsersFeignClient#preguntarUsuarioExiste(String,String,String)]: [true]
对于我的注册微服务:
型号:
@Document(collection = "registro")
public class Registro
@Id
private String id;
@NotBlank(message = "Username cannot be null")
@Size(max = 20)
@Indexed(unique = true)
@Pattern(regexp = "[A-Za-z0-9_.-]+", message = "Solo se permite:'_' o '.' o '-'")
private String username;
@NotBlank(message = "Password cannot be null")
@Pattern(regexp = "[^ ]*+", message = "Caracter: ' ' (Espacio en blanco) invalido")
@Size(min = 6, max = 20, message = "About Me must be between 6 and 20 characters")
private String password;
@NotBlank(message = "Cell phone cannot be null")
@Pattern(regexp = "[0-9]+", message = "Solo numeros")
@Size(max = 50)
@Indexed(unique = true)
private String cellPhone;
@NotBlank(message = "Email cannot be null")
@Size(max = 50)
@Pattern(regexp = "[^ ]*+", message = "Caracter: ' ' (Espacio en blanco) invalido")
@Email(message = "Email should be valid")
@Indexed(unique = true)
private String email;
private String codigo;
private List<String> roles;
** Constructors, setters and getters
我的客户:
@FeignClient(name = "app-usuarios")
public interface UsersFeignClient
@GetMapping("/users/usuarioExisteDatos")
public Boolean preguntarUsuarioExiste(@RequestParam(value = "username") String username,
@RequestParam(value = "email") String email, @RequestParam(value = "cellPhone") String cellPhone);
@GetMapping("/users/listar")
public List<Usuario> listarUsuarios();
我的控制器:
@RestController
public class RegistroController
private final Logger logger = LoggerFactory.getLogger(RegistroController.class);
@SuppressWarnings("rawtypes")
@Autowired
private CircuitBreakerFactory cbFactory;
@Autowired
UsersFeignClient uClient;
@GetMapping("/registro/listarUsuarios")
public List<Usuario> verUsuarios()
return uClient.listarUsuarios();
@PostMapping("/registro/crearNuevo")
@ResponseStatus(code = HttpStatus.CREATED)
public Boolean crearNuevo(@RequestBody @Validated Registro registro)
// return uClient.preguntarUsuarioExiste(registro.getUsername(),
// registro.getEmail(), registro.getCellPhone());
return (Boolean) cbFactory.create("usuarios").run(() -> uClient.preguntarUsuarioExiste(registro.getUsername(),
registro.getEmail(), registro.getCellPhone()), e -> preguntarUsuarioExiste2(registro.getUsername(), e));
private Object preguntarUsuarioExiste2(String username, Throwable e)
logger.info(e.getMessage());
return false;
我的应用程序属性:
#-------APP-------
spring.application.name=app-registro
server.port=$PORT:0
#-----MongoDb------
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=user
spring.data.mongodb.password=user
spring.data.mongodb.database=usuariosApp
spring.data.mongodb.auto-index-creation: true
#-----Eureka-------
eureka.instance.metadataMap.instanceId=$spring.application.name:$spring.application.instance_id:$random.value
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
#-----Feign-------
feign.client.config.default.connect-timeout=10000
feign.client.config.default.read-timeout=10000
feign.client.config.default.logger-level=full
我的 Pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.app.registro</groupId>
<artifactId>App-Registro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>App-Registro</name>
<description>Registro for App</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>$spring-cloud.version</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
我在微服务usuarios中的方法:
@GetMapping("/users/usuarioExisteDatos")
@ResponseStatus(HttpStatus.FOUND)
public Boolean preguntarUsuarioExiste(@RequestParam(value = "username") String username,
@RequestParam(value = "email") String email, @RequestParam(value = "cellPhone") String cellPhone)
throws InterruptedException
return uRepository.existsByUsernameOrEmailOrCellPhone(username, email, cellPhone);
请注意,您正在调用 MongoRepository 接口
如果我调用另一个客户端方法 listUsers,feign 客户端可以正常工作:
我在微服务usuarios中的方法是:
@GetMapping("/users/listar")
@ResponseStatus(code = HttpStatus.CREATED)
public List<Usuario> listarUsuarios()
return uRepository.findAll();
我不明白为什么会这样
【问题讨论】:
【参考方案1】:您在这里有几个选择,但让我澄清一下为什么会发生这种情况。 Feign 是 API 的 HTTP 绑定器。在正常情况下,当您进行后端-后端通信时,事实上接受的 HTTP 状态代码是 2xx
,表示一切都按预期工作。当 API 以 3xx
(在您的情况下为 302
)响应时,这表示重定向,通常用于指示浏览器在执行某些操作时将用户重定向到另一个页面。
无论如何,既然我们已经弄清楚了为什么会发生这种情况,让我们看看为什么您的 Feign 客户端会这样。所有 Feign 客户端都有一个名为 follow-redirects
的配置参数。这控制在收到3xx
HTTP 响应时,Feign 客户端是否应该自动尝试调用响应的Location
标头中指定的 API。
默认情况下,此参数设置为 true,这意味着将遵循重定向,并且对于作为客户端用户的您而言,它将是透明的。从异常中,我认为您以某种方式禁用了它,或者您可能使用了手动禁用重定向的 HTTP 客户端。
虽然我可以从您在preguntarUsuarioExiste
方法中的实现中清楚地看到您正在尝试确定系统中是否存在用户。在这种情况下,302 HTTP Found
状态没有意义,即使我理解您为什么要使用它(因为该术语反映了用户的存在)。
在这种情况下,我只需使用@ResponseStatus
注释删除固定的302
状态并更改API 以返回ResponseEntity
,而不是动态解析状态代码。像这样的:
@GetMapping("/users/usuarioExisteDatos")
public ResponseEntity<?> preguntarUsuarioExiste(@RequestParam(value = "username") String username, @RequestParam(value = "email") String email, @RequestParam(value = "cellPhone") String cellPhone) throws InterruptedException
boolean exists = uRepository.existsByUsernameOrEmailOrCellPhone(username, email, cellPhone);
if (exists)
return ResponseEntity.ok().build();
else
return ResponseEntity.notFound().build();
这样当你从 Feign 客户端调用 API 时,你可以简单地处理 404 的情况,因为没有找到用户。或者更好的是,您可以简单地创建一个对象作为 API 的响应,无论用户是否存在,该对象都具有布尔值,例如:
"exists": false
然后你可以在你的 Feign 客户端中映射这个对象并处理纯布尔值。
最后,如果您想坚持使用 302 状态码,您可以更改您的 Feign 客户端定义以返回 feign.Response
类而不是 Boolean
。
这样,它不会因异常而失败,但您将完全控制响应应该发生的事情。您可以访问状态码、正文以及您需要的一切。
我强烈建议您多了解一下 Feign,您可能会陷入更多的罪魁祸首,尤其是当您将它与 Eureka 和 Resilience4J 等服务弹性工具结合使用时。而且我不是想在这里做广告,但我真的相信你需要一些指导。
查看我的博客关于 Feign 的文章:arnoldgalovics.com Feign articles
另外,请查看我的 Feign、Spring Cloud OpenFeign 和 Resilience4J 集成课程。我几乎涵盖了您需要的所有内容:Mastering microservice communication with Spring Cloud Feign
【讨论】:
以上是关于在 Spring Boot 微服务中使用 FeignClient 报错 302的主要内容,如果未能解决你的问题,请参考以下文章
最新版Spring Cloud Alibaba微服务架构-Openfeign服务调用篇