一、OkHttp_网络请求流程

news2024/11/27 7:29:05

前言:

一直以来总想对android常用的某个第三方库深入研究一下,每次看完源码之后总是经常的忘记。
为了方便对三方库快速阅读,特此以写文章方式记述。
就从OKHttp开始吧。

再阅读源码之前,要明确 OKHttp是用来做什么的?
网络请求框架。

为什么要是开发中要使用OKHttp?

  • 减少请求延迟
  • 支持缓存节省带宽
  • 使用简单、高效

本章介绍内容:

OkHttp进行网络同步异步请求主要流程

在阅读前熟悉起码要阅读一下官方文档:

官方文档:
OkHttp官方文档

中文翻译:
OkHttp官方文档中文翻译

依赖版本:

implementation("com.squareup.okhttp3:okhttp:4.9.3")

关键类介绍:

为什么还没开始阅读源码,就先介绍几个关键的类。

  • 初步的印象
  • 遇到时重点关注
关键类介绍
OkHttpClientOkHttp框架的入口类,负责创建OkHttp客户端并配置各种参数和拦截器。
Request和Response于封装网络请求和响应的数据,包括请求头、请求体、响应头、响应体等信息。
Interceptor和Interceptor.ChainOkHttp框架的拦截器,用于拦截、修改和重试网络请求和响应。
RealCall和RealInterceptorChainOkHttp框架内部使用的类,用于将网络请求和拦截器串联起来,构建一个完整的请求处理链路。
Dispatcher和ExecutorService用于管理网络请求的调度器和线程池,包括连接池、DNS解析、请求重试等功能。
ConnectionPool用于管理HTTP/HTTPS连接的连接池,包括连接复用、空闲连接的清理和限制等功能。
ExchangeCodecExchangeCodec类是一个编解码器接口,它的作用是将HTTP请求和响应编码和解码为字节流。
Call和CallbackOkHttp框架的核心类,用于发送网络请求并处理响应,包括同步请求和异步请求两种方式。

OkHttpClient

OkHttpClient 应该被所有Http复用,

创建单个OkHttpClient实例并将用于所有Http调用时,OkHttp性能最佳。

意思就是App全局应该只使用一个OkHttpClient。通过newBuilder()可以复用OkHttpClient的builder

// 方式一:new OkHttpClient() 创建一个默认设置的共享实例:
public final OkHttpClient client = new OkHttpClient();

// 方式二:new OkHttpClient.Builder()创建一个自定义设置的共享实例:
public final OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor())
        .cache(new Cache(cacheDir, cacheSize))
        .build();
// 用newBuilder()自定义一个共享的OkHttpClient实例。
// 这将构建一个共享相同连接池、线程池和配置的 OkHttpClient
OkHttpClient eagerClient = client.newBuilder()
    .readTimeout(500, TimeUnit.MILLISECONDS)
    .build();
Response response = eagerClient.newCall(request).execute();

被占用的线程和连接如果处于空闲状态,将被自动释放。但是,如果您正在编写一个需要大量释放未使用资源的应用程序,则可以这样做。

// 使用Shutdown() 关闭dispatcher 服务。这将导致之后对client的调用被拒绝。
client.dispatcher().executorService().shutdown();

使用evictAll()清除连接池。注意,连接池的守护线程可能不会立即退出。

client.connectionPool().evictAll();

OkHttpClient的成员变量

感觉 对应OkHttpClient的初始化成员变量需要简单了解一下。

class Builder constructor() {

     //1、 调度器:用于调度执行 HTTP 请求和 WebSocket 请求的线程池。
     // 它实现了 ExecutorService 接口,可以并发地执行多个请求。
     // dispatcher 默认情况下创建了一个核心线程数为 0,最大线程数为 64,线程保活时间为 60 秒的线程池。
     // 在执行请求时,dispatcher 会将请求封装为一个 RealCall 对象并提交给线程池进行执行。
     // 线程池中的线程将会不断地从请求队列中获取请求并执行,直到队列为空或者线程池被关闭。
    internal var dispatcher: Dispatcher = Dispatcher()
    
