SpringCloud 整合 Dubbo

选择整合方式

1 用 dubbo 替换 feign 的 http 请求方式

2 正常的接口远程调用方式

0 共通基础

不管用什么方式使用dubbo 都得引入依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

1 用 dubbo 替代 feign

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
import feign.Feign;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;

import java.util.Objects;

import static com.alibaba.spring.util.AnnotationUtils.getAttributes;
import static org.springframework.core.annotation.AnnotationAttributes.fromMap;

/**
* Dubbo、Feign整合类
*/
@Slf4j
public class DubboFeignBuilder extends Feign.Builder {

@Autowired
@SuppressWarnings("all")
private ApplicationContext applicationContext;

public DubboReference defaultReference;

static final class DefaultReferenceClass {
@DubboReference(check = false)
String field;
}

public DubboFeignBuilder() {
this.defaultReference = Objects.requireNonNull(ReflectionUtils.findField(DefaultReferenceClass.class, "field")).getAnnotation(DubboReference.class);
}


@Override
public <T> T target(Target<T> target) {
ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder.create(fromMap(getAttributes(defaultReference,
applicationContext.getEnvironment(), true)), applicationContext).interfaceClass(target.type());
try {
T object = (T) beanBuilder.build().getObject();
return object;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514

import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.ArrayUtils;
import org.apache.dubbo.config.MethodConfig;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.annotation.Method;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.config.spring.ServiceBean;
import org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener;
import org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.*;
import org.springframework.beans.factory.support.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.*;

import java.lang.annotation.Annotation;
import java.util.*;

import static com.alibaba.spring.util.AnnotationUtils.getAttribute;
import static com.alibaba.spring.util.BeanRegistrar.registerInfrastructureBean;
import static com.alibaba.spring.util.ObjectUtils.of;
import static java.util.Arrays.asList;
import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import static org.springframework.context.annotation.AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes;
import static org.springframework.util.ClassUtils.getAllInterfacesForClass;
import static org.springframework.util.ClassUtils.resolveClassName;
import static org.springframework.util.StringUtils.hasText;


@Slf4j
public class DubboFeignProviderBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {

private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(
// @since 2.7.7 Add the @DubboService , the issue : https://github.com/apache/dubbo/issues/6007
DubboService.class,
// @since 2.7.0 the substitute @com.alibaba.dubbo.config.annotation.Service
Service.class,
// @since 2.7.3 Add the compatibility for legacy Dubbo's @Service , the issue : https://github.com/apache/dubbo/issues/4330
com.alibaba.dubbo.config.annotation.Service.class
);

private static final String SEPARATOR = ":";

private final Set<String> packagesToScan;

private Environment environment;

private ResourceLoader resourceLoader;

private ClassLoader classLoader;

@Autowired
private final DubboService defaultService;

public DubboFeignProviderBeanPostProcessor(String... packagesToScan) {
this(Arrays.asList(packagesToScan));
}

public DubboFeignProviderBeanPostProcessor(Collection<String> packagesToScan) {
this(new LinkedHashSet<String>(packagesToScan));
}

public DubboFeignProviderBeanPostProcessor(Set<String> packagesToScan) {
this.packagesToScan = packagesToScan;
@DubboService
final class DefaultServiceClass {
}
;
this.defaultService = DefaultServiceClass.class.getAnnotation(DubboService.class);
}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

// @since 2.7.5
registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME, DubboBootstrapApplicationListener.class);

Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
log.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}

/**
* Registers Beans whose classes was annotated {@link FeignClient}
*
* @param packagesToScan The base packages to scan
* @param registry {@link BeanDefinitionRegistry}
*/
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

scanner.setBeanNameGenerator(beanNameGenerator);

// refactor @since 2.7.7
serviceAnnotationTypes.forEach(annotationType -> {
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
});

for (String packageToScan : packagesToScan) {

// Registers @DubboService Bean first
scanner.scan(packageToScan);

// Finds all BeanDefinitionHolders of @DubboService whether @ComponentScan scans or not.
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
log.info(beanDefinitionHolders.size() + " annotated Dubbo's @DubboService Components { " +
beanDefinitionHolders +
" } were scanned under package[" + packageToScan + "]");
} else {
log.warn("No Spring Bean annotating Dubbo's @DubboService was found under package["
+ packageToScan + "]");
}

}

}

