Skip to content

Commit eb0ab84

Browse files
committed
Lookup methods can support arguments, find a target bean based on the return type, and be identified by an @lookup annotation
Issue: SPR-7431 Issue: SPR-5192
1 parent e753f23 commit eb0ab84

File tree

11 files changed

+428
-57
lines changed

11 files changed

+428
-57
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ public interface BeanFactory {
170170
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
171171
* overriding the specified default arguments (if any) in the bean definition.
172172
* @param name the name of the bean to retrieve
173-
* @param args arguments to use if creating a prototype using explicit arguments to a
174-
* static factory method. It is invalid to use a non-null args value in any other case.
173+
* @param args arguments to use if creating a prototype using explicit arguments
175174
* @return an instance of the bean
176175
* @throws NoSuchBeanDefinitionException if there is no such bean definition
177176
* @throws BeanDefinitionStoreException if arguments have been given but

spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.reflect.Method;
2626
import java.lang.reflect.Modifier;
2727
import java.util.ArrayList;
28+
import java.util.Collections;
2829
import java.util.Iterator;
2930
import java.util.LinkedHashSet;
3031
import java.util.LinkedList;
@@ -44,10 +45,12 @@
4445
import org.springframework.beans.factory.BeanFactory;
4546
import org.springframework.beans.factory.BeanFactoryAware;
4647
import org.springframework.beans.factory.BeanFactoryUtils;
48+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
4749
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4850
import org.springframework.beans.factory.config.DependencyDescriptor;
4951
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
5052
import org.springframework.beans.factory.config.RuntimeBeanReference;
53+
import org.springframework.beans.factory.support.LookupOverride;
5154
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
5255
import org.springframework.beans.factory.support.RootBeanDefinition;
5356
import org.springframework.core.BridgeMethodResolver;
@@ -96,6 +99,12 @@
9699
* thus the latter configuration will override the former for properties wired through
97100
* both approaches.
98101
*
102+
* <p>In addition to regular injection points as discussed above, this post-processor
103+
* also handles Spring's {@link Lookup @Lookup} annotation which identifies lookup
104+
* methods to be replaced by the container at runtime. This is essentially a type-safe
105+
* version of {@code getBean(Class, args)} and {@code getBean(String, args)},
106+
* See {@link Lookup @Lookup's javadoc} for details.
107+
*
99108
* @author Juergen Hoeller
100109
* @author Mark Fisher
101110
* @since 2.5
@@ -119,6 +128,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
119128

120129
private ConfigurableListableBeanFactory beanFactory;
121130

131+
private final Set<String> lookupMethodsChecked =
132+
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(64));
133+
122134
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache =
123135
new ConcurrentHashMap<Class<?>, Constructor<?>[]>(64);
124136

@@ -224,7 +236,28 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C
224236
}
225237