    // 2、连接池:是一个包含所有HTTP和HTTPS连接的连接池。
    // 连接池被用来重用之前的连接,从而减少应用程序的延迟和资源消耗。
    // 默认情况下,每个OkHttpClient对象都会有一个连接池,它最多可以持有5个HTTP连接和5个HTTPS连接。
    internal var connectionPool: ConnectionPool = ConnectionPool()
    
    // 3、拦截器容器
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    
    // 4、网络拦截器容器
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    
    // 5、EventListener是OkHttp的事件监听器接口,用于监控OkHttp的网络请求和响应过程,并提供相应的回调方法,
    // 如请求开始、DNS解析、连接建立、请求完成等。
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    
    // 6、失败重连,当连接失败时是否应该重试。默认情况下,
    // retryOnConnectionFailure为true,表示当连接失败时会自动重试。
    internal var retryOnConnectionFailure = true
    
    // 7、当服务器返回的响应需要进行身份验证时,OkHttp应该使用的身份验证器。
    // 当客户端向服务器发送请求时,服务器可能会返回401未授权响应,并要求客户端提供有效的身份验证凭据。
    // 在这种情况下,OkHttp会调用authenticator对象的authenticate()方法,以获取身份验证凭据并将其添加到请求头中。
    internal var authenticator: Authenticator = Authenticator.NONE
    
    // 8、用于指定是否自动跟随HTTP重定向。
    internal var followRedirects = true
    
    // 9、用于指定是否自动跟随SSL重定向。
    internal var followSslRedirects = true
    
    // 10、巴拉巴拉 没使用过(指定处理HTTP请求和响应中的cookie的策略)
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    
    // 11、缓存的实例,客户端在本地存储响应数据,以便在将来的请求中使用。
    // 缓存有助于减少对服务器的请求,减轻网络负担,提高响应速度。
    // 可以通过调用 new Cache(directory, maxSize) 来创建一个缓存实例,并将其传递给 OkHttpClient 实例的构造函数中。
    // 默认情况下,OkHttp 没有启用缓存
    internal var cache: Cache? = null
    
    // 12、DNS 解析器,用于将主机名解析为 IP 地址,
    // 在进行网络请求时,OkHttp 首先会将主机名传递给 DNS 解析器进行解析,以获取对应的 IP 地址。
    // 默认情况下,OkHttp 使用系统默认的 DNS 解析器进行解析。
    // 如果需要自定义 DNS 解析的行为,可以通过继承 Dns 类并实现其 lookup() 方法,
    // 然后将自定义的 DNS 解析器传递给 OkHttpClient 构造函数中的 DNS 参数。
    // 这样,在进行网络请求时,OkHttp 会使用自定义的 DNS 解析器来解析主机名。
    internal var dns: Dns = Dns.SYSTEM
    
    // 13、用于指定HTTP代理服务器的地址和端口号。具体添加方法参考: 片段①
    internal var proxy: Proxy? = null
    
