diff --git a/CHANGELOG.md b/CHANGELOG.md index a5c0b472b..178cee088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,4 @@ - [feat: add new key for java agent nacos discovery.](https://github.com/Tencent/spring-cloud-tencent/pull/1768) - [fix: beautify ServicesEagerLoadSmartLifecycle logging.](https://github.com/Tencent/spring-cloud-tencent/pull/1774) - [feat: support kafka lane.](https://github.com/Tencent/spring-cloud-tencent/pull/1765) +- [fix: ApplicationContextAwareUtils may not be ready in postProcessAfterInitialization.](https://github.com/Tencent/spring-cloud-tencent/pull/1779) diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/PolarisRefreshConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/PolarisRefreshConfiguration.java index f68d91048..81efe6252 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/PolarisRefreshConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/PolarisRefreshConfiguration.java @@ -23,6 +23,7 @@ import com.tencent.cloud.polaris.discovery.ConditionalOnPolarisDiscoveryEnabled; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; /** * Configuration for listening the change of service status. @@ -41,8 +42,8 @@ public class PolarisRefreshConfiguration { @Bean @ConditionalOnMissingBean - public ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager() { - return new ServiceInstanceChangeCallbackManager(); + public ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager(Environment environment) { + return new ServiceInstanceChangeCallbackManager(environment); } @Bean diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackManager.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackManager.java index 7a12db4b0..749ec7f76 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackManager.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackManager.java @@ -35,6 +35,7 @@ import reactor.util.annotation.NonNull; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; /** @@ -50,7 +51,10 @@ public class ServiceInstanceChangeCallbackManager implements ApplicationListener private final ScheduledThreadPoolExecutor serviceChangeListenerExecutor; - public ServiceInstanceChangeCallbackManager() { + private final Environment environment; + + public ServiceInstanceChangeCallbackManager(Environment environment) { + this.environment = environment; this.serviceChangeListenerExecutor = new ScheduledThreadPoolExecutor(4, new NamedThreadFactory("service-change-listener")); } @@ -101,7 +105,19 @@ public class ServiceInstanceChangeCallbackManager implements ApplicationListener if (clz.isAnnotationPresent(ServiceInstanceChangeListener.class)) { ServiceInstanceChangeListener serviceInstanceChangeListener = clz.getAnnotation(ServiceInstanceChangeListener.class); serviceName = serviceInstanceChangeListener.serviceName(); - serviceName = ApplicationContextAwareUtils.getApplicationContext().getEnvironment().resolvePlaceholders(serviceName); + String message = null; + try { + serviceName = environment.resolveRequiredPlaceholders(serviceName); + } + catch (Exception e) { + // resolve failed, reset service name. + message = e.getMessage(); + serviceName = null; + } + if (StringUtils.isBlank(serviceName)) { + LOG.warn("resolve service name failed, bean name:{}, config service name:{}, message:{}", + beanName, serviceInstanceChangeListener.serviceName(), message); + } } if (StringUtils.isBlank(serviceName)) { @@ -124,8 +140,8 @@ public class ServiceInstanceChangeCallbackManager implements ApplicationListener @Override public void onApplicationEvent(@NonNull ApplicationReadyEvent event) { - PolarisDiscoveryClient polarisDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisDiscoveryClient.class); - PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisReactiveDiscoveryClient.class); + PolarisDiscoveryClient polarisDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisDiscoveryClient.class, false); + PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisReactiveDiscoveryClient.class, false); for (String serviceName : callbackMap.keySet()) { try { if (polarisDiscoveryClient != null) { diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackTest.java new file mode 100644 index 000000000..24b2ef674 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/refresh/ServiceInstanceChangeCallbackTest.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.discovery.refresh; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import com.tencent.cloud.polaris.registry.PolarisAutoServiceRegistration; +import com.tencent.polaris.api.pojo.Instance; +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.test.context.SpringBootTest; +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.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test for {@link ServiceInstanceChangeCallback}. + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = ServiceInstanceChangeCallbackTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml", + "spring.main.web-application-type = servlet", + "spring.cloud.gateway.enabled = false"}) +public class ServiceInstanceChangeCallbackTest { + + @Autowired + ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager; + + @Test + public void test1() { + // Get callbackMap from serviceInstanceChangeCallbackManager via reflection + try { + Field callbackMapField = ServiceInstanceChangeCallbackManager.class.getDeclaredField("callbackMap"); + callbackMapField.setAccessible(true); + ConcurrentHashMap> callbackMap = + (ConcurrentHashMap>) callbackMapField.get(serviceInstanceChangeCallbackManager); + + // Verify + assertThat(callbackMap.containsKey("java_provider_test")).isTrue(); + assertThat(callbackMap.containsKey("QuickstartCalleeService")).isTrue(); + // ignore error and empty + assertThat(callbackMap.size()).isEqualTo(2); + + } + catch (Exception e) { + throw new RuntimeException("Failed to get callbackMap via reflection", e); + } + } + + @SpringBootApplication + protected static class TestApplication { + + @Bean + public SelfServiceChangeCallback selfServiceChangeCallback() { + return new SelfServiceChangeCallback(); + } + + @Bean + public CalleeServiceChangeCallback calleeServiceChangeCallback() { + return new CalleeServiceChangeCallback(); + } + + @Bean + public ErrorServiceChangeCallback errorServiceChangeCallback() { + return new ErrorServiceChangeCallback(); + } + + @Bean + public EmptyServiceChangeCallback emptyServiceChangeCallback() { + return new EmptyServiceChangeCallback(); + } + + @Bean + public ParsingEmptyServiceChangeCallback parseEmptyServiceChangeCallback() { + return new ParsingEmptyServiceChangeCallback(); + } + + @Bean + public TestBeanPostProcessor testBeanPostProcessor() { + return new TestBeanPostProcessor(); + } + + } + + @ServiceInstanceChangeListener(serviceName = "${spring.application.name}") + static class SelfServiceChangeCallback implements ServiceInstanceChangeCallback { + + @Override + public void callback(List currentServiceInstances, List addServiceInstances, List deleteServiceInstances) { + + } + } + + @ServiceInstanceChangeListener(serviceName = "${error.name}") + static class ErrorServiceChangeCallback implements ServiceInstanceChangeCallback { + + @Override + public void callback(List currentServiceInstances, List addServiceInstances, List deleteServiceInstances) { + + } + } + + @ServiceInstanceChangeListener(serviceName = "${test.empty}") + static class ParsingEmptyServiceChangeCallback implements ServiceInstanceChangeCallback { + + @Override + public void callback(List currentServiceInstances, List addServiceInstances, List deleteServiceInstances) { + + } + } + + @ServiceInstanceChangeListener(serviceName = "") + static class EmptyServiceChangeCallback implements ServiceInstanceChangeCallback { + + @Override + public void callback(List currentServiceInstances, List addServiceInstances, List deleteServiceInstances) { + + } + } + + @ServiceInstanceChangeListener(serviceName = "QuickstartCalleeService") + static class CalleeServiceChangeCallback implements ServiceInstanceChangeCallback { + + @Override + public void callback(List currentServiceInstances, List addServiceInstances, List deleteServiceInstances) { + + } + } + + 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-starter-tencent-polaris-discovery/src/test/resources/application-test.yml b/spring-cloud-starter-tencent-polaris-discovery/src/test/resources/application-test.yml index 81ab48154..2b418220e 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/resources/application-test.yml +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/resources/application-test.yml @@ -32,3 +32,5 @@ spring: username: nacos password: nacos cluster-name: polaris +test: + empty: \ No newline at end of file diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java index 3fd921776..54dd6dfe5 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java @@ -90,11 +90,17 @@ public class ApplicationContextAwareUtils implements ApplicationContextAware { } public static T getBeanIfExists(Class requiredType) { + return getBeanIfExists(requiredType, false); + } + + public static T getBeanIfExists(Class requiredType, boolean warnIfFailed) { try { return applicationContext.getBean(requiredType); } catch (Throwable e) { - LOGGER.warn("get bean failed, bean type: {}", requiredType.getName()); + if (warnIfFailed) { + LOGGER.warn("get bean failed, bean type: {}", requiredType.getName()); + } return null; } }