springboot3 - feign实战 - feign原生实现 - MVC实现方式以及用接口实现代理转发

几年前突发奇想,将@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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值