    // 14、一个用于选择网络请求代理的工厂类
    internal var proxySelector: ProxySelector? = null
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    
    // 15、用于创建 socket 连接的工厂。当 OkHttp 发起网络请求时,会调用该工厂的 createSocket() 方法创建一个 socket 连接。
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    
    // 16、用于存储用于创建 SSL socket 的 SSL 套接字工厂对象。
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    
    // 17、是用于对 SSL 证书进行验证的信任管理器。在进行 HTTPS 请求时,服务器会向客户端返回一个数字证书,客户端需要验证该证书是否可信。
    // X509TrustManager 可以检查证书链是否完整、证书是否在有效期内、证书是否被吊销等等。如果证书被验证不通过,则连接将被拒绝。
    // 片段②
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    
    // 18、包含支持的连接协议和socket协议的配置。
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    
    // 19、用于指定客户端支持的协议。该成员变量默认包含了 HTTP/2、HTTP/1.1 两个协议。
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    
    // 20、用于验证SSL握手期间服务器主机名和证书主题是否匹配的接口。
    // 当客户端连接到一个使用HTTPS的服务器时,服务器会提供一个证书,证书中包含了服务器的主机名,
    // 客户端可以使用该主机名与服务器的实际主机名进行比较,以确保服务器的身份是合法的。
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    
    // 21、用于验证服务器证书和公钥是否匹配的一个工具。
    // 在建立 HTTPS 连接时,服务器会向客户端发送一个数字证书,证书中包含了公钥,用于加密数据。
    // 客户端需要验证这个证书是否是服务器发送的,并且证书中的公钥是否和服务器的公钥匹配,以确保连接的安全性。
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    
    // 22、用于清理证书链。
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    
    // 23、表示在与服务器建立连接、请求和响应的过程中,每个网络操作的最长允许时间,以毫秒为单位。
    internal var callTimeout = 0
    
    // 24、连接建立的超时时间,即在连接成功建立之前,等待连接建立的时间
    internal var connectTimeout = 10_000
    
    // 25、用于设置从服务器读取数据的超时时间。
    // 当客户端发起一个请求,并且服务器接受该请求后,客户端会等待服务器响应数据,
    // 这个过程中如果服务器在 readTimeout 时间内没有返回数据,那么客户端就会认为该请求失败,抛出 SocketTimeoutException 异常。
    internal var readTimeout = 10_000
    
    // 26、写入数据到服务器时的超时时间
    internal var writeTimeout = 10_000
    
    // 27、表示WebSocket的心跳间隔时间。
    // 当客户端和服务器之间的连接使用WebSocket时,pingInterval表示客户端发送心跳包的时间间隔。
    // 如果在这个时间间隔内服务器没有收到客户端发送的数据,就会认为连接已经断开。默认情况下,这个时间间隔是0,也就是不发送心跳包,
    // 如果需要发送心跳包可以通过设置这个成员变量的值来实现。
    internal var pingInterval = 0
    
    // 28、用于指定 WebSocket 发送给服务器的最小消息大小,以启用 WebSocket 消息压缩。
    // WebSocket 是一种在 Web 应用程序中实现双向通信的协议,它允许浏览器和服务器之间的长连接,用于实时数据传输。
    // 当 WebSocket 发送大量数据时,启用消息压缩可以降低数据传输的带宽占用和延迟。
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    
    // 29、负责跟踪连接池中可用的路由和可用连接的数量。它的作用是优化连接的复用和减少不必要的连接建立。
    internal var routeDatabase: RouteDatabase? = null
    
    }

片段①:OKHttp添加代理:

Proxy proxy = new Proxy(Proxy.Type.HTTP, 
				new InetSocketAddress("proxy.example.com", 8080));
OkHttpClient client = new OkHttpClient.Builder()
        .proxy(proxy)
        .build();

片段②:

// 创建 MyX509TrustManager
public class MyX509TrustManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // 客户端验证逻辑 ,没有具体业务可以不实现,但是不安全
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // 服务器端验证逻辑
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}
// 使用 MyX509TrustManager
X509TrustManager trustManager = new MyX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);

OkHttpClient client = new OkHttpClient.Builder()
        .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
        .build();

OKHttp请求流程

接下来开始正式的源码分析流程

// 使用OkHttp 网络请求
fun run() {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url("www.baidu.com")
            .build();
    Call call = client.newCall(request);
    try {
       Response  response = call.execute();
       Log.("isSuccessful",response.isSuccessful)
    } catch (IOException e) {
        e.printStackTrace();
    }
}

1.创建 OkHttpClient

通过建造者模式创建OkHttpClient

OkHttpClient
constructor() : this(Builder())

2.构建Request

同样通过建造者模式创建Request,其构造方法

