此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。
本文为服务治理框架Dubbo泛化调用的实现
本系列文章中所使用的框架版本为Spring Boot 2.0.3-RELEASE,Spring 5.0.7-RELEASE,Dubbo 2.6.2。
泛化调用组件
Dubbo一般在内部系统间,通过RPC调用,正常情况下服务消费方需要导入服务提供skeleton的jar包,根据此接口编写服务消费者
为了便于开发及测试,实现泛化调用功能,即使用Controller封装请求,获取对应的服务,转接为RPC调用
泛化调用组件依赖需添加对应服务接口的jar包
控制器
- 获取泛化服务接口
- 组装服务调用参数
- 调用接口、封装数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
@RequestMapping(value = "/")
public class GenericController {
@RequestMapping(value = "/generic", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public Object call(@ModelAttribute("reqModel") GenericReqModel reqModel) throws ClassNotFoundException {
Object result = null;
// 1.获取泛化服务接口
GenericService service = DubboUtils.fetchGenericService(reqModel);
// 2.组装调用参数
String method = reqModel.getMethod();
String[] parameterTypes = DubboUtils.getMethodParamType(
reqModel.getService(), reqModel.getMethod());
Object[] args = JSON.parseArray(reqModel.getParamValues()).toArray(
new Object[] {});
// 3.调用接口
return JSON.toJSONString(service
.$invoke(method, parameterTypes, args));
}
}
泛化引用接口
Dubbo支持的泛化引用接口,可通过在 Spring Boot 配置申明 generic="true"
使用,详见泛化引用
在此组件中无需配置即可使用
1
<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
Properties
1
dubbo.reference.generic = true
接口定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.alibaba.dubbo.rpc.service;
/**
* Generic service interface
*
* @export
*/
public interface GenericService {
/**
* Generic invocation
*
* @param method Method name, e.g. findPerson. If there are overridden methods, parameter info is
* required, e.g. findPerson(java.lang.String)
* @param parameterTypes Parameter types
* @param args Arguments
* @return invocation return value
* @throws Throwable potential exception thrown from the invocation
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}
工具类
DubboUtils:
fetchGenericService
:获取泛化引用接口(如有缓存, 则从缓存取)getMethodParamType
:根据接口类名及方法名通过反射获取参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class DubboUtils {
private static Logger logger = LoggerFactory.getLogger(DubboUtils.class);
private static ApplicationConfig application = new ApplicationConfig(
"generic-reference");
private static Map<String, GenericService> serviceCache = Maps.newConcurrentMap();
/**
* 获取泛化服务接口(如有缓存, 则从缓存取)
* @param reqModel
* @return
*/
public static GenericService fetchGenericService(GenericReqModel reqModel) {
// 参数设置
String serviceInterface = reqModel.getService();
String serviceGroup = reqModel.getGroup();
String serviceVersion = reqModel.getVersion();
// 从缓存中获取服务
String serviceCacheKey = serviceInterface + "-" + serviceGroup + "-"
+ serviceVersion;
GenericService service = serviceCache.get(serviceCacheKey);
if (service != null) {
logger.info("fetched generic service from cache");
return service;
}
// 配置调用信息
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setApplication(application);
reference.setInterface(serviceInterface);
reference.setGroup(serviceGroup);
reference.setVersion(serviceVersion);
reference.setGeneric(true); // 声明为泛化接口
// 获取对应服务
service = reference.get();
// 缓存
serviceCache.put(serviceCacheKey, service);
return service;
}
/**
* 根据接口类名及方法名通过反射获取参数类型
*
* @param interfaceName 接口名
* @param methodName 方法名
* @return
* @throws ClassNotFoundException
*/
public static String[] getMethodParamType(String interfaceName,
String methodName) throws ClassNotFoundException {
String[] paramTypeList = null;
// 创建类
Class<?> clazz = Class.forName(interfaceName);
// 获取所有的公共的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] paramClassList = method.getParameterTypes();
paramTypeList = new String[paramClassList.length];
int i = 0;
for (Class<?> className : paramClassList) {
paramTypeList[i] = className.getTypeName();
i++;
}
break;
}
}
return paramTypeList;
}
}
启动器
若要使用不同的Web容器,需引入对应的Spring Boot启动依赖,e.g
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
项目使用Web容器启动
1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication(scanBasePackages = "com.spring.boot.generic.controller")
public class Starter {
public static void main(String[] args) {
new SpringApplicationBuilder(Starter.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
请求模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class GenericReqModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7389007812411509827L;
private String registry; // 注册中心地址
private String service; // 服务包下类名
private String version; // 版本
private String group; // 服务组
private String method; // 方法名
private String paramTypes; // 参数类型
private String paramValues; // 参数值
setter/getter
}
POSTMAN使用方法
1.Headers配置
Key | Value |
---|---|
Content-Type | application/json |
2.参数配置(Params)
Key | Value |
---|---|
registry | zookeeper://localhost |
service | |
version | 1.0.0 |
group | |
method | getWord |
paramValues | [{}] |
3.调用示例
源码:GitHub