/**
* It'd better to use BeanNameGenerator instance that should reference
* {@link ConfigurationClassPostProcessor},
* thus it maybe a potential problem on bean name generation.
*
* @param registry {@link BeanDefinitionRegistry}
* @return {@link BeanNameGenerator} instance
* @see SingletonBeanRegistry
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#processConfigBeanDefinitions
* @since 2.5.8
*/
private BeanNameGenerator resolveBeanNameGenerator(BeanDefinitionRegistry registry) {

BeanNameGenerator beanNameGenerator = null;

if (registry instanceof SingletonBeanRegistry) {
SingletonBeanRegistry singletonBeanRegistry = SingletonBeanRegistry.class.cast(registry);
beanNameGenerator = (BeanNameGenerator) singletonBeanRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
}

if (beanNameGenerator == null) {
log.info("BeanNameGenerator bean can't be found in BeanFactory with name ["
+ CONFIGURATION_BEAN_NAME_GENERATOR + "]");
log.info("BeanNameGenerator will be a instance of " +
AnnotationBeanNameGenerator.class.getName() +
" , it maybe a potential problem on bean name generation.");
beanNameGenerator = new AnnotationBeanNameGenerator();
}
return beanNameGenerator;
}
/**
* Finds a {@link Set} of {@link BeanDefinitionHolder BeanDefinitionHolders} whose bean type annotated
* {@link DubboService} Annotation.
*
* @param scanner {@link ClassPathBeanDefinitionScanner}
* @param packageToScan pachage to scan
* @param registry {@link BeanDefinitionRegistry}
* @return non-null
* @since 2.5.8
*/
private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
BeanNameGenerator beanNameGenerator) {

Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);

Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<BeanDefinitionHolder>(beanDefinitions.size());

for (BeanDefinition beanDefinition : beanDefinitions) {

String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
beanDefinitionHolders.add(beanDefinitionHolder);

}
return beanDefinitionHolders;

}

/**
* Registers {@link ServiceBean} from new annotated {@link DubboService} {@link BeanDefinition}
*
* @param beanDefinitionHolder
* @param registry
* @param scanner
* @see ServiceBean
* @see BeanDefinition
*/
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {

Class<?> beanClass = resolveClass(beanDefinitionHolder);

Annotation service = findServiceAnnotation(beanClass);

/**
* The {@link AnnotationAttributes} of @Service annotation
*/
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);

Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);

String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();

AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);

// ServiceBean Bean name
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);

if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
log.warn("The BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean has been registered with name : " + beanName);
} else {
log.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean[ bean name : " + beanName +
"] was be found , Did @DubboComponentScan scan to same package in many times?");
}
}

/**
* Generates the bean name of {@link ServiceBean}
*
* @param serviceAnnotationAttributes
* @param interfaceClass the class of interface annotated {@link Service}
* @return ServiceBean@interfaceClassName#annotatedServiceBeanName
* @since 2.7.3
*/
private String generateServiceBeanName(AnnotationAttributes serviceAnnotationAttributes, Class<?> interfaceClass) {
ServiceBeanNameBuilder builder = create(interfaceClass, environment)
.group(serviceAnnotationAttributes.getString("group"))
.version(serviceAnnotationAttributes.getString("version"));
return builder.build();
}

private Class<?> resolveServiceInterfaceClass(AnnotationAttributes attributes, Class<?> defaultInterfaceClass)
throws IllegalArgumentException {

ClassLoader classLoader = defaultInterfaceClass != null ? defaultInterfaceClass.getClassLoader() : Thread.currentThread().getContextClassLoader();
Class<?> interfaceClass = getAttribute(attributes, "interfaceClass");

if (void.class.equals(interfaceClass)) {

interfaceClass = null;

String interfaceClassName = getAttribute(attributes, "interfaceName");

if (hasText(interfaceClassName)) {
if (ClassUtils.isPresent(interfaceClassName, classLoader)) {
interfaceClass = resolveClassName(interfaceClassName, classLoader);
}
}
}

if (interfaceClass == null && defaultInterfaceClass != null) {
// Find all interfaces from the annotated class
// To resolve an issue : https://github.com/apache/dubbo/issues/3251
Class<?>[] allInterfaces = getAllInterfacesForClass(defaultInterfaceClass);

if (allInterfaces.length > 0) {
interfaceClass = allInterfaces[0];
}

}
Assert.notNull(interfaceClass,
"@Service interfaceClass() or interfaceName() or interface class must be present!");

Assert.isTrue(interfaceClass.isInterface(),
"The annotated type must be an interface!");

return interfaceClass;
}

private Class<?> resolveClass(BeanDefinitionHolder beanDefinitionHolder) {

BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
return resolveClass(beanDefinition);

}

private Class<?> resolveClass(BeanDefinition beanDefinition) {

String beanClassName = beanDefinition.getBeanClassName();
return resolveClassName(beanClassName, classLoader);

}


/**
* Find the {@link Annotation annotation} of @Service
*
* @param beanClass the {@link Class class} of Bean
* @return <code>null</code> if not found
* @since 2.7.3
*/
private Annotation findServiceAnnotation(Class<?> beanClass) {
return serviceAnnotationTypes
.stream()
.map(annotationType -> findMergedAnnotation(beanClass, annotationType))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

private Set<String> resolvePackagesToScan(Set<String> packagesToScan) {
Set<String> resolvedPackagesToScan = new LinkedHashSet<String>(packagesToScan.size());
for (String packageToScan : packagesToScan) {
if (StringUtils.hasText(packageToScan)) {
String resolvedPackageToScan = environment.resolvePlaceholders(packageToScan.trim());
resolvedPackagesToScan.add(resolvedPackageToScan);
}
}
return resolvedPackagesToScan;
}

/**
* Build the {@link AbstractBeanDefinition Bean Definition}
*
* @param serviceAnnotation
* @param serviceAnnotationAttributes
* @param interfaceClass
* @param annotatedServiceBeanName
* @return
* @since 2.7.3
*/
private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
AnnotationAttributes serviceAnnotationAttributes,
Class<?> interfaceClass,
String annotatedServiceBeanName) {

BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);

AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();

String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
"interface", "interfaceName", "parameters");

propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));

// References "ref" property to annotated-@Service Bean
addPropertyReference(builder, "ref", annotatedServiceBeanName);
// Set interface
builder.addPropertyValue("interface", interfaceClass.getName());
// Convert parameters into map
builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters")));
// Add methods parameters
List<MethodConfig> methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods"));
if (!methodConfigs.isEmpty()) {
builder.addPropertyValue("methods", methodConfigs);
}

/**
* Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference
*/
String providerConfigBeanName = serviceAnnotationAttributes.getString("provider");
if (StringUtils.hasText(providerConfigBeanName)) {
addPropertyReference(builder, "provider", providerConfigBeanName);
}

/**
* Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference
*/
String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor");
if (StringUtils.hasText(monitorConfigBeanName)) {
addPropertyReference(builder, "monitor", monitorConfigBeanName);
}

/**
* Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference
*/
String applicationConfigBeanName = serviceAnnotationAttributes.getString("application");
if (StringUtils.hasText(applicationConfigBeanName)) {
addPropertyReference(builder, "application", applicationConfigBeanName);
}

/**
* Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference
*/
String moduleConfigBeanName = serviceAnnotationAttributes.getString("module");
if (StringUtils.hasText(moduleConfigBeanName)) {
addPropertyReference(builder, "module", moduleConfigBeanName);
}


/**
* Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference
*/
String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry");

List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);

if (!registryRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("registries", registryRuntimeBeanReferences);
}

/**
* Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference
*/
String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol");

List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);

if (!protocolRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
}

return builder.getBeanDefinition();

}


private ManagedList<RuntimeBeanReference> toRuntimeBeanReferences(String... beanNames) {

ManagedList<RuntimeBeanReference> runtimeBeanReferences = new ManagedList<RuntimeBeanReference>();

if (!ObjectUtils.isEmpty(beanNames)) {

for (String beanName : beanNames) {

String resolvedBeanName = environment.resolvePlaceholders(beanName);

runtimeBeanReferences.add(new RuntimeBeanReference(resolvedBeanName));
}

}

return runtimeBeanReferences;

}

private void addPropertyReference(BeanDefinitionBuilder builder, String propertyName, String beanName) {
String resolvedBeanName = environment.resolvePlaceholders(beanName);
builder.addPropertyReference(propertyName, resolvedBeanName);
}


@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}

private List convertMethodConfigs(Object methodsAnnotation) {
if (methodsAnnotation == null) {
return Collections.EMPTY_LIST;
}
return MethodConfig.constructMethodConfig((Method[]) methodsAnnotation);
}

private Map<String, String> convertParameters(String[] parameters) {
if (ArrayUtils.isEmpty(parameters)) {
return null;
}

if (parameters.length % 2 != 0) {
throw new IllegalArgumentException("parameter attribute must be paired with key followed by value");
}

Map<String, String> map = new HashMap<>();
for (int i = 0; i < parameters.length; i += 2) {
map.put(parameters[i], parameters[i + 1]);
}
return map;
}
}

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

import feign.Feign;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.AbstractConfig;
import org.apache.dubbo.config.spring.beans.factory.annotation.DubboFeignBuilder;
import org.apache.dubbo.config.spring.beans.factory.annotation.DubboFeignProviderBeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;

import java.util.Set;

import static java.util.Collections.emptySet;
import static org.apache.dubbo.spring.boot.util.DubboUtils.*;

/**
* Dubbo配置
*/
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true, havingValue = "true")
@ConditionalOnClass(AbstractConfig.class)
public class DubboFeignConfiguration {

@ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
@ConditionalOnClass(ConfigurationPropertySources.class)
@Bean
public DubboFeignProviderBeanPostProcessor dubboFeignProviderBeanPostProcessor(Environment environment) {
Set<String> packagesToScan = environment.getProperty(DUBBO_SCAN_PREFIX + BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
return new DubboFeignProviderBeanPostProcessor(packagesToScan);
}

@Primary
@Bean
public Feign.Builder feignDubboBuilder() {
return new DubboFeignBuilder();
}
}

这种方式我目前没有实践过 代码参考 https://github.com/matevip/matecloud 的 mate-starter-dubbo 模块

2 dubbo 单独使用

首先 代码前提是 SpringCloud Nacos

provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
application:
name: provider #你的provider 名称 填写到 下面的 subscribed-services 中

dubbo:
cloud:
subscribed-services: provider #这里要填写 ${spring.application.name} 表示自己订阅自己 否则默认是 '*' 会订阅所有服务
#https://github.com/alibaba/spring-cloud-alibaba/issues/1564
#https://github.com/alibaba/spring-cloud-alibaba/issues/1141
protocol:
name: dubbo
#-1是自增长
port: -1
registry:
address: nacos://${spring.cloud.nacos.config.server-addr}

#address: spring-cloud://127.0.0.1
  • dubbo.scan.base-packages:指定 Dubbo 服务实现类的扫描基准包
  • dubbo.protocol:Dubbo服务暴露的协议配置,其中子属性name为协议名称,port为协议端口(-1 表示自增端口,从 20880 开始)
  • dubbo.registry:Dubbo 服务注册中心配置,其中子属性address 的值 “spring-cloud://192.168.44.129”,说明挂载到 Spring Cloud 注册中心
  • spring.application.name:Spring 应用名称,用于 Spring Cloud 服务注册和发现。该值在 Dubbo Spring Cloud 加持下被视作dubbo.application.name,因此,无需再显示地配置dubbo.application.name
  • spring.main.allow-bean-definition-overriding:在 Spring Boot 2.1 以及更高的版本增加该设定,因为 Spring Boot 默认调整了 Bean 定义覆盖行为。
  • spring.cloud.nacos.discovery:Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口。

dubbo.scan.base-packages 这个可以用 @DubboComponentScan 替代

配置文件我都放到 nacos 中公用了 这里就用 注解来扫描吧 毕竟每个 provider 扫描的包可能不一样

先定义一个基础 api 包

添加上接口

在 provider 中实现该接口 并添加 @DubboService 注解

consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dubbo:
protocol:
name: dubbo
port: -1
registry:
address: nacos://${spring.cloud.nacos.config.server-addr}
cloud:
subscribed-services: provider #要订阅的服务 默认是 *

spring:
application:
name: consumer
main:
allow-bean-definition-overriding: true
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #registry address 引用这个
file-extension: yml
shared-configs:
- data-id: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
- data-id: database-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
discovery:
server-addr: 127.0.0.1:8848

需要调用 provider 的地方使用

@DubboReference 注解需要调用的接口即可

具体可以参考

https://github.com/nemowang/springcloud-dubbo-nacos-example

其他

consumer 先启动不报错配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import org.apache.dubbo.config.ConsumerConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DubboConfig {

/**
* 消费者配置不主动监督服务
*/
@Bean
public ConsumerConfig consumerConfig() {
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setCheck(false);
consumerConfig.setTimeout(5000);
return consumerConfig;
}
}

链路追踪

消费者 访问 供应者 前把追踪码携带到请求中

MDC 在 “2021-03-31分布式 日志追踪 链路追踪.md” 中有提到过

1
2
3
4
5
6
7
8
9
10
11
12
@Activate(group = CommonConstants.CONSUMER)
public class DubboConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
RpcContext context = RpcContext.getContext();
String trace = MDC.get("logTrackId");
if(trace != null)
context.setAttachment("logTrackId", trace);
Result result = invoker.invoke(invocation);
return result;
}
}

供应者在执行准确方法前把追踪码放入 MDC

1
2
3
4
5
6
7
8
9
10
11
12
13
@Activate(group = CommonConstants.PROVIDER)
public class DubboProviderFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
//消费者携带的参数
Map<String, Object> attachments = RpcContext.getContext().getObjectAttachments();
String trace = attachments.get("logTrackId");
if(trace != null)
MDC.set("logTrackId",trace);
Result result = invoker.invoke(invocation);
return result;
}
}

链路追踪 完~

封装 dubbo 隐式传递

如果在调用 dubbo时 需要传递或转换的信息较多可以用一下方式处理

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@Slf4j
@Activate(group = CommonConstants.CONSUMER)
public class DubboConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
RpcContext context = RpcContext.getContext();
for (DubboProcess processor : ProcessUtils.getAllProcessor()) {
processor.before(context);
}

long startTime = System.currentTimeMillis();
try {
Result result = invoker.invoke(invocation);
for(DubboTransfer transfer : TransferUtils.getAllTransfer()) {
result = transfer.devanning(result);
}
return result;
} finally {
long endTime = System.currentTimeMillis();
log.debug("Dubbo Call [" + context.getMethodName() + "] Elapsed [" + (endTime - startTime) + "]");
}
}
}

@Activate(group = CommonConstants.PROVIDER)
public class DubboProviderFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
Map<String, Object> attachments = RpcContext.getContext().getObjectAttachments();
for (DubboProcess processor : ProcessUtils.getAllProcessor()) {
processor.after(attachments);
}

Result result = invoker.invoke(invocation);
for(DubboTransfer transfer : TransferUtils.getAllTransfer()) {
result = transfer.encasement(result);
}
return result;
}
}
public interface DubboProcess {
static String prefix = "rpc_hide_param_";
public void before(RpcContext context);
public void after(Map<String, Object> params);
}

public class ProcessUtils {
static List<DubboProcess> list = null;
public static List<DubboProcess> getAllProcessor(){
if(list == null) {
list = new ArrayList<>();
List<Class<?>> processors = CommClass.getAllClassByInterface(DubboProcess.class, "org.lqs1848.common.dubbo.process.impl");
for(Class<?> p : processors) {
try {
list.add((DubboProcess) p.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return list;
}
}

//分页处理器
public class PageProcess implements DubboProcess {
@Override
public void before(RpcContext context) {
Page page = PagePass.getLocalPage();
if(page != null) {
context.setAttachment(prefix + "isStartPage", page.getOrderBy());
context.setAttachment(prefix + Constants.PAGE_NUM, page.getPageNum());
context.setAttachment(prefix + Constants.PAGE_SIZE, page.getPageSize());
context.setAttachment(prefix + Constants.IS_ASC, page.getOrderBy());
}
}
@Override
public void after(Map<String, Object> params) {
if(params.containsKey(prefix + "isStartPage")) {
PagePass.tolerancePage(Convert.toInt(params.get(prefix + Constants.PAGE_NUM)), Convert.toInt(params.get(prefix + Constants.PAGE_SIZE)), Convert.toStr(params.get(prefix + Constants.IS_ASC)));
}
}
}
//分表标示携带
public class SliciTableProcess implements DubboProcess {
@Override
public void before(RpcContext context) {
Integer flag = SliciTableUtils.getCurSubFlag();
if(flag != null)
context.setAttachment(prefix + "sliciTableFlag", flag);
}
@Override
public void after(Map<String, Object> params) {
SliciTableUtils.clearSubFlag();
Integer flag = Convert.toInt(params.get(prefix + "sliciTableFlag"));
if(flag != null)
SliciTableUtils.setCurSubFlag(flag);
}
}
//追踪码
public class TraceProcess implements DubboProcess {
@Override
public void before(RpcContext context) {
String trace = Convert.toStr(TraceUtils.getTrace());
if(trace != null)
context.setAttachment(prefix + "logTrackId", trace);
}
@Override
public void after(Map<String, Object> params) {
String trace = Convert.toStr(params.get(prefix + "logTrackId"));
if(trace != null)
TraceUtils.setTrace(trace);
}
}
public interface DubboTransfer {
public int order();
//消费者实现
public Result devanning(Result result);
//提供者实现
public Result encasement(Result result);
}

public class TransferUtils {
static List<DubboTransfer> list = null;
public static List<DubboTransfer> getAllTransfer(){
if(list == null) {
list = new ArrayList<>();
List<Class<?>> transfers = CommClass.getAllClassByInterface(DubboTransfer.class, "org.lqs1848.common.dubbo.transfer.impl");
for(Class<?> p : transfers) {
try {
list.add((DubboTransfer) p.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
list.sort(new Comparator<DubboTransfer>() {
@Override
public int compare(DubboTransfer o1, DubboTransfer o2) {
if(o1.order() > o2.order())
return -1;
else if(o1.order() < o2.order())
return 1;
return 0;
}
});
}
return list;
}
}
//PageHelper 分页转换
public class PageTransfer implements DubboTransfer {
@Override
public int order() {
return 0;
}
@Override
public Result devanning(Result result) {
Object obj = result.getValue();
if (obj == null)
return result;
PageMessage page = (PageMessage) result.getObjectAttachment("PageMessage");
if (page != null) {
//消费者 查看传递的参数中有 Page 的分页信息
//把传输过程中丢失分页信息的 Page 重新封装成 PageHelper的Page
result.setValue(page.getPageToPageHelper((List) obj));
}
return result;
}
@Override
public Result encasement(Result result) {
Object obj = result.getValue();
if (obj == null)
return result;
if (obj instanceof Page) {
//提供者发现 返回的结果是 PageHelper 插件提供的 Page对象 把Page 转换成可以json传递的对象
result.setAttachment("PageMessage", new PageMessage((Page)obj));
}
return result;
}
}

process 用来传递参数 比如携带追踪码 分表标示信息 还有分页信息 等

​ MyBatis 的 PageHelper 插件的 Page 就是个 List 在传递过程中 被转换为 JSON 会丢失分页信息 所以 Page 需要单独处理

transfer 用来处理 result

这样封装后 有使用 dubbo的就集成这个jar包 provider和consumer都同时集成

注意:

要让dubbo的过滤器生效必须配置

META-INF 下添加 dubbo/internal/org.apache.dubbo.rpc.Filter 文件

文件内容为:

providerFilter=org.lqs1848.common.dubbo.filter.DubboProviderFilter
consumerFilter=org.lqs1848.common.dubbo.filter.DubboConsumerFilter

指明 Filter 的路径

反正过滤器不生效 百度下 org.apache.dubbo.rpc.Filter 或者 dubbo SPI

异常传递

Dubbo默认会把 项目的自定义异常 转换为 RuntimeException 给 consumer

会导致 consumer 全局异常拦截不生效

首先看一下 dubbo 自己的默认实现

org.apache.dubbo.rpc.filter.ExceptionFilter

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
@Activate(group = CommonConstants.PROVIDER)
public class ExceptionFilter implements Filter, Filter.Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
@Override
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return;
}
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException e) {
return;
}
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return;
}
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return;
}
if (exception instanceof RpcException) {
return;
}
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
}
}
@Override
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}

