概述
在微服務(wù)架構(gòu)中,最常見(jiàn)的場(chǎng)景就是微服務(wù)間的相互調(diào)用。微服務(wù)間的相互調(diào)用方式主要有RestTemplate、Feign 、和OpenFeign 。
RestTemplate
- RestTemplate是遠(yuǎn)程調(diào)用Http的工具,是對(duì)java底層http的封裝,使用RestTemplata用戶可以不再關(guān)注底層的連接建立;
- RestTemplate是Spring提供的用于訪問(wèn)Rest服務(wù)的客戶端,RestTemplate提供了多種便捷訪問(wèn)遠(yuǎn)程Http服務(wù)的方法,能夠大大提高客戶端的編寫(xiě)效率
- RestTemplata不僅支持RESTful規(guī)范,還可以定義返回值對(duì)象類型。
- RestTemplate 支持本地客戶端負(fù)載均衡,是對(duì)Ribbon的封裝。
個(gè)人整理了一些資料,有需要的朋友可以直接點(diǎn)擊領(lǐng)取。
對(duì)標(biāo)阿里P8的Java學(xué)習(xí)路線和資料
Feign
- Feign是Spring Cloud組件中的一個(gè)聲明式的輕量級(jí)RESTful的HTTP服務(wù)客戶端 ;
- Feign內(nèi)置了Ribbon,用來(lái)做客戶端負(fù)載均衡,去調(diào)用服務(wù)注冊(cè)中心的服務(wù);
- Feign的使用方式是:使用Feign的注解定義接口,然后調(diào)用這個(gè)接口,就可以調(diào)用服務(wù)注冊(cè)中心的服務(wù) ,用起來(lái)就好像調(diào)用本地方法一樣,完全感覺(jué)不到是調(diào)用的遠(yuǎn)程方法;
- Feign本身不支持SpringMVC的注解,它有一套自己的注解;
OpenFeign
- OpenFeign是Spring Cloud 在Feign的基礎(chǔ)上支持SpringMVC的注解,如@RequesMapping等等。
- OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通過(guò)動(dòng)態(tài)代理的方式產(chǎn)生實(shí)現(xiàn)類,實(shí)現(xiàn)類中做負(fù)載均衡并調(diào)用其他服務(wù)。
創(chuàng)建一個(gè)微服務(wù)消費(fèi)者子模塊
在 alibaba-server 子工程下創(chuàng)建一個(gè)微服務(wù)消費(fèi)者子模塊 springboot 項(xiàng)目alibaba-server-consumer,最終文件目錄如下:
在微服務(wù)消費(fèi)者子模塊 alibaba-server-consumer 的pom文件中添加依賴:
<dependencies>
<!-- web 應(yīng)用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 必須包含spring-boot-starter-actuator包,不然啟動(dòng)會(huì)報(bào)錯(cuò)。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 整合nacos配置中心所需jar包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- 整合nacos服務(wù)注冊(cè) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<optional>true</optional>
</dependency>
</dependencies>
將消費(fèi)者微服務(wù) alibaba-server-consumer 注冊(cè)到 nacos上
其中配置如下:
調(diào)用提供者微服務(wù)
使用 RestTemplate
提供者 alibaba-server-helloworld 微服務(wù)的 controller 如下:
@RestController
@RequestMapping("/lhj")
public class HelloWorlsController {
@RequestMapping("/hello")
public String hello(){
return "hello world!";
}
}
端口號(hào)是8006,上下文是hello
添加一個(gè) RestTemplate 的配置類
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced // 負(fù)載均衡,使得loadBalancerClient可以通過(guò)應(yīng)用名獲取對(duì)應(yīng)的url
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
啟動(dòng)類
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
controller 中調(diào)用遠(yuǎn)程服務(wù)
注意:使用 @Autowired 注入 restTemplate 只能通過(guò)第三種方式來(lái)調(diào)用服務(wù),@Autowired 注入的 restTemplate 因?yàn)椴荒苤苯釉L問(wèn)地址,只能通過(guò)注冊(cè)的服務(wù)應(yīng)用名來(lái)訪問(wèn)。
@RestController
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient; // 注入 LoadBalancerClient
@Autowired
private RestTemplate restTemplate; // 注入 RestTemplate
@GetMapping("/diaoyong")
public ResponseEntity<String> msg(){
//1.第一種方式(直接使用restTemplate,url寫(xiě)死)
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8006/hello/lhj/hello", String.class);
//2.弟二種方式(利用loadBalancerClient通過(guò)應(yīng)用名獲取url,然后再使用直接使用restTemplate)
ServiceInstance serviceInstance = loadBalancerClient.choose("alibaba-server-helloworld");
String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())+"/hello/lhj/hello";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
//3.弟二種方式(利用@LoadBalanced,可在restTemplate里使用應(yīng)用名字)
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://PRODUCT/msg", String.class);
log.info("response={}",response);
return response;
System.out.println(response.getStatusCode());
System.out.println(response.getBody());
System.out.println(response.getHeaders());
return response;
}
}
RestTemplate 調(diào)用的三種方式
第一種方式:直接使用被調(diào)用服務(wù)的訪問(wèn)地址,url寫(xiě)死(必須new一個(gè)restTemplate,不能使用注入的)
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://localhost:8006/hello/lhj/hello",String.class)
return response;
第二種方式:利用loadBalancerClient通過(guò)應(yīng)用名獲取url,然后再使用restTemplate(必須new一個(gè)restTemplate,不能使用注入的)
ServiceInstance serviceInstance = loadBalancerClient.choose("alibaba-server-helloworld");
String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())+"/hello/lhj/hello";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
return response;
第三種方式:利用配置類中的@LoadBalanced,可在restTemplate里直接使用服務(wù)應(yīng)用名字(可以使用注入的restTemplate)
String response = restTemplate.getForObject("http://alibaba-server-helloworld/hello/lhj/hello", String.class);
return response;
注意:用 @Autowired 注入 restTemplate 的不能直接訪問(wèn)地址,只能通過(guò)注冊(cè)的服務(wù)應(yīng)用名來(lái)訪問(wèn)
restTemplate 的返回值類型
返回值類型一共有兩類,getForEntity 和 getForObject,每一類有三個(gè)重載方法。
① getForEntity
既然 RestTemplate 發(fā)送的是 HTTP 請(qǐng)求,那么在響應(yīng)的數(shù)據(jù)中必然也有響應(yīng)頭,如果開(kāi)發(fā)者需要獲取響應(yīng)頭的話,那么就需要使用 getForEntity 來(lái)發(fā)送 HTTP 請(qǐng)求,此時(shí)返回的對(duì)象是一個(gè) ResponseEntity 的實(shí)例。這個(gè)實(shí)例中包含了響應(yīng)數(shù)據(jù)以及響應(yīng)頭。
② getForObject
getForObject 方法的參數(shù)和 getForEntity 一樣,getForObject 的返回值就是服務(wù)提供者返回的數(shù)據(jù),使用 getForObject 無(wú)法獲取到響應(yīng)頭。
瀏覽器訪問(wèn)
使用 Feign
消費(fèi)者 alibaba-server-consumer 微服務(wù)的文件結(jié)構(gòu)
提供者 alibaba-server-hellocloud 微服務(wù)的 controller 如下:
@RestController
@RequestMapping("zj")
public class HelloCloudController {
@RequestMapping("hello")
public String hello(String name){
return "hello cloud,"+name;
}
}
端口號(hào)是8007,上下文是hello
Feign 原理
● 啟動(dòng)時(shí),程序會(huì)進(jìn)行包掃描,掃描所有包下所有@FeignClient注解的類,并將這些類注入到spring的IOC容器中。當(dāng)定義的Feign中的接口被調(diào)用時(shí),通過(guò)JDK的動(dòng)態(tài)代理來(lái)生成RequestTemplate。
● RequestTemplate中包含請(qǐng)求的所有信息,如請(qǐng)求參數(shù),請(qǐng)求URL等。
● RequestTemplate聲場(chǎng)Request,然后將Request交給client處理,這個(gè)client默認(rèn)是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
● 最后client封裝成LoadBaLanceClient,結(jié)合ribbon負(fù)載均衡地發(fā)起調(diào)用。
(1) pom 文件中添加 Feign 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)啟動(dòng)類上添加注解 @EnableFeignClients
在服務(wù)的啟動(dòng)類上添加注解 @EnableFeignClients 以開(kāi)啟 Spring Cloud Feign 的支持。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
(3)聲明服務(wù)接口
@Service @FeignClient("alibaba-server-hellocloud") // 服務(wù)提供者的名稱spring.application.name public interface FeignService {
@RequestMapping(value="/hello/zj/hello") // 服務(wù)提供者方法的訪問(wèn)地址
String getService(@RequestParam(value="name") String name);
}
① 接口中的方法可以不用加public,方法名任意取。
② 在該接口中,使用 @FeignClient 注解指定要調(diào)用的服務(wù)名來(lái)綁定服務(wù),然后再使用Spring MVC的注解 @RequestMapping 來(lái)綁定具體該服務(wù)提供的REST接口。
③@RequestParam 注解必須要加上 value 屬性。
(4)controller 中調(diào)用服務(wù)接口
@RestController
public class ConsumerController {
@Autowired
private FeignService feignService;
@GetMapping("/diaoyong")
public String msg(){
return feignService.getService("lhj");
}
}
(5)瀏覽器訪問(wèn)
(6)Feign 開(kāi)啟日志
如果我們想追蹤Feign客戶端發(fā)送的數(shù)據(jù),就要啟用 Feign 的日志。
Feign 在構(gòu)建被 @FeignClient 注解修飾的服務(wù)客戶端時(shí),會(huì)為每一個(gè)客戶端都創(chuàng)建一個(gè)feign.Logger實(shí)例,這樣就可以利用該日志對(duì)象的DEBUG模式來(lái)幫助分析Feign的請(qǐng)求細(xì)節(jié)。
開(kāi)啟方法:
① 配置文件開(kāi)啟:在application.yml 中使用 logging.level.{Feign客戶端對(duì)應(yīng)的接口的全限定名} 的參數(shù)配置格式來(lái)開(kāi)啟指定客戶端日志
logging:
level:
{Feign客戶端對(duì)應(yīng)的接口的全限定名}: debug
② java bean 的方式開(kāi)啟:@EnableFeignClients 注解上有個(gè) defaultConfiguration 屬性,可以指定默認(rèn)Feign Client的一些配置。
@EnableFeignClients(defaultConfiguration =DefaultFeignConfiguration.class)
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
@Configuration
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
使用 Feign 的注意事項(xiàng)
① 在 Feign 的服務(wù)聲明接口使用對(duì)象作為參數(shù)時(shí)必須用 @RequestBody注解,讓其以json方式接收
void insert(@RequestBody User user);
② 在 Feign 的服務(wù)聲明接口中使用 @RequestParam 一定要加上value屬性
void delete(@RequestParam("accountCode") String accountCode);
③ 在 Feign 的服務(wù)聲明接口中使用 @PathVariable 一定要跟上面一樣加上value屬性
ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode);
④ 在消費(fèi)者模塊啟動(dòng)類上使用@EnableFeignClients注解后指明Feign接口所在的包路徑
@EnableFeignClients(basePackages = "com.javadaily.feign.*")