Skip to content

Commit 2caa808

Browse files
committed
Support for scalable beanfactory
1 parent abf9ce8 commit 2caa808

File tree

4 files changed

+3821
-3495
lines changed

4 files changed

+3821
-3495
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.beans.factory.support;
17+
18+
import java.util.ArrayDeque;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.HashMap;
22+
import java.util.LinkedHashSet;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Queue;
26+
import java.util.Set;
27+
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.concurrent.TimeUnit;
29+
30+
import org.springframework.beans.BeansException;
31+
import org.springframework.beans.factory.BeanFactory;
32+
import org.springframework.lang.Nullable;
33+
34+
/**
35+
* Extends the behavior of {@link DefaultListableBeanFactory} by providing a type to bean name index.
36+
* <p>
37+
* Since the configuration is frozen, bean definitions do not change and hence
38+
* we can build the mapping by going through all the beans in the registry once.
39+
* <p>
40+
* Spring's current implementation is O(N * M) where N is number of beans in
41+
* registry and M is the number of beans that get loaded during the
42+
* startup phase. To find the bean names, Spring computes the types for each of
43+
* the bean and matches that with the given type. It would do this against all
44+
* the N beans in the registry for every type that is being wired.
45+
* <p>
46+
* Instead the type is extracted once for all the beans and stored in a local
47+
* cache. The beans can then be retrieved in O(1) using the cache for all future
48+
* wiring of that type. Additionally we gather all the super types for our
49+
* bean-types for type match to satisfy wirings by parent types. If a given type
50+
* is not found in our cache, we revert to using Spring's default resolution
51+
* mechanism.
52+
*
53+
* @author Rahul Shinde
54+
*/
55+
public class ScalableDefaultListableBeanFactory extends DefaultListableBeanFactory {
56+
57+
/**
58+
* Mapping of Class to 1..* bean names.
59+
*/
60+
private final Map<Class<?>, String[]> beanNamesByTypeMappings;
61+
62+
/**
63+
* Create a new ScalableDefaultListableBeanFactory.
64+
*/
65+
public ScalableDefaultListableBeanFactory() {
66+
this.beanNamesByTypeMappings = new ConcurrentHashMap<>();
67+
}
68+
69+
/**
70+
* Create a new DefaultListableBeanFactory with the given parent.
71+
*
72+
* @param parentBeanFactory the parent BeanFactory
73+
*/
74+
public ScalableDefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
75+
super(parentBeanFactory);
76+
this.beanNamesByTypeMappings = new ConcurrentHashMap<>();
77+
}
78+
79+
/**
80+
* Looks for the mapping inside our local cache first,
81+
* else delegates to the super class method {@link DefaultListableBeanFactory#getBeansOfType(Class, boolean, boolean)}.
82+
*/
83+
@Override
84+
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
85+
String[] resolvedBeanNames = null;
86+
if (type != null) {
87+
resolvedBeanNames = this.beanNamesByTypeMappings.get(type);
88+
}
89+
return resolvedBeanNames != null ? resolvedBeanNames : super.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
90+
}
91+
92+
/**
93+
* Acts as a hook to populates our cache just before creating the eager beans.
94+
* The configuration should already be frozen as per Spring's implementation.
95+
*/
96+
@Override
97+
public void preInstantiateSingletons() throws BeansException {
98+
populateBeanTypeToNameMappings();
99+
super.preInstantiateSingletons();
100+
}
101+
102+
/**
103+
* Create mapping for types to bean-names using all the available bean definitions.
104+
* Additionally we gather all the parent Class(es) for the given bean type as part of the mappings.
105+
*/
106+
private void populateBeanTypeToNameMappings() {
107+
Map<Class<?>, List<String>> localBeanTypeMappings = new HashMap<>();
108+
long startNanos = System.nanoTime();
109+
Arrays.stream(this.getBeanDefinitionNames())
110+
// Filter bean definitions names that are alias as these are redundant
111+
.filter(beanName -> !isAlias(beanName))
112+
.forEach(beanName -> {
113+
try {
114+
// Use's Spring implementation to identify the target class for a given bean name.
115+
Class<?> targetClass = getType(beanName);
116+
if (targetClass != null) {
117+
// fetch all the super types as well for this targetClass
118+
Set<Class<?>> classTypes = getSuperTypes(targetClass);
119+
// add the current type as well to the mapping
120+
classTypes.add(targetClass);
121+
classTypes.forEach(clazz -> localBeanTypeMappings.compute(clazz, (k, v) -> {
122+
v = v == null ? new ArrayList<>() : v;
123+
v.add(beanName);
124+
return v;
125+
}));
126+
}
127+
}
128+
catch (Exception ex) {
129+
// ignore the missing bean
130+
if (logger.isTraceEnabled()) {
131+
logger.trace("No bean named '" + beanName + "' found ");
132+
}
133+
}
134+
});
135+
136+
// Convert values from List<String> to String[] as expected by getBeanNamesForType(...) API
137+
localBeanTypeMappings.forEach((k, v) -> {
138+
this.beanNamesByTypeMappings.put(k, v.toArray(new String[0]));
139+
});
140+
141+
if (logger.isDebugEnabled()) {
142+
logger.debug(String.format("DefaultListableBeanFactory beanNamesByType mappings populated with total records=%d in %d millis",
143+
this.beanNamesByTypeMappings.size(), TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos)));
144+
}
145+
}
146+
147+
/**
148+
* Finds all the super types for the given {@link Class}.
149+
*
150+
* @param givenClazz input class
151+
* @return set of all super types that it inherits from
152+
*/
153+
static Set<Class<?>> getSuperTypes(Class<?> givenClazz) {
154+
final Set<Class<?>> superTypeSet = new LinkedHashSet<>();
155+
final Queue<Class<?>> possibleCandidates = new ArrayDeque<>();
156+
possibleCandidates.add(givenClazz);
157+
if (givenClazz.isInterface()) {
158+
possibleCandidates.add(Object.class);
159+
}
160+
while (!possibleCandidates.isEmpty()) {
161+
Class<?> clz = possibleCandidates.remove();
162+
// skip the input class as we are only interested in parent types
163+
if (!clz.equals(givenClazz)) {
164+
superTypeSet.add(clz);
165+
}
166+
Class<?> superClz = clz.getSuperclass();
167+
if (superClz != null) {
168+
possibleCandidates.add(superClz);
169+
}
170+
possibleCandidates.addAll(Arrays.asList(clz.getInterfaces()));
171+
}
172+
return superTypeSet;
173+
}
174+
175+
}

0 commit comments

Comments
 (0)