Add native image runtime hints, register singletons instead of beans when transferring instances from the Config Data API.

Closes gh-667
This commit is contained in:
Mark Paluch
2023-01-27 14:46:19 +01:00
parent 20953c93eb
commit ac8512e4dd
7 changed files with 145 additions and 40 deletions

View File

@@ -84,11 +84,11 @@ import static java.nio.charset.StandardCharsets.UTF_8;
*/
class ClientAuthenticationFactory {
private static final boolean GOOGLE_CREDENTIAL_AVAILABLE = ClassUtils.isPresent(
private static final boolean GOOGLE_CREDENTIAL_PRESENT = ClassUtils.isPresent(
"com.google.api.client.googleapis.auth.oauth2.GoogleCredential",
ClientAuthenticationFactory.class.getClassLoader());
private static final boolean GOOGLE_CREDENTIALS_AVAILABLE = ClassUtils
private static final boolean GOOGLE_CREDENTIALS_PRESENT = ClassUtils
.isPresent("com.google.auth.oauth2.GoogleCredentials", ClientAuthenticationFactory.class.getClassLoader());
private final VaultProperties vaultProperties;
@@ -347,11 +347,11 @@ class ClientAuthenticationFactory {
private ClientAuthentication gcpIamAuthentication(VaultProperties vaultProperties) {
if (GOOGLE_CREDENTIAL_AVAILABLE) {
if (GOOGLE_CREDENTIAL_PRESENT) {
return GcpIamAuthenticationFactory.create(vaultProperties, this.restOperations);
}
if (GOOGLE_CREDENTIALS_AVAILABLE) {
if (GOOGLE_CREDENTIALS_PRESENT) {
return GcpIamCredentialsAuthenticationFactory.create(vaultProperties, this.restOperations);
}

View File

@@ -245,12 +245,12 @@ public class VaultAutoConfiguration {
this(taskScheduler, true);
}
TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet) {
public TaskSchedulerWrapper(ThreadPoolTaskScheduler taskScheduler, boolean acceptAfterPropertiesSet) {
this.taskScheduler = taskScheduler;
this.acceptAfterPropertiesSet = acceptAfterPropertiesSet;
}
ThreadPoolTaskScheduler getTaskScheduler() {
public ThreadPoolTaskScheduler getTaskScheduler() {
return this.taskScheduler;
}

View File

@@ -27,8 +27,8 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
@@ -62,6 +62,7 @@ import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.client.WebClientFactory;
import org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.core.env.LeaseAwareVaultPropertySource;
@@ -70,8 +71,6 @@ import org.springframework.vault.core.lease.domain.RequestedSecret;
import org.springframework.vault.core.lease.event.LeaseErrorListener;
import org.springframework.web.client.RestTemplate;
import static org.springframework.vault.config.AbstractVaultConfiguration.ClientFactoryWrapper;
/**
* {@link ConfigDataLoader} for Vault for {@link VaultConfigLocation}. This class
* materializes {@link PropertySource property sources} by using Vault and
@@ -94,13 +93,13 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
private final static ConfigData SKIP_LOCATION = null;
private final static boolean FLUX_AVAILABLE = ClassUtils.isPresent("reactor.core.publisher.Flux",
final static boolean reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Flux",
VaultConfigDataLoader.class.getClassLoader());
private final static boolean WEBCLIENT_AVAILABLE = ClassUtils.isPresent(
final static boolean webclientPresent = ClassUtils.isPresent(
"org.springframework.web.reactive.function.client.WebClient", VaultConfigDataLoader.class.getClassLoader());
private final static boolean REGISTER_REACTIVE_INFRASTRUCTURE = FLUX_AVAILABLE && WEBCLIENT_AVAILABLE;
private final static boolean REGISTER_REACTIVE_INFRASTRUCTURE = reactorPresent && webclientPresent;
private final DeferredLogFactory logFactory;
@@ -366,10 +365,16 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
GenericApplicationContext gac = (GenericApplicationContext) event.getApplicationContext();
ConfigurableListableBeanFactory factory = gac.getBeanFactory();
if (factory.containsSingleton(beanName) || factory.containsBeanDefinition(beanName)) {
return;
}
contextCustomizer.accept(gac);
T instance = event.getBootstrapContext().get(instanceType);
gac.registerBean(beanName, instanceType, () -> instance);
factory.registerSingleton(beanName, instance);
});
}
@@ -389,7 +394,6 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
field.setAccessible(true);
field.set(null, logFactory.getLog(type));
}, VaultConfigDataLoader::isUpdateableLogField);
}
@@ -399,7 +403,6 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
field.setAccessible(true);
field.set(object, logFactory.getLog(object.getClass()));
}, VaultConfigDataLoader::isUpdateableLogField);
}
@@ -419,7 +422,7 @@ public class VaultConfigDataLoader implements ConfigDataLoader<VaultConfigLocati
/**
* Support class to register imperative infrastructure bootstrap instances and beans.
*
* <p>
* Mirrors {@link VaultAutoConfiguration}.
*/
static class ImperativeInfrastructure {

View File

@@ -36,7 +36,6 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.core.util.PropertyTransformer;
import org.springframework.vault.core.util.PropertyTransformers;
@@ -201,17 +200,17 @@ public class VaultConfigDataLocationResolver implements ConfigDataLocationResolv
private static List<VaultSecretBackendDescriptor> findDescriptors(Binder binder) {
List<String> descriptorClasses = new ArrayList<>();
descriptorClasses.addAll(SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptor.class,
List<Object> descriptorsOrFactories = new ArrayList<>();
descriptorsOrFactories.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptor.class,
VaultConfigDataLocationResolver.class.getClassLoader()));
descriptorClasses.addAll(SpringFactoriesLoader.loadFactoryNames(VaultSecretBackendDescriptorFactory.class,
descriptorsOrFactories.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptorFactory.class,
VaultConfigDataLocationResolver.class.getClassLoader()));
List<VaultSecretBackendDescriptor> descriptors = new ArrayList<>(descriptorClasses.size());
List<VaultSecretBackendDescriptor> descriptors = new ArrayList<>(descriptorsOrFactories.size());
for (String className : descriptorClasses) {
for (Object descriptorOrFactory : descriptorsOrFactories) {
Class<?> descriptorClass = loadClass(className);
Class<?> descriptorClass = descriptorOrFactory.getClass();
MergedAnnotations annotations = MergedAnnotations.from(descriptorClass);
if (annotations.isPresent(ConfigurationProperties.class)) {
@@ -228,12 +227,13 @@ public class VaultConfigDataLocationResolver implements ConfigDataLocationResolv
else {
throw new IllegalStateException(String.format(
"Descriptor %s is neither implements VaultSecretBackendDescriptorFactory nor VaultSecretBackendDescriptor",
className));
ClassUtils.getQualifiedName(descriptorOrFactory.getClass())));
}
}
else {
throw new IllegalStateException(String.format(
"VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties", className));
throw new IllegalStateException(
String.format("VaultSecretBackendDescriptor %s is not annotated with @ConfigurationProperties",
ClassUtils.getQualifiedName(descriptorOrFactory.getClass())));
}
}
@@ -246,17 +246,4 @@ public class VaultConfigDataLocationResolver implements ConfigDataLocationResolv
VaultConfigDataLocationResolver.class.getClassLoader());
}
@SuppressWarnings("unchecked")
private static Class<?> loadClass(String className) {
try {
return ClassUtils.forName(className, VaultConfigDataLocationResolver.class.getClassLoader());
}
catch (ReflectiveOperationException e) {
ReflectionUtils.rethrowRuntimeException(e);
// should never happen.
return null;
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2018-2023 the original author or 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
*
* https://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 org.springframework.cloud.vault.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.LifecycleAwareSessionManagerSupport;
import org.springframework.vault.authentication.ReactiveLifecycleAwareSessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
import org.springframework.vault.core.env.LeaseAwareVaultPropertySource;
/**
* Runtime hints for Spring Cloud Vault usage with native images.
*
* @author Mark Paluch
*/
class VaultRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
ReflectionHints reflection = hints.reflection();
// reflection required for ConfigDataLoader, early logging capture
reflection.registerTypes(Arrays.asList(SimpleSessionManager.class, LifecycleAwareSessionManager.class,
LifecycleAwareSessionManagerSupport.class, ClientHttpRequestFactoryFactory.class,
org.springframework.vault.core.env.VaultPropertySource.class, LeaseAwareVaultPropertySource.class)
.stream().map(TypeReference::of).collect(Collectors.toList()),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS));
reflection.registerTypes(
Arrays.asList(VaultKeyValueBackendProperties.class).stream().map(TypeReference::of)
.collect(Collectors.toList()),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
reflection.registerType(
TypeReference.of("org.springframework.vault.core.lease.SecretLeaseContainer$LeaseRenewalScheduler"),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS));
reflection.registerType(
TypeReference.of("org.springframework.vault.core.lease.SecretLeaseEventPublisher$LoggingErrorListener"),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS));
reflection.registerType(TypeReference
.of("org.springframework.cloud.vault.config.VaultReactiveConfiguration$ReactiveSessionManagerAdapter"),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS));
if (VaultConfigDataLoader.webclientPresent && VaultConfigDataLoader.reactorPresent) {
reflection
.registerTypes(
Arrays.asList(ReactiveLifecycleAwareSessionManager.class).stream().map(TypeReference::of)
.collect(Collectors.toList()),
builder -> builder.withMembers(MemberCategory.DECLARED_FIELDS));
}
// presence checks
reflection.registerTypeIfPresent(classLoader, "reactor.core.publisher.Flux", MemberCategory.PUBLIC_CLASSES);
reflection.registerTypeIfPresent(classLoader, "org.springframework.web.reactive.function.client.WebClient",
MemberCategory.PUBLIC_CLASSES);
reflection.registerTypeIfPresent(classLoader, "org.bouncycastle.crypto.signers.PSSSigner",
MemberCategory.PUBLIC_CLASSES);
reflection.registerTypeIfPresent(classLoader, "com.google.api.client.googleapis.auth.oauth2.GoogleCredential",
MemberCategory.PUBLIC_CLASSES);
reflection.registerTypeIfPresent(classLoader, "com.google.auth.oauth2.GoogleCredentials",
MemberCategory.PUBLIC_CLASSES);
// reflection for pluggable config properties bindings
List<Object> pluggableDescriptors = new ArrayList<>();
pluggableDescriptors
.addAll(SpringFactoriesLoader.loadFactories(SecretBackendMetadataFactory.class, classLoader));
pluggableDescriptors
.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptor.class, classLoader));
pluggableDescriptors
.addAll(SpringFactoriesLoader.loadFactories(VaultSecretBackendDescriptorFactory.class, classLoader));
List<TypeReference> pluggableDescriptorReferences = pluggableDescriptors.stream().map(Object::getClass)
.map(TypeReference::of).collect(Collectors.toList());
reflection.registerTypes(pluggableDescriptorReferences, builder -> {
builder.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS);
});
}
}

View File

@@ -0,0 +1 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.cloud.vault.config.VaultRuntimeHints

View File

@@ -20,7 +20,7 @@
<description>Spring Cloud Vault Dependencies</description>
<properties>
<spring-vault.version>3.0.0</spring-vault.version>
<spring-vault.version>3.0.1-SNAPSHOT</spring-vault.version>
</properties>
<dependencyManagement>