8000 Lazy loading of HTTP Service classes · spring-projects/spring-framework@42409e2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 42409e2

Browse files
committed
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
1 parent 49e24b7 commit 42409e2

File tree

1 file changed

+93
-27
lines changed

1 file changed

+93
-27
lines changed

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

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616

1717
package org.springframework.web.service.registry;
1818

19-
import java.util.Arrays;
19+
import java.util.Collection;
2020
import java.util.LinkedHashMap;
2121
import java.util.LinkedHashSet;
2222
import java.util.Map;
2323
import java.util.Set;
24+
import java.util.stream.Collectors;
2425

2526
import org.jspecify.annotations.Nullable;
2627

@@ -89,7 +90,7 @@ public abstract class AbstractHttpServiceRegistrar implements
8990

9091
private @Nullable BeanFactory beanFactory;
9192

92-
private final Map<String, HttpServiceGroup> groupMap = new LinkedHashMap<>();
93+
private final Map<String, RegisteredGroup> groupMap = new LinkedHashMap<>();
9394

9495
private @Nullable ClassPathScanningCandidateComponentProvider scanner;
9596

@@ -143,11 +144,11 @@ public final void registerBeanDefinitions(
143144

144145
mergeHttpServices(proxyRegistryBeanDef);
145146

146-
this.groupMap.forEach((groupName, group) -> group.httpServiceTypes().forEach(type -> {
147+
this.groupMap.forEach((groupName, group) -> group.httpServiceTypeNames().forEach(type -> {
147148
GenericBeanDefinition proxyBeanDef = new GenericBeanDefinition();
148-
proxyBeanDef.setBeanClass(type);
149-
proxyBeanDef.setInstanceSupplier(() -> getProxyInstance(proxyRegistryBeanName, groupName, type));
149+
proxyBeanDef.setBeanClassName(type);
150150
String beanName = (groupName + "." + beanNameGenerator.generateBeanName(proxyBeanDef, beanRegistry));
151+
proxyBeanDef.setInstanceSupplier(() -> getProxyInstance(proxyRegistryBeanName, groupName, type));
151152
if (!beanRegistry.containsBeanDefinition(beanName)) {
152153
beanRegistry.registerBeanDefinition(beanName, proxyBeanDef);
153154
}
@@ -184,16 +185,16 @@ private void mergeHttpServices(GenericBeanDefinition proxyRegistryBeanDef) {
184185
ConstructorArgumentValues args = proxyRegistryBeanDef.getConstructorArgumentValues();
185186
ConstructorArgumentValues.ValueHolder valueHolder = args.getArgumentValue(0, Map.class);
186187
Assert.state(valueHolder != null, "Expected Map constructor argument at index 0");
187-
Map<String, HttpServiceGroup> targetMap = (Map<String, HttpServiceGroup>) valueHolder.getValue();
188+
Map<String, RegisteredGroup> targetMap = (Map<String, RegisteredGroup>) valueHolder.getValue();
188189
Assert.state(targetMap != null, "No constructor argument value");
189190

190191
this.groupMap.forEach((name, group) -> {
191-
HttpServiceGroup previousGroup = targetMap.putIfAbsent(name, group);
192+
RegisteredGroup previousGroup = targetMap.putIfAbsent(name, group);
192193
if (previousGroup != null) {
193194
if (!compatibleClientTypes(group.clientType(), previousGroup.clientType())) {
194195
throw new IllegalArgumentException("ClientType conflict for group '" + name + "'");
195196
}
196-
previousGroup.httpServiceTypes().addAll(group.httpServiceTypes());
197+
previousGroup.addHttpServiceTypeNames(group.httpServiceTypeNames());
197198
}
198199
});
199200
}
@@ -206,14 +207,23 @@ private static boolean compatibleClientTypes(
206207
clientTypeB == HttpServiceGroup.ClientType.UNSPECIFIED);
207208
}
208209

209-
private Object getProxyInstance(String registryBeanName, String groupName, Class<?> type) {
210+
private Object getProxyInstance(String registryBeanName, String groupName, String type) {
210211
Assert.state(this.beanFactory != null, "BeanFactory has not been set");
211212
HttpServiceProxyRegistry registry = this.beanFactory.getBean(registryBeanName, HttpServiceProxyRegistry.class);
212-
Object proxy = registry.getClient(groupName, type);
213-
Assert.notNull(proxy, "No proxy for HTTP Service [" + type.getName() + "]");
213+
Object proxy = registry.getClient(groupName, loadClass(type));
214+
Assert.notNull(proxy, "No proxy for HTTP Service [" + type + "]");
214215
return proxy;
215216
}
216217

218+
private static Class<?> loadClass(String type) {
219+
try {
220+
return ClassUtils.forName(type, AbstractHttpServiceRegistrar.class.getClassLoader());
221+
}
222+
catch (ClassNotFoundException ex) {
223+
throw new IllegalStateException("Failed to load '" + type + "'", ex);
224+
}
225+
}
226+
217227

218228
/**
219229
* Registry API to allow subclasses to register HTTP Services.
@@ -303,7 +313,7 @@ else if (defaultClientType != HttpServiceGroup.ClientType.UNSPECIFIED) {
303313

304314
@Override
305315
public GroupSpec register(Class<?>... serviceTypes) {
306-
addHttpServiceTypes(this.groupName, this.clientType, serviceTypes);
316+
getOrCreateGroup(groupName, clientType).addHttpServiceTypes(serviceTypes);
307317
return this;
308318
}
309319

@@ -325,29 +335,85 @@ public GroupSpec detectInBasePackages(String... packageNames) {
325335

326336
private void detect(String groupName, HttpServiceGroup.ClientType clientType, String packageName) {
327337
for (BeanDefinition definition : getScanner().findCandidateComponents(packageName)) {
328-
String className = definition.getBeanClassName();
329-
if (className != null) {
330-
try {
331-
Class<?> clazz = ClassUtils.forName(className, getClass().getClassLoader());
332-
addHttpServiceTypes(groupName, clientType, clazz);
333-
}
334-
catch (ClassNotFoundException ex) {
335-
throw new IllegalStateException("Failed to load '" + className + "'", ex);
336-
}
338+
if (definition.getBeanClassName() != null) {
339+
getOrCreateGroup(groupName, clientType).addHttpServiceTypeName(definition.getBeanClassName());
337340
}
338341
}
339342
}
340343

341-
private void addHttpServiceTypes(
342-
String groupName, HttpServiceGroup.ClientType clientType, Class<?>... serviceTypes) {
344+
private RegisteredGroup getOrCreateGroup(String groupName, HttpServiceGroup.ClientType clientType) {
345+
return groupMap.computeIfAbsent(groupName, name -> new RegisteredGroup(name, clientType));
346+
}
347+
}
348+
}
349+
350+
351+
/**
352+
* A simple holder of registered HTTP Service type names, deferring the
353+
* loading of classes until {@link #httpServiceTypes()} is called.
354+
*/
355+
private static class RegisteredGroup implements HttpServiceGroup {
356+
357+
private final String name;
358+
359+
private final Set<String> httpServiceTypeNames = new LinkedHashSet<>();
360+
361+
private final ClientType clientType;
362+
363+
public RegisteredGroup(String name, ClientType clientType) {
364+
this.name = name;
365+
this.clientType = clientType;
366+
}
367+
368+
@Override
369+
public String name() {
370+
return this.name;
371+
}
372+
373+
public Set<String> httpServiceTypeNames() {
374+
return this.httpServiceTypeNames;
375+
}
343376

344-
groupMap.computeIfAbsent(groupName, name -> new RegisteredGroup(name, new LinkedHashSet<>(), clientType))
345-
.httpServiceTypes().addAll(Arrays.asList(serviceTypes));
377+
@Override
378+
public Set<Class<?>> httpServiceTypes() {
379+
return httpServiceTypeNames.stream()
380+
.map(AbstractHttpServiceRegistrar::loadClass)
381+
.collect(Collectors.toSet());
382+
}
383+
384+
@Override
385+
public ClientType clientType() {
386+
return this.clientType;
387+
}
388+
389+
public void addHttpServiceTypes(Class<?>... httpServiceTypes) {
390+
for (Class<?> type : httpServiceTypes) {
391+
this.httpServiceTypeNames.add(type.getName());
346392
}
347393
}
348394

349-
private record RegisteredGroup(
350-
String name, Set<Class<?>> httpServiceTypes, ClientType clientType) implements HttpServiceGroup {
395+
public void addHttpServiceTypeNames(Collection<String> httpServiceTypeNames) {
396+
this.httpServiceTypeNames.addAll(httpServiceTypeNames);
397+
}
398+
399+
public void addHttpServiceTypeName(String httpServiceTypeName) {
400+
this.httpServiceTypeNames.add(httpServiceTypeName);
401+
}
402+
403+
@Override
404+
public final boolean equals(Object other) {
405+
return (other instanceof RegisteredGroup otherGroup && this.name.equals(otherGroup.name));
406+
}
407+
408+
@Override
409+
public int hashCode() {
410+
return this.name.hashCode();
411+
}
412+
413+
@Override
414+
public String toString() {
415+
return "RegisteredGroup[name='" + this.name + "', httpServiceTypes=" +
416+
this.httpServiceTypeNames + ", clientType=" + this.clientType + "]";
351417
}
352418
}
353419

0 commit comments

Comments
 (0)
0