想法的來源
學了netty框架以及看了一下一小部分的netty框架的源碼,聽說dubbo是基於netty框架的一個優秀的落地實現,所以看了一小部分dubbo的源碼,感覺學習netty總要有一個方式證明自己曾經學過,所以寫下這一篇小筆記,寫給自己看。
前提
zookeeper知識點
zookeeper有四種節點
1:PERSISTENT // 持久化節點
2:PERSISTENT_SEQUENTIAL // 持久化排序節點
3:EPHEMERAL // 臨時節點
4:EPHEMERAL_SEQUENTIAL // 臨時排序節點
netty知識點
https://blog.csdn.net/qq_37171353/category_9328166.html
其它參考百度。。。
Spring的生命週期
https://blog.csdn.net/qq_37171353/article/details/103165108
其它參考百度。。。
動手實踐
注意
1)下面只貼了部分代碼,全部代碼在 https://github.com/cbeann/Nettyy 中
2)項目寫的很爛,很爛,很爛
目標
1)zk爲註冊中心
2)服務消費者可以發送請求給服務提供者,但是方法的返回值我沒辦法在服務消費端獲取到,我只能打印,因爲dubbo重寫了future方法,我的目標是可以在服務消費者的netty中的handler中打印即可。
3)
服務註冊中心zookeeper
1)在zookeeper的/myrpc下創建兩個子節點/myrpc/provider 和 /myrpc/consumer,分別存儲消息提供者和消息消費者的註冊信息
2)ZkClient對providerBean和consumerBean按照一定的格式存到zk中,其中key是服務提供者和服務消費者的名稱
基本功能就是對zk
package com.zk;
import com.entity.rpc.ConsumerBean;
import com.entity.rpc.ProviderBean;
import com.untils.JsonUtils;
import org.apache.zookeeper.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author CBeann
* @create 2020-03-06 23:06
*/
public class ZKClient {
//根目錄
private static final String path = "/myrpc/";
//服務提供者
private static final String path_provider = "/myrpc/provider";
//服務消費者
private static final String path_consumer = "/myrpc/consumer";
/**
* zookeeper地址
*/
String CONNECT_ADDR = "39.105.30.146:2181";
/**
* session超時時間
*/
static final int SESSION_OUTTIME = 2000;// ms
/**
* 信號量,阻塞程序執行,用於等待zookeeper連接成功,發送成功信號
*/
static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
ZooKeeper zk;
public ZKClient(String url) {
this.CONNECT_ADDR = url;
try {
zk = new ZooKeeper(url, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 獲取事件的狀態
Event.KeeperState keeperState = event.getState();
Event.EventType eventType = event.getType();
// 如果是建立連接
if (Event.KeeperState.SyncConnected == keeperState) {
if (Event.EventType.None == eventType) {
// 如果建立連接成功,則發送信號量,讓後續阻塞程序向下執行
System.out.println("zk 建立連接");
connectedSemaphore.countDown();
}
}
}
});
// 進行阻塞
connectedSemaphore.await();
} catch (Exception e) {
e.printStackTrace();
}
}
//向zk註冊服務提供者
public void sendProviderBeanMsg(ProviderBean providerBean) {
String s = JsonUtils.objectToJson(providerBean);
try {
String path1 = zk.create(path_provider + "/" + providerBean.getProviderName(), s.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
System.out.println("Success create path: " + path1);
} catch (Exception e) {
e.printStackTrace();
}
}
//獲取服務提供者集合
public List<ProviderBean> getProviderBeanList() {
List<ProviderBean> providerBeanList = new ArrayList<>();
try {
List<String> children = zk.getChildren(path_provider, null);
for (String child : children) {
byte[] data = zk.getData(path_provider + "/" + child, false, null);
String json = new String(data);
ProviderBean providerBean = JsonUtils.jsonToPojo(json, ProviderBean.class);
providerBeanList.add(providerBean);
}
} catch (Exception e) {
e.printStackTrace();
}
return providerBeanList;
}
//獲取服務消費者集合
public List<ConsumerBean> getConsumerBeanList() {
List<ConsumerBean> consumerBeanList = new ArrayList<>();
try {
List<String> children = zk.getChildren(path_consumer, null);
for (String child : children) {
byte[] data = zk.getData(path_consumer + "/" + child, false, null);
String json = new String(data);
ConsumerBean providerBean = JsonUtils.jsonToPojo(json, ConsumerBean.class);
consumerBeanList.add(providerBean);
}
} catch (Exception e) {
e.printStackTrace();
}
return consumerBeanList;
}
//向zk註冊服務消費者
public void sendConsumerBeannMsg(ConsumerBean consumerBean) {
String s = JsonUtils.objectToJson(consumerBean);
try {
String path1 = zk.create(path_consumer + "/" + consumerBean.getConsumerName(), s.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
System.out.println("Success create path: " + path1);
} catch (Exception e) {
e.printStackTrace();
}
}
//關閉zk
public void closeZk() {
try {
zk.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服務提供者(思路)
因爲要整合Spring,所以我們要按照Bean的方式進行寫一些內容。
首先想到的是創建一個NettyServer,如下所示,大衆的想法
@Component
public class NettyServer {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
public void bind(int port) {}
}
但是NettyServer中ServerBootstrap中的port在是怎麼獲取的呢???
dubbo是配置在properties文件中,我們也是放在myrpc_provider.properties中,並且把信息映射到一個類ProviderConfigBean 中
provider.name=user-provider
provider.port=8888
provider.intername=com.service.StudentService
zk.url=39.105.30.146:2181
@Component
@PropertySource(value = "classpath:myrpc_provider.properties",ignoreResourceNotFound = true)
@Data
public class ProviderConfigBean {
@Value("${provider.name}")
private String providerName;
@Value("${provider.port}")
private Integer providerport;
@Value("${provider.intername}")
private String providerIntername;
@Value("${zk.url}")
private String zkUrl;
}
我們可以獲取properties中的信息,但是怎麼從 ProviderConfigBean 中的port怎麼傳到NettyServer中呢???
我們可以給NettyServer實現ApplicationContextAware接口,把ioc容器當做NettyServer的一個變量,然後就可以獲得ProviderConfigBean裏的port信息。
MyServerHandler是NettyServer中ServerBootstrap的hander,如果客戶端給服務端發送數據,那hander怎麼調用方法返回數據呢???
我們可以把applicationContext當做構造方法的參數傳入到hander中,這樣,我們就可以從客戶端發送的信息中獲取class、method和args,然後調用applicationContext根據class獲取類,然後有method和args可以通過反射執行方法,並且把結果寫回到channel中。
NettyServer中ServerBootstrap的bind方法什麼時候調用呢???
什麼時候調用都行,我是在ioc容器中加載完畢後調用bind方法,這裏讓NettyServer實現ApplicationListener<ApplicationEvent>接口,是在ioc最後的時候調用的。
ServerBootStrap.bind阻塞怎麼辦?
new Thread(() -> {
this.bind(providerConfigBean.getProviderport());
}).start();
服務提供者有自己專屬的類NettyServer和ProviderConfigBean,服務消費者也有,服務提供者需要排除一些類,怎麼又更好的重用性和可讀性???
自定義註解
@ComponentScan(value = "com",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ConsumerConfigBean.class, NettyClient.class})})
@Target(ElementType.TYPE)//方法註解
@Retention(RetentionPolicy.RUNTIME)//運行時註解
public @interface EnableProvider {
}
服務端的hander知道class、method、args怎麼執行,沒有實現類怎麼進行代理???
https://blog.csdn.net/qq_37171353/article/details/104757500
服務消費者
首先貼一下myrpc_consumer.properties和與其綁定的ProviderConfigBean
consumer.name=order-consumer
consumerref.intername=com.service.StudentService
zk.url=39.105.30.146:2181
@Component
@PropertySource(value = "classpath:myrpc_provider.properties",ignoreResourceNotFound = true)
@Data
public class ProviderConfigBean {
@Value("${provider.name}")
private String providerName;
@Value("${provider.port}")
private Integer providerport;
@Value("${provider.intername}")
private String providerIntername;
@Value("${zk.url}")
private String zkUrl;
}
Bean創建的時候是不能添加BeanDefination,也就是說先有全部的BeanDefination,然後有Bean。常情況下ioc容器會創建一個引用name爲studentService的類型爲StudentService的BeanDefination,但是在rpc中服務消費者只有一個接口,是不可能是上面那種中規中矩的方式。dubbo是怎麼實現的呢???
consumerref.intername在dubbo中其實是xml中ref標籤,然後通過解析xml向Spring中添加一個BeanDefination,其中name爲studentService。我沒有解析xml,說要我直接寫一個類@Component("studentService"),然後實現FactoryBean接口,然後重新getObject方法就能返回各種各樣的類。
package com.entity;
import com.consumer.ConsumerConfigBean;
import com.consumer.NettyClient;
import com.proxy.RpcFactoryProxy;
import com.service.StudentService;
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author CBeann
* @create 2020-03-09 19:27
*/
@Data
@Component("studentService")
public class RefRpcBean implements FactoryBean, ApplicationContextAware {
private String classname;
private ApplicationContext applicationContext = null;
private Object ref = null;
@Autowired
private NettyClient nettyClient;
@Override
public Object getObject() throws Exception {
Object proxy = null;
try {
RpcFactoryProxy factoryProxy = new RpcFactoryProxy(Class.forName(classname), nettyClient);
proxy = factoryProxy.getProxy();
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
// Student student = new Student();
// student.setName("refRpcBean");
// student.setId(1);
// return student;
}
@Override
public Class<?> getObjectType() {
return StudentService.class;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
ConsumerConfigBean bean = applicationContext.getBean(ConsumerConfigBean.class);
this.classname = bean.getConsumerrefIntername();
}
}
總結
1)文章寫的賊爛
2)第一次發現Spring中Bean的生命週期很重要
3)註解多了可以自己定義一個註解,就像SpringBoot的註解@SpringBootApplication一樣
4)盧本偉牛逼,反射牛逼,反射牛逼,反射牛逼
5)以前那種看視頻學FactoryBean接口時感覺沒啥卵用,現在是第一次發現FactoryBean接口的真正意義上的使用
6)動手實踐,思路和動手一樣重要,上來就動手反而是徒勞