class Request internal constructor(
    // url
  @get:JvmName("url") val url: HttpUrl,
  // 请求方法
  @get:JvmName("method") val method: String,
  // 请求Headers
  @get:JvmName("headers") val headers: Headers,
  // 请求体,默认为null
  @get:JvmName("body") val body: RequestBody?,
  internal val tags: Map<Class<*>, Any>
) 

3.创建Call

// Call 的唯一实现接口类 RealCall 
fun newCall(request: Request): Call = 
                        RealCall(this, request, forWebSocket = false)

4.通过Call执行同步请求

其中 execute 为同步方法、enqueue为异步方法

val response =  call.execute()

call.enqueue(object:Callback{
    override fun onFailure(call: Call, e: IOException) {
        TODO("Not yet implemented")
    }

    override fun onResponse(call: Call, response: Response) {
        TODO("Not yet implemented")
    }

})

小结:OkHttp网络请求过程:

  1. 创建OKHttpClient
  2. 创建Request
  3. 通过OKHttpClient调用newCall(request)方法 获得Call 的唯一实现类RealCall .
  4. 执行RealCall 的execute()方法 获取响应Response

RealCall执行execute()的过程

override fun execute(): Response {
    // 检查Call是否执行过 --> 如果当前值==期望值,则自动将值设置为给定的更新值。
  check(executed.compareAndSet(false, true)) { "Already Executed" }
    // 貌似和请求超时有关。暂时不做分析
  timeout.enter()
   // 请求事件监听器,回调:callStart() ---> eventListener.callStart(this)
  callStart()
  try {
    // 将call 添加到Dispatcher 分发器中
    client.dispatcher.executed(this)
    // runningSyncCalls.add(call)
    return getResponseWithInterceptorChain()
  } finally {
      // 请求完成 接着通过dispatcher继续下一个请求任务
    client.dispatcher.finished(this)
  }
}
1.对RealCall的getResponseWithInterceptorChain()方法分析:

核心方法:

internal fun getResponseWithInterceptorChain(): Response {
  // Build a full stack of interceptors.
  // 创建 interceptors 列表 并添加 自定义拦截器 和 默认的5个拦截器
  // 1、自定义拦截器 2、重试和重定向拦截器 3、Bridge拦截器 4、缓存拦截器 
  // 5、连接拦截器 6、自定义网络拦截器  7、CallServer拦截器
  val interceptors = mutableListOf<Interceptor>()
  interceptors += client.interceptors
  interceptors += RetryAndFollowUpInterceptor(client)
  interceptors += BridgeInterceptor(client.cookieJar)
  interceptors += CacheInterceptor(client.cache)
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    interceptors += client.networkInterceptors
  }
  interceptors += CallServerInterceptor(forWebSocket)
    // 创建拦截器链 RealInterceptorChain
  val chain = RealInterceptorChain(
      call = this,
      interceptors = interceptors,
      index = 0,
      exchange = null,
      request = originalRequest,
      connectTimeoutMillis = client.connectTimeoutMillis,
      readTimeoutMillis = client.readTimeoutMillis,
      writeTimeoutMillis = client.writeTimeoutMillis
  )

  var calledNoMoreExchanges = false
  try {
      // 通过 RealInterceptorChain proceed()方法获取response
    val response = chain.proceed(originalRequest)
    ...
    return response
  } catch (e: IOException) {
    ...
  } finally {
    ...
  }