226238
@Override
227-
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
239+
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeansException {
240+
if (!this.lookupMethodsChecked.contains(beanName)) {
241+
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
242+
@Override
243+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
244+
Lookup lookup = method.getAnnotation(Lookup.class);
245+
if (lookup != null) {
246+
LookupOverride override = new LookupOverride(method, lookup.value());
247+
try {
248+
RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName);
249+
mbd.getMethodOverrides().addOverride(override);
250+
}
251+
catch (NoSuchBeanDefinitionException ex) {
252+
throw new BeanCreationException(beanName,
253+
"Cannot apply @Lookup to beans without corresponding bean definition");
254+
}
255+
}
256+
}
257+
});
258+
this.lookupMethodsChecked.add(beanName);
259+
}
260+
228261
// Quick check on the concurrent map first, with minimal locking.
229262
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
230263
if (candidateConstructors == null) {
@@ -239,7 +272,8 @@ public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, Strin
239272
AnnotationAttributes annotation = findAutowiredAnnotation(candidate);
240273
if (annotation != null) {
241274
if (requiredConstructor != null) {
242-
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
275+
throw new BeanCreationException(beanName,
276+
"Invalid autowire-marked constructor: " + candidate +
243277
". Found another constructor with 'required' Autowired annotation: " +
244278
requiredConstructor);
245279
}
@@ -250,10 +284,10 @@ public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, Strin
250284
boolean required = determineRequiredStatus(annotation);
251285
if (required) {
252286
if (!candidates.isEmpty()) {
253-
throw new BeanCreationException(
287+
throw new BeanCreationException(beanName,
254288
"Invalid autowire-marked constructors: " + candidates +
255289
". Found another constructor with 'required' Autowired annotation: " +
256-
requiredConstructor);
290+
candidate);
257291
}
258292
requiredConstructor = candidate;
259293
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2014 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+
17+
package org.springframework.beans.factory.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* An annotation that indicates 'lookup' methods, to be overridden by the container
27+
* to redirect them back to the {@link org.springframework.beans.factory.BeanFactory}
28+
* for a {@code getBean} call. This is essentially an annotation-based version of the
29+
* XML {@code lookup-method} attribute, resulting in the same runtime arrangement.
30+
*
31+
* <p>The resolution of the target bean can either be based on the return type
32+
* ({@code getBean(Class)}) or on a suggested bean name ({@code getBean(String)}),
33+
* in both cases passing the method's arguments to the {@code getBean} call
34+
* for applying them as target factory method arguments or constructor arguments.
35+
*
36+
* <p>Such lookup methods can have default (stub) implementations that will simply
37+
* get replaced by the container, or they can be declared as abstract - for the
38+
* container to fill them in at runtime. In both cases, the container will generate
39+
* runtime subclasses of the method's containing class via CGLIB, which is why such
40+
* lookup methods can only work on beans that the container instantiates through
41+
* regular constructors (i.e. lookup methods cannot get replaced on beans returned
42+
* from factory methods where we can't dynamically provide a subclass for them).
43+
*
44+
* <p>Note: When used with component scanning or any other mechanism that filters
45+
* out abstract beans, provide stub implementations of your lookup methods to be
46+
* able to declare them as concrete classes.
47+
*
48+
* @author Juergen Hoeller
49+
* @since 4.1
50+
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
51+
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
52+
*/
53+
@Target(ElementType.METHOD)
54+
@Retention(RetentionPolicy.RUNTIME)
55+
@Documented
56+
public @interface Lookup {
57+
58+
/**
59+
* This annotation attribute may suggest a target bean name to look up.
60+
* If not specified, the target bean will be resolved based on the
61+
* annotated method's return type declaration.
62+
*/
63+
String value() default "";
64+
65+
}

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ protected <T> T doGetBean(
285285
if (dependsOn != null) {
286286
for (String dependsOnBean : dependsOn) {
287287
if (isDependent(beanName, dependsOnBean)) {
288-
throw new BeanCreationException("Circular depends-on relationship between '" +
289-
beanName + "' and '" + dependsOnBean + "'");
288+
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
289+
"Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'");
290290
}
291291
registerDependentBean(dependsOnBean, beanName);
292292
getBean(dependsOnBean);
@@ -1274,7 +1274,7 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
12741274
// Check validity of the usage of the args parameter. This can
12751275
// only be used for prototypes constructed via a factory method.
12761276
if (args != null && !mbd.isPrototype()) {
1277-
throw new BeanDefinitionStoreException(
1277+
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
12781278
"Can only specify arguments for the getBean method when referring to a prototype bean definition");
12791279
}
12801280
}
@@ -1625,8 +1625,7 @@ protected void registerDisposableBeanIfNecessary(String beanName, Object bean, R
16251625
* instantiation within this class is performed by this method.
16261626
* @param beanName the name of the bean
16271627
* @param mbd the merged bean definition for the bean
1628-
* @param args arguments to use if creating a prototype using explicit arguments to a
1629-
* static factory method. This parameter must be {@code null} except in this case.
1628+
* @param args arguments to use if creating a prototype using explicit arguments
16301629
* @return a new instance of the bean
16311630
* @throws BeanCreationException if the bean could not be created
16321631
*/

spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.springframework.beans.BeanInstantiationException;
2626
import org.springframework.beans.BeanUtils;
2727
import org.springframework.beans.factory.BeanFactory;
28-
2928
import org.springframework.cglib.core.SpringNamingPolicy;
3029
import org.springframework.cglib.proxy.Callback;
3130
import org.springframework.cglib.proxy.CallbackFilter;
@@ -34,6 +33,7 @@
3433
import org.springframework.cglib.proxy.MethodInterceptor;
3534
import org.springframework.cglib.proxy.MethodProxy;
3635
import org.springframework.cglib.proxy.NoOp;
36+
import org.springframework.util.StringUtils;
3737

3838
/**
3939
* Default object instantiation strategy for use in BeanFactories.
@@ -89,14 +89,13 @@ protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinitio
8989
*/
9090
private static class CglibSubclassCreator {
9191

92-
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[] { NoOp.class,
93-
LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class };
92+
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
93+
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
9494

9595
private final RootBeanDefinition beanDefinition;
9696

9797
private final BeanFactory owner;
9898

99-
10099
CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
101100
this.beanDefinition = beanDefinition;
102101
this.owner = owner;
@@ -113,7 +112,6 @@ private static class CglibSubclassCreator {
113112
*/
114113
Object instantiate(Constructor<?> ctor, Object[] args) {
115114
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
116-
117115
Object instance;
118116
if (ctor == null) {
119117
instance = BeanUtils.instantiate(subclass);
@@ -123,19 +121,17 @@ Object instantiate(Constructor<?> ctor, Object[] args) {
123121
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
124122
instance = enhancedSubclassConstructor.newInstance(args);
125123
}
126-
catch (Exception e) {
124+
catch (Exception ex) {
127125
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format(
128-
"Failed to invoke construcor for CGLIB enhanced subclass [%s]", subclass.getName()), e);
126+
"Failed to invoke constructor for CGLIB enhanced subclass [%s]", subclass.getName()), ex);
129127
}
130128
}
131-
132129
// SPR-10785: set callbacks directly on the instance instead of in the
133130
// enhanced class (via the Enhancer) in order to avoid memory leaks.
134131
Factory factory = (Factory) instance;
135-
factory.setCallbacks(new Callback[] { NoOp.INSTANCE,//
136-
new LookupOverrideMethodInterceptor(beanDefinition, owner),//
137-
new ReplaceOverrideMethodInterceptor(beanDefinition, owner) });
138-
132+
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
133+
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
134+
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
139135
return instance;
140136
}
141137

@@ -153,6 +149,7 @@ private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
153149
}
154150
}
155151

152+
156153
/**
157154
* Class providing hashCode and equals methods required by CGLIB to
158155
* ensure that CGLIB doesn't generate a distinct class per bean.
@@ -162,7 +159,6 @@ private static class CglibIdentitySupport {
162159

163160
private final RootBeanDefinition beanDefinition;
164161

165-
166162
CglibIdentitySupport(RootBeanDefinition beanDefinition) {
167163
this.beanDefinition = beanDefinition;
168164
}
@@ -173,8 +169,8 @@ RootBeanDefinition getBeanDefinition() {
173169

174170
@Override
175171
public boolean equals(Object other) {
176-
return other.getClass().equals(this.getClass())
177-
&& ((CglibIdentitySupport) other).getBeanDefinition().equals(this.getBeanDefinition());
172+
return (getClass().equals(other.getClass()) &&
173+
this.beanDefinition.equals(((CglibIdentitySupport) other).beanDefinition));
178174
}
179175

180176
@Override
@@ -183,14 +179,14 @@ public int hashCode() {
183179
}
184180
}
185181

182+
186183
/**
187184
* CGLIB callback for filtering method interception behavior.
188185
*/
189186
private static class MethodOverrideCallbackFilter extends CglibIdentitySupport implements CallbackFilter {
190187

191188
private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class);
192189

193-
194190
MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) {
195191
super(beanDefinition);
196192
}
@@ -210,11 +206,12 @@ else if (methodOverride instanceof LookupOverride) {
210206
else if (methodOverride instanceof ReplaceOverride) {
211207
return METHOD_REPLACER;
212208
}
213-
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: "
214-
+ methodOverride.getClass().getName());
209+
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " +
210+
methodOverride.getClass().getName());
215211
}
216212
}
217213

