Use bean class loader when creating interface clients
Update interface client code to replace `GroupsMetadata.loadClass` calls with `ClassUtils.resolveClassName` passing in the bean class loader. Since the bean class loader in injected after construction, some minor refactoring has been applied to `HttpServiceProxyRegistryFactoryBean`. The class now stores `GroupsMetadata` and only loads the types in in `afterPropertiesSet`. The `HttpServiceProxyFactory` class has also been updated to ensure that the proxy is created using the class loader of the service type, rather than the thread context class loader. Fixes gh-34846
This commit is contained in:
committed by
rstoyanchev
parent
13f9fed4bc
commit
abbee1a050
@@ -89,7 +89,14 @@ public final class HttpServiceProxyFactory {
|
||||
.map(method -> createHttpServiceMethod(serviceType, method))
|
||||
.toList();
|
||||
|
||||
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods));
|
||||
return getProxy(serviceType, httpServiceMethods);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> S getProxy(Class<S> serviceType, List<HttpServiceMethod> httpServiceMethods) {
|
||||
MethodInterceptor interceptor = new HttpServiceMethodInterceptor(httpServiceMethods);
|
||||
ProxyFactory proxyFactory = new ProxyFactory(serviceType, interceptor);
|
||||
return (S) proxyFactory.getProxy(serviceType.getClassLoader());
|
||||
}
|
||||
|
||||
private boolean isExchangeMethod(Method method) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.service.registry;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.service.annotation.HttpExchange;
|
||||
|
||||
/**
|
||||
@@ -74,7 +76,7 @@ import org.springframework.web.service.annotation.HttpExchange;
|
||||
* @see HttpServiceProxyRegistryFactoryBean
|
||||
*/
|
||||
public abstract class AbstractHttpServiceRegistrar implements
|
||||
ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware, BeanFactoryAware {
|
||||
ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware, BeanFactoryAware, BeanClassLoaderAware {
|
||||
|
||||
/**
|
||||
* The bean name of the {@link HttpServiceProxyRegistry}.
|
||||
@@ -91,6 +93,8 @@ public abstract class AbstractHttpServiceRegistrar implements
|
||||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
|
||||
private @Nullable ClassLoader beanClassLoader;
|
||||
|
||||
private final GroupsMetadata groupsMetadata = new GroupsMetadata();
|
||||
|
||||
private @Nullable ClassPathScanningCandidateComponentProvider scanner;
|
||||
@@ -121,6 +125,11 @@ public abstract class AbstractHttpServiceRegistrar implements
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void registerBeanDefinitions(
|
||||
@@ -197,7 +206,7 @@ public abstract class AbstractHttpServiceRegistrar implements
|
||||
private Object getProxyInstance(String groupName, String httpServiceType) {
|
||||
Assert.state(this.beanFactory != null, "BeanFactory has not been set");
|
||||
HttpServiceProxyRegistry registry = this.beanFactory.getBean(HTTP_SERVICE_PROXY_REGISTRY_BEAN_NAME, HttpServiceProxyRegistry.class);
|
||||
return registry.getClient(groupName, GroupsMetadata.loadClass(httpServiceType));
|
||||
return registry.getClient(groupName, ClassUtils.resolveClassName(httpServiceType, this.beanClassLoader));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -82,17 +84,11 @@ final class GroupsMetadata {
|
||||
/**
|
||||
* Create the {@link HttpServiceGroup}s for all registrations.
|
||||
*/
|
||||
public Collection<HttpServiceGroup> groups() {
|
||||
return this.groupMap.values().stream().map(DefaultRegistration::toHttpServiceGroup).toList();
|
||||
}
|
||||
|
||||
public static Class<?> loadClass(String type) {
|
||||
try {
|
||||
return ClassUtils.forName(type, GroupsMetadata.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Failed to load '" + type + "'", ex);
|
||||
}
|
||||
public Collection<HttpServiceGroup> groups(@Nullable ClassLoader classLoader) {
|
||||
return this.groupMap.values()
|
||||
.stream()
|
||||
.map(registration -> registration.toHttpServiceGroup(classLoader))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,10 +165,12 @@ final class GroupsMetadata {
|
||||
/**
|
||||
* Create the {@link HttpServiceGroup} from the metadata.
|
||||
*/
|
||||
public HttpServiceGroup toHttpServiceGroup() {
|
||||
public HttpServiceGroup toHttpServiceGroup(@Nullable ClassLoader classLoader) {
|
||||
return new RegisteredGroup(this.name,
|
||||
(this.clientType.isUnspecified() ? HttpServiceGroup.ClientType.REST_CLIENT : this.clientType),
|
||||
this.typeNames.stream().map(GroupsMetadata::loadClass).collect(Collectors.toSet()));
|
||||
this.typeNames.stream()
|
||||
.map(typeName -> ClassUtils.resolveClassName(typeName, classLoader))
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -57,27 +58,24 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
* @see AbstractHttpServiceRegistrar
|
||||
*/
|
||||
public final class HttpServiceProxyRegistryFactoryBean
|
||||
implements ApplicationContextAware, InitializingBean, FactoryBean<HttpServiceProxyRegistry> {
|
||||
implements ApplicationContextAware, BeanClassLoaderAware, InitializingBean,
|
||||
FactoryBean<HttpServiceProxyRegistry> {
|
||||
|
||||
private static final Map<HttpServiceGroup.ClientType, HttpServiceGroupAdapter<?>> groupAdapters =
|
||||
GroupAdapterInitializer.initGroupAdapters();
|
||||
|
||||
|
||||
private final Set<ProxyHttpServiceGroup> groupSet;
|
||||
private final GroupsMetadata groupsMetadata;
|
||||
|
||||
private @Nullable ApplicationContext applicationContext;
|
||||
|
||||
private @Nullable ClassLoader beanClassLoader;
|
||||
|
||||
private @Nullable HttpServiceProxyRegistry proxyRegistry;
|
||||
|
||||
|
||||
HttpServiceProxyRegistryFactoryBean(GroupsMetadata groupsMetadata) {
|
||||
this.groupSet = groupsMetadata.groups().stream()
|
||||
.map(group -> {
|
||||
HttpServiceGroupAdapter<?> adapter = groupAdapters.get(group.clientType());
|
||||
Assert.state(adapter != null, "No HttpServiceGroupAdapter for type " + group.clientType());
|
||||
return new ProxyHttpServiceGroup(group, adapter);
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
this.groupsMetadata = groupsMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +84,11 @@ public final class HttpServiceProxyRegistryFactoryBean
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return HttpServiceProxyRegistry.class;
|
||||
@@ -95,18 +98,25 @@ public final class HttpServiceProxyRegistryFactoryBean
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(this.applicationContext, "ApplicationContext not initialized");
|
||||
Assert.notNull(this.beanClassLoader, "BeanClassLoader not initialized");
|
||||
|
||||
// Create the groups from the metadata
|
||||
Set<ProxyHttpServiceGroup> groups = this.groupsMetadata.groups(this.beanClassLoader)
|
||||
.stream()
|
||||
.map(ProxyHttpServiceGroup::new)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Apply group configurers
|
||||
groupAdapters.forEach((clientType, groupAdapter) ->
|
||||
this.applicationContext.getBeanProvider(groupAdapter.getConfigurerType())
|
||||
.orderedStream()
|
||||
.forEach(configurer -> configurer.configureGroups(new DefaultGroups<>(clientType))));
|
||||
.forEach(configurer -> configurer.configureGroups(new DefaultGroups<>(groups, clientType))));
|
||||
|
||||
// Create proxies
|
||||
Map<String, Map<Class<?>, Object>> groupProxyMap = this.groupSet.stream()
|
||||
Map<String, Map<Class<?>, Object>> proxies = groups.stream()
|
||||
.collect(Collectors.toMap(ProxyHttpServiceGroup::name, ProxyHttpServiceGroup::createProxies));
|
||||
|
||||
this.proxyRegistry = new DefaultHttpServiceProxyRegistry(groupProxyMap);
|
||||
this.proxyRegistry = new DefaultHttpServiceProxyRegistry(proxies);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,12 +169,17 @@ public final class HttpServiceProxyRegistryFactoryBean
|
||||
|
||||
private BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer = (group, builder) -> {};
|
||||
|
||||
ProxyHttpServiceGroup(HttpServiceGroup group) {
|
||||
this(group, getHttpServiceGroupAdapter(group.clientType()));
|
||||
}
|
||||
|
||||
ProxyHttpServiceGroup(HttpServiceGroup group, HttpServiceGroupAdapter<?> groupAdapter) {
|
||||
this.declaredGroup = group;
|
||||
this.groupAdapter = groupAdapter;
|
||||
this.clientBuilder = groupAdapter.createClientBuilder();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return this.declaredGroup.name();
|
||||
@@ -208,17 +223,26 @@ public final class HttpServiceProxyRegistryFactoryBean
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[id=" + name() + "]";
|
||||
}
|
||||
|
||||
private static HttpServiceGroupAdapter<?> getHttpServiceGroupAdapter(HttpServiceGroup.ClientType clientType) {
|
||||
HttpServiceGroupAdapter<?> adapter = groupAdapters.get(clientType);
|
||||
Assert.state(adapter != null, "No HttpServiceGroupAdapter for type " + clientType);
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of Groups that helps to configure the set of declared groups.
|
||||
*/
|
||||
private final class DefaultGroups<CB> implements HttpServiceGroupConfigurer.Groups<CB> {
|
||||
private static final class DefaultGroups<CB> implements HttpServiceGroupConfigurer.Groups<CB> {
|
||||
|
||||
private final Set<ProxyHttpServiceGroup> groups;
|
||||
|
||||
private Predicate<HttpServiceGroup> filter;
|
||||
|
||||
DefaultGroups(HttpServiceGroup.ClientType clientType) {
|
||||
DefaultGroups(Set<ProxyHttpServiceGroup> groups, HttpServiceGroup.ClientType clientType) {
|
||||
this.groups = groups;
|
||||
this.filter = group -> group.clientType().equals(clientType);
|
||||
}
|
||||
|
||||
@@ -255,7 +279,7 @@ public final class HttpServiceProxyRegistryFactoryBean
|
||||
BiConsumer<HttpServiceGroup, CB> clientConfigurer,
|
||||
BiConsumer<HttpServiceGroup, HttpServiceProxyFactory.Builder> proxyFactoryConfigurer) {
|
||||
|
||||
groupSet.stream().filter(this.filter).forEach(group ->
|
||||
this.groups.stream().filter(this.filter).forEach(group ->
|
||||
group.apply(clientConfigurer, proxyFactoryConfigurer));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user