2.对RealInterceptorChain的proceed()方法分析
override fun proceed(request: Request): Response {
    // 初始值为0
  check(index < interceptors.size)
   // 初始值为0
  calls++

	// 异常检测 
  // if (exchange != null) {
    // check(exchange.finder.sameHostAndPort(request.url)) {
     //  "network interceptor ${interceptors[index - 1]} must retain the same host and port"
   //  }
   //  check(calls == 1) {
    //   "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
    // }
  // }

  // Call the next interceptor in the chain.
  // 获取创建下一个Interceptor对应的新的 RealInterceptorChain,将 index 改为 1 
  val next = copy(index = index + 1, request = request)
  // 获取 当前 index为 0的 interceptor 
  val interceptor = interceptors[index]

  @Suppress("USELESS_ELVIS")
  // 将RealItercepterChain传递到下一个拦截器中
  // 如果不添加自定义拦截器 interceptor 是 RetryAndFollowUpInterceptor
  // 之后 在 RetryAndFollowUpInterceptor 中调用 realChain.proceed(request)
  // 则 会再次调用RealInterceptorChain的proceed()方法。
  
  // 注意:会copy 创建一个新的RealInterceptorChain,只是其成员变量index 会累加一次
  // 这样就会使得 下一个interceptor对应一个新的 RealInterceptorChain
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")
    
  if (exchange != null) {
    check(index + 1 >= interceptors.size || next.calls == 1) {
      "network interceptor $interceptor must call proceed() exactly once"
    }
  }

  check(response.body != null) { "interceptor $interceptor returned a response with no body" }
  // 经过默认的5个拦截器,后获取response 返回
  return response
}

给出一个官方给出的拦截器流程图

在这里插入图片描述

自己绘制拦截器调用流程图,不含自定义拦截器和网络拦截器
在这里插入图片描述

RealCall执行enqueue()的过程

1.对RealCall的enqueue()方法分析
override fun enqueue(responseCallback: Callback) {
    ...
   // 网络请求事件监听回调 eventListener.callStart(this)
    callStart()
    // 通过参数CallBack 构建AsyncCall.其实就是构建 线程池可以执行的Runnable 
    // AsyncCall(private val responseCallback: Callback) : Runnable
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
2.对Dispatcher的enqueue()方法分析
internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
        // 将回调添加到 准备好异步调用 的队列中
      readyAsyncCalls.add(call)
        // 相同Host的请求,共享 callsPerHost = AtomicInteger(0) 变量,标记相同的Host请求数量
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        // 赋值对象,多个Asynccall共享同一个callsPerHost ---> this.callsPerHost = other.callsPerHost 
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

小结:Dispatcher的enqueue()方法主要是对相同Host AsyncCall ,设置一个共享原子类 计数变量 callsPerHost = AtomicInteger(0)。
主要为了 执行请求时判断相同的Host最大数量限制

3.对Dispatcher的promoteAndExecute()方法分析
private fun promoteAndExecute(): Boolean {
  // 临时 可以执行的call 列表,为了添加满足条件的Call
  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    val i = readyAsyncCalls.iterator()
    while (i.hasNext()) {
        // 从准备队列中取出元素
      val asyncCall = i.next()
        // 判断当前正在运行的Call数量是否大于 最大请求数量 64(默认)
      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      // 判断每个相同Host 请求数量是否超过5个(默认)
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
        // 从准备队列中移除
      i.remove()
      // 添加 Host的请求数量 ++ 
      asyncCall.callsPerHost.incrementAndGet()
      // 添加到 可以执行的call 列表中
      executableCalls.add(asyncCall)
      // 添加到 正在运行的 call列表中
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
    // 将可以运行的Call 执行
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    // 执行 executorService???是什么?线程池。
    // 即:将线程池传递到Call中,再执行。如下:
    asyncCall.executeOn(executorService)
  }
  return isRunning
}

注意:面试被问到过
其传递的线程池为:

// 每个线程空闲存活60秒,核心线程数 为0 ,最多线程数:Int.MAX_VALUE
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
4.对RealCall.AsyncCall的executeOn()方法分析
fun executeOn(executorService: ExecutorService) {
     // 断言线程没有获得锁???不要紧
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        // 线程池 运行 runnable,进而会触发 Runnable的run()方法
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        ...
        // 请求失败回调
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
            // 请求完成,执行后续的任务
            // 1.call.callsPerHost.decrementAndGet()  对应的Host 请求数量计数器 减一
            // 2. 再次从Dispatcher 获取可运行的Call 执行 promoteAndExecute()
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }
5.对RealCall.AsyncCall的run()方法分析
override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        // 看门狗线程处理一个挂起的超时链表,按照要触发的顺序排序
        timeout.enter()
        try {
            // 核心方法 在RealCall的execute()方法中分析过
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          // 请求成功结果回调
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
              // 请求失败结果回调
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
            // Call请求完成,执行后续的任务
          client.dispatcher.finished(this)
        }
      }
    }
  }

