dubbo中的SPI机制的使用和源码分析
SPI
为什么要分析 dubbo的SPI机制,我只能说dubbo的SPI机制是dubbo的基础,如果你想研究dubbo的底层实现原理, 那么dubbo的spi机制是基础,如果你不明白dubbo的spi机制原理,那么研究dubbo是如何和spring整合的,dubbo的服务导出,服务引入,服务调用对于你来说就是天书,比天书还难;所以dubbo的spi机制就必须要明白它的实现原理。
我个人认为,dubbo这个框架写的代码过于饶了点,服务导入和服务引入真的是烧脑,和spring底层的实现来说,感觉dubbo要难太多了,到现在dubbo消费端的调用有些逻辑我还没有理清楚,服务引入、服务导出和服务调用服务端的处理已经非常清楚了,后头有时间去研究下dubbo的消费端调用的细节流程。
SPI机制概述
SPI机制最早出现在JDK中,SPI全称为Service Provider Interface,简单来说就是服务发现机制,我们熟知的JDK使用的spi机制在连接数据库的过程中,JDK中有一套完整的SPI机制,不同的数据库厂商安装SPI机制提供相应的spi配置文件,就能被JDK所识别,从而被加载到jvm中进行初始化;Java提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的SPI有JDBC、JCE、JNDI、JAXP和JBI等,这些spi所提供的接口都是jdk所提供的核心库,而spi的实现是需要在应用程序中提供相对应的配置文件进行加载,但是JDK底层所提供的核心库是由引导类加载器(Bootstrap Classloader)加载器所加载的,而就算应用程序提供了spi的相关实现,但是引导类加载器是无法找到应用程序所提供的加载类来加载的,那么它是如何加载的,我们的应用程序包是通过classpath所指定的,是被应用加载器所加载,所以线程上下文加载器就应运而生,上下文线程加载器打破了双亲委派,可以进行向下加载。
JDK原生的SPI加载机制的出现使的java中的接口可实现可插拔的设计,由JDK提供一些标准的接口,这些标准接口由各家数据库厂商来实现这个标准,数据库厂商实现的标准通过spi机制被jdk所识别,我们通常使用mysql、oracle连接数据库,每家数据库厂商都会提供一个数据库驱动包,数据库驱动包里面就是通过JDK的spi的机制引入相对应的接口实现类,从而被jdk所加载并识别,从而应用程序可以进行数据库连接。
JDK中的数据库驱动加载的spi截图如下:
这就是DriverManger通过spi机制加载数据库的厂商的底层代码,其实就是通过ServiceLoader来加载数据库厂商的驱动,泛型所指的class的全限定名就是数据库厂商配置的spi文件全名称,所以如果mysql数据库厂商要接入java的数据连接标准,就需要开发一个包通过spi来引入mysql的数据库驱动类,就是通过spi来实现,每家数据库厂商的spi文件名字是固定的,目录也是固定的,都在META-INF/services下的java.sql.Driver文件中,比如mysql的:
这样DriverManager通过ServiceLoader就能加载到数据库厂商的驱动,这样可插拔的设计使得应用可以随意切换数据库厂商。
JDK中的SPI机制
那么在jdk默认提供的spi机制如何实现呢?下面就来模拟一下spi的机制,要实现spi机制,首先我们要定义一个标准,其实就是一个接口,所有的要接入我这个标准的都要定义一个spi文件,文件名称就是我提供的接口的全名称,比如我定义了一个接口Car,如下:
package org.apache.dubbo;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.UrlUtils;
public interface Car {
String getCar();
}
我的Car的全限定名就是org.apache.dubbo.Car,对于Car有两个实现类,一个是BlackCar,一个是RedCar,都是对Car标准的实现
public class BlackCar implements Car{
@Override
public String getCar() {
return "black";
}
}
public class RedCar implements Car{
@Override
public String getCar() {
return "red";
}
}
然后在META-INF/services下面创建org.apache.dubbo.Car文件,文件内容如下:
org.apache.dubbo.BlackCar
org.apache.dubbo.RedCar
编写测试程序:
public class JDKSpi {
public static void main(String[] args) {
ServiceLoader<Car> loader = ServiceLoader.load(Car.class);
for (Car car : loader) {
System.out.println(car.getCar());
}
}
}
输出如下:
可以根据spi配置文件动态的调整要引入的实现类,也就是服务,在dubbo里面也叫服务发现,当提供了这种文件过后,你配置了多少个实现类,jdk的spi就能发现几个从而进行加载。
dubbo SPI
dubbo中也有一套自己的spi机制,在spring boot的自动装配中也使用了spi机制,但是都是自己实现的一套spi机制,为什么不使用jdk的一套spi机制呢?因为各种框架需要开发出属于自己的一套spi机制,因为比较实用,而jdk也好,spring也好,都是自己实现的一套spi机制,在其他框架里面不适用,每一套框架都有自己的一套spi机制从而来更好的适配自己的框架,jdk的spi其实是有缺陷的,因为在spi配置文件中只能配置一大堆的扩展实现类,但是无法指定我想要那些实现类,就是一大堆的实现类列表,就不太适用于dubbo框架,所以dubbo实现了一套自己的spi机制,那么dubbo是如何实现的这套spi呢,和jdk有什么不同呢?其实我感觉除了思想,其他完全不太一样,dubbo在spi的配置文件中增加了key,类似于property一样,通过key来获取我想要的扩展实现类,这样就更精准的能够定位我们应用中想要得到的一些实现类,而不是将spi文件中的所有实现类都加载并且得到实例对象;dubbo的spi我们来看个例子,我们知道dubbo中会提供很多中协议,比如http,dubbo协议,那么这些协议的实现就是通过dubbo的spi来实现的,比如看下面的例子
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol http = loader.getExtension("http");
System.out.println(http);
这样就获取了dubbo中的http协议的实现类,而http这个协议也是配置在dubbo的spi中,具体的路径就是
dubbo的spi的路径可以有多个,待会儿在源码里面会有,看上图可以知道spi的扩展类是可以通过key来获取的,但是在spi配置文件中可以不配置key,但是必须要有条件,这和dubbo的aop包装类有关系,后面源码分析会有说明。
比如就上面jdk中的BlackCar和RedCar,我配置在dubbo的spi中,也一样可以实现,如下图:
ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);
Car red = loader.getExtension("black");
System.out.println(red);
输出:org.apache.dubbo.BlackCar@368102c8
dubbo spi架构图
dubbo aop
dubbo的aop机制,也就是包装类,aop是什么,相信大家都知道,很多框架中的aop都是通过动态代理来实现的,其实aop就是在方法真正调用前后做一些事情,也就是多了一层,而真正调用的是这多的一层,在这一层中真正的去调用我们要真正调用的方法,所以动态代理也就是为我们的调用的接口生成了有一个代理类,这个代理类也是目标接口的子类,大概就是这么个意思;dubbo中的aop也是非常简单一种实现,就是通过一个包装类来实现,这个包装类也一样实现了目标接口Car,所以在调用Car实现类的方法时候,其实是调用了包装类的对应接口,由这个接口去调用Car的实现类的目标方法,dubbo中aop实现看下图:
其实我们要调用的是DubboProtocol中的方法,但是它是被外层的两个包裹类所包裹,所以在调用DubboProtocol中的方法时候,首先要经过外层的两个包裹类,而这两个包裹类就可以进行在调用DubboProtocol的前后做一些前置后置的事情,也就是典型的aop的实现机制,所以dubbo是这么实现的aop,感觉是一种非常好的代理模式,主要比较易于理解。那么这种如何实现呢?
首先我们就Car这个接口做一个包装类CarWrapper
public class CarWrapper implements Car, Lifecycle {
private Car car;
public CarWrapper(Car car){
this.car = car;
}
@Override
public String getCar() {
System.out.println("调用前...");
String s = this.car.getCar();
System.out.println(s);
System.out.println("调用后...");
return s;
}
@Override
public void initialize() throws IllegalStateException {
System.out.println("inti....");
}
@Override
public void start() throws IllegalStateException {
}
@Override
public void destroy() throws IllegalStateException {
}
}
这个CarWrapper中有一个Car,并且具有一个构造方法,这个包裹类必须存在一个只带一个参数的构造方法,而且这个参数还必须是Car类型,也就是接口的类型,这个CarWrapper也必须是Car的子类,因为当Car的真正的实现类调用的时候其实是调用的CarWrapper的方法,而这个方法也是Car的实现类需要调用的方法,比如getCar,因为BlackCar和RedCar也是实现了Car接口的,所以他们具有相同的方法。dubbo底层会调用CarWrapper的一个参数的构造进行注入car这个对象,至于注入Car的那个实现类,就要看你通过spi获取的那个实现类,比如配置文件如下:
red=org.apache.dubbo.RedCar
black=org.apache.dubbo.BlackCar
org.apache.dubbo.CarWrapper
red和black是Car的两个扩展实现类的key,而CarWrapper是没有的key的,因为它是包裹类,所以当你获取的red或者black的时候,如果发现这个文件中有一个包裹类,而这个包裹类的类型并且构造方法也是满足red或者black的父类接口类型的时候,那么你通过red或者black获取到的实现类其实是CarWrapper,而当你真正调用BlackCar或者RedCar中的方法时候,其实是调用了CarWrapper中的相同方法,由CarWrapper中的方法去调用BlackCar或者RedCar中的方法。
ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);
Car red = loader.getExtension("black");
System.out.println(red);
System.out.println(red.getCar());
输出:
调用前…
black
调用后…
这样的aop理解一下,非常好理解,但是需要自己去想,有些东西写出来别人看就不是那么会儿事,需要自己去理解
dubbo的依赖注入
dubbo的依赖注入其实简单理解就是当获取到一个扩展类,比如BlackCar的时候,如果BlackCar里面有一个属性需要注入,那么它也会为其进行注入,注入的方式是通过set方法进行注入,也就是说这个属性必须要有对应的一个set方法才能进行注入,而且这个注入有点麻烦的是如果注入的类型是一个spi的接口类,这个接口类有很多实现类,那么dubbo也不会知道要注入那个接口的实现类,那么这个时候就需要通过dubbo的@Adaptive机制来实现了,通过@Adaptive机制来指定要注入的实现类或者说dubbo为你生成一个代理类,这个代理类实现了Car的所有方法,那么这些方法中在Car接口中必须全部定义为@Adaptive,并且方法必须传入一个URL参数,dubbo通过这个URL参数来给你传入指定的实现类,如果方法中没有加@Adaptive和URL的参数,那么生成的方法的方法体会抛出一个异常,而如果@Adaptive注解的方法没有加URL或者没法通过getURL获取URL,那么启动就会报错,我们看下下面的例子:
@SPI
public interface Car {
@Adaptive
String getCar();
}
@SPI
public interface Person {
Car getCar();
}
public class BlackPerson implements Person {
private Car car;
public void setCar(Car car) {
this.car = car;
}
@Override
public Car getCar() {
return car;
}
}
其中BlackPerson中有一个属性car,要进行注入,但是Car有两个实现类BlackCar和RedCar,dubbo在注入的时候要注入那个呢?它也不会知道的,所以需要你指定,有两种:
1.在Car的某一个实现类上加上@Adaptive注解,那么创建BlackPerson的时候就会注入加了@Adaptive的Car的实现类,比如:
@Adaptive
public class RedCar implements Car{
@Override
public String getCar() {
return "red";
}
}
ExtensionLoader<Person> loader = ExtensionLoader.getExtensionLoader(Person.class);
Person black = loader.getExtension("black");
//URL url = new URL("xx","localhost",8080);
//url=url.addParameter("car","black");
System.out.println(black.getCar().getCar());
输出red;但是如果不加@Adaptive,那么BlackPerson中的car就是一个null,没有办法注入,除非第二种方法:
2.增加一个参数URL,通过URL进行注入,就上面的代码
URL url = new URL("xx","localhost",8080);
url=url.addParameter("car","black");
在url中添加一个参数car,指定是black或者red,但是这样的话,那么Car中要进行调用的方法就都必须加上URL这个参数,比如
ExtensionLoader<Person> loader = ExtensionLoader.getExtensionLoader(Person.class);
Person black = loader.getExtension("black");
URL url = new URL("xx","localhost",8080);
url=url.addParameter("car","black");
System.out.println(black.getCar().getCar(url));
用url的这种方式就是相当于在BlackPerson中的car属性,dubbo为其生成了一个代理对象Car A d a p t i v e 类 , 而 在 C a r 的 实 现 类 的 某 一 个 上 加 了 @ A d a p t i v e , 那 么 B l a c k P e r s o n 中 的 c a r 注 入 的 就 是 加 了 @ A d a p t i v e 注 解 的 实 现 类 ; 但 是 如 果 生 成 C a r Adaptive类,而在Car的实现类的某一个上加了@Adaptive,那么BlackPerson中的car注入的就是加了@Adaptive注解的实现类;但是如果生成Car Adaptive类,而在Car的实现类的某一个上加了@Adaptive,那么BlackPerson中的car注入的就是加了@Adaptive注解的实现类;但是如果生成CarAdaptive的模式中,Car接口中方法加了@Adaptive,但是没有送URL参数,启动会直接报错,如果Car中没有有没有加@Adaptive注解的方法,那么调用该方法直接抛出异常,表示不支持的的方法。
@SPI
public interface Car {
@Adaptive
String getCar(URL url);
void test();
}
如果加了@Adaptive,没有加URL参数,启动会直接报错
dubbo中@SPI
dubbo中如果一个接口是一个SPI的标准接口,那么这个接口上面必须要加@SPI,否则会报错的,当你通过这个接口去创建扩展类加载器ExensionLoader的时候,而dubbo中有一个很有意思的功能就是这个@SPI可以定义一个默认的名字,定义了过后,可以通过true来获取,比如:
@SPI("red")
public interface Car {
@Adaptive
String getCar(URL url);
void test();
}
ExtensionLoader<Car> loader = ExtensionLoader.getExtensionLoader(Car.class);
Car red = loader.getExtension("true");
System.out.println(red.getCar(null));
就是定义了一个默认的名字而已
还有就是在spi配置文件中可以不用定义key,而在实现类上定义名字,但是dubbo现在不建议使用了,但是还是有这个功能的,并没有删除,就是通过一个注解@Extension("")来定义,比如:
red=org.apache.dubbo.RedCar
org.apache.dubbo.BlackCar
我并没有指定BlackCar的key,但是可以在BlackCar的类上加注解
@Extension("black")
public class BlackCar implements Car{
@Override
public String getCar(URL url) {
return "black";
}
@Override
public void test() {
}
}
通过black也是一样的能获取到BlackCar,但是已经不建议使用了,规范的用法还是通过配置文件配置key来使用
Dubbo SPI源码分析
ExtensionLoader
ExtensionLoader表示某个接口的扩展点加载器,可以用来加载某个扩展点实例。
在ExtensionLoader中除开有上文的static的Map外,还有两个非常重要的属性:
Class<?> type:表示当前ExtensionLoader实例是哪个接口的扩展点加载器
ExtensionFactory objectFactory:扩展点工厂(对象工厂),可以获得某个对象
ExtensionLoader和ExtensionFactory的区别在于:
ExtensionLoader最终所得到的对象是Dubbo SPI机制产生的
ExtensionFactory最终所得到的对象可能是Dubbo SPI机制所产生的,也可能是从Spring容器中所获得的对象
在ExtensionLoader中有三个常用的方法:
getExtension(“dubbo”):表示获取名字为dubbo的扩展点实例
getAdaptiveExtension():表示获取一个自适应的扩展点实例
getActivateExtension(URL url, String[] values, String group):表示一个可以被url激活的扩展点实例
getExtensionLoader(Class class)
/**
* 获取一个可扩展的加载器,就是SPI的加载器,这里是根据传过来的接口类型去获取的
* 就是每一个接口都会单独创建一个ExtensionLoader可扩展的SPI加载器,比如说协议接口类Protocol这个接口
* dubbo底层都是通过自身的spi机制获取对应的实现类,是根据Protocol这个接口类型获取的
* dubbo的spi机制需要在配置文件中指定一个key,这个key可以是一个字符串,也可以是多个字符串,通过key来获取对应的实现类
* 比如:
* key1,key2=com.xxx.Impl,那么久可以通过key1或者key2获取到对应的接口实现类Impl,它的spi机制不同于jdk的spi机制
* 如果说dubbo的spi配置文件中配置的实现类没有对应的key,那么这个实现类如果有一个构造方法是注入获取的key的话,那么
* 就是dubbo的一种aop的实现,也就是Wrapper包装类
*
* @param type
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
//SPI机制要求配置的都是接口类型才行
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 并且这是个接口必须要加@SPI注解才行,否则都不能认为是dubbo中的spi机制所提供的接口
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
/**
* 先从缓存中获取数据,如果缓存中没有,表示第一次来根据接口获取接口对应的扩展类加载器,那么会创建这个这个扩展类加载器
* 获取成功过后放入缓存中EXTENSION_LOADERS
* EXTENSION_LOADERS:
* key:接口类型Class
* value:接口所对应的扩展类加载器ExtensionLoader
*/
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//创建一个新的扩展类加载器
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
//放入缓存
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
org.apache.dubbo.common.extension.ExtensionLoader#ExtensionLoader
private ExtensionLoader(Class<?> type) {
this.type = type;
/**
* 这里是在创建ExtensionLoader的时候初始化的,ExtensionFactory有好几个实现类,这个也是从spi文件中读取的
* 而getAdaptiveExtension()方法获取分为两步:
* 1.如果ExtensionFactory的实现类中有加了@Adaptive注解的实现类,那么这个方法获取的就是加了注解的实现类;
* 2.如果没有,就会去创建一个Adaptive的代理对象返回给objectFactory
*
* 其中有一个实现类是加了注解的AdaptiveExtensionFactory,
* 所以这里getAdaptiveExtension()获取的就是AdaptiveExtensionFactory
*/
objectFactory = (type == ExtensionFactory.class ? null :
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
getExtensionLoader这个方法其实就是获取一个指定类型的扩展类加载器,根据传入的class 类型去获取一个 扩展类加载器,第一次会创建一个新的ExtensionLoader,如果说第二次还是传入相同的class 类型,那么会从缓存中获取,就是在一个应用中,一个SPI接口,比如Car,那么在系统中只会存在一份扩展类加载器,也可以简单理解为每个扩展类加载器细到SPI接口本身是以单例的形式存在的,而每一个类型所对应的扩展类加载器都会单独拥有一个新的ExtensionLoader,而ExtensionLoader中会为每个一个扩展类加载器创建一个单独的对象工厂objectFactory,对象工厂也是对于每一个扩展类加载器是单独存在的,不存在线程安全的问题。
getExtension(String name)
上面创建了扩展类加载器返回了一个扩展类加载器ExtensionLoader,通过ExtensionLoader的实例对象就可以获取到指定SPI接口所导入的一些扩展类,这些扩展类简单理解就是扩展类加载器中所代表的类型的子类,比如Car这个spi接口,那么通过getExtension获取的就是Car这个SPI接口所对应的一些实现类,其中方法中的name变量就是spi配置文件中的key,通过key来获取对应的实现类,然后通过反射得到这个实现类的对象,然后再进行依赖注入,最终返回依赖注入完成后的对象,也就是说dubbo的aop和ioc都是在这个方法中完成的
/**
* Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
* will be thrown.
*
* 根据spi中配置的名称获取所对应的实现类
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
/**
* 如果传入的名字为true,表示要获取一个默认的名字
* 默认的名字是在@SPI("")中配置的,如果配置了就有默认的名字,如果没有配置,那么是获取不到的
* 反正就是如果传入的name为true,那么就需要在@SPI中配置名称,如果没有配置,就类似于传入了空字符串,在
* getDefaultExtension会直接返回一个null
*/
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
/**
* 这里是来根据传入的名字获取一个名字所对应的实现类的对象,比如spi配置文件中配置的是
* abc=com.xxx.Impl
* 那么这里传入的是name=abc,最终得到的instance就是Impl的一个实例对象,实例对象是dubbo通过反射得到的一个对象
*
* 下面还有一个重要的概念就是并发里面的DCL,就是双重检查,当有多个线程进来的时候,关于DCL的就不说了,这里重要的是说一个
* 设计思路,在DCL中需要加锁,但是下面的代码用了一个Holder来加锁,因为对于多个线程来说,只有获取同一个类型的对象时加锁才有意义
* 否则你设计一个Object锁,那么这把锁就太粗了,因为不同的接口来获取对应是互不影响的,不应该拉入锁的控制,但是由于这里
* 还没有创建对象,所以null是不能作为锁的,所以dubbo的设计者将这里的instance对象作为一个属性包装到Hodler里面,那么这里Hodler
* 肯定是不为空的,只是刚开始其中的属性value是空的,但是不影响,所以这里的锁粒度就变细了,这样的并发空控制才是性能上的最优选择
*/
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//创建一个key所对应的实例对象
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
/**
* Return default extension, return <code>null</code> if it's not configured.
* 根据传入的name=true去获取@SPI中配置的一个默认的name,然后在回调getExtension
* 获取扩展实现类对象
*/
public T getDefaultExtension() {
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}
/**
* 获取一个Holder对象,这个Holder包装了spi配置文件中key所对应的实现类对象
* cachedInstances是一个map,里面存的就是key所对应的实现对象
* @param name
* @return
*/
private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
createExtension
在调用createExtension(String name)方法去创建一个扩展点实例时,要经过以下几个步骤:
1.根据name找到对应的扩展点实现类;
2.根据实现类生成一个实例,把实现类和对应生成的实例进行缓存;
3.对生成出来的实例进行依赖注入(给实例的属性进行赋值);
4.对依赖注入后的实例进行AOP(Wrapper),把当前接口类的所有的Wrapper全部一层一层包裹在实例对象上,没包裹个Wrapper后,也会对Wrapper对象进行依赖注入;
5.返回最终的Wrapper对象.
/**
* 创建一个name所对应的实例对象
*
* @param name
* @return
*/
@SuppressWarnings("unchecked")
private T createExtension(String name) {
//读取spi配置文件将配置文件中配置的扩展类,包括@Adaptive,aop包装类都读取放入到缓存中
Class<?> clazz = getExtensionClasses().get(name);
//如果从读取的缓存数据中没有获取到name对应的扩展类,就直接报错
if (clazz == null) {
throw findException(name);
}
try {
/**
* 根据name获取的扩展类对象在第一次过后是会放入缓存的,如果缓存中没有获取到
* 就会去创建,创建好了就放入缓存
* 这里可以理解为spring中的IOC功能
*/
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//依赖注入DI,比较复杂一点,但是比spring要好一点,没有spring的复杂
injectExtension(instance);
//cachedWrapperClasses是在读取spi配置文件的时候将所有的包装类(AOP)都缓存到了
//cachedWrapperClasses,这里就是来创建aop的代理对象,类似于
/**
* black=org.apache.dubbo.BlackCar
* org.apache.dubbo.CarWrapper
* 通过black获取到的不是BlackCar,而是CarWrapper,是对BlackCar的一个代理
*/
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
/**
* 所以在dubbo的aop中,比如CarWrapper是需要提供一个只有一个参数的构造,而且这个参数还必须是type类型
* 这里也就是Car接口类型,而BlackCar也是Car接口类型,否则要报错
* 执行了wrapperClass.getConstructor(type).newInstance(instance)过后得到的是CarWrapper,dubbo和spring一样
* 需要对其进行DI,就是属性注入,dubbo中这个版本的属性注入只能通过set方法,也就是injectExtension方法
*/
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//最后一步初始化生命周期方法
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
getExtensionClasses
getExtensionClasses()是用来加载当前接口所有的扩展点实现类的,返回一个Map。之后可以从这个Map中按照指定的name获取对应的扩展点实现类。
当把当前接口的所有扩展点实现类都加载出来后也会进行缓存,下次需要加载时直接拿缓存中的。
Dubbo在加载一个接口的扩展点时,思路是这样的:
根据接口的全限定名去META-INF/dubbo/internal/目录下寻找对应的文件,调用loadResource方法进行加载
根据接口的全限定名去META-INF/dubbo/目录下寻找对应的文件,调用loadResource方法进行加载
根据接口的全限定名去META-INF/services/目录下寻找对应的文件,调用loadResource方法进行加载
这里其实会设计到老版本兼容的逻辑
/**
* 加载spi配置文件,将所有的spi配置文件进行加载,如果第一次加载的话就进行读取
* 第一次加载完成过后放入缓存,后面的过来加载就直接从缓存获取加载spi配置
* @return
*/
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
//相同的DCL(Double Check Lock)控制
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//第一次加载,读取spi配置文件加载,返回一个map放入缓存
classes = loadExtensionClasses();
//放入缓存cachedClasses中
cachedClasses.set(classes);
}
}
}
return classes;
}
/**
* synchronized in getExtensionClasses
* 加载spi配置文件
*/
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
//加载的所有的spi配置信息都放入到extensionClasses中
Map<String, Class<?>> extensionClasses = new HashMap<>();
/**
* strategies:
* META-INF/dubbo/internal
* META-INF/dubbo
* META-INF/services
* dubbo从这三个目录中加载spi文件,所以文件放在这三个目录都是可以的,优先级如上
*/
for (LoadingStrategy strategy : strategies) {
/**
* 下面两个方法都是一样的,都是加载spi配置文件,目录都是一样的,
* 但是第二个方法是为了兼容之前的包命名规则
*/
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
}
return extensionClasses;
}
/**
* extract and cache default extension name if exists
*/
private void cacheDefaultExtensionName() {
/**
* 这里是去获取默认的SPI名字的,在接口上配置了@SPI("NAME")那么这个NAME就是一个默认的名字,会放入到
* cachedDefaultName中,并且是只有一个的,不能多个
*/
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
loadDirectory
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
//type就是一个接口的全限定名,dir就是目录,所以fileName=META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
String fileName = dir + type;
try {
/**
* 1.找到ExtensionLoader所在的类加载器;
* 2.通过类加载器去读取 fileName
*
*/
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//读取每一个文件,比如org.apache.dubbo.rpc.Protocol这个spi的配置文件,然后加载读取
loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, String... excludedPackages) {
try {
//逐行读取,每一行的配置是key=实现类或者只有实现类或者只有注释或者是在key=实现类后的注释,内容如下:
/**
* #协议的配置
* protocol=or.apache.xxx.HttpProtocol
* key=com.xxx.Impl #测试的SPI
*/
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
//这里要过滤掉注释的,就上面所写的格式,如果遇到#的,那么截取#号前面的字符串,如果截取到#前的字符长度大于0表示是”key=com.xxx.Impl #测试的SPI“这种类型
//否则就是整行都是注释
final int ci = line.indexOf('#');
if (ci >= 0) {
//如果#大于0,那么看下#号前面是否有字符
line = line.substring(0, ci);
}
line = line.trim();
//line的长度大于0,表示有需要处理的行
if (line.length() > 0) {
try {
String name = null;
//读取格式key=value的格式,如果没有key,那么line=value
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
//如果line大于0,那么如果line是不在排除之外的包下就进入加载的方法loadClass
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
loadResource方法就是完成对文件内容的解析,按行进行解析,会解析出"=“两边的内容,”="左边的内容就是扩展点的name,右边的内容就是扩展点实现类,并且会利用ExtensionLoader类的类加载器来加载扩展点实现类。然后调用loadClass方法对name和扩展点实例进行详细的解析,并且最终把他们放到Map中去。
loadClass
/**
* 读取spi配置文件中的实现类,也就是扩展类,接口对应的扩展类,加载到map中
* 得到扩展类的全限定名,然后通过反射得到实例放入map
* @param extensionClasses
* @param resourceURL
* @param clazz
* @param name
* @throws NoSuchMethodException
*/
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
/**
* type是接口,也就是接口的class类型,而clazz是在spi配置文件中配置的类全限定名得到的class
* 所有必须是type的实现类,否则就要报错
*/
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
/**
* 判断扩展实现类是否是Adaptive类,在dubbo的依赖注入,也就是属性注入的时候,如果
* 你指定了一个注入属性的实现类加了@Adaptive注解的话,那么就不用传入Url对象进行参数传递,换句话说就是
* 比如通过spi获取到BlackPerson中有一个属性Car car,而这个car的有一个实现类中有一个加了
* @Adaptive注解,那么dubbo就不用给你生成car的这个代理对象进行赋值了,依赖注入会直接将这个
* 加了@Adaptive注解的实现类直接注入到car这个属性里面去,还有一个限制就是如果car的实现类中如果超过1个
* 都加了@Adaptive注解,那么也是不生效的,所以car的实现类中只能加一个@Adaptive注解,表示属性的注入都
* 使用这个作为注入的实现类,否则就只能通过URL对象进行参数传递
*/
if (clazz.isAnnotationPresent(Adaptive.class)) {
//将加了@Adaptive注解的实现类的class类型缓存到一个对象cachedAdaptiveClass中,后面如果
//有那个类中使用了clazz作为属性的时候可以直接注入
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
//如果是包装类,也就是dubbo这里提供的aop机制,那么
/**
* 比如说spi配置是这样的
* black=org.apache.dubbo.BlackCar
* org.apache.dubbo.CarWrapper
* 而CarWrapper中有一个构造方法,参数类型是Car,而CarWrapper也是实现了Car,BlackCar也是
* 实现了Car,那么通过black获取到的对象其实就是CarWrapper,CarWrapper中实现了Car的接口方法
* 所以在嗲用BlackCar的接口方法时,会先调用CarWrapper中的接口方法,再会去调用BlackCar的接口
* 方法,这就是简单的aop机制,dubbo通过包装类来实现的,如果一个类具有包装类(aop),那么它是不
* 需要配置key的,在spi中直接配置一个实现类的全限定名就可以了
*/
//将这个包装类aop添加到缓存中
cacheWrapperClass(clazz);
} else {
/**
* 如果不是上面的两种类型,那么要获取这个实现类的一个默认的空构造方法,也就是说Car的实现类
* 中必须要有一个空的构造方法
*/
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
//如果说在spi的配置文件中没有配置key,但是又不是适配器类,也不是包装类,那么就需要配置
//你的这个实现类是否配置了@Extension,如果配置了就获取Extension中的配置名称,如果
//还是没有就报错,但是这个@Extension已经不推荐使用了,所以在配置文件中配置key才是
//最好的做法
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
/**
* 程序执行到这里的时候,必要要满足的条件是需要有一个name,和name对应的class
* 名字可以有多个,比如key1,key2,key3=com.xx.Impl
*/
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//Activate的处理
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//将class对应的名字添加到map cachedNames中缓存起来
cacheName(clazz, n);
//将名字为n的clazz添加到map extensionClasses中缓存起来
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
loadClass方法会做如下几件事情:
当前扩展点实现类上是否存在@Adaptive注解,如果存在则把该类认为是当前接口的默认自适应类(接口代理类),并把该类存到cachedAdaptiveClass属性上。
当前扩展点实现是否是一个当前接口的一个Wrapper类,如果判断的?就是看当前类中是否存在一个构造方法,该构造方法只有一个参数,参数类型为接口类型,如果存在这一的构造方法,那么这个类就是该接口的Wrapper类,如果是,则把该类添加到cachedWrapperClasses中去, cachedWrapperClasses是一个set。
如果不是自适应类,或者也不是Wrapper类,则判断是有存在name,如果没有name,则报错。
如果有多个name,则判断一下当前扩展点实现类上是否存在@Activate注解,如果存在,则把该类添加到cachedActivates中,cachedWrapperClasses是一个map。
最后,遍历多个name,把每个name和对应的实现类存到extensionClasses中去,extensionClasses就是上文所提到的map。
至此,加载类就走完了。
回到createExtension(String name)方法中的逻辑,当前这个接口的所有扩展点实现类都扫描完了之后,就可以根据用户所指定的名字,找到对应的实现类了,然后进行实例化,然后进行IOC(依赖注入)和AOP。
Dubbo中的IOC
1.根据当前实例的类,找到这个类中的setter方法,进行依赖注入;
2.先分析出setter方法的参数类型pt;
3.在截取出setter方法所对应的属性名property;
4.调用objectFactory.getExtension(pt, property)得到一个对象,这里就会从Spring容器或通过DubboSpi机制得到一个对象,比较特殊的是,如果是通过DubboSpi机制得到的对象,是pt这个类型的一个自适应对象(代理对象);
5.再反射调用setter方法进行注入.
injectExtension
/**
* 这是dubbo中的DI,就是在得到一个扩展类的时候,如果扩展类中有需要注入的属性,
* 那么dubbo都会为其注入的,dubbo注入的模式在这个版本会从spring容器中去获取bean
* 如果获取到了就会注入,在spring容器中获取是通过byname再bytype
* 如果spring容器中没有获取到,就会使用dubbo本身提供的一些机制去注入
* 但是是否有顺序,这个好像是没有顺序的
* @param instance
* @return
*/
private T injectExtension(T instance) {
//objectFactory是在创建扩展类加载器的时候初始化的
if (objectFactory == null) {
return instance;
}
try {
//这里就是获取BlackPerson(比如示例的类)中的所有的方法,进行循环判断
for (Method method : instance.getClass().getMethods()) {
//首先判断是否是一个set方法,dubbo的DI只支持set方法注入
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
* 判断这个set方法是否不需要注入,如果加入了@DisableInject注解,表示不需要dubbo帮你注入
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//获取set方法的第一个参数,比如setCar(Car car),这个获取的就是Car这个类型
Class<?> pt = method.getParameterTypes()[0];
//过滤掉set参数中一些私有的类和比如Date String 等等这些,这些不需要进行注入
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//根据set方法名称获取注入的属性,比如setCar,那么property获取的就是car这个名字
String property = getSetterProperty(method);
//pt=参数的类型(com.xxx.Car),property就是注入的参数的名字(car)
//这个的objectFactory类型是AdaptiveExtensionFactory
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//创建出Car这个对象过后,调用set方法进行注入赋值
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory#getExtension
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//这里也是通过dubbo的spi机制获取到所有实现了ExtensionFactory的实现类,请注意,这里获取到的ExtensionFactory不包含加了@Adaptive注解的实现类
//在读取spi配置文件那里的源码已经分析了,所以我的这个版本的ExtensionFactory可用的实现类有:
/**
* AdaptiveExtensionFactory
* SpiExtensionFactory
* SpringExtensionFactory
* 所以loader.getSupportedExtensions()得到的就只有SpiExtensionFactory和SpringExtensionFactory
*/
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
//所以factories中就两个实例对象SpiExtensionFactory SpringExtensionFactory
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
//循环获取对象,只有有一个获取到对象不为空,那么直接返回,不再获取其他的,这里的type就是依赖注入的属性的类型,比如Car
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
针对于上面的factory.getextension方法会执行两个工厂类,一个是spring的一个是dubbo自己实现的,spring的就从spring容器去找这个bean,如果找到直接返回,否则就让dubbo来帮我们找到这个注入对象,dubbo是如何找的,在最开始的时候已经演示了,简单来说明下就是:比如Car这个类spi接口,如果我们在Car的某一个实现类上加了@Adaptive 注解,那么dubbo的工厂类SpiExtensionFactory就会为我们找到这个实现类直接然后反射得到对象直接注入,如果没有加,就需要通过URL传入,这个URL参数必须在Car这个接口中的方法中指定,并且传入,说白了第二种方式就是要求开发者介入,自己手动传入参数来确定要注入的是哪个Car的实现类。
1.spring的工厂类:org.apache.dubbo.config.spring.extension.SpringExtensionFactory#getExtension
/**
* 如果这里的CONTETS不为空,那么就是dubbo和spring进行了整合,就是通过spring容器中获取bean,如果获取到就返回对象
* @param type object type.
* @param name object name.
* @param <T>
* @return
*/
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
//CONTEXTS是在与spring整合的时候添加进来的
for (ApplicationContext context : CONTEXTS) {
T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
if (bean != null) {
return bean;
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
return null;
}
2.dubbo自己实现的工厂类:org.apache.dubbo.common.extension.factory.SpiExtensionFactory#getExtension
/**
* 这里是从dubbo的spi中获取指定的属性对象
* 就是比如BlackPerson中有个属性Car car,通过set方法注入的时候,这里传过来的
* type就是Car的类型,name就是car,然后这里去获取,在创建这个car对象的过程中,如果发现
* 这个Car接口有实现类配置在spi中并且加了@Adaptive注解的,那么会直接使用这个加了注解的实现类作为car属性的注入对象
* 如果没有@Adaptive的话,那么就会为car这个属性生成一个代理对象Adaptve$Car好像是这样的一个代理对象,但是这个时候这个Car对象中的接口
* 方法,比如要调用的接口方法就只能加入一个参数URL参数才行,否则是生成代理对象的接口方法就会抛出异常,而且这个接口方法上面还必须要加
* @Adaptive注解,否则当调用这个方法的时候会直接抛出异常
* @param type object type.
* @param name object name.
* @param <T>
* @return
*/
@Override
public <T> T getExtension(Class<T> type, String name) {
//首先这个car接口必须是加了@SPI注解的
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
//获取car这个接口的一个扩展类加载器
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
//得到一个代理对象或者是一个加了@Adaptive注解的实现类对象
return loader.getAdaptiveExtension();
}
}
return null;
}
/**
* 这里就是去获取一个@Adaptive的实现类对象或者创建一个Adaptive代理对象
* @return
*/
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
//DCL控制
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//创建或者获取一个对象
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
createAdaptiveExtension
private T createAdaptiveExtension() {
try {
//创建出的这个对象如果还有属性要注入,也一样的调用DI;生成了代理类获取或者Adaptive实现类过后直接反射创建一个对象
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
//加载读取spi,如果已经加载了的话是不用加载的,这里是一个双重保险的效果
getExtensionClasses();
//cachedAdaptiveClass这个对象就是在读取spi文件时,如果这个发现了扩展类(实现类)中是一个加了@Adaptive注解的实现类,
// 那么会赋值给cachedAdaptiveClass,需要注意的是一种类型的,比如Car这个接口类型,只能有一个@Adaptive注解的实现类
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//如果没有加这个注解,下面就创建一个代理对象Adaptive$Car
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
如果你在Car的某个实现类上加了@Adaptive注解,那么久直接返回了,因为在解析spi配置文件的时候已经读取到了并且放入了缓存对象cachedAdaptiveClass中,是在loadClass读取spi文件的时候获取的
所以如果spi配置文件中某个实现类上加了@Adaptive注解,那么在loadClass中的cacheAdaptiveClass中就会将这个实现类缓存到cachedAdaptiveClass中,在这里就直接返回了,就不会去执行createAdaptiveExtensionClass方法,createAdaptiveExtensionClass是创建一个JDK的代理对象,代理对象通过JDK创建的代码文件,然后进行编译返回。
createAdaptiveExtensionClass
private Class<?> createAdaptiveExtensionClass() {
//通过JDK的字节码生成去创建一个代码文件
/**
* 这里需要注意的是生成代理类的代码文件中要满足的条件的是要代理的对象比如Car,那么生成的代理对象是
* public class Adaptive$Car implements Car
* 所以这个代理类也是要实现了接口Car中的所有方法,而这些方法都必须要满足的两个条件:
* 1.方法上面必须加@Adaptive注解;
* 2.方法的参数中必须要有URL参数;
* 如果没有加@Adaptive,那么这个方法也会生成,但是调用的时候会直接跑不支持之类的一些异常;
* 如果方法中没有加URL参数,那么启动就直接报错,必须要有Url,因为你想,生成的代理类,当代理类真正调用Car的实现类的时候总要知道
* 调用Car的那个实现类,所以就必须使用URL的addParameter来添加参数,然后传入到代理中
*/
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
/**
* 编码创建的代码文件,将其编译成一个java类对象,下面的第一行代码需要注意的是
* 也是通过spi的机制去加载的,而且最后也通过getAdaptiveExtension()去获取了一个spi中的扩展类,这个扩展类
* 也是默认获取的加了@Adaptive的,和之前的对象工厂objectFactory的处理方式一致,所以要找的spi文件就是:
* org.apache.dubbo.common.compiler.Compiler这个文件,这个文件的内容如下:
* adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
* jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
* javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler
* 只有AdaptiveCompiler是加了@Adaptive注解的,所以这里的compile编译就是调用的AdaptiveCompiler中的compile方法进行编译的
* 编译成功生成一个class,这个class就是Adaptive$Car(比如我的接口是Car),就是创建了一个代理对象的class
* 或者说你传入的对象不一定是URL这个对象,但是参数必须传入,而传入的参数中必须能够有方法getUrl这个方法才行
*/
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate
生成Adaptive代理类的明细
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
//生成包package信息
code.append(generatePackageInfo());
//生成import导包的信息
code.append(generateImports());
//生成class信息,比如public class 。。。。
code.append(generateClassDeclaration());
//生成方法信息
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
private String generateMethod(Method method) {
//方法的返回类型
String methodReturnType = method.getReturnType().getCanonicalName();
//方法名字
String methodName = method.getName();
//方法主体,方法内容,方法内容就做了判断,方法必须加@Adaptive注解,而且参数中必须是URL或者是能够getURl获取URL对象,
//如果没有加@Adaptive注解,最多后面调用方法的时候要报错,但是如果没有URL相关参数,启动就直接报错了
String methodContent = generateMethodContent(method);
//方法参数
String methodArgs = generateMethodArguments(method);
//方法的异常信息
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
//没有Adaptive注解就直接生成throw new UnsupportedOperationException异常,调用方法的时候报错
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
//获取URL参数,判断是否存在
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
if (urlTypeIndex != -1) {
// Null Point check
//生成URL主体代码
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
//如果方法参数中没有URL,就看参数中是否有getURL的方法
code.append(generateUrlAssignmentIndirectly(method));
}
......
一直在说这个代理类,如果 dubbo为我们生成的代理类是个什么样的呢?我们可以通过dubug来看下
code的内容如下:
package org.apache.dubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements org.apache.dubbo.Car {
public java.lang.String getCar(org.apache.dubbo.common.URL arg0) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("car");
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.Car) name from url (" + url.toString() + ") use keys([car])");
org.apache.dubbo.Car extension = (org.apache.dubbo.Car)ExtensionLoader.getExtensionLoader(org.apache.dubbo.Car.class).getExtension(extName);
return extension.getCar(arg0);
}
}
这就是dubbo为我们生成的一个代理类,这个代理类也是Car的一个子类,当你调用Car的方法时,必须传入URL,而且这个URL中还必须包含了car这个属性所对应的实现类的spi配置key,当你调用Car实现类的时候,会调用这个代理类中相对应的方法比如getCar,那么这个代理类会通过你传入的URL中获取car这个属性所对应spi配置key,也就是extName ,然后再通过ExtensionLoader获取
所对应的扩展实现类,最后在真正调用实现类的的getCar方法,这就是dubbo的依赖注入的一个自己实现的一种机制,如果不手动通过@Adaptive指定实现类,那么还是比较麻烦的,还要自己指定URL参数,然后生成了代理类,调用的时候是从代理类开始,dubbo中的协议选择就是通过这种方式实现的,我们可以看下dubbo中协议SPI接口Protocol

从dubbo中的协议spi接口Protocol是不是就可以看出dubbo默认使用就是dubbo协议了。
但是如果说我们在SPI接口中没有指定@Adaptive方法的时候,生成的代理类是什么样的呢?
@SPI
public interface Car {
@Adaptive
String getCar(URL url);
void test();
}
test很明显没有加@Adaptive注解,那么生成的代理类是这样的:
package org.apache.dubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements org.apache.dubbo.Car {
public void test() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.Car.test() of interface org.apache.dubbo.Car is not adaptive method!");
}
public java.lang.String getCar(org.apache.dubbo.common.URL arg0) {
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("car");
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.Car) name from url (" + url.toString() + ") use keys([car])");
org.apache.dubbo.Car extension = (org.apache.dubbo.Car)ExtensionLoader.getExtensionLoader(org.apache.dubbo.Car.class).getExtension(extName);
return extension.getCar(arg0);
}
}
如果这个时候你调用Car的实现类的test方法会直接抛出一个UnsupportedOperationException异常。
还有就是如果加了@Adaptive,但是没有加URL参数呢?会抛出一个异常,异常信息:
Can’t create adaptive extension interface org.apache.dubbo.Car, cause: Failed to create adaptive class for interface org.apache.dubbo.Car: not found url parameter or url attribute in parameters of method test
简单来说就是test方法没有加URL参数或者通过参数调用getURL能够获取到一个URL对象。
以上就是dubbo的SPI机制的原理和源码的分析流程,其实都不是很复杂,但是需要理解的是它的思想
最后我简单理了一下它的源码执行流程图,匆匆忙忙的画的,大概流程是明白的,细节的可以根据流程图去理清楚代码逻辑,当然如果不太像深入了解,只需要知道它的SPI机制是如何做的,IOC和AOP是如何做到就可以了,我这里是分析的基本是每一行代码,比较细