欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Spring Cloud Ribbon的原理

 更新時間:2018年02月22日 11:32:41   作者:白色的海  
這篇文章主要介紹了淺談Spring Cloud Ribbon的原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

Ribbon是Netflix發(fā)布的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)后面所有的機器,Ribbon會自動的幫助你基于某種規(guī)則(如簡單輪詢,隨即連接等)去連接這些機器。我們也很容易使用Ribbon實現(xiàn)自定義的負載均衡算法。

說起負載均衡一般都會想到服務端的負載均衡,常用產(chǎn)品包括LBS硬件或云服務、Nginx等,都是耳熟能詳?shù)漠a(chǎn)品。

而Spring Cloud提供了讓服務調(diào)用端具備負載均衡能力的Ribbon,通過和Eureka的緊密結(jié)合,不用在服務集群內(nèi)再架設負載均衡服務,很大程度簡化了服務集群內(nèi)的架構(gòu)。

具體也不想多寫虛的介紹,反正哪里都能看得到相關(guān)的介紹。

直接開擼代碼,通過代碼來看Ribbon是如何實現(xiàn)的。

配置

詳解:

1.RibbonAutoConfiguration配置生成RibbonLoadBalancerClient實例。

代碼位置:

spring-cloud-netflix-core-1.3.5.RELEASE.jar

org.springframework.cloud.netflix.ribbon

RibbonAutoConfiguration.class

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties(RibbonEagerLoadProperties.class)
public class RibbonAutoConfiguration {

 // 略

 @Bean
 @ConditionalOnMissingBean(LoadBalancerClient.class)
 public LoadBalancerClient loadBalancerClient() {
  return new RibbonLoadBalancerClient(springClientFactory());
 }
  // 略
}

先看配置條件項,RibbonAutoConfiguration配置必須在LoadBalancerAutoConfiguration配置前執(zhí)行,因為在LoadBalancerAutoConfiguration配置中會使用RibbonLoadBalancerClient實例。

RibbonLoadBalancerClient繼承自LoadBalancerClient接口,是負載均衡客戶端,也是負載均衡策略的調(diào)用方。

2.LoadBalancerInterceptorConfig配置生成:

1).負載均衡攔截器LoadBalancerInterceptor實例

包含:

LoadBalancerClient實現(xiàn)類的RibbonLoadBalancerClient實例

負載均衡的請求創(chuàng)建工廠LoadBalancerRequestFactory:實例

2).RestTemplate自定義的RestTemplateCustomizer實例

代碼位置:

spring-cloud-commons-1.2.4.RELEASE.jar

org.springframework.cloud.client.loadbalancer

LoadBalancerAutoConfiguration.class

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
 // 略
 @Bean
 @ConditionalOnMissingBean
 public LoadBalancerRequestFactory loadBalancerRequestFactory(
   LoadBalancerClient loadBalancerClient) {
  return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
 }

 @Configuration
 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
 static class LoadBalancerInterceptorConfig {
  @Bean
  public LoadBalancerInterceptor ribbonInterceptor(
    LoadBalancerClient loadBalancerClient,
    LoadBalancerRequestFactory requestFactory) {
   return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  }

  @Bean
  @ConditionalOnMissingBean
  public RestTemplateCustomizer restTemplateCustomizer(
    final LoadBalancerInterceptor loadBalancerInterceptor) {
   return new RestTemplateCustomizer() {
    @Override
    public void customize(RestTemplate restTemplate) {
     List<ClientHttpRequestInterceptor> list = new ArrayList<>(
       restTemplate.getInterceptors());
     list.add(loadBalancerInterceptor);
     restTemplate.setInterceptors(list);
    }
   };
  }
 }
 // 略
}

先看配置條件項:

要求在項目環(huán)境中必須要有RestTemplate類。

要求必須要有LoadBalancerClient接口的實現(xiàn)類的實例,也就是上一步生成的RibbonLoadBalancerClient。

