概述
在微服務架構中,最常見的場景就是微服務間的相互調用。微服務間的相互調用方式主要有RestTemplate、Feign 、和OpenFeign 。
RestTemplate
- RestTemplate是遠程調用Http的工具,是對java底層http的封裝,使用RestTemplata用戶可以不再關注底層的連接建立;
- RestTemplate是Spring提供的用於訪問Rest服務的客戶端,RestTemplate提供了多種便捷訪問遠程Http服務的方法,能夠大大提高客戶端的編寫效率
- RestTemplata不僅支持RESTful規範,還可以定義返回值對象類型。
- RestTemplate 支持本地客戶端負載均衡,是對Ribbon的封裝。
個人整理了一些資料,有需要的朋友可以直接點擊領取。
Feign
- Feign是Spring Cloud組件中的一個聲明式的輕量級RESTful的HTTP服務客戶端 ;
- Feign內置了Ribbon,用來做客戶端負載均衡,去調用服務註冊中心的服務;
- Feign的使用方式是:使用Feign的註解定義接口,然後調用這個接口,就可以調用服務註冊中心的服務 ,用起來就好像調用本地方法一樣,完全感覺不到是調用的遠程方法;
- Feign本身不支持SpringMVC的註解,它有一套自己的註解;
OpenFeign
- OpenFeign是Spring Cloud 在Feign的基礎上支持SpringMVC的註解,如@RequesMapping等等。
- OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping註解下的接口,並通過動態代理的方式產生實現類,實現類中做負載均衡並調用其他服務。
創建一個微服務消費者子模塊
在 alibaba-server 子工程下創建一個微服務消費者子模塊 springboot 項目alibaba-server-consumer,最終文件目錄如下:
在微服務消費者子模塊 alibaba-server-consumer 的pom文件中添加依賴:
<dependencies>
<!-- web 應用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 必須包含spring-boot-starter-actuator包,不然啓動會報錯。 -->
<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服務註冊 -->
<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>
將消費者微服務 alibaba-server-consumer 註冊到 nacos上
其中配置如下:
調用提供者微服務
使用 RestTemplate
提供者 alibaba-server-helloworld 微服務的 controller 如下:
@RestController
@RequestMapping("/lhj")
public class HelloWorlsController {
@RequestMapping("/hello")
public String hello(){
return "hello world!";
}
}
端口號是8006,上下文是hello
添加一個 RestTemplate 的配置類
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced // 負載均衡,使得loadBalancerClient可以通過應用名獲取對應的url
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
啓動類
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
controller 中調用遠程服務
注意:使用 @Autowired 注入 restTemplate 只能通過第三種方式來調用服務,@Autowired 注入的 restTemplate 因爲不能直接訪問地址,只能通過註冊的服務應用名來訪問。
@RestController
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient; // 注入 LoadBalancerClient
@Autowired
private RestTemplate restTemplate; // 注入 RestTemplate
@GetMapping("/diaoyong")
public ResponseEntity<String> msg(){
//1.第一種方式(直接使用restTemplate,url寫死)
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8006/hello/lhj/hello", String.class);
//2.弟二種方式(利用loadBalancerClient通過應用名獲取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裏使用應用名字)
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 調用的三種方式
第一種方式:直接使用被調用服務的訪問地址,url寫死(必須new一個restTemplate,不能使用注入的)
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://localhost:8006/hello/lhj/hello",String.class)
return response;
第二種方式:利用loadBalancerClient通過應用名獲取url,然後再使用restTemplate(必須new一個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裏直接使用服務應用名字(可以使用注入的restTemplate)
String response = restTemplate.getForObject("http://alibaba-server-helloworld/hello/lhj/hello", String.class);
return response;
注意:用 @Autowired 注入 restTemplate 的不能直接訪問地址,只能通過註冊的服務應用名來訪問
restTemplate 的返回值類型
返回值類型一共有兩類,getForEntity 和 getForObject,每一類有三個重載方法。
① getForEntity
既然 RestTemplate 發送的是 HTTP 請求,那麼在響應的數據中必然也有響應頭,如果開發者需要獲取響應頭的話,那麼就需要使用 getForEntity 來發送 HTTP 請求,此時返回的對象是一個 ResponseEntity 的實例。這個實例中包含了響應數據以及響應頭。
② getForObject
getForObject 方法的參數和 getForEntity 一樣,getForObject 的返回值就是服務提供者返回的數據,使用 getForObject 無法獲取到響應頭。
瀏覽器訪問
使用 Feign
消費者 alibaba-server-consumer 微服務的文件結構
提供者 alibaba-server-hellocloud 微服務的 controller 如下:
@RestController
@RequestMapping("zj")
public class HelloCloudController {
@RequestMapping("hello")
public String hello(String name){
return "hello cloud,"+name;
}
}
端口號是8007,上下文是hello
Feign 原理
● 啓動時,程序會進行包掃描,掃描所有包下所有@FeignClient註解的類,並將這些類注入到spring的IOC容器中。當定義的Feign中的接口被調用時,通過JDK的動態代理來生成RequestTemplate。
● RequestTemplate中包含請求的所有信息,如請求參數,請求URL等。
● RequestTemplate聲場Request,然後將Request交給client處理,這個client默認是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
● 最後client封裝成LoadBaLanceClient,結合ribbon負載均衡地發起調用。
(1) pom 文件中添加 Feign 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)啓動類上添加註解 @EnableFeignClients
在服務的啓動類上添加註解 @EnableFeignClients 以開啓 Spring Cloud Feign 的支持。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
(3)聲明服務接口
@Service @FeignClient("alibaba-server-hellocloud") // 服務提供者的名稱spring.application.name public interface FeignService {
@RequestMapping(value="/hello/zj/hello") // 服務提供者方法的訪問地址
String getService(@RequestParam(value="name") String name);
}
① 接口中的方法可以不用加public,方法名任意取。
② 在該接口中,使用 @FeignClient 註解指定要調用的服務名來綁定服務,然後再使用Spring MVC的註解 @RequestMapping 來綁定具體該服務提供的REST接口。
③@RequestParam 註解必須要加上 value 屬性。
(4)controller 中調用服務接口
@RestController
public class ConsumerController {
@Autowired
private FeignService feignService;
@GetMapping("/diaoyong")
public String msg(){
return feignService.getService("lhj");
}
}
(5)瀏覽器訪問
(6)Feign 開啓日誌
如果我們想追蹤Feign客戶端發送的數據,就要啓用 Feign 的日誌。
Feign 在構建被 @FeignClient 註解修飾的服務客戶端時,會爲每一個客戶端都創建一個feign.Logger實例,這樣就可以利用該日誌對象的DEBUG模式來幫助分析Feign的請求細節。
開啓方法:
① 配置文件開啓:在application.yml 中使用 logging.level.{Feign客戶端對應的接口的全限定名} 的參數配置格式來開啓指定客戶端日誌
logging:
level:
{Feign客戶端對應的接口的全限定名}: debug
② java bean 的方式開啓:@EnableFeignClients 註解上有個 defaultConfiguration 屬性,可以指定默認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 的注意事項
① 在 Feign 的服務聲明接口使用對象作爲參數時必須用 @RequestBody註解,讓其以json方式接收
void insert(@RequestBody User user);
② 在 Feign 的服務聲明接口中使用 @RequestParam 一定要加上value屬性
void delete(@RequestParam("accountCode") String accountCode);
③ 在 Feign 的服務聲明接口中使用 @PathVariable 一定要跟上面一樣加上value屬性
ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode);
④ 在消費者模塊啓動類上使用@EnableFeignClients註解後指明Feign接口所在的包路徑
@EnableFeignClients(basePackages = "com.javadaily.feign.*")