问题:
用过Spring Cloud的朋友都知道用 fegin 很香,甚至后来者居上的Spring Cloud Alibaba,仍然整合 fegin 作为服务调用组件。
但是很多项目用不到微服务的,就是普通的单体应用,如何使用呢…
feign 说明
fegin发展简介
com.netflix.feign 从2016 年后闭源,由 io.github.openfeign 来维护。
feign是什么呢,
fegin其实就是一个http客户端调度框架。
底层 fegin默认情况下,使用的jdk 原生的 UrlConnections 发送 http请求,没有连接池,所以一般使用fegin的时候,对采用 HttpClient 或者 Okhttp 作为fegin 的客户端进行使用。
使用说明
参考官网:https://github.com/OpenFeign/feign
引入依赖
注:我这里没有引入 feign-core,而是直接引入 feign-jackson
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-jackson -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>11.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.2</version>
</dependency>
创建 FeignClientFactory
这里只是简单配置测试使用,可进一步进行自定义封装。
public abstract class FeignClientFactory {
/**
* 创建 feign 客户端
*
* @param clazz class
* @param url url
* @param <T> 泛型参数
* @return T
*/
public static <T> T create(Class<T> clazz, String url) {
// 1、定义 okHttpClient
okhttp3.OkHttpClient okHttpClient = new okhttp3.OkHttpClient()
.newBuilder()
.connectionPool(new ConnectionPool(5, 5L, TimeUnit.MINUTES))
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
// 2、定义 feign 对应 OkHttpClient
OkHttpClient client = new OkHttpClient(okHttpClient);
// 3、创建 feign
return Feign.builder()
.client(client)
.retryer(Retryer.NEVER_RETRY) // 重试机制
.encoder(new FeignEncoder()) // 自定义:入参编码器
.decoder(new StringDecoder()) // 定义:出参解码器
.decode404() // 解码404,方便测试
.target(clazz, url);
}
}
配置自定义 encoder
为什么配置这个呢,可参考:https://cloud.tencent.com/developer/article/1588499
具体就是:
当方法参数没有标注@Param注解时,编码器会起作用。
eg:
如果方法参数并没有被模版使用,那么他会被收集放到一个Map里,然后交给Encoder处理。因为默认的 Encoder 是 StringDecoder,无法解析map就会报错了。
public class FeignEncoder implements Encoder {
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) {
if (object != null) {
String jsonString = JSON.toJSONString(object);
template.body(jsonString.getBytes(), StandardCharsets.UTF_8);
}
}
}
使用场景
这里我这里列举五种场景,基本可满足项目中用到场景。
1、表单传参 – 普通参数
1.1、定义api接口
public interface TestOpenFeignApi {
/**
* 表单传参 * V1
* 参数必须要加 @Param注解,否则会报错,
* 注意这里的 @Param 要区别于MyBatis @Param 注解
*
* @param id id
* @param code code
*/
@RequestLine("POST openFeignApi/findByIdAndCode?id={id_}&code={code_}")
String findByIdAndCode(@Param("id_") Integer id, @Param("code_") String code);
}
1.2、对应SpringMvc接口
@RestController
@RequestMapping(value = "openFeignApi/")
public class OpenFeignController {
/**
* 表单传参------测试普通表单传参
*
* @param id id
* @param code code
*/
@PostMapping("/findByIdAndCode")
public Map findByIdAndCode(@RequestParam(required = false) Integer id, @RequestParam(required = false) String code) {
HashMap<String, Object> hashMap = Maps.newHashMap();
hashMap.put("id", id);
hashMap.put("code", code);
return hashMap;
}
}
1.3、Fegin调用
@SpringBootTest
public class OpenFeignTest {
public static final String URL = "http://localhost:8080/";
/**
* 表单传参 -- 普通参数
*/
@Test
public void findById() {
String str = FeignClientFactory.create(TestOpenFeignApi.class, URL).findByIdAndCode(1, "ali");
System.out.println(str);
}
}
2、表单传参 – pojo
2.1、定义api接口
@QueryMap 作用等同于多个 @Param
/**
* 表单传参 (pojo) * V2
* 负责将对象编码为Map查询参数名到值的映射。
*
* @param city pojo
*/
@RequestLine("GET openFeignApi/findByCity")
String findByCity(@QueryMap City city);
2.2、对应SpringMvc接口
/**
* 表单传参------测试pojo表单传参,注意这里不能带 @RequestParam 注解
*
* @param city city
*/
@GetMapping("/findByCity")
public String findByCity(City city) {
System.out.println(city);
return JSON.toJSONString(city);
}
2.3、Fegin调用
/**
* 表单传参 -- pojo
*/
@Test
public void findByCity() {
String str = FeignClientFactory.create(TestOpenFeignApi.class, URL)
.findByCity(City.builder().cityCode(1001).cityName("杭州").build());
System.out.println(str);
}
3、表单传参 – map
3.1、定义api接口
/**
* 表单传参 (map) * V3
* 负责将对象编码为Map查询参数名到值的映射。
*
* @param map map
*/
@RequestLine("GET openFeignApi/findByMap")
String findByMap(@QueryMap Map map);
3.2、对应SpringMvc接口
/**
* 表单传参------测试 map表单传参,注意这里一定要带 @RequestParam 注解,否则无效
*
* @param map map
*/
@GetMapping("/findByMap")
public Map findByMap(@RequestParam Map map) {
System.out.println(map);
return map;
}
3.3、Fegin调用
/**
* 表单传参 -- map
*/
@Test
public void findByMap() {
HashMap<Object, Object> map = Maps.newHashMap();
map.put("cityCode", 1002);
map.put("cityName", "上海");
String str = FeignClientFactory.create(TestOpenFeignApi.class, URL)
.findByMap(map);
System.out.println(str);
}
4、json传参 (带 header)
4.1、定义api接口
/**
* json 传参 (& header传参) * V2
*
* @param city (json格式)
*/
@RequestLine("POST /openFeignApi/findByJson")
@Headers({"Content-Type: application/json","Accept: application/json"})
String findByJsonAndHeader(@HeaderMap Map<String, String> headerMap, City city) ;
4.2、对应SpringMvc接口
/**
* json传参
*
* @param city city
*/
@PostMapping("/findByJson")
public String findByJson(@RequestBody City city, HttpServletRequest request) {
System.out.println(city);
System.out.println(request.getHeader("token"));
return JSON.toJSONString(city);
}
4.3、Fegin调用
/**
* json传参(带 header) -- pojo
*/
@Test
public void findByJsonAndHeader() {
Map<String, String> headerMap = Maps.newHashMap();
headerMap.put("token", "123");
headerMap.put("cookie", "456");
City city = City.builder().cityCode(1001).cityName("杭州").build();
String str = FeignClientFactory.create(TestOpenFeignApi.class, URL)
.findByJsonAndHeader(headerMap, city);
System.out.println(str);
}
5、调用接口返回POJO对象(常用)
什么意思呢,就是调一个接口可以转换对应的 DTO,不用把返回接口的json字符串通过json解析工具解析为对象进行使用。
5.1 重定义 FeignClientFactory decoder
public static <T> T create2(Class<T> clazz, String url) {
return Feign.builder()
.encoder(new FeignEncoder())
// 使用JacksonDecoder做解码
.decoder(new JacksonDecoder())
.decode404()
.target(clazz, url);
}
5.2 定义api接口
这个接口是微信官方的,可以通过appid和secret及grantType直接调用获取token
public interface WechatSdkApi {
@RequestLine("GET /cgi-bin/token?appid={appid}&secret={secret}&grant_type={grant_type}")
WxToken getToken(@Param("appid") String appid,
@Param("secret") String secret,
@Param("grant_type") String grantType);
}
5.3 Fegin调用
@SpringBootTest
public class WeChatApi {
@Autowired
private SiteConfig siteConfig;
/**
* 根据 appid 和 secret 及 client_credential 获取 token
*/
@Test
public void getToken() {
WechatSdkApi sdkApi = FeignClientFactory.create2(WechatSdkApi.class, ApiConstants.WECHAT_GET_OPENID);
WxToken apiToken = sdkApi.getToken(siteConfig.getAppId(), siteConfig.getSecret(), siteConfig.getGrantType2());
System.out.println(apiToken.getAccessToken());
}
}