3.通過上面一步創(chuàng)建的RestTemplateCustomizer配置所有RestTemplate實例,就是將負載均衡攔截器設置給RestTemplate實例。

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
 // 略

 @Bean
 public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
   final List<RestTemplateCustomizer> customizers) {
  return new SmartInitializingSingleton() {
   @Override
   public void afterSingletonsInstantiated() {
    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
     for (RestTemplateCustomizer customizer : customizers) {
      customizer.customize(restTemplate);
     }
    }
   }
  };
 }

 // 略
 @Configuration
 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
 static class LoadBalancerInterceptorConfig {
  @Bean
  public LoadBalancerInterceptor ribbonInterceptor(
    LoadBalancerClient loadBalancerClient,
    LoadBalancerRequestFactory requestFactory) {
   return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  }

  @Bean
  @ConditionalOnMissingBean
  public RestTemplateCustomizer restTemplateCustomizer(
    final LoadBalancerInterceptor loadBalancerInterceptor) {
   return new RestTemplateCustomizer() {
    @Override
    public void customize(RestTemplate restTemplate) {
     List<ClientHttpRequestInterceptor> list = new ArrayList<>(
       restTemplate.getInterceptors());
     list.add(loadBalancerInterceptor);
     restTemplate.setInterceptors(list);
    }
   };
  }
 }
 // 略
}

restTemplate.setInterceptors(list)這個地方就是注入負載均衡攔截器的地方LoadBalancerInterceptor。

從這個地方實際上也可以猜出來,RestTemplate可以通過注入的攔截器來構(gòu)建相應的請求實現(xiàn)負載均衡。

也能看出來可以自定義攔截器實現(xiàn)其他目的。

4.RibbonClientConfiguration配置生成ZoneAwareLoadBalancer實例

代碼位置:

spring-cloud-netflix-core-1.3.5.RELEASE.jar

org.springframework.cloud.netflix.ribbon

RibbonClientConfiguration.class

@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
 // 略
 @Bean
 @ConditionalOnMissingBean
 public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
   ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
   IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
   return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  }
  return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
    serverListFilter, serverListUpdater);
 }

 // 略
}

ZoneAwareLoadBalancer繼承自ILoadBalancer接口,該接口有一個方法:

 /**
  * Choose a server from load balancer.
  * 
  * @param key An object that the load balancer may use to determine which server to return. null if 
  *   the load balancer does not use this parameter.
  * @return server chosen
  */
 public Server chooseServer(Object key);

ZoneAwareLoadBalancer就是一個具體的負載均衡實現(xiàn)類,也是默認的負載均衡類,通過對chooseServer方法的實現(xiàn)選取某個服務實例。

攔截&請求

1.使用RestTemplate進行Get、Post等各種請求,都是通過doExecute方法實現(xiàn)

代碼位置:
spring-web-4.3.12.RELEASE.jar

org.springframework.web.client

RestTemplate.class

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {

 // 略

 protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
   ResponseExtractor<T> responseExtractor) throws RestClientException {

  Assert.notNull(url, "'url' must not be null");
  Assert.notNull(method, "'method' must not be null");
  ClientHttpResponse response = null;
  try {
   ClientHttpRequest request = createRequest(url, method);
   if (requestCallback != null) {
    requestCallback.doWithRequest(request);
   }
   response = request.execute();
   handleResponse(url, method, response);
   if (responseExtractor != null) {
    return responseExtractor.extractData(response);
   }
   else {
    return null;
   }
  }
  catch (IOException ex) {
   String resource = url.toString();
   String query = url.getRawQuery();
   resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
   throw new ResourceAccessException("I/O error on " + method.name() +
     " request for \"" + resource + "\": " + ex.getMessage(), ex);
  }
  finally {
   if (response != null) {
    response.close();
   }
  }
 }

 // 略

}

支持的各種http請求方法最終都是調(diào)用doExecute方法,該方法內(nèi)調(diào)用創(chuàng)建方法創(chuàng)建請求實例,并執(zhí)行請求得到響應對象。

