diff --git a/spring-cloud-starter-tencent-polaris-discovery/pom.xml b/spring-cloud-starter-tencent-polaris-discovery/pom.xml
index 67bdae3ea..c03de3430 100644
--- a/spring-cloud-starter-tencent-polaris-discovery/pom.xml
+++ b/spring-cloud-starter-tencent-polaris-discovery/pom.xml
@@ -62,5 +62,11 @@
spring-boot-actuator-autoconfigure
true
+
+
+ io.github.openfeign
+ feign-slf4j
+ test
+
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java
index f0a900e29..28726e712 100644
--- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/config/PolarisEagerLoadAutoConfiguration.java
@@ -19,27 +19,33 @@ package com.tencent.cloud.polaris.eager.config;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
-import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
+import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadContextInitializer;
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerContextInitializer;
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerLoadProperties;
import com.tencent.cloud.polaris.eager.instrument.services.ServicesEagerLoadSmartLifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
+@EnableConfigurationProperties(LoadBalancerEagerLoadProperties.class)
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.enabled", havingValue = "true", matchIfMissing = true)
public class PolarisEagerLoadAutoConfiguration {
@Bean
@ConditionalOnClass(name = "feign.Feign")
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.feign.enabled", havingValue = "true", matchIfMissing = true)
- public FeignEagerLoadSmartLifecycle feignEagerLoadSmartLifecycle(
- ApplicationContext applicationContext, @Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient,
- @Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
- return new FeignEagerLoadSmartLifecycle(applicationContext, polarisDiscoveryClient, polarisReactiveDiscoveryClient);
+ public FeignEagerLoadContextInitializer feignEagerLoadContextInitializer(
+ ApplicationContext applicationContext,
+ LoadBalancerClientFactory loadBalancerClientFactory,
+ LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties) {
+ return new FeignEagerLoadContextInitializer(applicationContext, loadBalancerClientFactory, loadBalancerEagerLoadProperties);
}
@Bean
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.services.enabled", havingValue = "true", matchIfMissing = true)
@@ -48,5 +54,12 @@ public class PolarisEagerLoadAutoConfiguration {
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
return new ServicesEagerLoadSmartLifecycle(polarisDiscoveryClient, polarisReactiveDiscoveryClient);
}
+
+ @Bean
+ @ConditionalOnProperty(value = "spring.cloud.loadbalancer.eager-load.enabled", matchIfMissing = true)
+ public LoadBalancerEagerContextInitializer loadBalancerEagerContextInitializer(
+ LoadBalancerClientFactory loadBalancerClientFactory, LoadBalancerEagerLoadProperties properties) {
+ return new LoadBalancerEagerContextInitializer(loadBalancerClientFactory, properties.getClients());
+ }
}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializer.java
similarity index 54%
rename from spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java
rename to spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializer.java
index 87ce27905..ad8f03333 100644
--- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializer.java
@@ -20,9 +20,11 @@ package com.tencent.cloud.polaris.eager.instrument.feign;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
-import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
-import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerLoadProperties;
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerWarmUpUtils;
import com.tencent.polaris.api.utils.StringUtils;
import feign.Target;
import org.slf4j.Logger;
@@ -30,30 +32,48 @@ import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.framework.JdkDynamicAopProxyUtils;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
-import org.springframework.context.SmartLifecycle;
+import org.springframework.context.ApplicationListener;
-public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
+/**
+ * Feign eager load context initializer.
+ * Implements ApplicationListener to warm up FeignClient services
+ * after the application is ready.
+ *
+ * @author Yuwei Fu
+ */
+public class FeignEagerLoadContextInitializer implements ApplicationListener {
- private static final Logger LOG = LoggerFactory.getLogger(FeignEagerLoadSmartLifecycle.class);
+ private static final Logger LOG = LoggerFactory.getLogger(FeignEagerLoadContextInitializer.class);
- private final ApplicationContext applicationContext;
+ private ApplicationContext applicationContext;
- private final PolarisDiscoveryClient polarisDiscoveryClient;
+ private LoadBalancerClientFactory loadBalancerClientFactory;
- private final PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient;
+ private LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties;
- public FeignEagerLoadSmartLifecycle(ApplicationContext applicationContext, PolarisDiscoveryClient polarisDiscoveryClient,
- PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
+ public FeignEagerLoadContextInitializer(ApplicationContext applicationContext,
+ LoadBalancerClientFactory loadBalancerClientFactory,
+ LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties) {
this.applicationContext = applicationContext;
- this.polarisDiscoveryClient = polarisDiscoveryClient;
- this.polarisReactiveDiscoveryClient = polarisReactiveDiscoveryClient;
+ this.loadBalancerClientFactory = loadBalancerClientFactory;
+ this.loadBalancerEagerLoadProperties = loadBalancerEagerLoadProperties;
}
@Override
- public void start() {
+ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
LOG.info("feign eager-load start");
+
+ // Get services that are already warmed by LoadBalancerEagerContextInitializer
+ Set skipServices = getLoadBalancerEagerLoadServices();
+
+ // Set to track already warmed services
+ Set warmedServices = new HashSet<>();
+
+ // Warm up FeignClient services
for (Object bean : applicationContext.getBeansWithAnnotation(FeignClient.class).values()) {
try {
if (Proxy.isProxyClass(bean.getClass())) {
@@ -70,17 +90,22 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
}
String serviceName = URI.create(url).getHost();
- LOG.info("[{}] eager-load start, feign name: {}", serviceName, hardCodedTarget.name());
- if (polarisDiscoveryClient != null) {
- polarisDiscoveryClient.getInstances(serviceName);
+ // Skip if already warmed by LoadBalancerEagerContextInitializer
+ if (skipServices.contains(serviceName)) {
+ LOG.debug("[{}] skip eager-load, already configured in LoadBalancerEagerLoadProperties.clients", serviceName);
+ continue;
}
- else if (polarisReactiveDiscoveryClient != null) {
- polarisReactiveDiscoveryClient.getInstances(serviceName).subscribe();
+
+ // Skip if already warmed in this round
+ if (warmedServices.contains(serviceName)) {
+ LOG.debug("[{}] already warmed, skip.", serviceName);
+ continue;
}
- else {
- LOG.warn("[{}] no discovery client found.", serviceName);
- }
- LOG.info("[{}] eager-load end", serviceName);
+
+ LOG.info("[{}] eager-load start, feign name: {}", serviceName, hardCodedTarget.name());
+ LoadBalancerWarmUpUtils.warmUp(loadBalancerClientFactory, serviceName);
+
+ warmedServices.add(serviceName);
}
}
}
@@ -90,14 +115,28 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
}
}
LOG.info("feign eager-load end");
+ }
+ /**
+ * Get services configured in LoadBalancerEagerLoadProperties.
+ * These services are warmed by LoadBalancerEagerContextInitializer.
+ * @return set of service names to skip
+ */
+ private Set getLoadBalancerEagerLoadServices() {
+ Set services = new HashSet<>();
+ if (loadBalancerEagerLoadProperties != null
+ && loadBalancerEagerLoadProperties.isEnabled()
+ && loadBalancerEagerLoadProperties.getClients() != null) {
+ services.addAll(loadBalancerEagerLoadProperties.getClients());
+ }
+ return services;
}
public static Target.HardCodedTarget> getHardCodedTarget(Object proxy) {
try {
int count = 0;
Object invocationHandler = proxy;
- // 避免死循环
+ // Avoid infinite loop
while (count++ < 100) {
invocationHandler = Proxy.getInvocationHandler(invocationHandler);
if (invocationHandler instanceof AopProxy) {
@@ -122,19 +161,4 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
}
return null;
}
-
- @Override
- public void stop() {
-
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-
- @Override
- public int getPhase() {
- return 10;
- }
}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializer.java
new file mode 100644
index 000000000..f9d0e5dfc
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializer.java
@@ -0,0 +1,48 @@
+package com.tencent.cloud.polaris.eager.instrument.loadbalancer;
+
+
+import java.util.List;
+
+import com.tencent.polaris.api.utils.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * @author Yuwei Fu
+ */
+public class LoadBalancerEagerContextInitializer implements ApplicationListener {
+
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoadBalancerEagerContextInitializer.class);
+
+ private final LoadBalancerClientFactory factory;
+
+ private final List serviceNames;
+
+ public LoadBalancerEagerContextInitializer(LoadBalancerClientFactory factory, List serviceNames) {
+ this.factory = factory;
+ this.serviceNames = serviceNames;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
+
+ LOG.info("spring cloud eager-load start");
+ try {
+ if (!CollectionUtils.isEmpty(serviceNames)) {
+ for (String serviceName : serviceNames) {
+ LoadBalancerWarmUpUtils.warmUp(factory, serviceName);
+ }
+ }
+ LOG.info("spring cloud eager-load end");
+ } catch (Exception e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("spring cloud eager-load failed.", e);
+ }
+ }
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerLoadProperties.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerLoadProperties.java
new file mode 100644
index 000000000..3ddaa2084
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerLoadProperties.java
@@ -0,0 +1,30 @@
+package com.tencent.cloud.polaris.eager.instrument.loadbalancer;
+
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("spring.cloud.loadbalancer.eager-load")
+public class LoadBalancerEagerLoadProperties {
+
+ private List clients;
+
+ private boolean enabled = true;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public List getClients() {
+ return clients;
+ }
+
+ public void setClients(List clients) {
+ this.clients = clients;
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerWarmUpUtils.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerWarmUpUtils.java
new file mode 100644
index 000000000..ed289b26d
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerWarmUpUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.polaris.eager.instrument.loadbalancer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+
+/**
+ * Utility class for load balancer warm-up operations.
+ * Provides common warm-up logic for eager loading services.
+ *
+ * @author Yuwei Fu
+ */
+public final class LoadBalancerWarmUpUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoadBalancerWarmUpUtils.class);
+
+ private LoadBalancerWarmUpUtils() {
+ }
+
+ /**
+ * Warm up a service by triggering load balancer initialization.
+ * @param factory the LoadBalancerClientFactory
+ * @param serviceName the service name to warm up
+ * @return true if warm-up succeeded, false otherwise
+ */
+ public static boolean warmUp(LoadBalancerClientFactory factory, String serviceName) {
+ try {
+ ReactiveLoadBalancer loadBalancer = factory.getInstance(serviceName);
+ if (loadBalancer != null) {
+ loadBalancer.choose();
+ LOG.info("[{}] eager-load end", serviceName);
+ return true;
+ }
+ else {
+ LOG.warn("[{}] no loadBalancer found.", serviceName);
+ return false;
+ }
+ }
+ catch (Exception e) {
+ LOG.debug("[{}] eager-load failed.", serviceName, e);
+ return false;
+ }
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializerTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializerTest.java
new file mode 100644
index 000000000..c273a1728
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadContextInitializerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.polaris.eager.instrument.feign;
+
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerLoadProperties;
+import com.tencent.cloud.polaris.registry.PolarisAutoServiceRegistration;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
+
+/**
+ * Test for {@link FeignEagerLoadContextInitializer}.
+ *
+ * @author Test Author
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = DEFINED_PORT,
+ classes = FeignEagerLoadContextInitializerTest.TestApplication.class,
+ properties = {
+ "server.port=48085",
+ "spring.config.location = classpath:application-test.yml",
+ "spring.main.web-application-type = servlet",
+ "spring.cloud.gateway.enabled = false",
+ "spring.cloud.polaris.discovery.eager-load.enabled = true",
+ "spring.cloud.polaris.discovery.eager-load.feign.enabled = true",
+ "spring.cloud.loadbalancer.eager-load.enabled = true",
+ "spring.cloud.loadbalancer.eager-load.clients = test-service-1,test-service-2,test-feign-service-skip"
+ })
+public class FeignEagerLoadContextInitializerTest {
+
+ @MockBean
+ private LoadBalancerClientFactory loadBalancerClientFactory;
+
+ @Autowired
+ private FeignEagerLoadContextInitializer feignEagerLoadContextInitializer;
+
+ @Autowired
+ private LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties;
+
+ @Autowired
+ private ConfigurableApplicationContext applicationContext;
+
+ @BeforeEach
+ public void setUp() {
+ reset(loadBalancerClientFactory);
+ }
+
+ @Test
+ public void testLoadBalancerEagerLoadPropertiesLoaded() {
+ // Verify LoadBalancerEagerLoadProperties is loaded correctly
+ assertThat(loadBalancerEagerLoadProperties.getClients())
+ .isNotNull()
+ .containsExactlyInAnyOrder("test-service-1", "test-service-2", "test-feign-service-skip");
+ assertThat(loadBalancerEagerLoadProperties.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void testFeignClient() {
+ // Prepare mock
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance(anyString())).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ feignEagerLoadContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify services in LoadBalancerEagerLoadProperties.clients are NOT warmed (skipped)
+ // because they are handled by LoadBalancerEagerContextInitializer
+ verify(loadBalancerClientFactory, never()).getInstance("test-service-1");
+ verify(loadBalancerClientFactory, never()).getInstance("test-service-2");
+
+ // Verify FeignClient services are warmed (without url)
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-feign-service");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-feign-service-http");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-feign-service-https");
+
+ // Verify FeignClient with url is NOT warmed
+ verify(loadBalancerClientFactory, never()).getInstance("localhost:48085");
+
+ // Verify choose method is called
+ verify(mockLoadBalancer, times(3)).choose();
+ }
+
+ @Test
+ public void testSkipServicesInLoadBalancerEagerLoadProperties() {
+ // Prepare mock
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance(anyString())).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ feignEagerLoadContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify services configured in LoadBalancerEagerLoadProperties.clients are skipped
+ // test-feign-service-skip is configured in LoadBalancerEagerLoadProperties.clients
+ verify(loadBalancerClientFactory, never()).getInstance("test-feign-service-skip");
+ }
+
+ @Test
+ public void testFeignClientWithHttpPrefix() {
+ // Prepare mock
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance(anyString())).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ feignEagerLoadContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify service name is correctly extracted from http:// prefix
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-feign-service-http");
+ }
+
+ @Test
+ public void testFeignClientWithHttpsPrefix() {
+ // Prepare mock
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance(anyString())).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ feignEagerLoadContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify service name is correctly extracted from https:// prefix
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-feign-service-https");
+ }
+
+ @SpringBootApplication
+ @EnableFeignClients
+ @RestController
+ protected static class TestApplication {
+
+ @Bean
+ public TestBeanPostProcessor testBeanPostProcessor() {
+ return new TestBeanPostProcessor();
+ }
+
+ @Bean
+ public FeignAspect feignAspect() {
+ return new FeignAspect();
+ }
+
+ @RequestMapping("/test")
+ public String test() {
+ return "test";
+ }
+
+ // Normal FeignClient (without url)
+ @FeignClient(name = "test-feign-service")
+ public interface TestFeignClient {
+ @RequestMapping("/test")
+ String test();
+ }
+
+ // FeignClient with http:// prefix
+ @FeignClient(name = "http://test-feign-service-http")
+ public interface TestFeignClientWithHttp {
+ @RequestMapping("/test")
+ String test();
+ }
+
+ // FeignClient with https:// prefix
+ @FeignClient(name = "https://test-feign-service-https")
+ public interface TestFeignClientWithHttps {
+ @RequestMapping("/test")
+ String test();
+ }
+
+ // FeignClient with url (should skip warm-up)
+ @FeignClient(name = "test-feign-with-url", url = "http://localhost:48085")
+ public interface TestFeignClientWithUrl {
+ @RequestMapping("/test")
+ String test();
+ }
+
+ // FeignClient that is also in LoadBalancerEagerLoadProperties.clients
+ // This service should be skipped in FeignEagerLoadContextInitializer
+ @FeignClient(name = "test-feign-service-skip")
+ public interface TestFeignClientSkip {
+ @RequestMapping("/test")
+ String test();
+ }
+ }
+
+ static class TestBeanPostProcessor implements BeanPostProcessor {
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof PolarisAutoServiceRegistration) {
+ return org.mockito.Mockito.mock(PolarisAutoServiceRegistration.class);
+ }
+ return bean;
+ }
+ }
+
+ @Aspect
+ static class FeignAspect {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FeignAspect.class);
+
+ @Around("@within(org.springframework.cloud.openfeign.FeignClient)")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ LOG.info("FeignAspect around");
+ return joinPoint.proceed();
+ }
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializerTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializerTest.java
new file mode 100644
index 000000000..5be985860
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/eager/instrument/loadbalancer/LoadBalancerEagerContextInitializerTest.java
@@ -0,0 +1,164 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.polaris.eager.instrument.loadbalancer;
+
+import com.tencent.cloud.polaris.registry.PolarisAutoServiceRegistration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
+
+/**
+ * Test for {@link LoadBalancerEagerContextInitializer}.
+ *
+ * @author Test Author
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = DEFINED_PORT,
+ classes = LoadBalancerEagerContextInitializerTest.TestApplication.class,
+ properties = {
+ "server.port=48086",
+ "spring.config.location = classpath:application-test.yml",
+ "spring.main.web-application-type = servlet",
+ "spring.cloud.gateway.enabled = false",
+ "spring.cloud.polaris.discovery.eager-load.enabled = true",
+ "spring.cloud.loadbalancer.eager-load.enabled = true",
+ "spring.cloud.loadbalancer.eager-load.clients = test-service-1,test-service-2,test-service-3"
+ })
+public class LoadBalancerEagerContextInitializerTest {
+
+ @MockBean
+ private LoadBalancerClientFactory loadBalancerClientFactory;
+
+ @Autowired
+ private LoadBalancerEagerContextInitializer loadBalancerEagerContextInitializer;
+
+ @Autowired
+ private LoadBalancerEagerLoadProperties loadBalancerEagerLoadProperties;
+
+ @BeforeEach
+ public void setUp() {
+ reset(loadBalancerClientFactory);
+ }
+
+ @Test
+ public void testLoadBalancerEagerLoadPropertiesLoaded() {
+ // Verify LoadBalancerEagerLoadProperties is loaded correctly
+ assertThat(loadBalancerEagerLoadProperties.getClients())
+ .isNotNull()
+ .containsExactlyInAnyOrder("test-service-1", "test-service-2", "test-service-3");
+ assertThat(loadBalancerEagerLoadProperties.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void testWarmUpServices() {
+ // Prepare mock
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance(anyString())).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ loadBalancerEagerContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify all services in LoadBalancerEagerLoadProperties.clients are warmed
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-1");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-2");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-3");
+
+ // Verify choose method is called for each service
+ verify(mockLoadBalancer, times(3)).choose();
+ }
+
+ @Test
+ public void testWarmUpWithNullLoadBalancer() {
+ // Prepare mock - return null for some services
+ when(loadBalancerClientFactory.getInstance("test-service-1")).thenReturn(null);
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance("test-service-2")).thenReturn(mockLoadBalancer);
+ when(loadBalancerClientFactory.getInstance("test-service-3")).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ loadBalancerEagerContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify all services are attempted
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-1");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-2");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-3");
+
+ // Verify choose method is only called for non-null load balancers
+ verify(mockLoadBalancer, times(2)).choose();
+ }
+
+ @Test
+ public void testWarmUpWithException() {
+ // Prepare mock - throw exception for some services
+ when(loadBalancerClientFactory.getInstance("test-service-1"))
+ .thenThrow(new RuntimeException("Test exception"));
+ ReactiveLoadBalancer mockLoadBalancer = mock(ReactiveLoadBalancer.class);
+ when(loadBalancerClientFactory.getInstance("test-service-2")).thenReturn(mockLoadBalancer);
+ when(loadBalancerClientFactory.getInstance("test-service-3")).thenReturn(mockLoadBalancer);
+
+ // Execute warm-up by triggering ApplicationReadyEvent
+ loadBalancerEagerContextInitializer.onApplicationEvent(mock(ApplicationReadyEvent.class));
+
+ // Verify all services are attempted (exception should not stop the loop)
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-1");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-2");
+ verify(loadBalancerClientFactory, times(1)).getInstance("test-service-3");
+ }
+
+ @SpringBootApplication
+ protected static class TestApplication {
+
+ @Bean
+ public TestBeanPostProcessor testBeanPostProcessor() {
+ return new TestBeanPostProcessor();
+ }
+
+ }
+
+ static class TestBeanPostProcessor implements BeanPostProcessor {
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof PolarisAutoServiceRegistration) {
+ return org.mockito.Mockito.mock(PolarisAutoServiceRegistration.class);
+ }
+ return bean;
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java
index e14689a70..e61c1e843 100644
--- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java
@@ -17,10 +17,12 @@
package com.tencent.cloud.plugin.unit.config;
-import com.tencent.cloud.plugin.unit.discovery.UnitFeignEagerLoadSmartLifecycle;
+import com.tencent.cloud.plugin.unit.discovery.UnitFeignEagerLoadContextInitializer;
+import com.tencent.cloud.plugin.unit.discovery.UnitLoadBalancerEagerContextInitializer;
import com.tencent.cloud.plugin.unit.discovery.UnitPolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
-import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
+import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadContextInitializer;
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerContextInitializer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -35,8 +37,12 @@ public class UnitBeanPostProcessor implements BeanPostProcessor {
return new UnitPolarisDiscoveryClient(discoveryClient);
}
- if (bean instanceof FeignEagerLoadSmartLifecycle) {
- return new UnitFeignEagerLoadSmartLifecycle();
+ if (bean instanceof FeignEagerLoadContextInitializer) {
+ return new UnitFeignEagerLoadContextInitializer();
+ }
+
+ if (bean instanceof LoadBalancerEagerContextInitializer) {
+ return new UnitLoadBalancerEagerContextInitializer();
}
return bean;
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadContextInitializer.java
similarity index 69%
rename from spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java
rename to spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadContextInitializer.java
index 60cf1f5d6..c53e4dcb5 100644
--- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadContextInitializer.java
@@ -17,21 +17,27 @@
package com.tencent.cloud.plugin.unit.discovery;
-import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
+import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadContextInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class UnitFeignEagerLoadSmartLifecycle extends FeignEagerLoadSmartLifecycle {
+import org.springframework.boot.context.event.ApplicationReadyEvent;
- private static final Logger LOG = LoggerFactory.getLogger(UnitFeignEagerLoadSmartLifecycle.class);
+/**
+ * Unit Feign eager load context initializer.
+ * Ignores feign eager load in unit mode.
+ */
+public class UnitFeignEagerLoadContextInitializer extends FeignEagerLoadContextInitializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UnitFeignEagerLoadContextInitializer.class);
- public UnitFeignEagerLoadSmartLifecycle() {
+ public UnitFeignEagerLoadContextInitializer() {
super(null, null, null);
}
@Override
- public void start() {
+ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
LOG.info("ignore feign eager load in unit mode");
}
}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitLoadBalancerEagerContextInitializer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitLoadBalancerEagerContextInitializer.java
new file mode 100644
index 000000000..10e65d0e5
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitLoadBalancerEagerContextInitializer.java
@@ -0,0 +1,42 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.discovery;
+
+import com.tencent.cloud.polaris.eager.instrument.loadbalancer.LoadBalancerEagerContextInitializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+
+/**
+ * Unit LoadBalancer eager load context initializer.
+ * Ignores loadbalancer eager load in unit mode.
+ */
+public class UnitLoadBalancerEagerContextInitializer extends LoadBalancerEagerContextInitializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UnitLoadBalancerEagerContextInitializer.class);
+
+ public UnitLoadBalancerEagerContextInitializer() {
+ super(null, null);
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
+ LOG.info("ignore loadbalancer eager load in unit mode");
+ }
+}