使用spring cloud时,当遇到服务与其他服务调用的场景,一般都会使用spring cloud openfeign来进行调用。
通过@feign注解让代码变得非常简单而又优雅,即便是跨服务的调用,写起来就像项目内部自己调自己的方法一样,之顺畅~
但当项目是非spring cloud项目,在服务内调用外部的http服务时,可能首先想到的就是httpclient或okhttp
将httpclient封装为一个工具类,哪里用到就拿出来调哪里,将请求url和参数信息传入,再将请求和响应的前后加上编解码的处理代码
如果项目中又遇到需要调很多这样接口的地方,又遇到各种请求头的适配、请求method的适配、参数类型的适配……,工具类的代码可能都能封装出好几十个方法来,代码看起来可能还是会有点别扭丑丑的~
那么有没有什么更好的方法来解决上面这一问题呢,答案是必须有的:feign就是专门干这个事的
正如feign官网所说的那样:Feign makes writing java http clients easier
为了能让大家在传统项目中一起了解下feign这个组件,这里特意将feign快速引入到传统项目中的流程进行简单记录一下。
引入方式
以maven项目为例,添加pom依赖
<properties>
<openfeign.version>11.9.1</openfeign.version>
</properties>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<!--序列化方式支持gson、Jackson、fastjson、Sax等-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
</dependency>
<!--可选项,slf4j日志支持-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-bom</artifactId>
<version>${openfeign.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
其中openfeign.version为选择的feign版本,最新的版本可直接从feign的github中获取
请求配置
普通请求
import com.haiyang.javastu.springtransactionmanager.service.TestApiService;
import feign.Feign;
import feign.Logger;
import feign.Request;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.slf4j.Slf4jLogger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class ApiRegisterConf {
private final String testApiUrl = "http://127.0.0.1:8080";
@Bean
public TestApiService testApiService() {
return Feign.builder()
.decoder(new GsonDecoder())
.encoder(new GsonEncoder())
.options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
//需要引入feign-slf4j依赖
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.target(TestApiService.class, testApiUrl);
}
}
其中TestApiService为:
import com.haiyang.javastu.springtransactionmanager.model.JsonResult;
import com.haiyang.javastu.springtransactionmanager.model.TestInfo;
import com.haiyang.javastu.springtransactionmanager.model.TestInfoResult;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
public interface TestApiService {
@Headers("Content-Type: application/json")
@RequestLine("POST /hello/test.do")
JsonResult<TestInfoResult> helloTest(TestInfo testInfo);
}
写个controller测试一下:
import com.haiyang.javastu.springtransactionmanager.model.JsonResult;
import com.haiyang.javastu.springtransactionmanager.model.TestInfo;
import com.haiyang.javastu.springtransactionmanager.model.TestInfoResult;
import com.haiyang.javastu.springtransactionmanager.service.TestApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping(value = "test/feign/")
@RestController
public class TestFeignController {
private final TestApiService testApiService;
@Autowired
public TestFeignController(TestApiService testApiService) {
this.testApiService = testApiService;
}
@PostMapping(value = "helloTest")
public JsonResult<TestInfoResult> helloTest(@RequestBody TestInfo testInfo) {
return testApiService.helloTest(testInfo);
}
}
发个请求测试一下
POST http://localhost:8080/test/feign/helloTest
Content-Type: application/json
{"id": 123}
输出结果:
{
"msg": null,
"code": 200,
"data": {
"id": 123,
"name": "zhangsan",
"age": 27,
"hobbys": [
"programing",
"reading"
]
}
}
如果想要输出feign的请求详细日志,记得需要将feign包的日志级别设为DEBUG级别
logging:
level:
feign: debug
请求携带自定义header
feign提供了requestInterceptor来实现对请求的拦截处理,如果遇到需要进行token之类的header透传的场景,用它来实现就可以了。
示例:
import com.haiyang.javastu.springtransactionmanager.service.TestApiService;
import feign.*;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.slf4j.Slf4jLogger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
@Configuration
public class ApiRegisterConf {
private final String testApiUrl = "http://127.0.0.1:8080";
@Bean
public TestApiService testApiService() {
return Feign.builder()
.decoder(new GsonDecoder())
.encoder(new GsonEncoder())
.options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
//自定义请求头
.requestInterceptor(new MyHeaderRequestInterceptor())
.target(TestApiService.class, testApiUrl);
}
public static class MyHeaderRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//添加token,设置进请求头
template.header("token", request.getHeader("token"));
}
}
}
写个测试请求测试一下
动态请求host
有时需要根据业务需要,可能需要向不同host发起请求。在feign中也提供了动态切换host的方式。
只需要在请求的参数中包含URI参数就可以进行host的自定义了。
如上面的示例中,改成这样:
public interface TestApiService {
@Headers("Content-Type: application/json")
@RequestLine("POST /hello/test.do")
JsonResult<TestInfoResult> helloTest(URI hostUri, TestInfo testInfo);
}
调用的地方将uri传入即可:
@PostMapping(value = "helloTest")
public JsonResult<TestInfoResult> helloTest(@RequestBody TestInfo testInfo) {
URI uri = URI.create("http://192.168.1.4:8080");
return testApiService.helloTest(uri, testInfo);
}
其调用效果也是一样的。
spring cloud openfeign、openfeign、feign的区别
spring cloud openfeign
OpenFeign
feign