2.生成請求實例創(chuàng)建工廠

上一步代碼中,調(diào)用createRequest方法創(chuàng)建請求實例,這個方法是定義在父類中。

先整理出主要的繼承關(guān)系:

createRequest方法實際是定義在HttpAccessor抽象類中。

public abstract class HttpAccessor {
 private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
 public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
  Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
  this.requestFactory = requestFactory;
 }
 public ClientHttpRequestFactory getRequestFactory() {
  return this.requestFactory;
 }
 protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
  ClientHttpRequest request = getRequestFactory().createRequest(url, method);
  if (logger.isDebugEnabled()) {
   logger.debug("Created " + method.name() + " request for \"" + url + "\"");
  }
  return request;
 }
}

在createRequest方法中調(diào)用getRequestFactory方法獲得請求實例創(chuàng)建工廠,實際上getRequestFactory并不是當前HttpAccessor類中定義的,而是在子類InterceptingHttpAccessor中定義的。

public abstract class InterceptingHttpAccessor extends HttpAccessor {

 private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

 public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
  this.interceptors = interceptors;
 }

 public List<ClientHttpRequestInterceptor> getInterceptors() {
  return interceptors;
 }

 @Override
 public ClientHttpRequestFactory getRequestFactory() {
  ClientHttpRequestFactory delegate = super.getRequestFactory();
  if (!CollectionUtils.isEmpty(getInterceptors())) {
   return new InterceptingClientHttpRequestFactory(delegate, getInterceptors());
  }
  else {
   return delegate;
  }
 }
}

在這里做了個小動作,首先還是通過HttpAccessor類創(chuàng)建并獲得SimpleClientHttpRequestFactory工廠,這個工廠主要就是在沒有攔截器的時候創(chuàng)建基本請求實例。

其次,在有攔截器注入的情況下,創(chuàng)建InterceptingClientHttpRequestFactory工廠,該工廠就是創(chuàng)建帶攔截器的請求實例,因為注入了負載均衡攔截器,所以這里就從InterceptingClientHttpRequestFactory工廠創(chuàng)建。

3.通過工廠創(chuàng)建請求實例

創(chuàng)建實例就看工廠的createRequest方法。

public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

 private final List<ClientHttpRequestInterceptor> interceptors;

 public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
   List<ClientHttpRequestInterceptor> interceptors) {

  super(requestFactory);
  this.interceptors = (interceptors != null ? interceptors : Collections.<ClientHttpRequestInterceptor>emptyList());
 }


 @Override
 protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
  return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
 }

}

就是new了個InterceptingClientHttpRequest實例,并且把攔截器、基本請求實例創(chuàng)建工廠注進去。

4.請求實例調(diào)用配置階段注入的負載均衡攔截器的攔截方法intercept

可從第1步看出,創(chuàng)建完請求實例后,通過執(zhí)行請求實例的execute方法執(zhí)行請求。

ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
 requestCallback.doWithRequest(request);
}
response = request.execute();

實際請求實例是InterceptingClientHttpRequest,execute實際是在它的父類中。

類定義位置:

spring-web-4.3.12.RELEASE.jar

org.springframework.http.client

InterceptingClientHttpRequest.class

看一下它們的繼承關(guān)系。

在execute方法中實際調(diào)用了子類實現(xiàn)的executeInternal方法。

public abstract class AbstractClientHttpRequest implements ClientHttpRequest {

 private final HttpHeaders headers = new HttpHeaders();

 private boolean executed = false;

 @Override
 public final HttpHeaders getHeaders() {
  return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
 }

 @Override
 public final OutputStream getBody() throws IOException {
  assertNotExecuted();
  return getBodyInternal(this.headers);
 }

 @Override
 public final ClientHttpResponse execute() throws IOException {
  assertNotExecuted();
  ClientHttpResponse result = executeInternal(this.headers);
  this.executed = true;
  return result;
 }