至此RealCall的异步请求 分析完成

总结:通过对OKHttp整个请求过程的源码主流程分析,大概知道OKHttp的几个核心类:

  • RealCall
  • Dispatcher
  • Intercapter
  • RealInterceptorChain

后续对齐每个类结合各种拦截器 进行独立分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

交互原型图设计必备软件,这5款太赞了

如果你是UI/UX设计师&#xff0c;你肯定会在工作中涉及到交互原型图的设计。 在设计交互原型图时&#xff0c;我们通常需要找到一些适合自己的交互原型图设计软件来制作高质量的交互原型图。 与您分享5款易于使用的交互原型图设计软件 1.即时设计 即时设计是国内UI/UX设计师…

用代码点亮儿童节烟花游乐园

文章目录 概述代码烟花效果爆炸效果 结果 概述 尊敬的读者朋友们&#xff0c;六一儿童节到了&#xff01;这是一个属于孩子们的节日&#xff0c;为了庆祝这个特殊的日子&#xff0c;我们将以计算机代码为媒介&#xff0c;打造一个虚拟的烟花游乐园&#xff0c;让我们一起点亮这…

K8s in Action 阅读笔记——【12】Securing the Kubernetes API server

K8s in Action 阅读笔记——【12】Securing the Kubernetes API server 12.1 Understanding authentication 在上一章中&#xff0c;我们提到API服务器可以配置一个或多个认证插件&#xff08;授权插件也是同样的情况&#xff09;。当API服务器接收到一个请求时&#xff0c;它…

【LeetCode热题100】打卡第15天:搜索旋转排序数组在排序数组中查找元素的第一个和最后一个位置

文章目录 【LeetCode热题100】打卡第15天&#xff1a;搜索旋转排序数组&在排序数组中查找元素的第一个和最后一个位置⛅前言 搜索旋转排序数组&#x1f512;题目&#x1f511;题解 在排序数组中查找元素的第一个和最后一个位置&#x1f512;题目 【LeetCode热题100】打卡第…

企业为什么要统一身份认证管理?

