想法的来源
学了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)动手实践,思路和动手一样重要,上来就动手反而是徒劳