微服务5-服务调用

服务间调用

微服务中,很多服务系统都在独立的进程中运行,通过各个服务系统之间的协作来实现一个大项目的所有业务功能。服务系统间 使用多种跨进程的方式进行通信协作,而RESTful风格的网络请求是最为常见的交互方式之一。

http。

思考:如果让我们写服务调用如何写。

  1. 硬编码。不好。ip域名写在代码中。目的:找到服务。

  2. 根据服务名,找相应的ip。目的:这样ip切换或者随便变化,对调用方没有影响。

    Map<服务名,服务列表> map;

  3. 加上负载均衡。目的:高可用。

spring cloud提供的方式:

  1. RestTemplate
  2. Feign

使用Springcloud做服务间调用的原因

  1. Spring生态

  2. 异构平台,调用其他第三方接口(可能是php.net)

  3. 无状态,可插拔

    image-20220126233134219

Rest

RESTful网络请求是指RESTful风格的网络请求,其中REST是Resource Representational State Transfer的缩写,直接翻译即“资源表现层状态转移”。
Resource代表互联网资源。所谓“资源”是网络上的一个实体,或者说网上的一个具体信息。它可以是一段文本、一首歌曲、一种服务,可以使用一个URI指向它,每种“资源”对应一个URI。
Representational是“表现层”意思。“资源”是一种消息实体,它可以有多种外在的表现形式,我们把“资源”具体呈现出来的形式叫作它的“表现层”。比如说文本可以用TXT格式进行表现,也可以使用XML格式、JSON格式和二进制格式;视频可以用MP4格式表现,也可以用AVI格式表现。URI只代表资源的实体,不代表它的形式。它的具体表现形式,应该由HTTP请求的头信息Accept和Content-Type字段指定,这两个字段是对“表现层”的描述。
State Transfer是指“状态转移”。客户端访问服务的过程中必然涉及数据和状态的转化。如果客户端想要操作服务端资源,必须通过某种手段,让服务器端资源发生“状态转移”。而这种转化是建立在表现层之上的,所以被称为“表现层状态转移”。客户端通过使用HTTP协议中的四个动词来实现上述操作,它们分别是:获取资源的GET、新建或更新资源的POST、更新资源的PUT和删除资源的DELETE。

RestTemplate是Spring提供的同步HTTP网络客户端接口,它可以简化客户端与HTTP服务器之间的交互,并且它强制使用RESTful风格。它会处理HTTP连接和关闭,只需要使用者提供服务器的地址(URL)和模板参数。

第一个层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP 和 XML-RPC 都属于此类。
第二个层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达。
第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源。
第四个层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。

image-20220126231521579

git的restful api

https://developer.github.com/v3/

RestTemplate

get 请求处理

getForEntity

getForEntity方法的返回值是一个ResponseEntity,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。

entity:<200,Hi!,我的port:82,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"19", Date:"Wed, 26 Jan 2022 15:51:13 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>

返回一个Map

调用方

		String url ="http://provider/getMap";
		   
		ResponseEntity<Map> entity = restTemplate.getForEntity(url, Map.class);
		   
		System.out.println("respStr: "  + entity.getBody() );

生产方

	@GetMapping("/getMap")
	public Map<String, String> getMap() {		
		HashMap<String, String> map = new HashMap<>();
		map.put("name", "500");
		return map; 
	}

返回对象

调用方

		ResponseEntity<Person> entity = restTemplate.getForEntity(url, Person.class);
		   
		System.out.println("respStr: "  + ToStringBuilder.reflectionToString(entity.getBody() ));

生产方

	@GetMapping("/getObj")
	public Person getObj() {
		Person person = new Person();
		person.setId(100);
		person.setName("xiaoming");
		return person; 
	}

Person类

	private int id;
	private String name;

传参调用

使用占位符

	String url ="http://provider/getObjParam?name={1}";
	   
	ResponseEntity<Person> entity = restTemplate.getForEntity(url, Person.class,"hehehe...");

使用map

		String url ="http://provider/getObjParam?name={name}";		   
		Map<String, String> map = Collections.singletonMap("name", " memeda");
		ResponseEntity<Person> entity = restTemplate.getForEntity(url, Person.class,map);

返回对象

Person person = restTemplate.getForObject(url, Person.class,map);

post 请求处理

调用方

		String url ="http://provider/postParam";		   
		Map<String, String> map = Collections.singletonMap("name", " memeda");
		 ResponseEntity<Person> entity = restTemplate.postForEntity(url, map, Person.class);

生产方

	@PostMapping("/postParam")
	public Person postParam(@RequestBody String name) {
		System.out.println("name:" + name);
		Person person = new Person();
		person.setId(100);
		person.setName("xiaoming" + name);
		return person; 
	}

postForLocation