身份认证管理(Identity and Access Management&#xff0c;IAM)是一套用来控制用户获取网络系统或应用访问权限的技术与流程。主要包括: 1. 身份管理&#xff1a;创建、删除和维护用户账号&#xff0c;管理用户关键信息如姓名、电子邮件等。这是进行访问控制的基础。 2. 认证管…

Spring Boot 统一功能处理

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 ⽤户登录权限效验Spring Boot 拦截器自定义拦截器将自定义拦截器加入到系统配置 拦截器实现原理 统一异常处理创建一个异常处…

金融投资心得(个人领悟篇)

金融投资心得 前言金融还是要参与的如何参与金融始终相信中国经济把控风险选股技巧不赚最后一块"铜板"多学习&#xff0c;学会筛选有用消息 其它思考推荐学习我的投资 前言 本人从2015年开始接触金融&#xff0c;不知不觉跟金融已经打了8年交道了&#xff0c;一路走…

基于STM32的智能饮水机系统设计

一、项目背景 随着智能化的迅速发展&#xff0c;人们对于生活中的各类设备也越来越有智能化的需求&#xff0c;其中智能饮水机是一种比较常见的设备。智能饮水机不仅可以提供饮用水&#xff0c;还可以通过智能化的技术满足人们对于水质、水温、出水量等方面的需求。因此&#…

深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

OpenGL蓝宝书第九章学习笔记:片段着色器和帧缓存

前言 本篇在讲什么 OpenGL蓝宝书第九章学习笔记之片段着色器和帧缓存 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践&am…

Node服务器 - koa框架

1 koa的基本使用 2 koa的参数解析 3 koa响应和错误 4 koa静态服务器 5 koa的源码解析 6 和express对比 koa的基本使用过程 const Koa require(koa)// 创建app对象 const app new Koa()// 注册中间件(middleware) // koa的中间件有两个参数: ctx/next app.use((ctx, next…

Apple Vision Pro:空间计算的未来已来,你准备好了吗?

“ 正如iPhone带我们进入移动计算时代&#xff0c;Apple Vision Pro将带我们进入空间计算时代。” 我虽然没有亲身体验&#xff0c;但观看了许多国内外第一批体验者的体验分享&#xff0c;看得出来&#xff0c;这些体验者都十分兴奋&#xff0c;根据他们的描述&#xff0c;我…

Mac安装zookeeper

文章目录 1.下载zookeeper安装包2.解压安装包3.修改配置文件4.启动服务端5.启动客户端 1.下载zookeeper安装包 https://archive.apache.org/dist/zookeeper/ 选择需要的版本下载 下载的时候要注意下载已经编译好的二进制版本 2.解压安装包 将下载的安装包解压到你想要的位…

基于Faster RCNN时间钢铁表面的缺陷检测

目标检测在许多行业中都有许多实际应用。大多数时候,在工业环境中,物体检测目标很小。因此,有效地训练目标检测模型变得非常困难。其中一个问题是钢材表面缺陷检测。即使使用深度学习,也很难高精度地解决问题。在本文中,我们将使用 PyTorch 库训练 Faster RCNN 对象检测模…

【3DsMAX】从零开始建房(5)

目录 1. 制作护栏 2. 制作梯子 3. 制作二层窗户 1. 制作护栏 选中顶部三条边线 点击“利用所选内容创建图形” 选择线性 此时我们就成功的创建了一个二维样条线 选中样条线上其中的一个点&#xff0c;移动点使线条缩短 缩小一点 渲染 同样的方法再制作一根 新建一个圆柱体 …

浅谈Spring Cloud OpenFeign

OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign&#xff0c;可以做到使用HTTP请求访问远程服务&#xff0c;就像调用本地方法一样的&#xff0c;开发者完全感知不到这是在调用远程方法&#xff0c;更感知不到在访问HTTP请求。 Spring Cloud OpenFe…

临期食品电商 APP 的设计与开发

摘 要 &#xff1a; 在移动互联网和电子商务产业的快速发展中&#xff0c;越来越多的消费者开始慢慢的接触网上购 物&#xff0c;互联网经济的全面动员将席卷全球&#xff0c;各种电商应用将在时代的浪潮中层出不穷。在未来各国的 不断发展中互联网很可能会成为销售各种货物的…

在线商城前台开发环境配置

一、项目配置 node 15.14.0 官网下载 https://nodejs.org/zh-cn/download/releases npm 7.7.6 下载node后自动安装npm&#xff0c;如果版本不对就更换对应版本 npm install react7.7.6 下载项目源码 链接&#xff1a;https://www.123pan.com/s/bT07Vv-WICcv.html 解压到一…

【P50】JMeter 汇总报告(Summary Report)

文章目录 一、汇总报告&#xff08;Summary Report&#xff09;参数说明二、准备工作三、测试计划设计 一、汇总报告&#xff08;Summary Report&#xff09;参数说明 可以查看事务或者取样器在某个时间范围内执行的汇总结果 使用场景&#xff1a;用于评估测试结果 使用频率…

java boot将一组yml配置信息装配在一个对象中

其实将一组yml数据封进一个对象中才是以后的主流开发方式 我们创建一个springboot项目 找到项目中的启动类所在目录 在同目录下创建一个类 名字你们可以随便取 我这里直接叫 dataManager 然后 在yml中定义这样一组数据信息 然后 我们在类中定义三个和这个配置信息相同的字段…