在接入前,先自定義一組傳輸對象,而不是原來傳輸一個Invocation,返回則是一個String,這裏需要用到netty的編解碼器,當然這裏可以用netty已經實現好的對象編解碼、第三方的Probuff編解碼器,而如果想要實現自己的傳輸協議,就要繼承netty的編解碼,他可以用來自定義解析對象以及封裝對象,這裏對對象處理採用本來是採用gson,但是有很多bug,便用了另一種進行序列化,這樣自定義協議的話,可以做很多其他的處理,比如協議的魔數、格式校驗等等,自定義協議的處理
定一個序列化接口
/**
* @author: lele
* @date: 2019/11/19 下午5:15
* 對象-字節轉換接口
*/
public interface RpcSerializer {
/**
* 序列化
* @param target
* @return
*/
byte[] serialize(Object target);
/**
* 反序列化
* @param target
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
<T> T deserialize(byte[] target,Class<T> clazz) throws Exception;
}
具體的序列化實現類,需要引入protostuff-stuff,protostuff-runtime依賴
public class ProtobufSerializer {
private static Map<Class, Schema> schemaMap = new HashMap<Class, Schema>();
// objenesis是一個小型Java類庫用來實例化一個特定class的對象。
private static Objenesis objenesis = new ObjenesisStd(true);
// 存儲模式對象映射
private static Schema getSchema(Class cls) {
Schema schema = schemaMap.get(cls);
if (null == schema) {
schema = RuntimeSchema.createFrom(cls);
if (null != schema) {
schemaMap.put(cls, schema);
}
}
return schema;
}
public byte[] serialize(Object target) {
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
Class cls = target.getClass();
try {
Schema schema = getSchema(cls);
byte[] bytes = ProtobufIOUtil.toByteArray(target, schema, buffer);
return bytes;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
buffer.clear();
}
}
public <T> T deserialize(byte[] target, Class<T> clazz) throws Exception {
try {
T instance = objenesis.newInstance(clazz);
Schema schema = getSchema(clazz);
ProtobufIOUtil.mergeFrom(target, instance, schema);
return instance;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
//單例
private static class Holder {
private static final ProtobufSerializer j = new ProtobufSerializer();
}
public static ProtobufSerializer getInstance() {
return ProtobufSerializer.Holder.j;
}
}
然後定義請求/返回的對象,這裏定義的requestID用於標記該次請求,作用往後再談
/**
* @author: lele
* @date: 2019/11/15 下午7:01
* 封裝調用方所想調用的遠程方法信息
*/
@Data
@AllArgsConstructor
public class RpcRequest {
private String requestId;
private String interfaceName;
private String methodName;
private Object[] params;
//防止重載
private Class[] paramsTypes;
}
/**
* @author: lele
* @date: 2019/11/19 下午5:12
*/
@Data
public class RpcResponse {
private String requestId;
private Object result;
private String error;
}
定義處理對象的編解碼器
/**
* @author: lele
* @date: 2019/11/19 下午5:16
* 把字節轉爲實體類,如byte->request供後續處理
*/
public class RpcDecoder extends ByteToMessageDecoder {
//處理的對象
private Class<?> target;
private ProtobufSerializer serializer=ProtobufSerializer.getInstance();
public RpcDecoder(Class<?> target) {
this.target = target;
}
/**
* byte轉實體
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
System.out.println("收到字節");
//如果小於一個int的長度,不作處理
if (byteBuf.readableBytes() < 4) {
return;
}
//獲取數據長度
int dataLength = byteBuf.readInt();
//寫入byte數組
byte[] data = new byte[dataLength];
byteBuf.readBytes(data);
//解碼轉成對象
Object res = serializer.deserialize(data, target);
//給後面的handler處理
list.add(res);
}
}
/**
* @author: lele
* @date: 2019/11/19 下午5:16
* 把實體變爲字節,可用於req->byte、response->byte
*/
public class RpcEncoder extends MessageToByteEncoder {
//處理的對象
private Class<?> entity;
ProtobufSerializer serializer=ProtobufSerializer.getInstance();
public RpcEncoder(Class<?> entity) {
this.entity = entity;
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
System.out.println("轉成字節");
if (entity.equals(o.getClass())) {
byte[] data=serializer.serialize(o);
//寫入消息長度,這裏還可以寫入版本號、魔數等協議信息
byteBuf.writeInt(data.length);
//寫入消息主體
byteBuf.writeBytes(data);
}
}
}
這樣,傳輸對象及其序列化、相應的編解碼器就寫好了,把原來自帶的對象編解碼器替換掉即可
而以前那種{interfaceName:{url:具體實現類}的zk存儲方式不適合具體的業務處理,以接口爲粒度不方便管理,而且現有的設計是每請求一次,就會新建一個client,不能複用,造成資源浪費,然後client如果保持和以接口獲取的服務進行鏈接,當請求的接口時,client就無法複用,比如下圖,client可以與serverA保持鏈接後,訪問interfaceC,interfaceA都是可以的,如果調用interfaceB,就不能保持請求
但如果以client和對應服務來寫,則不會出現上述情況,也可以保持鏈接以複用
最後設計爲/服務名/url的格式,這次更改的結果是,相應的獲取可用地址、註冊服務的格式也要進行更改。
接下來就是接入spring了,有了上面的基礎,這時候我們可以仿feign的功能,把某一個包下帶有某個註解的接口注入spring中,同時爲這些接口生成代理,當執行這些接口的方法時,進行動態代理訪問遠端接口
定義作用在要代理的接口上,這裏的name爲服務名,zk則在註冊服務改爲——/服務名/ip,服務端通過傳來的接口名通過反射獲取類,或者通過給spring託管獲取其class(下面server端就是這樣處理)
/**
* @author: lele
* @date: 2019/11/20 下午2:44
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//用於接口上,name爲服務名,zk則在註冊服務改爲 服務名/ip,服務端通過傳來的接口名通過反射獲取類,或者通過給spring託管獲取其class
public @interface RpcStudyClient {
String name();
}
流程爲:自定義一個可以動態註冊bean的類,然後通過獲取所要掃描的包,添加掃描條件,符合掃描條件之後,再複寫判斷是否爲接口,是的話對其進行註冊,註冊時通過對象工廠來定製化生成bean,這裏是加入了動態代理攔截其方法進行遠程調用
/**
* @author: lele
* @date: 2019/11/20 下午3:06
* 第一個接口獲取註冊bean能力,第二個接口獲取類加載器,仿feignregister寫法
*/
public class RpcStudyClientRegisty implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (DemoApplication.mode == 1) {
//獲取指定路徑中註解bean定義掃描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
//獲取掃描的包,通過enable那個註解的屬性
Set<String> basePackages = getBasePackages(importingClassMetadata);
//添加過濾規則,屬於rpcstudyclient的加入,excludeFilter則是排除
scanner.addIncludeFilter(new AnnotationTypeFilter(RpcStudyClient.class));
Set<BeanDefinition> candidateBeans = new HashSet<>();
//獲取符合條件的bean
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
candidateBeans.addAll(candidateComponents);
}
//spring中用BeanDefintion來表示bean,這裏判斷bean類型是否合適,合適就註冊
for (BeanDefinition candidateBean : candidateBeans) {
//如果bean還沒有註冊
if (!registry.containsBeanDefinition(candidateBean.getBeanClassName())) {
//判讀是否含有註解
if (candidateBean instanceof AnnotatedBeanDefinition) {
//存儲該類信息的bean,methodMetadata(方法),AnnotationMetadata(裏面也包括methodMetadata,可以獲取註解,類信息等等)
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateBean;
//獲取bean的類信息
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
//判斷其否爲接口
Assert.isTrue(annotationMetadata.isInterface(), "@RpcStudeyClient註解只能用在接口上");
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(RpcStudyClient.class.getCanonicalName());
this.registerRpcClient(registry, annotationMetadata, attributes);
}
}
}
}
}
//註冊bean
private void registerRpcClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
//獲取bean類名
String className = annotationMetadata.getClassName();
//使用自定義的對象工廠定製化生成bean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(RpcStudyClientFactoryBean.class);
//設置根據類型的注入方式
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.addPropertyValue("type", className);
String name = attributes.get("name") == null ? "" : (String) (attributes.get("name"));
String alias = name + "RpcStudyClient";
//獲取bean基類
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
//防止其他有實現,設置此實現爲首要
beanDefinition.setPrimary(true);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[]{alias});
//註冊bean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
//複寫bean掃描的判斷
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false
) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//存放註解相關信息,具備了class、註解的信息
AnnotationMetadata metadata = beanDefinition.getMetadata();
//是否是獨立能創建對象的,比如class、內部類、靜態內部類
if (metadata.isIndependent()) {
//用於過濾註解爲@RpcClient的註解
if (metadata.isInterface() &&
metadata.getInterfaceNames().length == 1 &&
Annotation.class.getName().equals(metadata.getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(metadata.getClassName(),
RpcStudyClientRegisty.this.classLoader);
return !target.isAnnotation();
} catch (Exception ex) {
this.logger.error(
"Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
}
}
return true;
}
return false;
}
};
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
//獲取需要掃描的包位置
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableRpcStudyClient.class.getCanonicalName());
String[] scanPackages = (String[]) attributes.get("basePackages");
Set<String> basePackages = new HashSet<>();
if (scanPackages.length > 0) {
//掃描指定包
for (String pkg : scanPackages) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
} else {
//掃描主入口所在的包
basePackages.add(
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
}
處理接口的FactoryBean,爲每個接口生成代理
/**
* @author: lele
* @date: 2019/11/20 下午3:08
* bean工廠類,這裏爲接口代理其方法
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class RpcStudyClientFactoryBean implements FactoryBean<Object> {
private Class<?> type;
@Override
public Object getObject() throws Exception {
return ProxyFactory.getProxy(this.type);
}
@Override
public Class<?> getObjectType() {
return this.type;
}
}
把上面的註冊配置類注入到該註解
/**
* @author: lele
* @date: 2019/11/20 下午2:42
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(RpcStudyClientRegisty.class)
public @interface EnableRpcStudyClient {
//掃描的包,如果爲空,根據啓動類所在的包名掃描
String[] basePackages() default {};
}
需要掃描的接口上添加RpcStudyClient註解
@RpcStudyClient(name="user")
public interface HelloService {
String sayHello(String userName);
String qq();
}
啓動類上添加@EnableRpcStudyClient註解指定要掃描的包,這樣就完成了client端的處理
然後到server端註冊具體的服務實現類,定義一個spring會掃描有該註解的類
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcStudyService {
// 用來指定實現的接口
Class<?> value();
}
服務註冊類,核心是通過getBeanWithAnnonation來獲取相應的bean,然後存儲在map裏面,並且在初始化後註冊本實例的ip地址,netty的handler則通過這個map直接獲取實例來執行相應的方法
@Component
@Data
public class RpcStudyRegister implements InitializingBean,ApplicationContextAware {
public static Map<String,Object> serviceMap;
@Value("${spring.application.name}")
private String name;
@Value("${rpcstudy.port}")
private Integer port;
@Override
public void afterPropertiesSet() throws Exception {
if(DemoApplication.mode==0){
String hostAddress = InetAddress.getLocalHost().getHostName();
URL url=new URL(hostAddress,port);
Protocol server= ProtocolFactory.netty();
//註冊服務
ZkRegister.register(name,url);
server.start(url);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//把有RpcStudyService註解的bean添加到map裏面,key爲該註解的接口
if(DemoApplication.mode==0){
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(RpcStudyService.class);
if (beans != null && beans.size() > 0) {
serviceMap = new HashMap<>(beans.size());
for (Object o : beans.values()) {
RpcStudyService rpcService = o.getClass().getAnnotation(RpcStudyService.class);
String interfaceName = rpcService.value().getName();
serviceMap.put(interfaceName, o);
}
serviceMap = Collections.unmodifiableMap(serviceMap);
}
}
}
}
這樣當本地的netty獲取class時,可以通過該map從傳來的request的interface爲參數來獲取相應的實現類
這裏由於沒有做模塊劃分,而server和client也需要分開,所以在啓動類上面定義一個mode變量,mode爲0時爲client端,只啓動tomcat服務,mode爲1時是server端只啓動netty,可以看到上面的client/server的register類在相應模式下才做處理,啓動client(手動改mode=1)和server(手動改mode=0),訪問localhost:8080/t2,得到下面的結果
client:
server: