这一部分主要是解析dubboxml 的标签,以及referenceBean的动态代理,注册zkclient 以及zk 的监听器
个人理解,如有不对还望指出,一同进步
首先,根据dubbo 自定义的继承于namespacehandlesupport 将会去解析 dubbo xml 里面的各个标签。 为什么会提交给namespacehandlesupport 呢?准确的说应该是dubbonamespacehandle 。 因为在dubbo的jar 包下面有个META-INF 下面有个spring.handlers 和spring.schemas, handler 里面写着交给哪个类去处理,schemas 顾名思义是标签的规则。
当运行dubbo 项目的时候, 他就会先跳到DubboNamespaceHandler
这个里面的init()方法中 会把各种标签都交给DubboBeanDefinitionPaser 处理,每一个标签就会掉对应的’elementName’方法一次,这里elementName的意思是图上registerBeanDefinitionParser()方法的第一个参数。这里经过老师的讲解,觉得这里做的不好,因为全部交给DubboBeanDefinitionParser来做的话里面会有很多的判断。
用来判断很多的类型等等,所以觉得应该这样写比较好
每个标签交给不同的解析类去解析,可读性会好一点。
之后跳到DubbobeanDefinationParse中的parse方法
Element 就是dubbo xml 中的元素标签,parserContext是我们等会要注入的springioc容器
beanClass我刚开始以为是interface 后来发现实际上是dubbo标签的类型class com.alibaba.dubbo.config.ConsumerConfig比如这个, 实际上是上上图的registerBeanDefinitionParser()方法中第二个参数的 参数的类的全路径名。
如果我配置了一个<dubbo:reference id="userDao" interface="mydubboprovide.UserDao" check="false" />
那么beanClass就是class com.alibaba.dubbo.config.spring.ReferenceBean, id就是userDao
这边一个判断是看看springioc 容器中有没有和这个id 一样的被代理类, 没有的话就将id作为key,beanDefinition 作为value 注入了spring 容器中。
其他的属性 像interface也是通过这个方法将属性注入beanDefinition中,然后
从com.alibaba.dubbo.config.spring.ReferenceBean拿到所有的方法,找到已set方法开头的方法, 说白了 这就是set注入。 比如有个方法名称为setInteface的方法,如果这个方法的长度大于3并且以set开头,方法类型是公共的,方法有且只有一个参数, 那么他会获得这个方法的参数属性, 并且用’beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);拿到与之匹配的get方法。但是get方法就判断了是不是空,修饰符是否为public 判断完了continue;完全看不懂有什么用。
Set后面跟的方法名字不是图上那三个的话, 直接从标签上面取得,
之后会调到referenceBean 的 实现类InitializingBean的afterPropertiesSet()方法。顾名思义实在属性设置完成后调到这个方法的。这个方法我看了一下就是设置zk 注册地址,monitor 等等。然后继续走
实现类的FactoryBean getObject()方法。
这是做什么呢?实际上返回了一个ReferenceBean的代理实例。即对ReferenceBean的动态代理。走到get()方法里面发现里面有个appendProperties(config)方法config是这个 <dubbo:reference singleton="true" interface="mydubboprovide.UserDao" generic="false" check="false" id="userDao" />
里面通过这个方法又开始找set方法, 和上面一样,只不过如果system里面有就调用method.invoke方法貌似。 (这个不太清楚)。
再回到init()方法中 他将renference中的各个属性啊比如interface id interface中的method等等 放入一个map中然后去调用createproxy 方法,其实是将全局配置加载进map中然后传过去。createproxy 方法中会先看看是不是injvm的,如果是就本地调用。不是的话然后会看到有个loadRegistry方法。里面会将zk 地址放入到registries中。有个UrlUtils.parseURLs(address, map)方法这个会拼装出一个Url, 然后如果修改zk协议为registry协议。(就是url本来是zookeeper://.... 变成了 registry://....)并且是协议中多个属性 registry=zookeeper;
然后在给monitor和createproxy(map)方法中的map 放入url.
然后在调用 refprotocol;
Refprotocol运用了spi 技术。
这里url是registry 开头所以调到了registryProtocol的 refer方法。
,这里先看看有没有registry参数,会发现registry=zookeeper,然后移除registry参数,url变成了zookeeper://...
这里的registryFactory.getRegistry(url) 后面就是建立zk 链接了。
这里面首先会尝试着从缓存中看看有没有这个值,首次当然是没有
那么就调用createRegistry();
这个方法会调到ZookeeperRegistry();zookeeperTransporter 怎么来的呢? spi
注册中心挂掉也能调用服务
上面的super(url)往上翻会做什么呢?这边做了一个很重要的事情,这个一直往上翻,翻到AbstractRegistry类中。这就是为什么dubbo的注册中心挂掉还能够调用服务。
public AbstractRegistry(URL url) {
setUrl(url);
// 启动文件保存定时器
syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
//找到本地文件 文件名如下
String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if(! file.exists() && file.getParentFile() != null && ! file.getParentFile().exists()){
if(! file.getParentFile().mkdirs()){
throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
}
this.file = file;
// 将此文件加载到properties 中
loadProperties();
//notify 一下
notify(url.getBackupUrls());
}
private void loadProperties() {
if (file != null && file.exists()) {
InputStream in = null;
try {
in = new FileInputStream(file);
properties.load(in);
if (logger.isInfoEnabled()) {
logger.info("Load registry store file " + file + ", data: " + properties);
}
} catch (Throwable e) {
logger.warn("Failed to load registry store file " + file, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.size() == 0)
&& ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
//notify 中 会将文件保存一下
saveProperties(url);
listener.notify(categoryList);
}
}
zookeeperTransporter.connect(url),这个跟了一下,结合addStateListener看 贼有意思。
zookeeperTransporter.connect(url)意思就是新建一个zkclient
这边 很明显匿名内部类,clientsubscribeStateChange 会把这个监听器放入zkClient 的 一个Set 中,这个Set 专门用来存储注册进来的监听器。stateChanged 方法执行后会遍历Set,每个监听器都调用他的statechanged方法 handlenewSession 说明session 不一样了, 那不就是断了要重新连了么, statechanged 方法就会调到上上图的recover方法。我们回到进来的refer方法,往下走doRefer。
再往下就是zk 的notify 了。以后写