• 第二篇:Sentinel之SPI机制使用与源码分析


    之所以要先讲spi机制是因为sentinel后面的一些扩展都需要基于这个机制,所以在这里先大概说下spi的使用与其具体的实现

    SPI

    SPI 全称Service Provider Interface,

    原生Java SPI

    JAVA中的SPI机制_清朝程序猿的博客-CSDN博客_java spi

    其他细节这里就不说了

    Sentinel SPI

    Sentinel SPI是对原生的spi进行的一种扩展,提供了缓存功能和排序功能默认实现等,不过相对于dubbo spi还是要差一点,这点以后有机会写dubbo的时候再说一下,不过对于sentinel来说它自己实现的spi也够用了,下面就来稍微看下使用方式与源码的基本解析

    使用

    定义接口

    1. public interface Dog {
    2. void eat();
    3. }
    4. 以下是实现类
    5. /**
    6. * 设置为默认值
    7. */
    8. @Spi(isDefault = true)
    9. public class RedDog implements Dog {
    10. @Override
    11. public void eat() {
    12. System.out.println("红狗吃饭");
    13. }
    14. }
    15. /**
    16. * 设置别名
    17. */
    18. @Spi(value = "white", order = -1)
    19. public class WhiteDog implements Dog {
    20. @Override
    21. public void eat() {
    22. System.out.println("白狗吃饭");
    23. }
    24. }

    配置文件

    sentinel spi机制的配置文件地址是固定,前缀为 com.alibaba.csp.sentinel.spi.SpiLoader#SPI_FILE_PREFIX定义的值,也就是

    META-INF/services/,而文件名为接口的全类名,不包含后缀,文件内容以换行分割,为实现类的全类名,如下

    1. 文件名为
    2. com.zxc.study.sentinel.spi.Dog
    3. 文件内容为
    4. com.zxc.study.sentinel.spi.RedDog
    5. com.zxc.study.sentinel.spi.WhiteDog

    spi使用

    利用Sentinel提供的SpiLoader进行操作即可,以下为一些测试

    1. public class SPITest {
    2. public static void main(String[] args) {
    3. //加载SpiLoader对象
    4. SpiLoader spiLoader = SpiLoader.of(Dog.class);
    5. //获取所有的实现类
    6. List dogList = spiLoader.loadInstanceList();
    7. for (Dog dog : dogList) {
    8. dog.eat();
    9. }
    10. //获取默认狗对象
    11. Dog dog = spiLoader.loadDefaultInstance();
    12. dog.eat();
    13. //通过别名加载狗对象
    14. Dog white = spiLoader.loadInstance("white");
    15. white.eat();
    16. //加载第一个实例进行调用,配置文件哪个先放进去就拿拿个
    17. spiLoader.loadFirstInstance().eat();
    18. //可以通过 @spi注解的order进行设置,默认都是0,值越少排的越前面,最优化和最底级别的
    19. spiLoader.loadHighestPriorityInstance().eat();
    20. spiLoader.loadLowestPriorityInstance().eat();
    21. //获取排序后的列表
    22. spiLoader.loadInstanceListSorted();
    23. //其他几个api也都比较简单,这个功能比原生的java spi机制已经要强大很多了,帮我们做了很多事
    24. }
    25. }

    最后再看下具体的结构图,其实也是比较简单的

    总结:sentinel的spi机制使用还是比较简单的,扩展性也比较强,到时候看到源码你就会看到spi机制的作用了,这种spi机制在dubbo也是用到了,而且是dubbo的核心扩展机制,后面会再说到这个问题,接下来就是要看下实现了,也不是很复杂,目前的话都是以代码来描述的,以后再尝试去画图,理解起来可能更好点 

    源码分析 

    spiLoader构建

    1. 创建入口: com.alibaba.csp.sentinel.spi.SpiLoader#of
    2. public static SpiLoader of(Class service) {
    3. //基本校验,一看就懂!
    4. AssertUtil.notNull(service, "SPI class cannot be null");
    5. AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()),
    6. "SPI class[" + service.getName() + "] must be interface or abstract class");
    7. //获取全类名
    8. String className = service.getName();
    9. //从获取中获取SpiLoader对象,就是一个全局Map维护的
    10. SpiLoader spiLoader = SPI_LOADER_MAP.get(className);
    11. //没有的话要进行创建,使用双重检索机制处理并发问题,这也是阿里惯用手法!
    12. if (spiLoader == null) {
    13. synchronized (SpiLoader.class) {
    14. spiLoader = SPI_LOADER_MAP.get(className);
    15. if (spiLoader == null) {
    16. //创建并方到缓存中,直接new对象然后放到缓存中
    17. SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service));
    18. spiLoader = SPI_LOADER_MAP.get(className);
    19. }
    20. }
    21. }
    22. //如果缓存有直接返回,没有则创建
    23. return spiLoader;
    24. }
    25. 这段逻辑还是比较简单的,除了双重检锁机制可能稍微复杂点,其他的没有啥,加锁是为了并发问题处理

    spiLoader的具体操作

    spiLoader对象获取实例的操作,也不是很复杂,除了获取对象的那部分代码,其他的逻辑还是比较简单的

    1. 首先先看下load方法,这也是核心加载方法,这个看完了,其他的是比较简单的
    2. //使用juc包下原子类控制只加载一次
    3. if (!loaded.compareAndSet(false, true)) {
    4. return;
    5. }
    6. //拼接文件全类名, META-INF/services/com.zxc.study.sentinel.spi.Dog
    7. String fullFileName = SPI_FILE_PREFIX + service.getName();
    8. //获取类加载器逻辑...
    9. ClassLoader classLoader;
    10. if (SentinelConfig.shouldUseContextClassloader()) {
    11. classLoader = Thread.currentThread().getContextClassLoader();
    12. } else {
    13. classLoader = service.getClassLoader();
    14. }
    15. if (classLoader == null) {
    16. classLoader = ClassLoader.getSystemClassLoader();
    17. }
    18. //加载资源
    19. Enumeration urls = null;
    20. try {
    21. urls = classLoader.getResources(fullFileName);
    22. } catch (IOException e) {
    23. fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e);
    24. }
    25. if (urls == null || !urls.hasMoreElements()) {
    26. RecordLog.warn("No SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader);
    27. return;
    28. }
    29. //循环处理文件,可能有多个,这里也是体现扩展的地方
    30. //你可能引用的是sentinel的jar包,他里面会有这个文件,然后你也可以在自己项目对应的文件下配置该文件
    31. while (urls.hasMoreElements()) {
    32. URL url = urls.nextElement();
    33. InputStream in = null;
    34. BufferedReader br = null;
    35. try {
    36. in = url.openStream();
    37. br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
    38. String line;
    39. while ((line = br.readLine()) != null) {
    40. if (StringUtil.isBlank(line)) {
    41. // Skip blank line
    42. continue;
    43. }
    44. line = line.trim();
    45. int commentIndex = line.indexOf("#");
    46. if (commentIndex == 0) {
    47. // Skip comment line
    48. continue;
    49. }
    50. if (commentIndex > 0) {
    51. line = line.substring(0, commentIndex);
    52. }
    53. line = line.trim();
    54. Class clazz = null;
    55. try {
    56. clazz = (Class) Class.forName(line, false, classLoader);
    57. } catch (ClassNotFoundException e) {
    58. fail("class " + line + " not found", e);
    59. }
    60. if (!service.isAssignableFrom(clazz)) {
    61. fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file=" + fullFileName);
    62. }
    63. //上面是解析一行行的数据,然后反射加载类...
    64. classList.add(clazz);
    65. //获取spi注解,这个是标记在实现类上的
    66. Spi spi = clazz.getAnnotation(Spi.class);
    67. //获取别名
    68. String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value();
    69. if (classMap.containsKey(aliasName)) {
    70. Classextends S> existClass = classMap.get(aliasName);
    71. fail("Found repeat alias name for " + clazz.getName() + " and "
    72. + existClass.getName() + ",SPI configuration file=" + fullFileName);
    73. }
    74. classMap.put(aliasName, clazz);
    75. //是否有别名,有的话则进行设置
    76. if (spi != null && spi.isDefault()) {
    77. //别名只能有一个,多个时则报错
    78. if (defaultClass != null) {
    79. fail("Found more than one default Provider, SPI configuration file=" + fullFileName);
    80. }
    81. defaultClass = clazz;
    82. }
    83. RecordLog.info("[SpiLoader] Found SPI implementation for SPI {}, provider={}, aliasName={}"
    84. + ", isSingleton={}, isDefault={}, order={}",
    85. service.getName(), line, aliasName
    86. , spi == null ? true : spi.isSingleton()
    87. , spi == null ? false : spi.isDefault()
    88. , spi == null ? 0 : spi.order());
    89. }
    90. } catch (IOException e) {
    91. fail("error reading SPI configuration file", e);
    92. } finally {
    93. closeResources(in, br);
    94. }
    95. }
    96. sortedClassList.addAll(classList);
    97. //进行排序,默认顺序为0,值越小排的越前
    98. Collections.sort(sortedClassList, new Comparatorextends S>>() {
    99. @Override
    100. public int compare(Class o1, Class o2) {
    101. Spi spi1 = o1.getAnnotation(Spi.class);
    102. int order1 = spi1 == null ? 0 : spi1.order();
    103. Spi spi2 = o2.getAnnotation(Spi.class);
    104. int order2 = spi2 == null ? 0 : spi2.order();
    105. return Integer.compare(order1, order2);
    106. }
    107. });
    108. }
    109. 再看看其他的方法
    110. 1. 加载全部实例的方法 com.alibaba.csp.sentinel.spi.SpiLoader#loadInstanceList
    111. public List loadInstanceList() {
    112. //加载对象,如果加载过则不会再加载了
    113. load();
    114. //根据文件配置顺序实现化对象出来
    115. return createInstanceList(classList);
    116. }
    117. 再到里面去看
    118. private List createInstanceList(List> clazzList) {
    119. //为空则返回
    120. if (clazzList == null || clazzList.size() == 0) {
    121. return Collections.emptyList();
    122. }
    123. List instances = new ArrayList<>(clazzList.size());
    124. //循环创建实例
    125. for (Classextends S> clazz : clazzList) {
    126. //创建实例
    127. S instance = createInstance(clazz);
    128. instances.add(instance);
    129. }
    130. return instances;
    131. }
    132. private S createInstance(Class clazz) {
    133. //获取注解
    134. Spi spi = clazz.getAnnotation(Spi.class);
    135. //标记是否为单例,默认是单例的
    136. boolean singleton = true;
    137. if (spi != null) {
    138. singleton = spi.isSingleton();
    139. }
    140. //创建实例
    141. return createInstance(clazz, singleton);
    142. }
    143. private S createInstance(Class clazz, boolean singleton) {
    144. S instance = null;
    145. try {
    146. //如果是单例的话仍然使用双重检锁机制解决并发问题
    147. if (singleton) {
    148. instance = singletonMap.get(clazz.getName());
    149. if (instance == null) {
    150. synchronized (this) {
    151. instance = singletonMap.get(clazz.getName());
    152. if (instance == null) {
    153. //实例化对象并放到缓存中
    154. instance = service.cast(clazz.newInstance());
    155. singletonMap.put(clazz.getName(), instance);
    156. }
    157. }
    158. }
    159. } else {
    160. //非单例直接实例化对象
    161. instance = service.cast(clazz.newInstance());
    162. }
    163. } catch (Throwable e) {
    164. fail(clazz.getName() + " could not be instantiated");
    165. }
    166. return instance;
    167. }
    168. 代码看着有点多,但其实很容易理解
    169. 2. 加载默认对象 : com.alibaba.csp.sentinel.spi.SpiLoader#loadDefaultInstance
    170. public S loadDefaultInstance() {
    171. //仍然是要加载,如果有就不再次加载了
    172. load();
    173. //没有默认class直接返回空
    174. if (defaultClass == null) {
    175. return null;
    176. }
    177. //有的化使用通用方法加载对象,又走到上面的逻辑了
    178. return createInstance(defaultClass);
    179. }
    180. 3. 根据别名获取对象: com.alibaba.csp.sentinel.spi.SpiLoader#loadInstance(aliasName)
    181. public S loadInstance(String aliasName) {
    182. AssertUtil.notEmpty(aliasName, "aliasName cannot be empty");
    183. load();
    184. //之前load的时候已经把别名和对象放在对应的map里面了
    185. Classextends S> clazz = classMap.get(aliasName);
    186. if (clazz == null) {
    187. fail("no Provider class's aliasName is " + aliasName);
    188. }
    189. //创建对象,,这里idea有个显示bug,其实上面fail()方法里面已经抛异常了,但是这里感知不到,所以会提示说clazz不能为null
    190. return createInstance(clazz);
    191. }
    192. 4. 获取第一个实例,根据你配置文件的进行拿取
    193. public S loadFirstInstance() {
    194. //加载
    195. load();
    196. if (classList.size() == 0) {
    197. return null;
    198. }
    199. //获取第一个并返回,你配置文件怎么配的就拿哪个
    200. Classextends S> serviceClass = classList.get(0);
    201. S instance = createInstance(serviceClass);
    202. return instance;
    203. }
    204. 5. 获取优先级最高的 com.alibaba.csp.sentinel.spi.SpiLoader#loadHighestPriorityInstance
    205. //加载
    206. load();
    207. if (sortedClassList.size() == 0) {
    208. return null;
    209. }
    210. //从排好序的获取最考前的,排序是@Spi注解的order属性定义的,越小越考前
    211. Classextends S> highestClass = sortedClassList.get(0);
    212. return createInstance(highestClass);
    213. }
    214. 6. 获取优先级最低的,跟上面反着来...
    215. public S loadLowestPriorityInstance() {
    216. load();
    217. if (sortedClassList.size() == 0) {
    218. return null;
    219. }
    220. Classextends S> lowestClass = sortedClassList.get(sortedClassList.size() - 1);
    221. return createInstance(lowestClass);
    222. }
    223. 7. 获取排序的实例列表
    224. public List loadInstanceListSorted() {
    225. //加载
    226. load();
    227. //把维护好排好序的放进去创建实例...
    228. return createInstanceList(sortedClassList);
    229. }
    230. 其他的就不说了,有兴趣可以自己看看

    主要逻辑都在load()方法里面,其他的方法实现其实很简单,就是在对一个List进行操作,拿第一个,最后一个,等等,估计你一看就懂了.....

    好了,这篇就说到这里,这个spi机制在后面会有使用,而且我们对sentinel的扩展有一部分就是基于这个机制进行扩展的..

  • 相关阅读:
    Jmeter测试关联接口
    北京某中厂凉经
    (数据科学学习手札162)Python GIS神器geopandas 1.0版本发布
    vue.mixin全局混合选项
    POI版本升级需要调整的代码整理(3.15升级到5.1.0版本)
    Microsoft Developer Studio generated include file-视频
    【一起进大厂】最新Java并发面试题整理
    SFP-10G-SR光模块指南
    LeetCode面向运气之Javascript—第20题-有效的括号-95.97%
    论文阅读 (71):Optimal Margin Distribution Machine for Multi-Instance Learning
  • 原文地址:https://blog.csdn.net/zxc_user/article/details/125915826