项目场景:
政务管理系统,采用spring-cloud微服务架构,模块间采用feign进行互相调用,在其他模块调用用户管理模块查询用户信息时候,出现如下问题。
问题及解决
问题feign客户端示例代码如下:
@FeignClient(name = "sys-user-manage", fallback = UserClientFallback.class)
public interface IUserClient {
String API_PREFIX = "/client";
String USER_INFO_BY_ID = API_PREFIX + "/user-info-by-id";
/**
* 获取用户信息
*
* @param userId 用户id
* @return
*/
@GetMapping(USER_INFO_BY_ID)
User userInfoById(@RequestParam("userId") Long userId);
}
1.服务端没有收到feign请求。
- 排查代码,发现服务端feign实现未注册http资源。问题代码示例如下:
@Slf4j
@Component
public class UserResource implements IUserClient {
@Override
public User userInfoById(Long userId) {
return null;
}
}
- 解决方式为:将feign服务端实现注册为http资源(@RestController),示例代码如下:
@RestController
@Slf4j
public class UserResource implements IUserClient {
@Override
public User userInfoById(Long userId) {
return null;
}
}
2.客户端请求成功,内容为null。
- 排查代码发现,FeignClient的fallback实现并未输出错误提示,问题代码示例如下:
@Slf4j
@Component
public class UserClientFallback implements IUserClient{
@Override
public User userInfoById(Long userId) {
return null;
}
}
- 解决方式为,修改fallback实现方式为fallbackFactory跟踪回退触发器的原因,最终修改的示例代码如下:
@FeignClient(name = "sys-user-manage", fallbackFactory = UserClientFallBackFactory.class)
//@FeignClient(name = "sys-user-manage", fallback = UserClientFallback.class)
public interface IUserClient {
String API_PREFIX = "/client";
String USER_INFO_BY_ID = API_PREFIX + "/user-info-by-id";
/**
* 获取用户信息
*
* @param userId 用户id
* @return
*/
@GetMapping(USER_INFO_BY_ID)
User userInfoById(@RequestParam("userId") Long userId);
}
@Slf4j
@Component
class UserClientFallBackFactory implements FallbackFactory<IUserClient> {
@Override
public IUserClient create(Throwable cause) {
return new IUserClient(){
@Override
public User userInfoById(Long userId) {
log.error("userInfoById error", cause);
return null;
}
};
}
}
关于FeignResource
需要将FeignClient实现并注册为http资源。@FeignClient不应在服务器和客户端之间共享接口,并且不再支持在类级别上批注接口。@FeignClient@RequestMapping
。示例代码如下:
//Feign客户端
@FeignClient("users")
public interface UserClient {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
//feign服务端资源
@RestController
public class UserResource implements UserClient {
@Override
public User getUser(Long id) {
//具体业务逻辑
return null;
}
}
关于FeignClient
1.写法
1.1.不推荐:不需要考虑feign客户端请求回退原因,可以使用如下方式,提供feign请求错误的默认代码路径,@FeignClientfallback。(该方式不方便feign客户端和资源端链接问题排查)。
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
1.2.推荐:如果需要访问进行回退触发器的原因,则可以使用 里面的属性,fallbackFactory@FeignClient。(该方式既能提供feign请求错误的默认代码路径,同时也能访问进行回退触发器的原因,方便问题排查。写法也可以参照上面问题2的内部实现方式。)
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}
}
static class FallbackWithFactory implements TestClientWithFactory {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
2.手动创建FeignClient。在某些情况下,可能不可以使用上述方法创建客户端。在这种情况下,您可以使用 Feign Builder API 创建客户端。下面是一个示例 它创建两个具有相同接口的 Feign 客户端,但为每个客户端配置 单独的请求拦截器。
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}
3.特殊参数
3.1.将 POJO 或 Map 参数注释为查询参数映射,Spring Cloud OpenFeign 提供了一个等效的注释@SpringQueryMap。示例如下:
// Params.java
public class Params {
private String param1;
private String param2;
// [Getters and setters omitted for brevity]
}
@FeignClient("demo")
public interface DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
3.2.如果将map作为方法参数传递,则通过将map中的键值对用‘=’连接来创建@MatrixVariable路径段。示例代码如下:
@GetMapping("/objects/links/{matrixVars}")
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars);