K8S下挖空Dubbo的一些思路--@Sevice

通过@Service的定义可以发现,这个注解可以作用在类上

从dubbo的角度说来:@Service作用的类会去spring中注册成单例bean,只不是需要增强一下。这里同样可以将@Component作为元注解使用

这里的增强其实只是要把有@Service标记的类按照/classFullName/methodName格式去vertx的路由里注册,能进行http访问即可

实际上这里使用BeanPostProcessor在实例完全初始化完成之后去执行添加路由就可以了

@Reference定义

定义里的属性出了value都可以只做为占位符,大部分都是没有什么用处的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.alibaba.dubbo.config.annotation;

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";

......
}

BeanPostProcessor

之前说到InstantiationAwareBeanPostProcessorBeanPostProcessor的子接口,但是提供了实例化前的能力

BeanPostProcessor是在实例化之后、各种Aware配置之后,围绕afterPropertiesSetinitMethod前后的扩展点。有两个自己定义的方法

  • postProcessBeforeInitialization:在实例init之前的执行,返回输入的bean或者包装过的bean,如果为空不会调用其它的BeanPostProcessor
  • postProcessAfterInitialization:在实例init之后的执行,返回结果同上

具体实现

@Service的实现要比@Reference简单的多,实现postProcessAfterInitialization即可

这里只需要注意,其它系统只知道接口名而不是实现类名,所以要获取到接口名

  1. 查看每个bean是否有定义@Service
  2. 如果有,这个bean只实现一个接口,则返回接口类全名;否则获取方法最多的接口,并返回这个接口类全名
  3. 将这些接口和@Service定义加入缓存,在spring完全启动之后注册到vertx的路由里
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
public class DubboBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
Class<?> clazz = bean.getClass();
Service service = clazz.getDeclaredAnnotation(Service.class);
if (null != service) {
Class<?> most = getMostAvailableInterface(clazz);
if (null != most) {
DubboRouterCache.addService(most, service);
}
}
return bean;
}

private Class<?> getMostAvailableInterface(Class<?> clazz) {
Class<?>[] interfaces = clazz.getInterfaces();
int length = interfaces.length;
if (length == 0) {
return null;
}
if (length == 1) {
return interfaces[0];
}

String simple = clazz.getSimpleName();
for (Class<?> one : interfaces) {
if (simple.startsWith(one.getSimpleName())) {
return one;
}
}
int max = -1;
Class<?> most = null;
for (Class<?> one : interfaces) {
int num = one.getDeclaredMethods().length;
if (num > max) {
max = num;
most = one;
}
}
return most;
}
}

注册路由

vertx同样实在spring启动之后才会启动,这里的Router实在vertx启动时,主动调用传进来的

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
@Override
public void buildRouter(Router router) {
final Map<Class<?>, Annotation> allService = DubboRouterCache.getAllService();
if (CollectionUtil.isEmpty(allService)) {
return;
}
final ConfigurableApplicationContext context = Bottle.obtain(ConfigurableApplicationContext.class);
for (Class<?> serviceClass : allService.keySet()) {
// 必须有且只有一个实现类
final Map<String, ?> beans = context.getBeansOfType(serviceClass);
if (CollectionUtil.isEmpty(beans)) {
throw new ShutdownException("Interface: " + serviceClass + " does not implement class");
}
if (beans.size() > 1) {
throw new ShutdownException("Interface: " + serviceClass + " has multiple implement classes");
}
final Iterator<?> iterator = beans.values().iterator();
buildRouter(router, serviceClass, iterator.next());
}
}

private void buildRouter(Router router, Class<?> serviceClass, Object serviceImpl) {
final Method[] declaredMethods = serviceClass.getDeclaredMethods();
final String serviceName = serviceClass.getName();
final Set<String> methodNames = CollectionUtil.newHashSet(declaredMethods.length);
for (Method method : declaredMethods) {
methodNames.add(method.getName());
try {
// 建立方法句柄到bean的绑定
MethodInvoker.warmup(serviceImpl, method);
} catch (Throwable err) {
throw new InnerException("Build route failed, can not reflect methods");
}
}
for (String methodName : methodNames) {
try {
router.post("/" + serviceName + "/" + methodName).blockingHandler(ctx -> {
// 通过方法句柄调用bean的方法并返回
});
} catch (Throwable err) {
throw new InnerException("Build route failed, not found methods");
}
}
}

K8S下挖空Dubbo的一些思路--@Sevice
https://back.pub/post/hh-code-replace-dubbo-2/
作者
Dash
发布于
2020年5月29日
许可协议