214+
218215
/**
219216
* CGLIB MethodInterceptor to override methods, replacing them with an
220217
* implementation that returns a bean looked up in the container.
@@ -223,7 +220,6 @@ private static class LookupOverrideMethodInterceptor extends CglibIdentitySuppor
223220

224221
private final BeanFactory owner;
225222

226-
227223
LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
228224
super(beanDefinition);
229225
this.owner = owner;
@@ -233,10 +229,17 @@ private static class LookupOverrideMethodInterceptor extends CglibIdentitySuppor
233229
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
234230
// Cast is safe, as CallbackFilter filters are used selectively.
235231
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
236-
return this.owner.getBean(lo.getBeanName());
232+
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
233+
if (StringUtils.hasText(lo.getBeanName())) {
234+
return this.owner.getBean(lo.getBeanName(), argsToUse);
235+
}
236+
else {
237+
return this.owner.getBean(method.getReturnType(), argsToUse);
238+
}
237239
}
238240
}
239241

242+
240243
/**
241244
* CGLIB MethodInterceptor to override methods, replacing them with a call
242245
* to a generic MethodReplacer.
@@ -245,7 +248,6 @@ private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySuppo
245248

246249
private final BeanFactory owner;
247250

248-
249251
ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
250252
super(beanDefinition);
251253
this.owner = owner;
@@ -255,7 +257,7 @@ private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySuppo
255257
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
256258
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
257259
// TODO could cache if a singleton for minor performance optimization
258-
MethodReplacer mr = owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
260+
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
259261
return mr.reimplement(obj, method, args);
260262
}
261263
}

0 commit comments

Comments
 (0)