在后续一段时间里, 我会写一系列文章来讲述如何实现一个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。