依赖管理
统一依赖版本号
<!--SpringBoot版本号-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--版本号-->
<properties>
<java.version>1.8</java.version>
<mybatis.version>2.1.0</mybatis.version>
<mysql.connector.version>6.0.6</mysql.connector.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<spring-cloud-netflix.version>2.2.0.RELEASE</spring-cloud-netflix.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<!--依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>${spring-cloud-netflix.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
前提
说Feign之前,得先说说RestTemplate,RestTemplate用于访问远程http接口,比如A服务需要调用B方服务的方法,就可以使用RestTemplate,使用方式:
微服务使用nacos注册发现,这里需要加入ribbon作为负载均衡,添加依赖
<!--依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
</dependency>
Java代码
Application:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.example.feign.client")
@MapperScan("com.example.service.order.dao")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
Service:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@Override
public Order getOrderById(int id) {
Order order = orderDao.selectById(id);
// RestTemplate调用
User user = getUser(order.getUserId());
order.setUser(user);
return order;
}
private User getUser(int userId) {
// service-user是用户服务的服务名称,已经注册到nacos,这里不用写IP地址和端口号直接,使用服务名称即可
String url = "http://service-user/user/get/" + userId;
return restTemplate.getForObject(url, User.class);
}
}
注意:用服务名称代替服务地址和端口号这种方式一定要加@LoadBalanced服务均衡注解和ribbon依赖,本人尝试不用这个注解,不加依赖,出现service-user服务找不到错误,这里踩了个坑!!!
以上是RestTemplate使用方式,看起来已经很简单了,但还是有点缺点:需要在代码上拼接URL地址,用起来不够优雅,来,我们们看看Feign优雅的做法
Feign使用
Feign也是远程服务调用的框架,它可以让我们使用接口一样调用远程服务,具体使用方法:
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Application代码:
@SpringBootApplication
@EnableDiscoveryClient
// 允许使用Feign远程调用
@EnableFeignClients
@MapperScan("com.example.service.order.dao")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
// @LoadBalanced
// @Bean
// public RestTemplate getRestTemplate() {
// return new RestTemplate();
// }
}
创建user服务FeignClient
// service-user是在nacos注册的服务名称
@FeignClient(value = "service-user")
public interface UserClient {
// 只要把User服务的Controller对应的方法copy过来即可,不用实现
// Feign会通过动态代理的方式帮我们调用Http请求
@GetMapping("/user/get/{id}")
User getUser(@PathVariable("id") int userId);
}
Service:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@Override
public Order getOrderById(int id) {
Order order = orderDao.selectById(id);
// RestTemplate调用
// User user = getUser(order.getUserId());
// Feign调用
User user = userClient.getUser(order.getUserId());
order.setUser(user);
return order;
}
// private User getUser(int userId) {
// String url = "http://service-user/user/get/" + userId;
// return restTemplate.getForObject(url, User.class);
// }
}
是不是变得更加优雅?!
性能优化
Feign默认底层使用URLConnection方式进行Http请求,这种方式是没有连接池的,调用一次连接一次,性能上可以优化,我们一般是使用HttpClient或OkHttp。
这里我使用OkHttp作为访问Http的方式,如下:
添加依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
application.yml修改
feign:
httpclient:
enabled: false
okhttp:
enabled: true
这样我们就允许使用OkHttp进行Http请求,如果需要设置最大连接数、单个服务最大连接数、连接超时时间等简单参数,只要添加配置
feign:
httpclient:
enabled: false
max-connections: 200
max-connections-per-route: 50
okhttp:
enabled: true
但是我们要一些复杂的配置,则需要添加配置类,这里注意了,我们不能简简单单弄一个@Bean出来,否则okhttp会失效。
我们需要copy一份OkHttpFeignConfiguration的代码,对其进行修改即可。
参考链接:https://blog.csdn.net/eric520zenobia/article/details/103547552
最佳实践
生产环境中,会出现A服务和B服务都调用C服务的方法,如果A服务和B服务都定义C服务的FeignClient,是不是很多余,我们可以把C服务的FeignClient抽取出来,作为公共模块使用。
所以一般会新建一个模块(feign-api),把C服务的FeignClient挪到这个模块,A服务和B服务依赖这个模块就行。
这里会出现一个情况,我们在Service使用Autowire注入的方式使用FeignClient:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private UserClient userClient;
@Override
public Order getOrderById(int id) {
Order order = orderDao.selectById(id);
User user = userClient.getUser(order.getUserId());
order.setUser(user);
return order;
}
}
因为UserClient被我挪到了feign-api模块,包名不一样,也不在OrderApplication所在的包,所以会扫描不到,userClient会是空的
所以我们还要在Application代码修改一下:
@SpringBootApplication
@EnableDiscoveryClient
// 添加包名,这样SpringBoot才能扫描
@EnableFeignClients(basePackages = "com.example.feign.client")
// 还可以使用这种方式,用哪一个加哪一个
// @EnableFeignClients(clients = {UserClient.class})
@MapperScan("com.example.service.order.dao")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}