调用方

		String url ="http://provider/postParam";		   
		Map<String, String> map = Collections.singletonMap("name", " memeda");
		URI location = restTemplate.postForLocation(url, map, Person.class);
		
		System.out.println(location);
		response.sendRedirect(location.toURL().toString());

生产方

需要设置头信息,不然返回的是null

	public URI postParam(@RequestBody Person person,HttpServletResponse response) throws Exception {

	URI uri = new URI("https://www.baidu.com/s?wd="+person.getName());
	response.addHeader("Location", uri.toString());

exchange

可以自定义http请求的头信息,同时保护get和post方法

拦截器

需要实现ClientHttpRequestInterceptor接口

拦截器

public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {

	@Override
	public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
			throws IOException {

		System.out.println("拦截啦!!!");
		System.out.println(request.getURI());

		ClientHttpResponse response = execution.execute(request, body);

		System.out.println(response.getHeaders());
		return response;
	}

添加到resttemplate中

	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
		return restTemplate;
	}

Feign

OpenFeign是Netflix 开发的声明式、模板化的HTTP请求客户端。可以更加便捷、优雅地调用http api。

OpenFeign会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign会将函数的参数值设置到这些请求模板中。

feign主要是构建微服务消费端。只要使用OpenFeign提供的注解修饰定义网络请求的接口类,就可以使用该接口的实例发送RESTful的网络请求。还可以集成Ribbon和Hystrix,提供负载均衡和断路器。

英文表意为“假装,伪装,变形”, 是一个 Http 请求调用的轻量级框架,可以以 Java 接口注解的方式调用 Http 请求,而不用像 Java 中通过封装 HTTP 请求报文的方式直接调用。通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign 封装 了HTTP 调用流程,面向接口编程,回想第一节课的SOP。

Feign和OpenFeign的关系

Feign本身不支持Spring MVC的注解,它有一套自己的注解

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

原理

  1. 主程序入口添加@EnableFeignClients注解开启对Feign Client扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClient注解。
  2. 当程序启动时,会进行包扫描,扫描所有@FeignClient注解的类,并将这些信息注入Spring IoC容器中。当定义的Feign接口中的方法被调用时,通过JDK代理方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装了HTTP请求需要的全部信息,如请求参数名、请求方法等信息都在这个过程中确定。
  3. 然后由RequestTemplate生成Request,然后把这个Request交给client处理,这里指的Client可以是JDK原生的URLConnection、Apache的Http Client,也可以是Ok http。最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发起服务之间的调用。

声明式服务调用方法

服务端provider

com.mashibing.User-API创建一个接口 RegisterApi

package com.mashibing.UserAPI;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/User")
public interface RegisterApi {

	@GetMapping("/isAlive")
	public String isAlive();
}

创建UserController

实现Api的接口

package com.mashibing.UserProvider;

import com.mashibing.UserAPI.RegisterApi;
@RestController
public class UserController implements RegisterApi {

	@Override
	public String isAlive() {
		// TODO Auto-generated method stub
		return "ok";
	}

}

配置文件及依赖

eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/
server.port=81
spring.application.name=user-provider
<dependency>
    <groupId>com.mashibing.User-API</groupId>
    <artifactId>User-API</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

客户端Consumer

使用FeignClient方式创建Service接口

package com.mashibing.UserConsumer;

import org.springframework.cloud.openfeign.FeignClient;

import com.mashibing.UserAPI.RegisterApi;

@FeignClient(name = "user-provider")
public interface UserConsumerService extends RegisterApi {

}

不结合eureka直接使用FeignClient(配置url),没有继承接口使用简洁。

image-20220128224722513

创建Controller

package com.mashibing.UserConsumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConsumerController {

	@Autowired
	UserConsumerService consumerSrv;
	
	@GetMapping("/alive")
	public String alive() {
		
		return consumerSrv.isAlive();
	}
	
}

修改启动类加入注解EnableFeignClients

package com.mashibing.UserConsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class UserConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserConsumerApplication.class, args);
	}

}

Get和Post

Feign默认所有带参数的请求都是Post,想要使用指定的提交方式需引入依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

并指明提交方式

//带参请求
/**
* 这里 getMapping 是给Feign看的 get请求 user-provider/getMap?id={1}
* @RequestParam("id") 也是给Feign看的
* 
* HttpClient Http协议
* @param id
* @return
*/
@GetMapping("/getMap")
Map<Integer, String> getMap(@RequestParam("id") Integer id);

@PostMapping("/register")
public Map<String, String> reg(@RequestBody User user);

开启日志

配置文件

logging.level.com.mashibing.UserConsumer=debug

重写日志等级

package com.mashibing.UserConsumer;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Logger;
@Configuration
public class FeiginConfig {

	@Bean
	Logger.Level logLevel(){
		
		return Logger.Level.BASIC;
	}
}
posted @ 2022-01-28 23:55  gary2048  阅读(321)  评论(0编辑  收藏  举报