微服务5-服务调用
服务间调用
微服务中,很多服务系统都在独立的进程中运行,通过各个服务系统之间的协作来实现一个大项目的所有业务功能。服务系统间 使用多种跨进程的方式进行通信协作,而RESTful风格的网络请求是最为常见的交互方式之一。
http。
思考:如果让我们写服务调用如何写。
-
硬编码。不好。ip域名写在代码中。目的:找到服务。
-
根据服务名,找相应的ip。目的:这样ip切换或者随便变化,对调用方没有影响。
Map<服务名,服务列表> map;
-
加上负载均衡。目的:高可用。
spring cloud提供的方式:
- RestTemplate
- Feign
使用Springcloud
做服务间调用的原因
-
Spring生态
-
异构平台,调用其他第三方接口(可能是
php
,.net
) -
无状态,可插拔
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。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。
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注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
原理
- 主程序入口添加
@EnableFeignClients
注解开启对Feign Client扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClient
注解。 - 当程序启动时,会进行包扫描,扫描所有
@FeignClient
注解的类,并将这些信息注入Spring IoC
容器中。当定义的Feign接口中的方法被调用时,通过JDK
的代理方式,来生成具体的RequestTemplate
。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate
对象,该对象封装了HTTP请求需要的全部信息,如请求参数名、请求方法等信息都在这个过程中确定。 - 然后由
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),没有继承接口使用简洁。
创建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;
}
}
本文来自博客园,作者:gary2048,转载请注明原文链接:https://www.cnblogs.com/zhoum/p/15854156.html