# 微服务远程调用
# 声明式服务调用 Feign
Spring Cloud OpenFeign
为微服务架构下服务之间的调用提供了解决方案。使用Feign
进行微服务间调用非常简单,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,调用方使用该接口即可完成服务间访问。下面以contract
合同服务访问user
用户服务为例演示服务间的调用过程。
1.数据库中创建合同表
CREATE TABLE `contract` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`name` varchar(32) DEFAULT NULL COMMENT '名称',
`signer` varchar(32) DEFAULT NULL COMMENT '合同签订人',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_id` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`create_organization_id` varchar(32) DEFAULT NULL COMMENT '创建人组织ID',
`update_id` varchar(32) DEFAULT NULL COMMENT '修改人ID',
`tenant_id` varchar(32) DEFAULT NULL COMMENT '租户ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='合同表';
2.按照创建user
用户服务的方式在工程中创建contract
合同服务。
3.在user
用户服务和contract
合同服务的启动类com.mediway.UserApplication
和com.mediway.ContractApplication
上分别添加@EnableFeignClients
注解开启对Feign
的支持。
package com.mediway;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan(basePackages = {"com.mediway.hos.*.mapper"})
@EnableFeignClients
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
package com.mediway;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan(basePackages = {"com.mediway.hos.*.mapper"})
@EnableFeignClients
@SpringBootApplication
public class ContractApplication {
public static void main(String[] args) {
SpringApplication.run(ContractApplication.class, args);
}
}
4.在hos-user-service
模块的com.mediway.oa.user.controller.StaffController
中添加selectStaffListByName
方法。
/**
* 根据名称模糊查询用户
*
* @return
*/
@GetMapping("/selectStaffListByName")
public BaseResponse<List<Staff>> selectStaffListByName(String name) {
return BaseResponse.success(staffService.selectStaffListByName(name));
}
5.在hos-user-api
模块的com.mediway.oa.user.api
包下创建StaffApi
员工信息接口,用于供合同服务调用。添加selectStaffListByName
方法,该方法作用是根据姓名模糊查询员工信息。
package com.mediway.oa.user.api;
import com.mediway.hos.base.model.BaseResponse;
import com.mediway.oa.user.model.entity.Staff;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(value = "oa-user-service", path = "/user/staff")
public interface StaffApi {
@GetMapping("/selectStaffListByName")
BaseResponse<List<Staff>> selectStaffListByName(@RequestParam String name);
}
6.在hos-contract-controller
模块的com.mediway.oa.contract.controller.ContractController
中添加selectStaffListByName
,该方法作用是根据姓名远程调用用户服务获取员工信息。
package com.mediway.oa.contract.controller;
import com.mediway.hos.database.controller.BaseController;
import com.mediway.hos.base.model.BaseResponse;
import com.mediway.oa.contract.model.entity.Contract;
import com.mediway.oa.user.api.StaffApi;
import com.mediway.oa.user.model.entity.Staff;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/contract")
public class ContractController extends BaseController<Contract>{
@Autowired
private StaffApi staffFeignClient;
/**
* 根据员工姓名获取员工信息
*
* @param name 员工姓名
* @return
*/
@GetMapping("/selectLikeList")
public BaseResponse<List<Staff>> selectStaffListByName(@RequestParam("name") String name) {
return staffFeignClient.selectStaffListByName(name);
}
}
7.分别启动user
服务(端口8003)、contract
服务(端口8007)和hos-managecenter
模块下managecenter-gateway
服务(端口7100)
8.我们通过网关请求ContractController
的selectStaffListByName
方法,接口的请求方式、请求参数和响应结果如下
请求方式
url:http://localhost:7100/contract/contract/selectLikeList
method:GET
请求参数
&name=张三
响应结果
{
"code":"200",
"msg":"success",
"data":[
{
"id":"9e9c735844b841c0a970decdb2b5e182",
"createTime":"2022-01-1414:57:21",
"updateTime":"2022-01-1415:25:01",
"pageIndex":null,
"pageSize":null,
"name":"张三",
"gender":"男",
"age":28,
"orgId":"1",
"email":"zhangsan@qq.com",
"phone":"13812345678",
"description":"开发部张三"
}
],
"success":true
}
Spring Cloud Feign
客户端默认开启支持Ribbon
,有两个重要的超时时间即连接超时ConnectTimeout
和读取超时ReadTimeout
,在默认情况下,两个超时时间都是1秒。由于默认超时时间很短,所以在调用过程中可能会出现请求超时的情况。通过下边配置可修改超时时间。
我们可修改nacos
上服务调用方即contract
服务的配置文件hos-contract
,添加ribbon
超时设置。
ribbon:
#建立连接超时时间,单位:ms
ConnectTimeout: 30000
#建立连接之后,读取响应资源超时时间,单位:ms
ReadTimeout: 30000
# 事务代码说明
# 添加事务注解
在 contract
模块调用 user
模块的 service
方法上加两个注解:
本地注解: @Transactional(rollbackFor = Exception.class)
分布式注解:@GlobalTransactional(rollbackFor = Exception.class)
代码示例如下:
@GlobalTransactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public Contract saveContract(String name, String signer) {
Contract contract = new Contract();
contract.setName(name);
contract.setSigner(signer);
save(contract);
// 远程调用用户服务
BaseResponse<Staff> staffBaseResponse = staffFApi.saveUser(signer);
log.info("调用用户服务结果:{}", staffBaseResponse);
// 此处需要手动抛出异常,避免异常被全局异常处理拦截导致seata无法获取到异常
if (!staffBaseResponse.isSuccess()) {
throw new RuntimeException("调用用户服务发生异常");
}
return contract;
}
# 单体模式使用本地事务
在单体模块中因为不需要使用分布式事务,因此需要在启动类上的@SpringBootApplication
注解上通过 exclude
排除分布式事务的相关配置,
具体代码示例如下:
@SpringBootApplication(exclude = {SeataPropertiesAutoConfiguration.class, SeataDataSourceAutoConfiguration.class, SeataAutoConfiguration.class, HttpAutoConfiguration.class})
public class HosAppApplication {
public static void main(String[] args) {
SpringApplication.run(HosAppApplication.class, args);
}
}
# 启动方式说明
# 单体启动方式
右击oa-runner
下OAApplication
类,选择“Run
”方法即可。
# 微服务启动方式
分别选择hos-user-cloud-runner
和hos-contract-cloud-runner
下Application
类,选择“Run”方法即可。