如何写一个RPC框架(二):利用Bean容器和动态代理简化客户端代码

释放双眼,带上耳机,听听看~!

在后续一段时间里, 我会写一系列文章来讲述如何实现一个RPC框架(我已经实现了一个示例框架, 代码在我的github上)。 这是系列第二篇文章, 主要讲述了如何利用Spring以及Java的动态代理简化调用别的服务的代码。

在本系列第一篇文章中,我们说到了RPC框架需要关注的第一个点,通过创建代理的方式来简化客户端代码。

如果不使用代理?

如果我们不用代理去帮我们操心那些服务寻址、网络通信的问题,我们的代码会怎样?

我们每调用一次远端服务,就要在业务代码中重复一遍那些复杂的逻辑,这肯定是不能接受的!

目标代码

而我们的目标是写出简洁的代码,就像这样:


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
1//这个接口应该被单独打成一个jar包,同时被server和client所依赖
2@RPCService(HelloService.class)
3public interface HelloService {
4
5    String hello(String name);
6}
7
8@Component
9@Slf4j
10public class AnotherService {
11    @Autowired
12    HelloService helloService;
13
14    public void callHelloService() {
15        //就像调用本地方法一样自如!
16        log.info("Result of callHelloService: {}", helloService.hello("world"));
17    }
18}
19
20@EnableRPCClients(basePackages = {"pw.hshen.hrpc"})
21public class HelloClient {
22
23    public static void main(String[] args) throws Exception {
24        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
25        AnotherService anotherService = context.getBean(AnotherService.class);
26        anotherService.callHelloService();
27    }
28}
29
30
31

代码中的AnotherService可以简单调用远端的HelloService的方法,就像调用本地的service一样简单! 在这段代码中,HelloService可以视作server, 而AnotherService则是它的调用者,可以视作是client。

实现思路

1.获取要被创建代理的接口

首先,我们要知道需要为哪些接口来创建代理。

我们需要为这种特殊的接口创建一个注解来标注,即RPCService。然后我们就可以通过扫描某个包下面所有包含这个注解的interface来获取了。

那么,怎么知道要扫描哪个包呢?方法就是获取MainClass的EnableRPCClients注解的basePackages的值。

2.为这些接口创建动态代理

我们可以利用jdk的动态代理来做这件事儿:


