前言
我之前的笔记中集成nacos同时进行远程调用的是使用的RestTemplate,但是自己去写过的小伙伴就会发现一定的问题,具体如下
resttemplate的写法
1、注入resttemplate对象
2、使用resttemplate进行远程调用
package com.example.controller;
import com.example.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* Created by IntelliJ IDEA.
*
* @Author : wlh
* @create 2023/1/18 12:34
*/
@RestController
@RequestMapping("/test2")
@RefreshScope
public class Test2 {
// 我自己注入了一个RestTemplate 对象在spring 容器中
@Autowired
private RestTemplate restTemplate;
// 调用远程user服务
@GetMapping("/get/{id}")
public User get(@PathVariable("id") Integer id){
// 格式为:http://注册到服务中心的服务名称/请求
User forObject = restTemplate.getForObject("http://userserver/user/get/" + id, User.class);
return forObject;
}
}
这里是直接通过test调用了user服务的,所以看起来比较简短,但是在实际开发中实现的远程调用还是比较复杂的,例如我需要查看
但是在实际开发中实现的远程调用还是比较复杂的,在后续的业务复杂起来的时候去每个业务实现层写的话就太浪费时间了,甚至需要修改的时候也不易于维护,同时在代码的可读性比较差
1、解决问题 – 使用feign替换resttemplate来实现远程调用
0、feign说明
Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。
是最方便、最优雅的方式是通过 Spring Cloud Open Feign 进行服务间的调用 Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring Mvc 的注解从而让 Feign 的使用更加方便。
1、引入依赖
<!-- openfeign依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${openfeign.version}</version>
</dependency>
2、编写远程调用代码(定义client接口)
package space.ikiku.clients;
import dto.Result;
import entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @Classname UserClient
* @Description TODO User服务的api接口
* @Version 1.0.0
* @Date 2023/1/31 23:05
* @Created by wlh12
*/
@FeignClient("userService") // 参数为远程服务的名称
public interface UserClient {
// 请求类型和UserController一致、参数一致、返回类型一致
@GetMapping("/user/{id}")
Result<User> userClintGet(@PathVariable("id") Integer id);
@GetMapping("/user/body")
Result<User> userClientBody(@RequestBody User user);
}
3、启动添加注解@EnableFeignClients开启扫描
@EnableFeignClients注解参数
package space.ikiku;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients //开启feign
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
4、两个服务的controller
order服务
@Autowired
private UserClient userClient; //自动装配
// @GetMapping("/{id}")
// public Result<Orders> ordersResult(@PathVariable("id") Integer id){
// Orders orders = new Orders();
// orders.setCommodityId(id+10);
// orders.setCommodityName("shangping:"+id);
// orders.setCreateTime(new Date());
// Result<User> userResult = userClient.userClintGet(id);
// orders.setUser(userResult.getData());
// return Result.successData(orders);
// }
@GetMapping("/{id}")
public Result<Orders> body(@PathVariable("id") Integer id){
Orders orders = new Orders();
orders.setCommodityId(id+10);
orders.setCommodityName("shangping:"+id);
orders.setCreateTime(new Date());
User user = new User();
user.setId(id);
user.setName("username+"+id);
// 调用远程
Result<User> userResult = userClient.userClientBody(user);
orders.setUser(userResult.getData());
return Result.successData(orders);
}
user服务,接受一个user对象直接返回
5、启动nacos和两个项目进行测试调用
http://localhost:8082/order/2 – 测试结果:实现了远程调用
注意:访问可能报错,报错信息是远程调用使用的是post请求而不是我们定义的userclient的get类型,原因是因为feign发现存在body体对象时会换成post请求,解决办法是引入下面的依赖:
<!-- 解决Feign只要发现你存在body体对象,就会将Get请求转成Post。-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
这个依赖也可用于性能优化,因为feign底层默认是URLconnection,上面的依赖是换成了Apache的httpclient可以实现性能优化
2、性能优化
feign:
httpclient:
enabled: true # 开启httpClient
max-connections: 200 #连接池最大数量
max-connections-per-route: 50 #单个路径的最大连接数
3、feign开发时如何使用?
feign是用于远程调用的,如果在多个模块对同一个模块进行了调用在不同的模块会进行重复编写代码,所以在使用的时候我们可以把它整成一个模块,用于feign-api的模块
1、新建模块
2、在父工程里面引入feign-api依赖
3、编写userclientapi
代码:
package space.ikiku.clients;
import dto.Result;
import entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @Classname UserClientApi
* @Description TODO
* @Version 1.0.0
* @Date 2023/2/1 22:08
* @Created by wlh12
*/
@FeignClient("userservice")
public interface UserClientApi {
@GetMapping("/user/{id}")
Result<User> userClintGet(@PathVariable("id") Integer id);
@GetMapping("/user/body")
Result<User> userClientBody(@RequestBody User user);
}
4、order服务controller
@Autowired
private UserClientApi userClientApi; //换成了UserClientApi
// @GetMapping("/{id}")
// public Result<Orders> ordersResult(@PathVariable("id") Integer id){
// Orders orders = new Orders();
// orders.setCommodityId(id+10);
// orders.setCommodityName("shangping:"+id);
// orders.setCreateTime(new Date());
// Result<User> userResult = userClient.userClintGet(id);
// orders.setUser(userResult.getData());
// return Result.successData(orders);
// }
@GetMapping("/{id}")
public Result<Orders> body(@PathVariable("id") Integer id){
Orders orders = new Orders();
orders.setCommodityId(id+10);
orders.setCommodityName("shangping:"+id);
orders.setCreateTime(new Date());
User user = new User();
user.setId(id);
user.setName("username+"+id);
Result<User> userResult = userClientApi.userClientBody(user);
orders.setUser(userResult.getData());
return Result.successData(orders);
}
重 点!!:由于不在同一个模块下,这样启动项目会报错,会提示找不到UserClientApi 这个bean,@EnableFeignClients不指定扫描的时候找不到UserClientApi ,需要修改
5、修改@EnableFeignClients参数
package space.ikiku;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.openfeign.EnableFeignClients;
import space.ikiku.clients.UserClientApi;
@SpringBootApplication
@EnableDiscoveryClient
// 修改clients的值,指定具体的api
//也可以使用@EnableFeignClients(basePackages = {"space.ikiku.clients"})扫描所在包
@EnableFeignClients(clients = {UserClientApi.class})
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
6、重启项目进行测试
通过测试
7、总结
这样把所有的client放在一个模块里面,包括相关的config配置类,方便管理也不存在重复代码。
到此feign的使用就结束了,还有就是新版的nacos去掉了ribbon,使用了loadbalance实现负载均衡,同时feign在新版的依赖包也找不到ribbon,说明也是去掉了转而使用的是loadbalance