From 15ce5b4d51e912dc23dbe1f7a757ae37436a4b11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:29:55 +0000 Subject: [PATCH 1/5] Initial plan From 1f6071fc466318ce99be6f2b7b61b9e499cae18c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:40:03 +0000 Subject: [PATCH 2/5] Add Spring Boot 4.0 compatibility for bootstrap context classes In Spring Boot 4.0, the bootstrap-related classes were moved from org.springframework.boot to org.springframework.boot.bootstrap. This change adds a BootstrapRegistryHelper utility class that uses reflection to support both package locations, ensuring compatibility with Spring Boot 2.x, 3.x, and 4.x. Co-authored-by: nobodyiam <837658+nobodyiam@users.noreply.github.com> --- ...polloClientExtensionInitializeFactory.java | 3 +- ...ClientLongPollingExtensionInitializer.java | 5 +- ...polloClientWebClientCustomizerFactory.java | 9 +- ...loClientWebsocketExtensionInitializer.java | 5 +- .../data/importer/ApolloConfigDataLoader.java | 29 +-- .../ApolloConfigDataLoaderInitializer.java | 5 +- ...olloSpringApplicationRegisterListener.java | 9 +- .../data/util/BootstrapRegistryHelper.java | 180 ++++++++++++++++++ 8 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/initialize/ApolloClientExtensionInitializeFactory.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/initialize/ApolloClientExtensionInitializeFactory.java index 1405c3e3..1518aec8 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/initialize/ApolloClientExtensionInitializeFactory.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/initialize/ApolloClientExtensionInitializeFactory.java @@ -23,7 +23,6 @@ import com.ctrip.framework.apollo.config.data.extension.websocket.ApolloClientWebsocketExtensionInitializer; import com.ctrip.framework.apollo.config.data.util.Slf4jLogMessageFormatter; import org.apache.commons.logging.Log; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; @@ -42,7 +41,7 @@ public class ApolloClientExtensionInitializeFactory { private final ApolloClientWebsocketExtensionInitializer apolloClientWebsocketExtensionInitializer; public ApolloClientExtensionInitializeFactory(DeferredLogFactory logFactory, - ConfigurableBootstrapContext bootstrapContext) { + Object bootstrapContext) { this.log = logFactory.getLog(ApolloClientExtensionInitializeFactory.class); this.apolloClientPropertiesFactory = new ApolloClientPropertiesFactory(); this.apolloClientLongPollingExtensionInitializer = new ApolloClientLongPollingExtensionInitializer( diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/ApolloClientLongPollingExtensionInitializer.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/ApolloClientLongPollingExtensionInitializer.java index 337ae6ad..03185e56 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/ApolloClientLongPollingExtensionInitializer.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/ApolloClientLongPollingExtensionInitializer.java @@ -24,7 +24,6 @@ import com.ctrip.framework.foundation.internals.ServiceBootstrap; import java.util.List; import org.apache.commons.logging.Log; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; @@ -40,10 +39,10 @@ public class ApolloClientLongPollingExtensionInitializer implements private final Log log; - private final ConfigurableBootstrapContext bootstrapContext; + private final Object bootstrapContext; public ApolloClientLongPollingExtensionInitializer(DeferredLogFactory logFactory, - ConfigurableBootstrapContext bootstrapContext) { + Object bootstrapContext) { this.log = logFactory.getLog(ApolloClientLongPollingExtensionInitializer.class); this.bootstrapContext = bootstrapContext; } diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/customizer/spi/ApolloClientWebClientCustomizerFactory.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/customizer/spi/ApolloClientWebClientCustomizerFactory.java index 8e3448ed..f7feb1ff 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/customizer/spi/ApolloClientWebClientCustomizerFactory.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/webclient/customizer/spi/ApolloClientWebClientCustomizerFactory.java @@ -19,7 +19,6 @@ import com.ctrip.framework.apollo.config.data.extension.properties.ApolloClientProperties; import com.ctrip.framework.apollo.core.spi.Ordered; import org.apache.commons.logging.Log; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; @@ -37,11 +36,15 @@ public interface ApolloClientWebClientCustomizerFactory extends Ordered { * @param binder properties binder * @param bindHandler properties binder Handler * @param log deferred log - * @param bootstrapContext bootstrapContext + * @param bootstrapContext bootstrapContext (can be either + * org.springframework.boot.ConfigurableBootstrapContext for + * Spring Boot 3.x or + * org.springframework.boot.bootstrap.ConfigurableBootstrapContext + * for Spring Boot 4.x) * @return WebClientCustomizer instance or null */ @Nullable WebClientCustomizer createWebClientCustomizer(ApolloClientProperties apolloClientProperties, Binder binder, BindHandler bindHandler, Log log, - ConfigurableBootstrapContext bootstrapContext); + Object bootstrapContext); } diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/websocket/ApolloClientWebsocketExtensionInitializer.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/websocket/ApolloClientWebsocketExtensionInitializer.java index 17d1dca5..35a8f7ed 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/websocket/ApolloClientWebsocketExtensionInitializer.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/extension/websocket/ApolloClientWebsocketExtensionInitializer.java @@ -19,7 +19,6 @@ import com.ctrip.framework.apollo.config.data.extension.initialize.ApolloClientExtensionInitializer; import com.ctrip.framework.apollo.config.data.extension.properties.ApolloClientProperties; import org.apache.commons.logging.Log; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; @@ -31,10 +30,10 @@ public class ApolloClientWebsocketExtensionInitializer implements ApolloClientEx private final Log log; - private final ConfigurableBootstrapContext bootstrapContext; + private final Object bootstrapContext; public ApolloClientWebsocketExtensionInitializer(DeferredLogFactory logFactory, - ConfigurableBootstrapContext bootstrapContext) { + Object bootstrapContext) { this.log = logFactory.getLog(ApolloClientWebsocketExtensionInitializer.class); this.bootstrapContext = bootstrapContext; } diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java index 8e15007b..c6476053 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java @@ -18,6 +18,7 @@ import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.config.data.util.BootstrapRegistryHelper; import com.ctrip.framework.apollo.config.data.util.Slf4jLogMessageFormatter; import com.ctrip.framework.apollo.spring.config.ConfigPropertySource; import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; @@ -26,8 +27,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; -import org.springframework.boot.BootstrapRegistry.InstanceSupplier; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; import org.springframework.boot.context.config.ConfigDataLoaderContext; @@ -55,22 +54,24 @@ public ApolloConfigDataLoader(DeferredLogFactory logFactory) { @Override public ConfigData load(ConfigDataLoaderContext context, ApolloConfigDataResource resource) throws IOException, ConfigDataResourceNotFoundException { - ConfigurableBootstrapContext bootstrapContext = context.getBootstrapContext(); - Binder binder = bootstrapContext.get(Binder.class); + Object bootstrapContext = context.getBootstrapContext(); + Binder binder = BootstrapRegistryHelper.get(bootstrapContext, Binder.class); BindHandler bindHandler = this.getBindHandler(context); - bootstrapContext.registerIfAbsent(ApolloConfigDataLoaderInitializer.class, InstanceSupplier - .from(() -> new ApolloConfigDataLoaderInitializer(this.logFactory, binder, bindHandler, - bootstrapContext))); - ApolloConfigDataLoaderInitializer apolloConfigDataLoaderInitializer = bootstrapContext - .get(ApolloConfigDataLoaderInitializer.class); + BootstrapRegistryHelper.registerIfAbsentFromSupplier(bootstrapContext, + ApolloConfigDataLoaderInitializer.class, + () -> new ApolloConfigDataLoaderInitializer(this.logFactory, binder, bindHandler, + bootstrapContext)); + ApolloConfigDataLoaderInitializer apolloConfigDataLoaderInitializer = + BootstrapRegistryHelper.get(bootstrapContext, ApolloConfigDataLoaderInitializer.class); // init apollo client List> initialPropertySourceList = apolloConfigDataLoaderInitializer .initApolloClient(); // load config - bootstrapContext.registerIfAbsent(ConfigPropertySourceFactory.class, - InstanceSupplier.from(() -> SpringInjector.getInstance(ConfigPropertySourceFactory.class))); - ConfigPropertySourceFactory configPropertySourceFactory = bootstrapContext - .get(ConfigPropertySourceFactory.class); + BootstrapRegistryHelper.registerIfAbsentFromSupplier(bootstrapContext, + ConfigPropertySourceFactory.class, + () -> SpringInjector.getInstance(ConfigPropertySourceFactory.class)); + ConfigPropertySourceFactory configPropertySourceFactory = + BootstrapRegistryHelper.get(bootstrapContext, ConfigPropertySourceFactory.class); String namespace = resource.getNamespace(); Config config = ConfigService.getConfig(namespace); ConfigPropertySource configPropertySource = configPropertySourceFactory @@ -83,7 +84,7 @@ public ConfigData load(ConfigDataLoaderContext context, ApolloConfigDataResource } private BindHandler getBindHandler(ConfigDataLoaderContext context) { - return context.getBootstrapContext().getOrElse(BindHandler.class, null); + return BootstrapRegistryHelper.getOrElse(context.getBootstrapContext(), BindHandler.class, null); } @Override diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoaderInitializer.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoaderInitializer.java index f85e71fb..22f6acbb 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoaderInitializer.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoaderInitializer.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; -import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; @@ -54,11 +53,11 @@ class ApolloConfigDataLoaderInitializer { private final BindHandler bindHandler; - private final ConfigurableBootstrapContext bootstrapContext; + private final Object bootstrapContext; public ApolloConfigDataLoaderInitializer(DeferredLogFactory logFactory, Binder binder, BindHandler bindHandler, - ConfigurableBootstrapContext bootstrapContext) { + Object bootstrapContext) { this.logFactory = logFactory; this.log = logFactory.getLog(ApolloConfigDataLoaderInitializer.class); this.binder = binder; diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/listener/ApolloSpringApplicationRegisterListener.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/listener/ApolloSpringApplicationRegisterListener.java index 69ec5770..9578fc8c 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/listener/ApolloSpringApplicationRegisterListener.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/listener/ApolloSpringApplicationRegisterListener.java @@ -16,8 +16,7 @@ */ package com.ctrip.framework.apollo.config.data.listener; -import org.springframework.boot.BootstrapRegistry.InstanceSupplier; -import org.springframework.boot.ConfigurableBootstrapContext; +import com.ctrip.framework.apollo.config.data.util.BootstrapRegistryHelper; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.context.ApplicationListener; @@ -30,8 +29,8 @@ public class ApolloSpringApplicationRegisterListener implements @Override public void onApplicationEvent(ApplicationStartingEvent event) { - ConfigurableBootstrapContext bootstrapContext = event.getBootstrapContext(); - bootstrapContext.registerIfAbsent(SpringApplication.class, - InstanceSupplier.of(event.getSpringApplication())); + Object bootstrapContext = BootstrapRegistryHelper.getBootstrapContext(event); + BootstrapRegistryHelper.registerIfAbsent(bootstrapContext, SpringApplication.class, + event.getSpringApplication()); } } diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java new file mode 100644 index 00000000..b8a359cc --- /dev/null +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java @@ -0,0 +1,180 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.ctrip.framework.apollo.config.data.util; + +import java.lang.reflect.Method; +import java.util.function.Supplier; +import org.springframework.boot.context.event.ApplicationStartingEvent; +import org.springframework.util.ClassUtils; + +/** + * Helper class to provide compatibility between Spring Boot 3.x and 4.x for bootstrap context + * operations. + *

+ * In Spring Boot 4.0, the bootstrap-related classes were moved from + * {@code org.springframework.boot} to {@code org.springframework.boot.bootstrap}. + * + * @author vdisk + */ +public class BootstrapRegistryHelper { + + private static final boolean SPRING_BOOT_4_PRESENT; + private static final Method GET_BOOTSTRAP_CONTEXT_METHOD; + private static final Method REGISTER_IF_ABSENT_METHOD; + private static final Method INSTANCE_SUPPLIER_OF_METHOD; + private static final Method INSTANCE_SUPPLIER_FROM_METHOD; + private static final Method GET_METHOD; + private static final Method GET_OR_ELSE_METHOD; + + static { + ClassLoader classLoader = BootstrapRegistryHelper.class.getClassLoader(); + SPRING_BOOT_4_PRESENT = ClassUtils.isPresent( + "org.springframework.boot.bootstrap.ConfigurableBootstrapContext", classLoader); + + try { + GET_BOOTSTRAP_CONTEXT_METHOD = ApplicationStartingEvent.class.getMethod("getBootstrapContext"); + + Class bootstrapRegistryClass; + Class bootstrapContextClass; + if (SPRING_BOOT_4_PRESENT) { + bootstrapRegistryClass = ClassUtils.forName( + "org.springframework.boot.bootstrap.BootstrapRegistry", classLoader); + bootstrapContextClass = ClassUtils.forName( + "org.springframework.boot.bootstrap.BootstrapContext", classLoader); + } else { + bootstrapRegistryClass = ClassUtils.forName( + "org.springframework.boot.BootstrapRegistry", classLoader); + bootstrapContextClass = ClassUtils.forName( + "org.springframework.boot.BootstrapContext", classLoader); + } + + Class instanceSupplierClass = findInnerClass(bootstrapRegistryClass, "InstanceSupplier"); + REGISTER_IF_ABSENT_METHOD = bootstrapRegistryClass.getMethod("registerIfAbsent", + Class.class, instanceSupplierClass); + INSTANCE_SUPPLIER_OF_METHOD = instanceSupplierClass.getMethod("of", Object.class); + INSTANCE_SUPPLIER_FROM_METHOD = instanceSupplierClass.getMethod("from", Supplier.class); + GET_METHOD = bootstrapContextClass.getMethod("get", Class.class); + GET_OR_ELSE_METHOD = bootstrapContextClass.getMethod("getOrElse", Class.class, Object.class); + } catch (Exception e) { + throw new IllegalStateException("Failed to initialize BootstrapRegistryHelper", e); + } + } + + private static Class findInnerClass(Class outerClass, String innerClassName) { + for (Class innerClass : outerClass.getDeclaredClasses()) { + if (innerClass.getSimpleName().equals(innerClassName)) { + return innerClass; + } + } + throw new IllegalStateException( + "Cannot find inner class " + innerClassName + " in " + outerClass.getName()); + } + + /** + * Get the bootstrap context from an ApplicationStartingEvent using reflection to support both + * Spring Boot 3.x and 4.x. + * + * @param event the ApplicationStartingEvent + * @return the bootstrap context (either from old or new package) + */ + public static Object getBootstrapContext(ApplicationStartingEvent event) { + try { + return GET_BOOTSTRAP_CONTEXT_METHOD.invoke(event); + } catch (Exception e) { + throw new IllegalStateException("Failed to invoke getBootstrapContext on event", e); + } + } + + /** + * Register an instance if absent in the bootstrap context using reflection. + * + * @param bootstrapContext the bootstrap context + * @param type the type to register + * @param instance the instance to register + * @param the type parameter + */ + public static void registerIfAbsent(Object bootstrapContext, Class type, T instance) { + try { + Object wrappedSupplier = INSTANCE_SUPPLIER_OF_METHOD.invoke(null, instance); + REGISTER_IF_ABSENT_METHOD.invoke(bootstrapContext, type, wrappedSupplier); + } catch (Exception e) { + throw new IllegalStateException("Failed to register instance in bootstrap context", e); + } + } + + /** + * Register an instance supplier if absent in the bootstrap context using reflection. + * + * @param bootstrapContext the bootstrap context + * @param type the type to register + * @param instanceSupplier supplier for the instance + * @param the type parameter + */ + public static void registerIfAbsentFromSupplier(Object bootstrapContext, Class type, + Supplier instanceSupplier) { + try { + Object wrappedSupplier = INSTANCE_SUPPLIER_FROM_METHOD.invoke(null, instanceSupplier); + REGISTER_IF_ABSENT_METHOD.invoke(bootstrapContext, type, wrappedSupplier); + } catch (Exception e) { + throw new IllegalStateException("Failed to register instance in bootstrap context", e); + } + } + + /** + * Get an instance from the bootstrap context using reflection. + * + * @param bootstrapContext the bootstrap context + * @param type the type to get + * @param the type parameter + * @return the instance + */ + @SuppressWarnings("unchecked") + public static T get(Object bootstrapContext, Class type) { + try { + return (T) GET_METHOD.invoke(bootstrapContext, type); + } catch (Exception e) { + throw new IllegalStateException("Failed to get instance from bootstrap context", e); + } + } + + /** + * Get an instance from the bootstrap context or return a default value. + * + * @param bootstrapContext the bootstrap context + * @param type the type to get + * @param defaultValue the default value + * @param the type parameter + * @return the instance or default value + */ + @SuppressWarnings("unchecked") + public static T getOrElse(Object bootstrapContext, Class type, T defaultValue) { + try { + return (T) GET_OR_ELSE_METHOD.invoke(bootstrapContext, type, defaultValue); + } catch (Exception e) { + throw new IllegalStateException("Failed to get instance from bootstrap context", e); + } + } + + /** + * Check if Spring Boot 4.x is present. + * + * @return true if Spring Boot 4.x classes are available + */ + public static boolean isSpringBoot4Present() { + return SPRING_BOOT_4_PRESENT; + } +} From 7eb7469d5c88332823a6a4b38db0d73acbabec6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:46:25 +0000 Subject: [PATCH 3/5] Improve error messages and use ReflectiveOperationException - Use more specific ReflectiveOperationException instead of generic Exception - Simplified static initializer by reusing bootstrap package variable Co-authored-by: nobodyiam <837658+nobodyiam@users.noreply.github.com> --- .../data/util/BootstrapRegistryHelper.java | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java index b8a359cc..e5c8d1b5 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java @@ -50,17 +50,11 @@ public class BootstrapRegistryHelper { Class bootstrapRegistryClass; Class bootstrapContextClass; - if (SPRING_BOOT_4_PRESENT) { - bootstrapRegistryClass = ClassUtils.forName( - "org.springframework.boot.bootstrap.BootstrapRegistry", classLoader); - bootstrapContextClass = ClassUtils.forName( - "org.springframework.boot.bootstrap.BootstrapContext", classLoader); - } else { - bootstrapRegistryClass = ClassUtils.forName( - "org.springframework.boot.BootstrapRegistry", classLoader); - bootstrapContextClass = ClassUtils.forName( - "org.springframework.boot.BootstrapContext", classLoader); - } + String bootstrapPackage = SPRING_BOOT_4_PRESENT + ? "org.springframework.boot.bootstrap" + : "org.springframework.boot"; + bootstrapRegistryClass = ClassUtils.forName(bootstrapPackage + ".BootstrapRegistry", classLoader); + bootstrapContextClass = ClassUtils.forName(bootstrapPackage + ".BootstrapContext", classLoader); Class instanceSupplierClass = findInnerClass(bootstrapRegistryClass, "InstanceSupplier"); REGISTER_IF_ABSENT_METHOD = bootstrapRegistryClass.getMethod("registerIfAbsent", @@ -69,8 +63,18 @@ public class BootstrapRegistryHelper { INSTANCE_SUPPLIER_FROM_METHOD = instanceSupplierClass.getMethod("from", Supplier.class); GET_METHOD = bootstrapContextClass.getMethod("get", Class.class); GET_OR_ELSE_METHOD = bootstrapContextClass.getMethod("getOrElse", Class.class, Object.class); + } catch (ClassNotFoundException e) { + throw new IllegalStateException( + "Failed to initialize BootstrapRegistryHelper: Bootstrap classes not found. " + + "Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException( + "Failed to initialize BootstrapRegistryHelper: Required method not found. " + + "Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } catch (Exception e) { - throw new IllegalStateException("Failed to initialize BootstrapRegistryHelper", e); + throw new IllegalStateException( + "Failed to initialize BootstrapRegistryHelper: Unexpected error during reflection setup. " + + "Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } @@ -94,8 +98,10 @@ private static Class findInnerClass(Class outerClass, String innerClassNam public static Object getBootstrapContext(ApplicationStartingEvent event) { try { return GET_BOOTSTRAP_CONTEXT_METHOD.invoke(event); - } catch (Exception e) { - throw new IllegalStateException("Failed to invoke getBootstrapContext on event", e); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke ApplicationStartingEvent.getBootstrapContext() via reflection. " + + "Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } @@ -111,8 +117,10 @@ public static void registerIfAbsent(Object bootstrapContext, Class type, try { Object wrappedSupplier = INSTANCE_SUPPLIER_OF_METHOD.invoke(null, instance); REGISTER_IF_ABSENT_METHOD.invoke(bootstrapContext, type, wrappedSupplier); - } catch (Exception e) { - throw new IllegalStateException("Failed to register instance in bootstrap context", e); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke BootstrapRegistry.registerIfAbsent() for type " + type.getName() + + " via reflection. Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } @@ -129,8 +137,10 @@ public static void registerIfAbsentFromSupplier(Object bootstrapContext, Cla try { Object wrappedSupplier = INSTANCE_SUPPLIER_FROM_METHOD.invoke(null, instanceSupplier); REGISTER_IF_ABSENT_METHOD.invoke(bootstrapContext, type, wrappedSupplier); - } catch (Exception e) { - throw new IllegalStateException("Failed to register instance in bootstrap context", e); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke BootstrapRegistry.registerIfAbsent() with supplier for type " + + type.getName() + " via reflection. Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } @@ -146,8 +156,10 @@ public static void registerIfAbsentFromSupplier(Object bootstrapContext, Cla public static T get(Object bootstrapContext, Class type) { try { return (T) GET_METHOD.invoke(bootstrapContext, type); - } catch (Exception e) { - throw new IllegalStateException("Failed to get instance from bootstrap context", e); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke BootstrapContext.get() for type " + type.getName() + + " via reflection. Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } @@ -164,8 +176,10 @@ public static T get(Object bootstrapContext, Class type) { public static T getOrElse(Object bootstrapContext, Class type, T defaultValue) { try { return (T) GET_OR_ELSE_METHOD.invoke(bootstrapContext, type, defaultValue); - } catch (Exception e) { - throw new IllegalStateException("Failed to get instance from bootstrap context", e); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke BootstrapContext.getOrElse() for type " + type.getName() + + " via reflection. Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); } } From 5f54fdfaabfc86dcba7bf366f3bead301bfd7b77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 02:39:53 +0000 Subject: [PATCH 4/5] Fix ConfigDataLoaderContext.getBootstrapContext() compatibility with Spring Boot 4.0 Add reflection-based method to handle ConfigDataLoaderContext.getBootstrapContext() which also has a different return type in Spring Boot 4.0. This fixes the second NoSuchMethodError that occurs when loading config data. Co-authored-by: nobodyiam <837658+nobodyiam@users.noreply.github.com> --- .../data/importer/ApolloConfigDataLoader.java | 5 ++-- .../data/util/BootstrapRegistryHelper.java | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java index c6476053..41c84b64 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/importer/ApolloConfigDataLoader.java @@ -54,7 +54,7 @@ public ApolloConfigDataLoader(DeferredLogFactory logFactory) { @Override public ConfigData load(ConfigDataLoaderContext context, ApolloConfigDataResource resource) throws IOException, ConfigDataResourceNotFoundException { - Object bootstrapContext = context.getBootstrapContext(); + Object bootstrapContext = BootstrapRegistryHelper.getBootstrapContext(context); Binder binder = BootstrapRegistryHelper.get(bootstrapContext, Binder.class); BindHandler bindHandler = this.getBindHandler(context); BootstrapRegistryHelper.registerIfAbsentFromSupplier(bootstrapContext, @@ -84,7 +84,8 @@ public ConfigData load(ConfigDataLoaderContext context, ApolloConfigDataResource } private BindHandler getBindHandler(ConfigDataLoaderContext context) { - return BootstrapRegistryHelper.getOrElse(context.getBootstrapContext(), BindHandler.class, null); + Object bootstrapContext = BootstrapRegistryHelper.getBootstrapContext(context); + return BootstrapRegistryHelper.getOrElse(bootstrapContext, BindHandler.class, null); } @Override diff --git a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java index e5c8d1b5..bc0abb85 100644 --- a/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java +++ b/apollo-client-config-data/src/main/java/com/ctrip/framework/apollo/config/data/util/BootstrapRegistryHelper.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.util.function.Supplier; +import org.springframework.boot.context.config.ConfigDataLoaderContext; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.util.ClassUtils; @@ -33,7 +34,8 @@ public class BootstrapRegistryHelper { private static final boolean SPRING_BOOT_4_PRESENT; - private static final Method GET_BOOTSTRAP_CONTEXT_METHOD; + private static final Method GET_BOOTSTRAP_CONTEXT_FROM_EVENT_METHOD; + private static final Method GET_BOOTSTRAP_CONTEXT_FROM_LOADER_CONTEXT_METHOD; private static final Method REGISTER_IF_ABSENT_METHOD; private static final Method INSTANCE_SUPPLIER_OF_METHOD; private static final Method INSTANCE_SUPPLIER_FROM_METHOD; @@ -46,7 +48,8 @@ public class BootstrapRegistryHelper { "org.springframework.boot.bootstrap.ConfigurableBootstrapContext", classLoader); try { - GET_BOOTSTRAP_CONTEXT_METHOD = ApplicationStartingEvent.class.getMethod("getBootstrapContext"); + GET_BOOTSTRAP_CONTEXT_FROM_EVENT_METHOD = ApplicationStartingEvent.class.getMethod("getBootstrapContext"); + GET_BOOTSTRAP_CONTEXT_FROM_LOADER_CONTEXT_METHOD = ConfigDataLoaderContext.class.getMethod("getBootstrapContext"); Class bootstrapRegistryClass; Class bootstrapContextClass; @@ -97,7 +100,7 @@ private static Class findInnerClass(Class outerClass, String innerClassNam */ public static Object getBootstrapContext(ApplicationStartingEvent event) { try { - return GET_BOOTSTRAP_CONTEXT_METHOD.invoke(event); + return GET_BOOTSTRAP_CONTEXT_FROM_EVENT_METHOD.invoke(event); } catch (ReflectiveOperationException e) { throw new IllegalStateException( "Failed to invoke ApplicationStartingEvent.getBootstrapContext() via reflection. " @@ -105,6 +108,23 @@ public static Object getBootstrapContext(ApplicationStartingEvent event) { } } + /** + * Get the bootstrap context from a ConfigDataLoaderContext using reflection to support both + * Spring Boot 3.x and 4.x. + * + * @param context the ConfigDataLoaderContext + * @return the bootstrap context (either from old or new package) + */ + public static Object getBootstrapContext(ConfigDataLoaderContext context) { + try { + return GET_BOOTSTRAP_CONTEXT_FROM_LOADER_CONTEXT_METHOD.invoke(context); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException( + "Failed to invoke ConfigDataLoaderContext.getBootstrapContext() via reflection. " + + "Spring Boot 4.x detected: " + SPRING_BOOT_4_PRESENT, e); + } + } + /** * Register an instance if absent in the bootstrap context using reflection. * From 993c724eaaecf19eeff873cb69a55d4664727ded Mon Sep 17 00:00:00 2001 From: Jason Song Date: Sun, 7 Dec 2025 13:38:32 +0800 Subject: [PATCH 5/5] update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 718d9634..2060a6a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Apollo Java 2.5.0 * [Feature Provide a new open APl to return the organization list](https://github.com/apolloconfig/apollo-java/pull/102) * [Feature Added a new feature to get instance count by namespace.](https://github.com/apolloconfig/apollo-java/pull/103) * [Feature Support retry in open api client.](https://github.com/apolloconfig/apollo-java/pull/105) +* [Support Spring Boot 4.0 bootstrap context package relocation for apollo-client-config-data](https://github.com/apolloconfig/apollo-java/pull/115) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/5?closed=1)