mirror of
https://github.com/Estom/notes.git
synced 2026-02-12 06:46:22 +08:00
学习了spi机制
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
## sofabolt基础通信模型
|
||||
|
||||
> 四个线程的模型:(非常重要。非常简单。非常关键)
|
||||
> 1. 有的时候,不必非得给一次通信扣上同步或者一部的的帽子。
|
||||
> 2. 客户端的同步异步称为阻塞和非阻塞,服务器端的同步异步称为同步异步。
|
||||
> 3. 只要链路上有一处是一部的,我们称整个调用链路就是异步的。
|
||||
> 4. 将客户端分为客户端线程和客户端连接线程,将服务端分为服务端线程和服务端连接线程。
|
||||
> 1. 如果客户端线程等待连接线程返回结果,则称为阻塞的。如果不等待连接线程的结果,称为非阻塞的。
|
||||
> 2. 如果服务端连接线程等待服务端线程的结果,则称为同步的。如果不等待服务端处理线程的结果,称为异步的。
|
||||
### 阻塞与同步的再讨论
|
||||
四个线程的模型:(非常重要。非常简单。非常关键)
|
||||
1. 有的时候,不必非得给一次通信扣上同步或者一部的的帽子。
|
||||
2. 客户端的同步异步称为阻塞和非阻塞,服务器端的同步异步称为同步异步。
|
||||
3. 只要链路上有一处是一部的,我们称整个调用链路就是异步的。
|
||||
4. 将客户端分为客户端线程和客户端连接线程,将服务端分为服务端线程和服务端连接线程。
|
||||
1. 如果客户端线程等待连接线程返回结果,则称为阻塞的。如果不等待连接线程的结果,称为非阻塞的。
|
||||
2. 如果服务端连接线程等待服务端线程的结果,则称为同步的。如果不等待服务端处理线程的结果,称为异步的。
|
||||
|
||||
|
||||
### 四种客户端模型
|
||||
|
||||
@@ -722,7 +724,6 @@ public class RpcServerDemoByMain {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 2 进阶功能
|
||||
|
||||
### 请求上下文
|
||||
@@ -827,6 +828,16 @@ public class RpcServerDemoByMain {
|
||||
|
||||
### 建立多连接与连接预热
|
||||
|
||||
通常来说,点对点的直连通信,客户端和服务端,一个 IP 一个连接对象就够用了。不管是吞吐能力还是并发度,都能满足一般业务的通信需求。而有一些场景,比如不是点对点直连通信,而是经过了 LVS VIP,或者 F5 设备的连接,此时,为了负载均衡和容错,会针对一个 URL 地址建立多个连接。我们提供如下方式来建立多连接,即发起调用时传入的 URL 增加如下参数 127.0.0.1:12200?_CONNECTIONNUM=30&_CONNECTIONWARMUP=true,表示针对这个 IP 地址,需要建立30个连接,同时需要预热连接。其中预热与不预热的区别是:
|
||||
|
||||
预热:即第一次调用(比如 Sync 同步调用),就建立30个连接
|
||||
不预热:每一次调用,创建一个连接,直到创建满30个连接
|
||||
|
||||
|
||||
* 客户端能够创建多个连接connection,放到连接池,
|
||||
* 客户端和服务端都能注册多个Processor。
|
||||
|
||||
Connection与Processor是多对多的关系。
|
||||
|
||||
### 自动断连与重连
|
||||
|
||||
@@ -109,6 +109,70 @@ public class TestSuperSub{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**继承的实用价值**
|
||||
|
||||
|
||||
|
||||
关于父类和子类的作用的说明。记得在写毕设的时候,自己把父类当做工具类,子类在调用很多方法的时候,为了少写代码,就直接调用父类中的方法,然后导致父类中的流程函数,会调用子类中的方法,子类中的函数又会调用父类中的方法,非常凌乱,两个类相互冗余。当时也在思考,这些工具函数写在父类中供所有的子类调用与写一个util类有什么区别?
|
||||
|
||||
现在发现,应该遵循一些默认的编码规则,父类用来构建整体的流程,而子类用来完善丰富一些子流程。相当于父类在构建主流程的时候,空出一些细节实现的部分,让子类来完善。而不是写一写类似的工具函数,让子类来调用,子类能够看到更加全面丰富的流程,那么父类就没有存在的必要了,父类的作用可能就是一个接口了,只提供一些对外的方法声明。
|
||||
|
||||
|
||||
综上所属:
|
||||
* 接口:只提供对外的方法声明
|
||||
* 父类:提供完整的流程,由父类调用未经实现的抽象方法完成整体流程的构建。
|
||||
* 子类:提供丰富的细节实现,由子类实现抽象方法的细节。
|
||||
* 工具类:提供给所有子类的通用的处理函数。
|
||||
|
||||
|
||||
以上感受来自于springboot和springmvc中的代码,是如何一步一步封装原生的servelet代码,然后逐渐构造细节,添加更多的逻辑。
|
||||
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
|
||||
object HttpServlet{
|
||||
doGet
|
||||
doPost
|
||||
doPut
|
||||
doDelete
|
||||
}
|
||||
note bottom : 这是所有http请求的入口。\
|
||||
\n 可以通过子类,不断丰富和具体要执行的内容。
|
||||
|
||||
|
||||
object HttpServletBean{
|
||||
|
||||
}
|
||||
|
||||
object FrameworkServlet{
|
||||
doGet--> processRequest
|
||||
doPost --> processRequest
|
||||
doPut --> processRequest
|
||||
doDelete --> processRequest
|
||||
processRequest -->doService
|
||||
}
|
||||
|
||||
object DispatcherServlet{
|
||||
doService -->doDispatcher
|
||||
doDispatcher
|
||||
}
|
||||
|
||||
|
||||
FrameworkServlet -|> HttpServlet : 重写do方法,全部调用\nprocessRequest->doService
|
||||
|
||||
FrameworkServlet -> DispatcherServlet : 重写doService方法\n调用doDispatch进行处理
|
||||
|
||||
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 3 多态
|
||||
|
||||
**多态的定义**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
## lambda表达式概述
|
||||
|
||||
> AKA 函数式编程。只有一个方法的类。方法即可代表类。
|
||||
|
||||
### 简介
|
||||
|
||||
lambda运行将函数作为一个方法的参数,也就是函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁。
|
||||
|
||||
242
Java基础教程/Java语言基础/16 javaSPI机制.md
Normal file
242
Java基础教程/Java语言基础/16 javaSPI机制.md
Normal file
@@ -0,0 +1,242 @@
|
||||
|
||||
> 参考文献https://www.zhihu.com/question/486985113/answer/2627178730
|
||||
## 1 概述
|
||||
### JavaSPI机制概述
|
||||
Java SPI机制:SPI全称为Service Provider Interface,服务提供接口,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
|
||||
|
||||
SPI就是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展行性的机制。引入服务提供者就是即SPI接口的实现者,通过本地注册来发现获取到具体的实现类。实现轻松可插拔。
|
||||
|
||||

|
||||
|
||||
> Java SPI本质上其实就是“基于接口编程+策略模式+配置文件”组合实现的**动态加载机制**。
|
||||
|
||||
> 为了实现在模块装配的时候不用在程序里动态指明,这就需要一种本地**服务发现机制**。Java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。
|
||||
|
||||
> JavaSpi起本身也是一种**控制反转思想**。通过额外的程序注入类的实现。包括控制反转和依赖注入两个过程。“Service Provider”和相应的工具”ServiceLoader”。其声明文件相当于Spring的Bean配置文件,实现控制反转,ServiceLoader类实现了依赖的注入。
|
||||
|
||||
|
||||
### 底层原理
|
||||
|
||||
|
||||
相关标准中对”Service Provider”的定义可以总结为三句话:
|
||||
|
||||
1. ”Service”(服务)。是一组知名的接口或(通常是抽象)类的集合,“Service Provider“(服务提供者)就是对服务的特定实现;
|
||||
2. 服务提供者。通过jar包中的“META-INF/services/fully-qualified.name.of.service.Interface“文件包含实现类的完全限定名,将实现类发布为提供者;
|
||||
3. 服务查找机制。通过遍历ClassPath中所有的上述文件内容,来查找并创建提供者的实例
|
||||
|
||||
|
||||
### 应用举例
|
||||
* 脚本引擎ScriptEngine,
|
||||
* 字符集Charset,
|
||||
* 文件系统FileSystems,
|
||||
* 网络通讯NIO
|
||||
* Web标准Servlet3.0,
|
||||
* 通用日志接口slf4j-api:1.3
|
||||
* jdbc(Java 规定了JDBC的接口,可由不同厂商来实现MySQL,Oracle等)
|
||||
|
||||
|
||||
## 2 使用教程
|
||||
|
||||
### 服务发现机制
|
||||
|
||||
SPI的使用当服务的提供者,提供了接口的一种实现后,需要在Jar包的**META-INF/services/**目录下,创建一个以接口的名称(包名.接口名的形式)命名的文件,在文件中配置接口的实现类(完整的包名+类名)。
|
||||
|
||||
当外部程序通过「java.util.ServiceLoader」类装载这个接口时,就能够通过该Jar包的**META/Services/**目录里的配置文件找到具体的实现类名,装载实例化,完成注入。
|
||||
|
||||
同时,SPI的规范规定了接口的实现类必须有一个无参构造方法。SPI中查找接口的实现类是通过「java.util.ServiceLoader」,而在「java.util.ServiceLoader」类中有一行代码如下:
|
||||
```
|
||||
// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下
|
||||
private static final String PREFIX = "META-INF/services/";
|
||||
```
|
||||
|
||||
### 创建Maven工程
|
||||
1. 在IDEA中创建Maven项目spi-demo,如下:
|
||||
|
||||
|
||||

|
||||
|
||||
```xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<artifactId>spi-demo</artifactId>
|
||||
<groupId>io.binghe.spi</groupId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
```
|
||||
2. 创建类加载工具。创建MyServiceLoader,MyServiceLoader类中直接调用JDK的ServiceLoader类加载Class。代码如下所示
|
||||
|
||||
```java
|
||||
/**
|
||||
* Alipay.com Inc.
|
||||
* Copyright (c) 2004-2022 All Rights Reserved.
|
||||
*/
|
||||
package com.alipay.ykl.spidemo;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* @author faran
|
||||
* @version : SpiLoader, v 0.1 2022-11-25 14:30 faran Exp $
|
||||
*/
|
||||
public class SpiLoader {
|
||||
|
||||
/**
|
||||
* 使用SPI机制加载所有的Class
|
||||
*/
|
||||
public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) {
|
||||
return ServiceLoader.load(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
3. 创建接口服务名。创建接口MyService,作为测试接口,接口中只有一个方法,打印传入的字符串信息。
|
||||
```java
|
||||
/**
|
||||
* Alipay.com Inc.
|
||||
* Copyright (c) 2004-2022 All Rights Reserved.
|
||||
*/
|
||||
package com.alipay.ykl.spidemo;
|
||||
|
||||
/**
|
||||
* @author faran
|
||||
* @version : SpiService, v 0.1 2022-11-25 14:32 faran Exp $
|
||||
*/
|
||||
public interface SpiService {
|
||||
|
||||
/**
|
||||
* 打印信息
|
||||
*/
|
||||
void print(String info);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
4. 创建多个实现类。
|
||||
|
||||
```java
|
||||
package com.alipay.ykl.spidemo;
|
||||
|
||||
/**
|
||||
* @author faran
|
||||
* @version : SpiServiceImpl1, v 0.1 2022-11-25 14:32 faran Exp $
|
||||
*/
|
||||
public class SpiServiceImpl1 implements SpiService{
|
||||
@Override
|
||||
public void print(String info) {
|
||||
System.out.println(SpiServiceImpl1.class.getName() + " print " + info);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
package com.alipay.ykl.spidemo;
|
||||
|
||||
/**
|
||||
* @author faran
|
||||
* @version : SpiServiceImpl2, v 0.1 2022-11-25 14:33 faran Exp $
|
||||
*/
|
||||
public class SpiServiceImpl2 implements SpiService{
|
||||
@Override
|
||||
public void print(String info) {
|
||||
System.out.println(SpiServiceImpl2.class.getName() + " print " + info);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. 创建接口文件。在项目的src/main/resources目录下创建**META-INF/services/**目录,文件必须是接口MyService的全名,之后将实现MyService接口的类配置到文件中
|
||||
|
||||
|
||||
```
|
||||
com.alipay.ykl.spidemo.SpiServiceImpl1
|
||||
com.alipay.ykl.spidemo.SpiServiceImpl2
|
||||
```
|
||||
|
||||
6. 创建测试类
|
||||
|
||||
```java
|
||||
package com.alipay.ykl.spidemo;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* @author faran
|
||||
* @version : SpiTest, v 0.1 2022-11-25 14:04 faran Exp $
|
||||
*/
|
||||
public class SpiTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpi(){
|
||||
ServiceLoader<SpiService> loader = SpiLoader.loadAll(SpiService.class);
|
||||
for (SpiService spiService : loader) {
|
||||
spiService.print("hello world");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
7. 查看结果
|
||||
|
||||
```java
|
||||
com.alipay.ykl.spidemo.SpiServiceImpl1 print hello world
|
||||
com.alipay.ykl.spidemo.SpiServiceImpl2 print hello world
|
||||
|
||||
Process finished with exit code 0
|
||||
```
|
||||
|
||||
|
||||
## 3 源码解读
|
||||
|
||||
### java.util.SeerviceLoader
|
||||
|
||||
* 可以遍历。「java.util.ServiceLoader」的源码,可以看到ServiceLoader类实现了「java.lang.Iterable」接口,说明ServiceLoader类是可以遍历迭代的。
|
||||
* 内部变量
|
||||
```java
|
||||
|
||||
private static final String PREFIX = "META-INF/services/";
|
||||
|
||||
// The class or interface representing the service being loaded
|
||||
private final Class<S> service;
|
||||
|
||||
// The class loader used to locate, load, and instantiate providers
|
||||
private final ClassLoader loader;
|
||||
|
||||
// The access control context taken when the ServiceLoader is created
|
||||
private final AccessControlContext acc;
|
||||
|
||||
// Cached providers, in instantiation order
|
||||
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
|
||||
|
||||
// The current lazy-lookup iterator
|
||||
private LazyIterator lookupIterator;
|
||||
```
|
||||
BIN
Java基础教程/Java语言基础/image/2022-11-25-14-12-57.png
Normal file
BIN
Java基础教程/Java语言基础/image/2022-11-25-14-12-57.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
Java基础教程/Java语言基础/image/2022-11-25-14-29-35.png
Normal file
BIN
Java基础教程/Java语言基础/image/2022-11-25-14-29-35.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -15,10 +15,11 @@ Spring5添加的新模块。用于web开发的,功能与SpringMVC类似。Webf
|
||||
|
||||
### 异步非阻塞
|
||||
|
||||
* **异步和同步是针对调用者**,调用者发送请求,如果等着对方回应之后采取做其他事情就是同步,如果发送后不等着对方回应就去做其他事情就是异步。
|
||||
我更喜欢反过来理解这里。同步异步是针对被调用者的,阻塞和非阻塞是针对调用者的。
|
||||
|
||||
* **阻塞和非阻塞针对被调用者**,被调用者收到一个请求之后,做完嘞请求任务之后,次啊给出返回,就阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。
|
||||
* **阻塞和非阻塞针对调用者**。阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。。
|
||||
|
||||
* **异步和同步是针对被调用者**,同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
|
||||
|
||||
### 与SpringMVC对比
|
||||
|
||||
|
||||
Reference in New Issue
Block a user