手寫RPC架構-遠端服務調用
- 遠端服務的核心
-
- 使用方法
- 核心點
- 實作邏輯
-
- 1.解析添加指定注解的接口
-
- 自定義解析器
- 拿到要解析的路徑
- 生成代理類 并注入到spring工廠中
-
- 主要元件
- 生成代理類邏輯
-
- 主要點
- 生成代理源碼
- 項目位址
遠端服務的核心
使用方法
點選這裡檢視詳細使用方法
// provider為遠端服務的服務名稱
@WbClient("provider")
public interface TestService {
// value為想要調用的路徑 method為請求的方法,POST GET
@WbRequestMapping(value = "/test", method = WbRequestMethod.GET)
void test(@WbRequestParam(value = "asd") String str, String str2);
@WbRequestMapping(value = "/testbody", method = WbRequestMethod.POST)
TestUser testpost(@WbRequestBody TestUser testUser);
}
核心點
掃描标注注解的接口,生成實際的代理類,在代理類中實作遠端調用(由于在注冊中心中已經儲存了遠端服務的IP和端口号,隻需要拿到調用即可)。把生成的代理類注入到spring工廠中,在調用端使用Autowired注解即可自動注入代理類。
實作邏輯
1.解析添加指定注解的接口
自定義解析器
自定義解析類實作ClassPathScanningCandidateComponentProvider接口,可以解析接口。
public class InterfacePathScanningCandidateComponentProvider extends ClassPathScanningCandidateComponentProvider {
public InterfacePathScanningCandidateComponentProvider(boolean useDefaultFilters) {
super(useDefaultFilters);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
}
拿到要解析的路徑
@Bean
public MyBeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor(Environment environment) {
// 通過配置的wb.packageScan拿到需要解析的路徑
String packagesScan = environment.getProperty("wb.packageScan", String.class, null);
return new MyBeanDefinitionRegistryPostProcessor(packagesScan);
}
生成代理類 并注入到spring工廠中
主要元件
通過BeanDefinitionRegistryPostProcessor修改BeanDefinitionRegistry,把代理類注入到spring中。
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
private String packagesScan;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
// 解析
Set<BeanDefinition> defs = parseInterfaces();
for (BeanDefinition def : defs) {
try {
Class<?> clazz = forName(def.getBeanClassName());
// 生成代理類
Class<?> proxyClass = WbClientProxy.buildProxy(clazz);
// 代理類注冊為RootBeanDefinition後放入Spring中
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(proxyClass);
beanDefinitionRegistry.registerBeanDefinition(proxyClass.getSimpleName().toLowerCase(), beanDefinition);
} catch (ClassNotFoundException | NotFoundException | CannotCompileException e) {
e.printStackTrace();
}
}
}
/**
* 解析
*
* @return
*/
private Set<BeanDefinition> parseInterfaces() {
// 解析
InterfacePathScanningCandidateComponentProvider scanner = new InterfacePathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(WbClient.class));
return new HashSet<>(scanner.findCandidateComponents(packagesScan));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
public MyBeanDefinitionRegistryPostProcessor(String packagesScan) {
this.packagesScan = packagesScan;
}
}
生成代理類邏輯
主要點
- 解析接口注解@WbClient拿到要調用的服務端
- 解析接口中的方法注解,拿到請求路徑和請求方法
- 在代理類中注入注冊工廠(IRegister實作類)和WbClientCall調用
- 在代理類中方法中擷取服務,得到路徑,處理響應資訊
生成代理源碼
public class WbClientProxy {
/**
* 生成的代理對象名稱字首
*/
private static final String PROXY_PREFIX = "WbRpcProxy";
/**
* 生成的代理對象名稱字尾
*/
private static final String PROXY_SUFFIX = "Impl";
/**
* 參數為@WbRequestBody的類型
*/
private static final String REQUEST_BODY_PARAMETER = "REQUEST_BODY_PARAMETER";
public static <T> Class<T> buildProxy(Class<T> t) throws NotFoundException, CannotCompileException, ClassNotFoundException {
// 擷取調用的服務名稱
String clientServerName = t.getAnnotation(WbClient.class).value();
ClassPool pool = ClassPool.getDefault();
//建立代理類對象
CtClass ctClass = pool.makeClass(getImplName(t));
//設定代理類的接口
CtClass interj = pool.getCtClass(t.getName());
// 設定方法的屬性
initClassField(ctClass, interj, pool);
//代理類的所有方法
CtMethod[] methods = interj.getDeclaredMethods();
// 擷取所有方法的參數資訊
Map<String, List<String>> parameterMap = buildParametersMap(t);
for(CtMethod method : methods) {
// 建立代理類的方法
CtMethod ctMethod = initClassMethod(method, ctClass, parameterMap, t, clientServerName);
ctClass.addMethod(ctMethod);
}
return (Class) ctClass.toClass();
}
private static CtMethod initClassMethod(CtMethod method, CtClass ctClass, Map<String,
List<String>> parameterMap, Class<?> t, String clientServerName) throws NotFoundException, ClassNotFoundException, CannotCompileException {
String methodName = method.getName();
CtMethod cm = new CtMethod(method.getReturnType(), methodName, method.getParameterTypes(), ctClass);
// 擷取參數清單名稱
String methodFullName = buildMethodFullName(method);
// 該方法參數的映射值
List<String> parameterNames = parameterMap.get(methodFullName);
// 解析通路的路徑
WbRequestMapping requestMapping = (WbRequestMapping) method.getAnnotation(WbRequestMapping.class);
StringBuilder url = new StringBuilder(requestMapping.value());
boolean containSymbol = false;
String requestBody = "null";
if (!CollectionUtils.isEmpty(parameterNames)) {
String connect = "?";
for (int i = 0; i < parameterNames.size(); i++) {
String parameterName = parameterNames.get(i);
if (StringUtils.EMPTY.equals(parameterName)) {
System.err.println(t.getName() + "類中," + method.getName() + "方法的第" + (i + 1) + "參數沒有加WbRequestParam注解,無法比對,請添加WbRequestParam,或在打包時,使用-parameters,可根據參數名自動比對。");
continue;
}
if (REQUEST_BODY_PARAMETER.equals(parameterName)) {
requestBody = "$" + (i + 1);
continue;
}
containSymbol = true;
if ("?".equals(connect)) {
url = new StringBuilder(url + connect + parameterName + "=\"+" + "$" + (i + 1));
} else {
url.append("+\"").append(connect).append(parameterName).append("=\"+").append("$").append(i + 1);
}
connect = "&";
}
}
if (!containSymbol) {
url = new StringBuilder(url + "\"");
}
String returnType = "void".equals(cm.getReturnType().getName()) ? "com.wb.spring.boot.autoconfigure.proxy.Void" : cm.getReturnType().getName();
String isReturn = "com.wb.spring.boot.autoconfigure.proxy.Void".equals(returnType) ? "" : "return (" + returnType + ")";
cm.setBody("{" + isReturn + " clientCall.call(\"http://\" + register.getServer(\"" + clientServerName + "\") + \"" + url + ",\"" + requestMapping.method() + "\", " + requestBody + ", Class.forName(\"" + returnType+ "\"));}");
return cm;
}
/**
* 建立類的屬性
* @param ctClass
* 生成的代理類
* @param interj
* 代理類的接口
* @param pool
* @return
* @throws CannotCompileException
* @throws NotFoundException
*/
private static CtClass initClassField(CtClass ctClass, CtClass interj, ClassPool pool) throws CannotCompileException, NotFoundException {
CtClass[] interfaces = new CtClass[]{interj};
ctClass.setInterfaces(interfaces);
// 擷取常量池
ConstPool constPool = ctClass.getClassFile().getConstPool();
// 建立屬性,調用遠端服務使用
CtField ctField = new CtField(pool.get("com.wb.spring.boot.autoconfigure.proxy.WbClientCall"), "clientCall", ctClass);
ctField.setModifiers(AccessFlag.PUBLIC);
FieldInfo fieldInfo = ctField.getFieldInfo();
// 添加Autowired注解,後續注冊到spring中會自動裝配
AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation filedAnnotation = new Annotation(Autowired.class.getName(), constPool);
annotationsAttribute.addAnnotation(filedAnnotation);
fieldInfo.addAttribute(annotationsAttribute);
CtField registerField = new CtField(pool.get("com.wb.spring.boot.autoconfigure.register.IRegister"), "register", ctClass);
ctField.setModifiers(AccessFlag.PUBLIC);
FieldInfo registerFieldInfo = registerField.getFieldInfo();
registerFieldInfo.addAttribute(annotationsAttribute);
// 加入到類檔案中
ctClass.addField(ctField);
ctClass.addField(registerField);
return ctClass;
}
/**
* 擷取全限定名稱
* @param method
* @return
* @throws NotFoundException
*/
private static String buildMethodFullName(CtMethod method) throws NotFoundException {
StringBuilder methodFullName = new StringBuilder(method.getName());
if (method.getParameterTypes() != null) {
for (CtClass parameterType : method.getParameterTypes()) {
methodFullName.append(parameterType.getName());
}
}
return methodFullName.toString();
}
/**
* key-方法名稱+方法參數類型
* value-方法參數名稱集合
* @param clazz
* 要解析的類
*
* @return
* 方法的類對應參數名稱
*/
private static Map<String, List<String>> buildParametersMap(Class<?> clazz) {
// 同名參數映射使用 此處使用1.8之上的--parameters才能支援,否則隻能使用注解映射。
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
Map<String, List<String>> parameterMap = new HashMap<>(8);
for (Method method : clazz.getMethods()) {
StringBuilder name = new StringBuilder(method.getName());
List<String> parameterRequestNames = new ArrayList<>();
if (method.getParameters() != null) {
for (Parameter parameter : method.getParameters()) {
name.append(parameter.getType().getName());
WbRequestParam annotation = parameter.getAnnotation(WbRequestParam.class);
String parameterRequestName = StringUtils.EMPTY;
if (annotation != null) {
parameterRequestName = annotation.value();
}
WbRequestBody wbRequestBody = parameter.getAnnotation(WbRequestBody.class);
if (wbRequestBody != null) {
parameterRequestName = REQUEST_BODY_PARAMETER;
}
parameterRequestNames.add(parameterRequestName);
}
}
String[] parameterNames = discoverer.getParameterNames(method);
if (parameterNames == null) {
parameterMap.put(name.toString(), parameterRequestNames);
continue;
}
List<String> correctParameterRequestNames = new ArrayList<>();
for (int i = 0; i < parameterRequestNames.size(); i++) {
String parameterRequestName = parameterRequestNames.get(i);
if (StringUtils.EMPTY.equals(parameterRequestName)) {
correctParameterRequestNames.add(parameterNames[i]);
continue;
}
correctParameterRequestNames.add(parameterRequestName);
}
parameterMap.put(name.toString(), correctParameterRequestNames);
}
return parameterMap;
}
/**
* 擷取代理類的名稱
* @param t
* 實作類類型
* @return
* 實作類名稱
*/
private static <T> String getImplName(Class<T> t) {
return t.getPackage() + "." + PROXY_PREFIX + t.getSimpleName() + PROXY_SUFFIX;
}
}
項目位址
https://gitee.com/xu–wenbin/wb-spring-boot-project