深入讲解SpringCloud Alibaba 微服务调用,还不
概述
在微服务架构中,最常见的场景就是微服务间的相互调用。微服务间的相互调用方式主要有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.*")