1
2
3
4
5
6
7
8
9
10
11
1// Interface是需要被创建代理的那个接口
2Proxy.newProxyInstance(
3            interface.getClassLoader(),
4            new Class<?>[]{interface},
5            new InvocationHandler() {
6                @Override
7                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
8            // TODO: Do RPC action here and return the result
9                }
10
11

3.将创建出来的代理对象注册到bean容器中

关于如何动态向spring容器中注册自定义的bean, 可以参考这篇文章。
在我的框架中, 我选择了使用BeanDefinitionRegistryPostProcessor所提供的hook。

注入到bean容器之后,我们就可以在代码中愉快的用Autowired等注解来获取所创建的代理啦!

完整代码

定义需要的注解


1
2
3
4
5
6
7
8
9
10
1/**
2 * @author hongbin
3 * Created on 22/10/2017
4 */
5@Target(ElementType.TYPE)
6@Retention(RetentionPolicy.RUNTIME)
7public @interface EnableRPCClients {
8    String[] basePackages() default {};
9}
10

1
2
3
4
5
6
7
8
9
10
11
12
13
1/**
2 * @author hongbin
3 * Created on 21/10/2017
4 */
5@Target(ElementType.TYPE)
6@Retention(RetentionPolicy.RUNTIME)
7@Component
8@Inherited
9public @interface RPCService {
10
11    Class<?> value();
12}
13

利用Spring的hook机制, 向容器中注册我们自己的proxy bean:


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
1/**
2 * Register proxy bean for required client in bean container.
3 * 1. Get interfaces with annotation RPCService
4 * 2. Create proxy bean for the interfaces and register them
5 *
6 * @author hongbin
7 * Created on 21/10/2017
8 */
9@Slf4j
10@RequiredArgsConstructor
11public class ServiceProxyProvider extends PropertySourcesPlaceholderConfigurer implements BeanDefinitionRegistryPostProcessor {
12
13    @NonNull
14    private ServiceDiscovery serviceDiscovery;
15
16    @Override
17    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
18        log.info("register beans");
19        ClassPathScanningCandidateComponentProvider scanner = getScanner();
20        scanner.addIncludeFilter(new AnnotationTypeFilter(RPCService.class));
21
22        for (String basePackage: getBasePackages()) {
23            Set<BeanDefinition> candidateComponents = scanner
24                    .findCandidateComponents(basePackage);
25            for (BeanDefinition candidateComponent : candidateComponents) {
26                if (candidateComponent instanceof AnnotatedBeanDefinition) {
27                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
28                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
29
30                    BeanDefinitionHolder holder = createBeanDefinition(annotationMetadata);
31                    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
32                }
33            }
34        }
35    }
36
37    private ClassPathScanningCandidateComponentProvider getScanner() {
38        return new ClassPathScanningCandidateComponentProvider(false) {
39
40            @Override
41            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
42                if (beanDefinition.getMetadata().isIndependent()) {
43
44                    if (beanDefinition.getMetadata().isInterface()
45                            && beanDefinition.getMetadata().getInterfaceNames().length == 1
46                            && Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
47
48                        try {
49                            Class<?> target = Class.forName(beanDefinition.getMetadata().getClassName());
50                            return !target.isAnnotation();
51                        } catch (Exception ex) {
52
53                            log.error("Could not load target class: {}, {}",
54                                    beanDefinition.getMetadata().getClassName(), ex);
55                        }
56                    }
57                    return true;
58                }
59                return false;
60            }
61        };
62    }
63
64    private BeanDefinitionHolder createBeanDefinition(AnnotationMetadata annotationMetadata) {
65        String className = annotationMetadata.getClassName();
66        log.info("Creating bean definition for class: {}", className);
67
68        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class);
69        String beanName = StringUtils.uncapitalize(className.substring(className.lastIndexOf('.') + 1));
70
71        definition.addPropertyValue("type", className);
72        definition.addPropertyValue("serviceDiscovery", serviceDiscovery);
73
74        return new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
75    }
76
77    private Set<String> getBasePackages() {
78        String[] basePackages = getMainClass().getAnnotation(EnableRPCClients.class).basePackages();
79        Set set = new HashSet<>();
80        Collections.addAll(set, basePackages);
81        return set;
82    }
83
84    private Class<?> getMainClass() {
85        for (final Map.Entry<String, String> entry : System.getenv().entrySet()) {
86            if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) {
87                String mainClass = entry.getValue();
88                log.debug("Main class: {}", mainClass);
89                try {
90                    return Class.forName(mainClass);
91                } catch (ClassNotFoundException e) {
92                    throw new IllegalStateException("Cannot determine main class.");
93                }
94            }
95        }
96        throw new IllegalStateException("Cannot determine main class.");
97    }
98
99    @Override
100    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
101
102    }
103}
104
105

对应的ProxyBeanFactory:


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
1/**
2 * FactoryBean for service proxy
3 *
4 * @author hongbin
5 * Created on 24/10/2017
6 */
7@Slf4j
8@Data
9public class ProxyFactoryBean implements FactoryBean<Object> {
10    private Class<?> type;
11
12    private ServiceDiscovery serviceDiscovery;
13
14    @SuppressWarnings("unchecked")
15    @Override
16    public Object getObject() throws Exception {
17        return Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, this::doInvoke);
18    }
19
20    @Override
21    public Class<?> getObjectType() {
22        return this.type;
23    }
24
25    @Override
26    public boolean isSingleton() {
27        return true;
28    }
29
30    private Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
31        // TODO:这里处理服务发现、负载均衡、网络通信等逻辑
32    }
33}
34
35

就这样, 我们实现了客户端启动时的扫包、创建代理的过程,接下来要做的事情就只是填充代理的逻辑了。 完整代码请看我的github。

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Google AdSense 全面解析(申请+操作+作弊+忠告)

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索