概念
Dubbo是一个分布式RPC中间件。RPC(Remote Procedure Call)
远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,
而不需要了解底层网络技术的协议。
spi(service provider interface)
是JDK内置的一种服务提供发现机制。其实也就是一种动态替换发现的机制。
eg:有个接口,想运行时动态的给它添加实现,这时只需要添加一个实现。
JAVA的spi机制
1、需要一个目录 META/service,置于ClassPath下面
2、目录下面放置一个配置文件
(1)文件名是要扩展的接口全名
(2)文件内部要为实现的接口实现类
(3)文件必须为UTF-8编码
eg:
配置文件的名字:接口或者抽象类的全名
配置文件的内容:里面是两个扩展的实现类的全名
com.spi.impl.TextHellocom.chaochao.spi.impl.ImageHello
3、使用方法,主要是ServiceLoad
(1)ServiceLoad.load(xx.class)
(2)ServiceLoad<HelloInterface> loads = ServiceLoad.load(HelloInterface.class)
4、把新加的实现,描述给JDK,让JDK知晓即可(通过修改一个文本文件)
DUBBO的SPI机制
基于java的spi机制,不同点
1、可以方便的获取某一个想要的扩展实现,Java的Spi机制是没有提供此功能的
2、对于扩展实现IOC依赖注入功能
eg: 接口A,实现者A1,A2
接口B,实现者B1,B2
现在实现者A1含有setB()方法,会自动注入一个接口B的实现者,此时注入B1还是B2不得而知,这个时候其注入的是一个动态生成的接口B的实现者B$Adpative(可以理解为接口B的代理实现),改实现者可以根据传入参数的不同,自动引用B1或者B2来完成相应的功能。
3、对扩展采用装饰器模式进行功能增强,类似AOP实现的功能
装饰器模式:动态的给对象添加新的功能(提供了比继承更加有弹性的替代方案)
ExtensionLoder中含有一个静态变量,用于缓存所有的扩展加载实例
ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
以Protocol为例子
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
这里加载Protocol.class,就以Protocol.class为key,创建的ExtensionLoader为value存储到上述EXTENSION_LOADERS中
接着,ExtensionLoader实例是如何加载Protocol的实现类的:
1、先解析Protocol上的Extension注解的name,存至String cachedDefaultName属性中,作为默认的实现。
2、到类路径下的加载 MEATA-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件
ps:dubbo可以从以下路径去加载配置文件,在alibaba提供的源码中,是在META-INF/dubbo/internal下加载配置文件的
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
配置文件的名字:接口的全名
org.apache.dubbo.rpc.Protocol
配置文件的内容格式: 别名 = 接口扩展的全名
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
DUBBO的关键注解
@SPI : 作用在扩展点的接口上,表面该接口是一个扩展点。可以被Dubbo的ExtensionLoader加载。如果没有这个注解,ExtensionLoader调用会报异常,注解里面如果配置了参数,则表示默认的实现类别名。
@Adaptive: 作用在扩展接口的方法上,表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展。
设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。
利用javasist生成设配类的条件
1)接口方法中必须至少有一个方法打上了@Adaptive注解
2)被@Adaptive 注解了的方法参数必须有URL类型参数或者有参数中存在getURL()方法。
Dubbo中SPI扩展机制详解 (源码详解)
http://cxis.me/2017/02/18/Dubbo%E4%B8%ADSPI%E6%89%A9%E5%B1%95%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/
Dubbo的原理解析
https://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235
类的初始化机制方法
类的加载:
1、通过一个类的全限定名来获取其定义的二进制字节流(由类加载器来完成);
这里的字节流并不只是单纯的从class文件获取,它还可以从jar、ear、war包,网络中获取(Applet),或者由其他文件(jsp)生成、运行时计算生成,最典型的是动态代理技术。
2、将class文件中的字节码读入内存,将其放在运行时数据区的方法区内;
3、最终在堆区中创建一个代表这个类的java.lang.Class对象,作为对方法区中对这些数据的访问入口。
一般来说,只有第一次主动调用某个类时才会去进行类加载。如果有一个类有父类,会先去加载其父类,然后再加载其本身。
类加载器:
对于任意一个类,都需要由它的类加载器和这个类的本身一同确定其在虚拟机中的唯一性,也就是说,即便两个类来源于同一个class文件,只要加载它们的类加载器不同,那么这两个类必定不相等。
虚拟机的角度:
1、启动类加载器:C++写的,它属于虚拟机的一部分
作用:负责将存放在<JAVA_HOME>/lib/re.jar目录下的核心类,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机按照文件名识别的类库加载到虚拟机内存中。
启动类加载器无法被java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那就直接使用null代替即可。
JDK中常用的类基本都是由类加载器加载,如java.lang.String,java.util.List等。Main class也是由启动类加载器加载。
2、所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,
开发人员的角度:
1、启动类加载器(Bootstrap ClassLoader)
2、扩展类加载器(Extension ClassLoader)
由sun.misc.Launcher$ExtClassLoader实现。
负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
开发者可以直接使用扩展类加载器
3、应用程序类加载器(Application ClassLoader)
由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器时ClassLoader.getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。
应用程序基本都是由以上三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。
自定义类加载器(User ClassLoader)
类加载器的层级关系
启动类加载器
扩展类加载器
应用程序加载器
自定义加载器
类的验证
主要是保证类符合java语法规范,确保不会影响JVM的运行,包括但不限于
1、bytecode的完整性。
2、检查final类没有被继承,final方法有咩有被覆盖
3、确保没有不兼容的方法签名
类的准备
在这个阶段,JVM会为类成员变量(不包括实例变量)分配内存空间并且赋予默认初始值,需要注意的这个阶段不会执行任何代码,而只是根据变量类型决定初始值。如果不进行默认初始化,分配的空间的值值是随机的。
双亲委派机制
工作流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载器请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
example: 是
在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载,则调用父类的加载器加载,若父类的加载器为空,则默认使用启动类加载器作为父类加载器,如果父类加载器失败,则抛出ClassNotFoundException异常,再调用自己的findClass()方法进行加载。
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
//check the class has been loaded or not
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name,false);
}else{
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//if throws the exception ,the father can not complete the load
}
if(c == null){
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
使用双亲委派模型来组织类加载器之间的关系,有一个好处:
就是类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层级关系,这个对于保证JAVA程序的稳定运作很重要。
eg : java.lang.Object,它由启动类加载器加载。双亲委派模型保证任何类加载器收到的java.lang.Object的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此,Object类在程序的各种类加载器环境中都是同一个类。
如果没有使用双亲委派模型,由各个类加载器自行去加载的话,会出现同一个类由不同的类加载器去加载,保证不了java的基本行为,容易引起混乱
自定义类加载器的正确姿势:1、继承ClassLoader
2、应当仅覆写ClassLoader #findClass()方法,以支持自定义类的加载方式。不建议覆写ClassLoader #loadClass()(以使用默认的来加载逻辑,即双亲委派模型);如果需要覆写,则不应该破坏双亲委派模型。