几年前突发奇想,将@RestController和@FeignClient放在了一下,这样测试的时候会方便很多,不用走业务就可以调用接口,然后误打误撞发现真的可以,
新项目使用springboot3,在pom.xml文件,引用dependency.version会自动提示版本号,所以就没有去查相应支持的版本,用的提示版本3.1.4,然后出现了很多问题
问题列表
- 无法同时使用@RestController和@FeignClient,在springboot2.6中就已经不可以使用,
- Method search not annotated with HTTP method type (ex. GET, POST)
- @Param,@Header注解的使用方式
- Feign自己实现方式导致找不到FeignContext - Consider defining a bean of type openfeign.FeignContext
- Consider revisiting the conditions above or defining a bean of type ‘org.springframework.cloud.netflix.feign.FeignContext’ in your configuration.
- No qualifying bean of type ‘org.springframework.cloud.openfeign.FeignContext’ available
- NoSuchBeanDefinitionException: No qualifying bean of type
首先,检查你的版本,我当前用的是
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.0.3</version>
</dependency>
这个就能解决很多问题了,然后用MVC实现的方式去写接口即可
用接口实现代理转发
@RestController和@FeignClient无法同时使用时,继承RequestMappingHandlerMapping,复写isHandler方法
public class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping implements WebMvcRegistrations {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, RestController.class) ||
// 当前类上存在RequestMapping 但是不存在FeignClient注解
(AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)
&& !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class)
));
}
}
//注册
@Component
public class FeignWebMvcRegistrations implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignRequestMappingHandlerMapping();
}
}
参考文章:当@FeignClient遇到@RequestMapping,spring容器启动报错
https://blog.csdn.net/sssdal19995/article/details/121023316
feign请求接口路径映射
因为springboot改版, @RestController/@RequestMapping和@FeignClient不可同时出现在同一个类中
当@RequestMapping与@FeignClient同时出现在一个类中时,两个注解中的path必须统一
这个path路径单独映射问题好像出现在2.5版本以后
@Component
public class FeignSpringMvcContract extends SpringMvcContract {
private static final Log LOG = LogFactory.getLog(FeignSpringMvcContract.class);
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class);
if (collectionFormat != null) {
data.template().collectionFormat(collectionFormat.value());
}
}
}
调用方式
说明一下SpringMvcContract
它是Feign框架中的一个契约实现类,用SpringMVC的方式去做接口调用服务,说白了就是okhttp和httpclient的区别,或者jpa中jsql和hibernate,querydsl的区别
MvcContract例子
@RestController
@RequestMapping(path = "/provider")
@FeignClient(name = "ProviderUserServerApi", url = "${feign.userServerHost}/api", path = "/provider", fallback = UserServerApiHystrix.class)
@Schema(description = "用户(服务接口)")
public interface UserServerApi {
@GetMapping(value = "/user/getUserByToken")
@Headers({"Content-Type: application/json;charset=UTF-8"})
FeignResult<UserDto> getUserByToken(@RequestHeader("token") String token);
@GetMapping("/user/login/{code}")
FeignResult<UserDto> login(@Valid @PathVariable("code") String code);
@GetMapping("/user/testLogin/{userId}")
FeignResult<UserDto> testLogin(@Valid @PathVariable("userId") Long userId);
@GetMapping("/user/userInfoByToken")
FeignResult<UserDto> userInfoByToken(@Valid @RequestParam("token") String token);
}
Feign原生例子
//Application启动类上添加注解: @ImportAutoConfiguration({FeignAutoConfiguration.class})
public interface UserServerApi {
@RequestLine("GET /user/getUserByToken")
@Headers({"Content-Type: application/json;charset=UTF-8", "token: ${token}"})
FeignResult<ThirdPartyUser> getUserByToken(@Param String token);
@RequestLine("GET /user/login/{code}")
FeignResult<ThirdPartyUser> login(@Valid @Param("code") String code);
}
@Component
public class UserServerApiClient {
public FeignResult<UserDto> getUserByToken(@RequestParam("token") String token) {
UserServerApi api = Feign.builder()
.encoder(new FastEncoder())
.decoder(new FastDecoder())
.options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
.target(UserServerApi.class, userServerHost + "/api/provider");
return api.getUserByToken(token);
}
}
动态URL方式
@FeignClient(name = "ProviderServerFeign", url = "EMPTY", fallback = ProviderServerHystrix.class)
public interface ProviderServerFeign {
/**
* 服务状态,心跳
*
* @return 查询结果
*/
@ApiOperation(value = "服务状态")
@GetMapping(value = "/live/serviceStatus")
FeignResult<String> serviceStatus(URI uri);
}
//调用方式
providerServerFeign.serviceStatus(URI.create("http://" + serverAddress + "/api"));
偷懒小技巧
如果只是作为代理用可以省去参数验证什么的
@RestController
@RequestMapping(path = "/provider")
@FeignClient(name = "ProviderUserServerApi", url = "${feign.userServerHost}/api", path = "/provider", fallback = UserServerApiHystrix.class)
@Schema(description = "用户(服务接口)")
public interface UserServerApi {
@GetMapping(value = "/user/getUserByToken")
@Headers({"Content-Type: application/json;charset=UTF-8"})
Object getUserByToken(@RequestHeader("token") Object token);
@GetMapping("/user/login/{code}")
Object login(@PathVariable Object code);
@GetMapping("/user/testLogin/{userId}")
Object testLogin(@PathVariable Object userId);
@GetMapping("/user/userInfoByToken")
Object userInfoByToken(@RequestParam Object token);
@GetMapping("/user/userInfoByToken")
Object addUser(@RequestBody Object vo);
}
编码解码
编码解码需要单独引用类,例如feign的Gson,但是因为解析问题,我借鉴了Gson的实现方式用fastjson实现了编解码类
//gson引用
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>12.4</version>
</dependency>
//fastjson实现方式
public class FastEncoder implements Encoder {
public void encode(Object object, Type bodyType, RequestTemplate template) {
template.body(JSON.toJSONString(object));
}
}
public class FastDecoder implements Decoder {
public Object decode(Response response, Type type) throws IOException {
if (response.status() != 404 && response.status() != 204) {
if (response.body() == null) {
return null;
} else {
Reader reader = response.body().asReader(Util.UTF_8);
Object var4;
try {
Response.Body body = response.body();
InputStream inputStream = body.asInputStream();
var4 = JSON.parseObject(inputStream, type);
} catch (JsonIOException var8) {
if (var8.getCause() != null && var8.getCause() instanceof IOException) {
throw (IOException) IOException.class.cast(var8.getCause());
}
throw var8;
} finally {
Util.ensureClosed(reader);
}
return var4;
}
} else {
return Util.emptyValueOf(type);
}
}
}
参考文章:Feign github
https://github.com/OpenFeign/feign
容错回调 Hystrix
UserServerApiHystrix继承UserServerApi,其他的我没有配置
public class UserServerApiHystrix implements UserServerApi {
@Override
public FeignResult<UserDto> getUserByToken(String token) {
return FeignResult.fail();
}
}
拦截器
可以添加一些信息或者修改信息
public class ProviderFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Content-Type", "application/json; charset=utf-8");
template.header("ticket", "***");
template.header("x-forwarded-for", AppSecUtils.getXRealIp());
}
}
如果觉得本文还不错的话帮我点个赞
推荐文章:
在SpringCloud中使用Feign的两种方式
https://www.cnblogs.com/wugang/p/14477803.html
当@FeignClient遇到@RequestMapping,spring容器启动报错
https://blog.csdn.net/sssdal19995/article/details/121023316
Feign实现动态URL
https://cloud.tencent.com/developer/article/1990111