Browse Source

Lazy loading of HTTP Service classes

To help with AOT support, update AbstractHttpServiceRegistrar to store
HTTP Service types by name, and avoid loading classes during the bean
definition registration phase.

See gh-33992
pull/34696/head
rstoyanchev 1 year ago
parent
commit
42409e21fa
  1. 120
      spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java

120
spring-web/src/main/java/org/springframework/web/service/registry/AbstractHttpServiceRegistrar.java

@ -16,11 +16,12 @@ @@ -16,11 +16,12 @@
package org.springframework.web.service.registry;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
@ -89,7 +90,7 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -89,7 +90,7 @@ public abstract class AbstractHttpServiceRegistrar implements
private @Nullable BeanFactory beanFactory;
private final Map<String, HttpServiceGroup> groupMap = new LinkedHashMap<>();
private final Map<String, RegisteredGroup> groupMap = new LinkedHashMap<>();
private @Nullable ClassPathScanningCandidateComponentProvider scanner;
@ -143,11 +144,11 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -143,11 +144,11 @@ public abstract class AbstractHttpServiceRegistrar implements
mergeHttpServices(proxyRegistryBeanDef);
this.groupMap.forEach((groupName, group) -> group.httpServiceTypes().forEach(type -> {
this.groupMap.forEach((groupName, group) -> group.httpServiceTypeNames().forEach(type -> {
GenericBeanDefinition proxyBeanDef = new GenericBeanDefinition();
proxyBeanDef.setBeanClass(type);
proxyBeanDef.setInstanceSupplier(() -> getProxyInstance(proxyRegistryBeanName, groupName, type));
proxyBeanDef.setBeanClassName(type);
String beanName = (groupName + "." + beanNameGenerator.generateBeanName(proxyBeanDef, beanRegistry));
proxyBeanDef.setInstanceSupplier(() -> getProxyInstance(proxyRegistryBeanName, groupName, type));
if (!beanRegistry.containsBeanDefinition(beanName)) {
beanRegistry.registerBeanDefinition(beanName, proxyBeanDef);
}
@ -184,16 +185,16 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -184,16 +185,16 @@ public abstract class AbstractHttpServiceRegistrar implements
ConstructorArgumentValues args = proxyRegistryBeanDef.getConstructorArgumentValues();
ConstructorArgumentValues.ValueHolder valueHolder = args.getArgumentValue(0, Map.class);
Assert.state(valueHolder != null, "Expected Map constructor argument at index 0");
Map<String, HttpServiceGroup> targetMap = (Map<String, HttpServiceGroup>) valueHolder.getValue();
Map<String, RegisteredGroup> targetMap = (Map<String, RegisteredGroup>) valueHolder.getValue();
Assert.state(targetMap != null, "No constructor argument value");
this.groupMap.forEach((name, group) -> {
HttpServiceGroup previousGroup = targetMap.putIfAbsent(name, group);
RegisteredGroup previousGroup = targetMap.putIfAbsent(name, group);
if (previousGroup != null) {
if (!compatibleClientTypes(group.clientType(), previousGroup.clientType())) {
throw new IllegalArgumentException("ClientType conflict for group '" + name + "'");
}
previousGroup.httpServiceTypes().addAll(group.httpServiceTypes());
previousGroup.addHttpServiceTypeNames(group.httpServiceTypeNames());
}
});
}
@ -206,14 +207,23 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -206,14 +207,23 @@ public abstract class AbstractHttpServiceRegistrar implements
clientTypeB == HttpServiceGroup.ClientType.UNSPECIFIED);
}
private Object getProxyInstance(String registryBeanName, String groupName, Class<?> type) {
private Object getProxyInstance(String registryBeanName, String groupName, String type) {
Assert.state(this.beanFactory != null, "BeanFactory has not been set");
HttpServiceProxyRegistry registry = this.beanFactory.getBean(registryBeanName, HttpServiceProxyRegistry.class);
Object proxy = registry.getClient(groupName, type);
Assert.notNull(proxy, "No proxy for HTTP Service [" + type.getName() + "]");
Object proxy = registry.getClient(groupName, loadClass(type));
Assert.notNull(proxy, "No proxy for HTTP Service [" + type + "]");
return proxy;
}
private static Class<?> loadClass(String type) {
try {
return ClassUtils.forName(type, AbstractHttpServiceRegistrar.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load '" + type + "'", ex);
}
}
/**
* Registry API to allow subclasses to register HTTP Services.
@ -303,7 +313,7 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -303,7 +313,7 @@ public abstract class AbstractHttpServiceRegistrar implements
@Override
public GroupSpec register(Class<?>... serviceTypes) {
addHttpServiceTypes(this.groupName, this.clientType, serviceTypes);
getOrCreateGroup(groupName, clientType).addHttpServiceTypes(serviceTypes);
return this;
}
@ -325,29 +335,85 @@ public abstract class AbstractHttpServiceRegistrar implements @@ -325,29 +335,85 @@ public abstract class AbstractHttpServiceRegistrar implements
private void detect(String groupName, HttpServiceGroup.ClientType clientType, String packageName) {
for (BeanDefinition definition : getScanner().findCandidateComponents(packageName)) {
String className = definition.getBeanClassName();
if (className != null) {
try {
Class<?> clazz = ClassUtils.forName(className, getClass().getClassLoader());
addHttpServiceTypes(groupName, clientType, clazz);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load '" + className + "'", ex);
}
if (definition.getBeanClassName() != null) {
getOrCreateGroup(groupName, clientType).addHttpServiceTypeName(definition.getBeanClassName());
}
}
}
private void addHttpServiceTypes(
String groupName, HttpServiceGroup.ClientType clientType, Class<?>... serviceTypes) {
private RegisteredGroup getOrCreateGroup(String groupName, HttpServiceGroup.ClientType clientType) {
return groupMap.computeIfAbsent(groupName, name -> new RegisteredGroup(name, clientType));
}
}
}
/**
* A simple holder of registered HTTP Service type names, deferring the
* loading of classes until {@link #httpServiceTypes()} is called.
*/
private static class RegisteredGroup implements HttpServiceGroup {
private final String name;
private final Set<String> httpServiceTypeNames = new LinkedHashSet<>();
private final ClientType clientType;
public RegisteredGroup(String name, ClientType clientType) {
this.name = name;
this.clientType = clientType;
}
@Override
public String name() {
return this.name;
}
public Set<String> httpServiceTypeNames() {
return this.httpServiceTypeNames;
}
groupMap.computeIfAbsent(groupName, name -> new RegisteredGroup(name, new LinkedHashSet<>(), clientType))
.httpServiceTypes().addAll(Arrays.asList(serviceTypes));
@Override
public Set<Class<?>> httpServiceTypes() {
return httpServiceTypeNames.stream()
.map(AbstractHttpServiceRegistrar::loadClass)
.collect(Collectors.toSet());
}
@Override
public ClientType clientType() {
return this.clientType;
}
public void addHttpServiceTypes(Class<?>... httpServiceTypes) {
for (Class<?> type : httpServiceTypes) {
this.httpServiceTypeNames.add(type.getName());
}
}
private record RegisteredGroup(
String name, Set<Class<?>> httpServiceTypes, ClientType clientType) implements HttpServiceGroup {
public void addHttpServiceTypeNames(Collection<String> httpServiceTypeNames) {
this.httpServiceTypeNames.addAll(httpServiceTypeNames);
}
public void addHttpServiceTypeName(String httpServiceTypeName) {
this.httpServiceTypeNames.add(httpServiceTypeName);
}
@Override
public final boolean equals(Object other) {
return (other instanceof RegisteredGroup otherGroup && this.name.equals(otherGroup.name));
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return "RegisteredGroup[name='" + this.name + "', httpServiceTypes=" +
this.httpServiceTypeNames + ", clientType=" + this.clientType + "]";
}
}

Loading…
Cancel
Save