 protected void assertNotExecuted() {
  Assert.state(!this.executed, "ClientHttpRequest already executed");
 }

 protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;

 protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;

}

其實就是InterceptingClientHttpRequest類的executeInternal方法,其中,又調(diào)用了一個執(zhí)行器InterceptingRequestExecution的execute,通關(guān)判斷如果有攔截器注入進來過,就調(diào)用攔截器的intercept方法。

這里的攔截器實際上就是在配置階段注入進RestTemplate實例的負載均衡攔截器LoadBalancerInterceptor實例,可參考上面配置階段的第2步。

class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {

 // 略

 @Override
 protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
  InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
  return requestExecution.execute(this, bufferedOutput);
 }


 private class InterceptingRequestExecution implements ClientHttpRequestExecution {

  private final Iterator<ClientHttpRequestInterceptor> iterator;

  public InterceptingRequestExecution() {
   this.iterator = interceptors.iterator();
  }

  @Override
  public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
   if (this.iterator.hasNext()) {
    ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
    return nextInterceptor.intercept(request, body, this);
   }
   else {
    ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
    for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
     List<String> values = entry.getValue();
     for (String value : values) {
      delegate.getHeaders().add(entry.getKey(), value);
     }
    }
    if (body.length > 0) {
     StreamUtils.copy(body, delegate.getBody());
    }
    return delegate.execute();
   }
  }
 }

}

5.負載均衡攔截器調(diào)用負載均衡客戶端

在負載均衡攔截器LoadBalancerInterceptor類的intercept方法中,又調(diào)用了負載均衡客戶端LoadBalancerClient實現(xiàn)類的execute方法。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

 private LoadBalancerClient loadBalancer;
 private LoadBalancerRequestFactory requestFactory;

 public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
  this.loadBalancer = loadBalancer;
  this.requestFactory = requestFactory;
 }

 public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
  // for backwards compatibility
  this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
 }

 @Override
 public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
   final ClientHttpRequestExecution execution) throws IOException {
  final URI originalUri = request.getURI();
  String serviceName = originalUri.getHost();
  Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
  return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
 }
}

在配置階段的第1步,可以看到實現(xiàn)類是RibbonLoadBalancerClient。

6.負載均衡客戶端調(diào)用負載均衡策略選取目標服務實例并發(fā)起請求

在RibbonLoadBalancerClient的第一個execute方法以及getServer方法中可以看到,實際上是通過ILoadBalancer的負載均衡器實現(xiàn)類作的chooseServer方法選取一個服務,交給接下來的請求對象發(fā)起一個請求。

這里的負載均衡實現(xiàn)類默認是ZoneAwareLoadBalancer區(qū)域感知負載均衡器實例,其內(nèi)部通過均衡策略選擇一個服務。

ZoneAwareLoadBalancer的創(chuàng)建可以參考配置階段的第4步。

public class RibbonLoadBalancerClient implements LoadBalancerClient {
 @Override
 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  Server server = getServer(loadBalancer);
  if (server == null) {
   throw new IllegalStateException("No instances available for " + serviceId);
  }
  RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
    serviceId), serverIntrospector(serviceId).getMetadata(server));

  return execute(serviceId, ribbonServer, request);
 }

 @Override
 public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  Server server = null;
  if(serviceInstance instanceof RibbonServer) {
   server = ((RibbonServer)serviceInstance).getServer();
  }
  if (server == null) {
   throw new IllegalStateException("No instances available for " + serviceId);
  }

  RibbonLoadBalancerContext context = this.clientFactory
    .getLoadBalancerContext(serviceId);
  RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

  try {
   T returnVal = request.apply(serviceInstance);
   statsRecorder.recordStats(returnVal);
   return returnVal;
  }
  // catch IOException and rethrow so RestTemplate behaves correctly
  catch (IOException ex) {
   statsRecorder.recordStats(ex);
   throw ex;
  }
  catch (Exception ex) {
   statsRecorder.recordStats(ex);
   ReflectionUtils.rethrowRuntimeException(ex);
  }
  return null;
 }
  
 // 略 

 protected Server getServer(ILoadBalancer loadBalancer) {
  if (loadBalancer == null) {
   return null;
  }
  return loadBalancer.chooseServer("default"); // TODO: better handling of key
 }

 protected ILoadBalancer getLoadBalancer(String serviceId) {
  return this.clientFactory.getLoadBalancer(serviceId);
 }

 public static class RibbonServer implements ServiceInstance {
  private final String serviceId;
  private final Server server;
  private final boolean secure;
  private Map<String, String> metadata;

  public RibbonServer(String serviceId, Server server) {
   this(serviceId, server, false, Collections.<String, String> emptyMap());
  }

  public RibbonServer(String serviceId, Server server, boolean secure,
    Map<String, String> metadata) {
   this.serviceId = serviceId;
   this.server = server;
   this.secure = secure;
   this.metadata = metadata;
  }

  // 略
 }

}