网上大多数文章都是只写了 这一段 但是不告诉你 这个是dubbo 自己的实现

并且也没说 如果不配置 spi 覆盖 dubbo 中默认的 ExceptionFilter 并不会生效

要想自己的自定义异常不被dubbo拦截掉 就得覆盖 默认 ExceptionFilter

首先 SPI 配置文件 dubbo/internal/org.apache.dubbo.rpc.Filter

添加一行

exception=org.lqs1848.common.dubbo.filter.DubboExceptionFilter

再把 dubbo 默认ExceptionFilter(org.apache.dubbo.rpc.filter.ExceptionFilter) 复制到 org.lqs1848.common.dubbo.filter 目录 并改名 DubboExceptionFilter

1
2
3
4
5
6
if(className.startsWith("org.lqs1848.")) {
//过滤掉自己的自定义异常
return;
}
//dubbo 把它无法识别的异常封装为 RuntimeException 之前把自己的异常放过 ↑↑↑↑↑↑↑↑↑↑↑↑
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));

这里只写了自定义异常

实际使用中 可能还有 com.netflix.client.ClientException: Load balancer does not have available server for client

这样的降级熔断之类的项目公用的异常

也是需要单独处理

总结

我引入 dubbo 时面临的需求是 service 已经写完了 必须把 部分 service 分到一个单独的微服务进行维护

所以在不改动任何业务代码的情况下 引入 dubbo

引入 dubbo 后 原先 controller 调用 service 时 分页是 PageHelper 内部是用 ThreadLocal 传递的 分页信息

不想改所有service 就只能在 dubbo 调用上进行传递了

我并没有用 dubbo 来替代 feign

引入 dubbo 纯粹是解决业务上的需求 所以只使用了 2 dubbo 单独使用