在後續一段時間裏, 我會寫一系列文章來講述如何實現一個RPC框架(我已經實現了一個示例框架, 代碼在我的github上)。 這是系列第二篇文章, 主要講述瞭如何利用Spring以及Java的動態代理簡化調用別的服務的代碼。
在本系列第一篇文章中,我們說到了RPC框架需要關注的第一個點,通過創建代理的方式來簡化客戶端代碼。
如果不使用代理?
如果我們不用代理去幫我們操心那些服務尋址、網絡通信的問題,我們的代碼會怎樣?
我們每調用一次遠端服務,就要在業務代碼中重複一遍那些複雜的邏輯,這肯定是不能接受的!
目標代碼
而我們的目標是寫出簡潔的代碼,就像這樣:
//這個接口應該被單獨打成一個jar包,同時被server和client所依賴
@RPCService(HelloService.class)
public interface HelloService {
String hello(String name);
}
@Component
@Slf4j
public class AnotherService {
@Autowired
HelloService helloService;
public void callHelloService() {
//就像調用本地方法一樣自如!
log.info("Result of callHelloService: {}", helloService.hello("world"));
}
}
@EnableRPCClients(basePackages = {"pw.hshen.hrpc"})
public class HelloClient {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
AnotherService anotherService = context.getBean(AnotherService.class);
anotherService.callHelloService();
}
}
代碼中的AnotherService可以簡單調用遠端的HelloService的方法,就像調用本地的service一樣簡單! 在這段代碼中,HelloService可以視作server, 而AnotherService則是它的調用者,可以視作是client。
實現思路
1.獲取要被創建代理的接口
首先,我們要知道需要爲哪些接口來創建代理。
我們需要爲這種特殊的接口創建一個註解來標註,即RPCService。然後我們就可以通過掃描某個包下面所有包含這個註解的interface來獲取了。
那麼,怎麼知道要掃描哪個包呢?方法就是獲取MainClass的EnableRPCClients註解的basePackages的值。
2.爲這些接口創建動態代理
我們可以利用jdk的動態代理來做這件事兒:
// Interface是需要被創建代理的那個接口
Proxy.newProxyInstance(
interface.getClassLoader(),
new Class<?>[]{interface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO: Do RPC action here and return the result
}
3.將創建出來的代理對象註冊到bean容器中
關於如何動態向spring容器中註冊自定義的bean, 可以參考這篇文章。
在我的框架中, 我選擇了使用BeanDefinitionRegistryPostProcessor所提供的hook。
注入到bean容器之後,我們就可以在代碼中愉快的用Autowired等註解來獲取所創建的代理啦!
完整代碼
定義需要的註解
/**
* @author hongbin
* Created on 22/10/2017
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableRPCClients {
String[] basePackages() default {};
}
/**
* @author hongbin
* Created on 21/10/2017
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Inherited
public @interface RPCService {
Class<?> value();
}
利用Spring的hook機制, 向容器中註冊我們自己的proxy bean:
/**
* Register proxy bean for required client in bean container.
* 1. Get interfaces with annotation RPCService
* 2. Create proxy bean for the interfaces and register them
*
* @author hongbin
* Created on 21/10/2017
*/
@Slf4j
@RequiredArgsConstructor
public class ServiceProxyProvider extends PropertySourcesPlaceholderConfigurer implements BeanDefinitionRegistryPostProcessor {
@NonNull
private ServiceDiscovery serviceDiscovery;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
log.info("register beans");
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.addIncludeFilter(new AnnotationTypeFilter(RPCService.class));
for (String basePackage: getBasePackages()) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
BeanDefinitionHolder holder = createBeanDefinition(annotationMetadata);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
}
}
private ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isIndependent()) {
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = Class.forName(beanDefinition.getMetadata().getClassName());
return !target.isAnnotation();
} catch (Exception ex) {
log.error("Could not load target class: {}, {}",
beanDefinition.getMetadata().getClassName(), ex);
}
}
return true;
}
return false;
}
};
}
private BeanDefinitionHolder createBeanDefinition(AnnotationMetadata annotationMetadata) {
String className = annotationMetadata.getClassName();
log.info("Creating bean definition for class: {}", className);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class);
String beanName = StringUtils.uncapitalize(className.substring(className.lastIndexOf('.') + 1));
definition.addPropertyValue("type", className);
definition.addPropertyValue("serviceDiscovery", serviceDiscovery);
return new BeanDefinitionHolder(definition.getBeanDefinition(), beanName);
}
private Set<String> getBasePackages() {
String[] basePackages = getMainClass().getAnnotation(EnableRPCClients.class).basePackages();
Set set = new HashSet<>();
Collections.addAll(set, basePackages);
return set;
}
private Class<?> getMainClass() {
for (final Map.Entry<String, String> entry : System.getenv().entrySet()) {
if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) {
String mainClass = entry.getValue();
log.debug("Main class: {}", mainClass);
try {
return Class.forName(mainClass);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Cannot determine main class.");
}
}
}
throw new IllegalStateException("Cannot determine main class.");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
對應的ProxyBeanFactory:
/**
* FactoryBean for service proxy
*
* @author hongbin
* Created on 24/10/2017
*/
@Slf4j
@Data
public class ProxyFactoryBean implements FactoryBean<Object> {
private Class<?> type;
private ServiceDiscovery serviceDiscovery;
@SuppressWarnings("unchecked")
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, this::doInvoke);
}
@Override
public Class<?> getObjectType() {
return this.type;
}
@Override
public boolean isSingleton() {
return true;
}
private Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO:這裏處理服務發現、負載均衡、網絡通信等邏輯
}
}
就這樣, 我們實現了客戶端啓動時的掃包、創建代理的過程,接下來要做的事情就只是填充代理的邏輯了。 完整代碼請看我的github。