代碼擼完,總結(jié)下。

普通使用RestTemplate請求其他服務時,內(nèi)部使用的就是常規(guī)的http請求實例發(fā)送請求。

為RestTemplate增加了@LoanBalanced 注解后,實際上通過配置,為RestTemplate注入負載均衡攔截器,讓負載均衡器選擇根據(jù)其對應的策略選擇合適的服務后,再發(fā)送請求。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java使用FileReader讀取文件詳解

    Java使用FileReader讀取文件詳解

    本文將為大家介紹FileReader類的基本用法,包括如何創(chuàng)建FileReader對象,如何讀取文件,以及如何關(guān)閉流,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-09-09
  • Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為實例分析

    Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為實例分析

    這篇文章主要介紹了Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為,結(jié)合實例形式分析了java構(gòu)造器內(nèi)部多態(tài)方法相關(guān)原理、功能及操作技巧,需要的朋友可以參考下
    2019-10-10
  • java中為何重寫equals時必須重寫hashCode方法詳解

    java中為何重寫equals時必須重寫hashCode方法詳解

    這篇文章主要給大家介紹了關(guān)于java中為什么重寫equals時必須重寫hashCode方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-11-11
  • java高效文件流讀寫操作詳解

    java高效文件流讀寫操作詳解

    這篇文章主要介紹了java高效文件流讀寫操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-07-07
  • Spring依賴注入的三種方式小結(jié)

    Spring依賴注入的三種方式小結(jié)

    本篇文章主要介紹了Spring依賴注入的三種方式小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 使用記事本編寫java程序全過程圖解

    使用記事本編寫java程序全過程圖解

    這篇文章主要介紹了如何使用記事本編寫java程序,需要的朋友可以參考下
    2014-03-03
  • java實現(xiàn)百度坐標的摩卡托坐標與火星坐標轉(zhuǎn)換的示例

    java實現(xiàn)百度坐標的摩卡托坐標與火星坐標轉(zhuǎn)換的示例

    這篇文章主要介紹了java實現(xiàn)百度坐標的摩卡托坐標與火星坐標轉(zhuǎn)換的示例,需要的朋友可以參考下
    2014-03-03
  • idea中如何使用(Undo Commit...)

    idea中如何使用(Undo Commit...)

    這篇文章主要介紹了idea中如何使用(Undo Commit...)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • spring集成okhttp3的步驟詳解

    spring集成okhttp3的步驟詳解

    okhttp是一個封裝URL,比HttpClient更友好易用的工具,下面這篇文章主要給大家介紹了關(guān)于spring集成okhttp3的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2018-04-04
  • Spring中ApplicationEventPublisher發(fā)布訂閱模式的實現(xiàn)

    Spring中ApplicationEventPublisher發(fā)布訂閱模式的實現(xiàn)

    本文主要介紹了Spring中ApplicationEventPublisher發(fā)布訂閱模式的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07

最新評論