Spring Cloud Netflix 学习笔记(三)—服务映射(Feign)
Feign是一个声明式的REST客户端,它能让REST调用更加简单。
Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。
Spring Cloud对Feign进行了封装,使其支持SpringMVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
1、简单使用
1.1、导入依赖包
在调用方(emp模块)导入openfeign的依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2、添加注解
在启动类上添加@EnableFeignClients注解
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients(basePackages = "org.silence.emp.remote")
public class EmpApplication {
public static void main(String[] args) {
SpringApplication.run(EmpApplication.class, args);
}
}
1.3、添加映射接口
在调用方添加被调用服务的接口——服务提供方controller的接口拷贝。
@FeignClient注解标识当前是一个Feign的客户端,value属性是对应的服务名称。
package org.silence.emp.remote;
@FeignClient("dept")//映射服务名
public interface DeptClient {
//服务提供方方法的copy
@RequestMapping(method = RequestMethod.GET,value = "/dept/all")
String getAll();
}
1.4、调用
通过方法调用的方式,调用映射接口访问远程服务。
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private DeptClient dept;
@GetMapping("/all")
public String getAll() {
String result = dept.getAll();
System.out.println(result);
return "emp" + "\t" + result;
}
}
2、服务调用方式变化
可以发现,我们的调用方式变得越来越简单了,从最开始的指定地址,到后面通过Eureka中的服务名称来调用,再到现在直接通过定义接口来调用。
譬如,emp模块调用dept中的如下接口:
@RestController
@RequestMapping("/dept")
public class DeptController {
@Value("${server.port}")
private String port;
@GetMapping("/all")
public String getAll() {
return "dept=====>" + port;
}
}
2.1、原始RestTemplate方式
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private EurekaClient eurekaClient;
@GetMapping("/emp/all")
public String getAll() {
//使用eurekaClient对象通过服务名获取目标服务信息
InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("DEPT", false);
//获取访问地址
String url = instanceInfo.getHomePageUrl();
System.out.println(url);
//通过restTemplate对象访问目标服务
String result = restTemplate.getForObject(url + "/dept/all", String.class);
//获取目标结果并返回
System.out.println(result);
return "emp: " + "\t" + result;
}
}
2.2、RestTemplate + Ribbon方式
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/emp/all")
public String getAll() {
//通过restTemplate对象访问目标服务
String result = restTemplate.getForObject("http://dept/dept/all", String.class);
//获取目标结果并返回
System.out.println(result);
return "emp: " + "\t" + result;
}
}
2.3、Feign方式
- 映射接口
@FeignClient("dept")//服务名
public interface DeptClient {
//服务提供方方法的copy
@RequestMapping(method = RequestMethod.GET,value = "/dept/all")
String getAll();
@RequestMapping(value = "/dept/getDept", method = RequestMethod.GET)
Dept getDept(@RequestParam Integer deptNo);
}
- 调用
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private DeptClient deptClient;
@GetMapping("/all")
public String getAll() {
String result = deptClient.getAll();
return "emp: " + "\t" + result;
}
}
3、参数传递
使用feign实现服务调用时,需要注意如下几点:
-
当传递的参数比较复杂时,服务提供方会采用POST请求的方式接收数据,即使显示设置了请求方法为GET
-
复杂对象作为参数传递时,统一使用POST方法,数据使用json格式传输,注意添加@RequestBody注解(可不写)
-
映射接口的方法必须使用@RequestMapping注解来映射路径
3.1、定义方法
修改dept的controller,添加不同的带参方法:
@RestController
@RequestMapping("/dept")
public class DeptController {
@Value("${server.port}")
private String port;
@GetMapping("/all")
public String getAll() {
return "dept====>" + port;
}
@GetMapping("/getDept")
public Dept getDept(Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return new Dept(10, "ACCOUNTING", "NEW YORK");
}
@GetMapping("/getDept1/{deptNo}")
public Dept getDept1(@PathVariable Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return new Dept(deptNo, "ACCOUNTING", "NEW YORK");
}
@GetMapping("/getDept2")
public Dept getDept2(@RequestParam Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return new Dept(deptNo, "ACCOUNTING", "NEW YORK");
}
@GetMapping("/byDeptNo/{deptNo}/{dName}")
public Dept getByDeptNo(@PathVariable Integer deptNo, @PathVariable String dName) {
System.out.println(deptNo + "\t" + dName);
return new Dept(deptNo, dName, "CHICAGO");
}
@GetMapping("/byDeptNo1")
public Dept getByDeptNo1(@RequestParam Integer deptNo, @RequestParam String dName) {
System.out.println(deptNo + "\t" + dName);
return new Dept(deptNo, dName, "CHICAGO");
}
@GetMapping("/byDeptNo2")
public Dept getByDeptNo2(Integer deptNo, String dName) {
System.out.println(deptNo + "\t" + dName);
return new Dept(deptNo, dName, "CHICAGO");
}
@PostMapping("/save")
public Dept save(@RequestBody Dept dept) {
System.out.println(dept);
return dept;
}
}
3.2、修改映射
修改emp中的映射接口:
@FeignClient("dept")//服务名
public interface DeptClient {
//服务提供方方法的copy
@RequestMapping(method = RequestMethod.GET,value = "/dept/all")
String getAll();
@RequestMapping(value = "/dept/getDept", method = RequestMethod.GET)
Dept getDept(@RequestParam Integer deptNo);
@RequestMapping(value = "/dept/getDept1/{deptNo}", method = RequestMethod.GET)
Dept getDept1(@PathVariable Integer deptNo);
@RequestMapping(value = "/dept/getDept2", method = RequestMethod.GET)
Dept getDept2(@RequestParam Integer deptNo);
@RequestMapping(value = "/dept/byDeptNo/{deptNo}/{dName}", method = RequestMethod.GET)
Dept getByDeptNo(@PathVariable Integer deptNo, @PathVariable String dName);
@RequestMapping(value = "/dept/byDeptNo1", method = RequestMethod.GET)
Dept getByDeptNo1(@RequestParam Integer deptNo, @RequestParam String dName);
@RequestMapping(value = "/dept/byDeptNo2", method = RequestMethod.GET)
Dept getByDeptNo2(@RequestParam Integer deptNo, @RequestParam String dName);
@RequestMapping(value = "/dept/save")
Dept save(Dept dept);
}
3.3、调用
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DeptClient deptClient;
@GetMapping("/all")
public String getAll() {
String result = deptClient.getAll();
System.out.println(result);
return "emp" + "\t" + result;
}
@GetMapping("/getDept")
public Dept getDept(Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return deptClient.getDept(deptNo);
}
@GetMapping("/getDept1/{deptNo}")
public Dept getDept1(@PathVariable Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return deptClient.getDept1(deptNo);
}
@GetMapping("/getDept2")
public Dept getDept2(@RequestParam Integer deptNo) {
System.out.println("deptNo: " + deptNo);
return deptClient.getDept2(deptNo);
}
@GetMapping("/byDeptNo/{deptNo}/{dName}")
public Dept getByDeptNo(@PathVariable Integer deptNo, @PathVariable String dName) {
System.out.println(deptNo + "\t" + dName);
return deptClient.getByDeptNo(deptNo, dName);
}
@GetMapping("/byDeptNo1")
public Dept getByDeptNo1(@RequestParam Integer deptNo, @RequestParam String dName) {
System.out.println(deptNo + "\t" + dName);
return deptClient.getByDeptNo1(deptNo, dName);
}
@GetMapping("/byDeptNo2")
public Dept getByDeptNo2(Integer deptNo, String dName) {
System.out.println(deptNo + "\t" + dName);
return deptClient.getByDeptNo2(deptNo, dName);
}
@GetMapping("/save")
public Dept save(Dept dept, HttpServletRequest request) {
System.out.println(request.getMethod());
System.out.println(dept);
return deptClient.save(dept);
}
}
4、Feign配置
4.1、日志输出
正常情况下,控制台不会输出Feign调用的详细信息,而是要自定义Feign日志的配置。
Feign日志等级有4种,分别是:
-
NONE:不输出日志。
-
BASIC:只输出请求方法的URL和响应的状态码以及接口执行的时间。
-
HEADERS:将BASIC信息和请求头信息输出。
-
FULL:输出完整的请求信息。
4.1.1、自定义配置类
在emp中,定义一个Feign的日志配置类,输出Feign的全部日志。
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
4.1.2、修改日志配置
在emp的application.yml中添加如下配置:
#自定义springboot的日志输出
logging:
level:
#Feign映射接口全路径名
org.silence.emp.remote.DeptClient: debug
运行后,可在控制台看到如下日志: