提交 eb0ab843 编写于 作者: J Juergen Hoeller

Lookup methods can support arguments, find a target bean based on the return...

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
上级 e753f231
......@@ -170,8 +170,7 @@ public interface BeanFactory {
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
* overriding the specified default arguments (if any) in the bean definition.
* @param name the name of the bean to retrieve
* @param args arguments to use if creating a prototype using explicit arguments to a
* static factory method. It is invalid to use a non-null args value in any other case.
* @param args arguments to use if creating a prototype using explicit arguments
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanDefinitionStoreException if arguments have been given but
......
......@@ -25,6 +25,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
......@@ -44,10 +45,12 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.LookupOverride;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.BridgeMethodResolver;
......@@ -96,6 +99,12 @@ import org.springframework.util.StringUtils;
* thus the latter configuration will override the former for properties wired through
* both approaches.
*
* <p>In addition to regular injection points as discussed above, this post-processor
* also handles Spring's {@link Lookup @Lookup} annotation which identifies lookup
* methods to be replaced by the container at runtime. This is essentially a type-safe
* version of {@code getBean(Class, args)} and {@code getBean(String, args)},
* See {@link Lookup @Lookup's javadoc} for details.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @since 2.5
......@@ -119,6 +128,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
private ConfigurableListableBeanFactory beanFactory;
private final Set<String> lookupMethodsChecked =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(64));
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache =
new ConcurrentHashMap<Class<?>, Constructor<?>[]>(64);
......@@ -224,7 +236,28 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeansException {
if (!this.lookupMethodsChecked.contains(beanName)) {
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Lookup lookup = method.getAnnotation(Lookup.class);
if (lookup != null) {
LookupOverride override = new LookupOverride(method, lookup.value());
try {
RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName);
mbd.getMethodOverrides().addOverride(override);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(beanName,
"Cannot apply @Lookup to beans without corresponding bean definition");
}
}
}
});
this.lookupMethodsChecked.add(beanName);
}
// Quick check on the concurrent map first, with minimal locking.
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
......@@ -239,7 +272,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
AnnotationAttributes annotation = findAutowiredAnnotation(candidate);
if (annotation != null) {
if (requiredConstructor != null) {
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructor: " + candidate +
". Found another constructor with 'required' Autowired annotation: " +
requiredConstructor);
}
......@@ -250,10 +284,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
boolean required = determineRequiredStatus(annotation);
if (required) {
if (!candidates.isEmpty()) {
throw new BeanCreationException(
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructors: " + candidates +
". Found another constructor with 'required' Autowired annotation: " +
requiredConstructor);
candidate);
}
requiredConstructor = candidate;
}
......
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation that indicates 'lookup' methods, to be overridden by the container
* to redirect them back to the {@link org.springframework.beans.factory.BeanFactory}
* for a {@code getBean} call. This is essentially an annotation-based version of the
* XML {@code lookup-method} attribute, resulting in the same runtime arrangement.
*
* <p>The resolution of the target bean can either be based on the return type
* ({@code getBean(Class)}) or on a suggested bean name ({@code getBean(String)}),
* in both cases passing the method's arguments to the {@code getBean} call
* for applying them as target factory method arguments or constructor arguments.
*
* <p>Such lookup methods can have default (stub) implementations that will simply
* get replaced by the container, or they can be declared as abstract - for the
* container to fill them in at runtime. In both cases, the container will generate
* runtime subclasses of the method's containing class via CGLIB, which is why such
* lookup methods can only work on beans that the container instantiates through
* regular constructors (i.e. lookup methods cannot get replaced on beans returned
* from factory methods where we can't dynamically provide a subclass for them).
*
* <p>Note: When used with component scanning or any other mechanism that filters
* out abstract beans, provide stub implementations of your lookup methods to be
* able to declare them as concrete classes.
*
* @author Juergen Hoeller
* @since 4.1
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lookup {
/**
* This annotation attribute may suggest a target bean name to look up.
* If not specified, the target bean will be resolved based on the
* annotated method's return type declaration.
*/
String value() default "";
}
......@@ -285,8 +285,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
if (isDependent(beanName, dependsOnBean)) {
throw new BeanCreationException("Circular depends-on relationship between '" +
beanName + "' and '" + dependsOnBean + "'");
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'");
}
registerDependentBean(dependsOnBean, beanName);
getBean(dependsOnBean);
......@@ -1274,7 +1274,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// Check validity of the usage of the args parameter. This can
// only be used for prototypes constructed via a factory method.
if (args != null && !mbd.isPrototype()) {
throw new BeanDefinitionStoreException(
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
"Can only specify arguments for the getBean method when referring to a prototype bean definition");
}
}
......@@ -1625,8 +1625,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* instantiation within this class is performed by this method.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args arguments to use if creating a prototype using explicit arguments to a
* static factory method. This parameter must be {@code null} except in this case.
* @param args arguments to use if creating a prototype using explicit arguments
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
*/
......
......@@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
......@@ -34,6 +33,7 @@ import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.util.StringUtils;
/**
* Default object instantiation strategy for use in BeanFactories.
......@@ -89,14 +89,13 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
*/
private static class CglibSubclassCreator {
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[] { NoOp.class,
LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class };
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
private final RootBeanDefinition beanDefinition;
private final BeanFactory owner;
CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
this.beanDefinition = beanDefinition;
this.owner = owner;
......@@ -113,7 +112,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
*/
Object instantiate(Constructor<?> ctor, Object[] args) {
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance;
if (ctor == null) {
instance = BeanUtils.instantiate(subclass);
......@@ -123,19 +121,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
instance = enhancedSubclassConstructor.newInstance(args);
}
catch (Exception e) {
catch (Exception ex) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format(
"Failed to invoke construcor for CGLIB enhanced subclass [%s]", subclass.getName()), e);
"Failed to invoke constructor for CGLIB enhanced subclass [%s]", subclass.getName()), ex);
}
}
// SPR-10785: set callbacks directly on the instance instead of in the
// enhanced class (via the Enhancer) in order to avoid memory leaks.
Factory factory = (Factory) instance;
factory.setCallbacks(new Callback[] { NoOp.INSTANCE,//
new LookupOverrideMethodInterceptor(beanDefinition, owner),//
new ReplaceOverrideMethodInterceptor(beanDefinition, owner) });
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
return instance;
}
......@@ -153,6 +149,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
}
}
/**
* Class providing hashCode and equals methods required by CGLIB to
* ensure that CGLIB doesn't generate a distinct class per bean.
......@@ -162,7 +159,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final RootBeanDefinition beanDefinition;
CglibIdentitySupport(RootBeanDefinition beanDefinition) {
this.beanDefinition = beanDefinition;
}
......@@ -173,8 +169,8 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
@Override
public boolean equals(Object other) {
return other.getClass().equals(this.getClass())
&& ((CglibIdentitySupport) other).getBeanDefinition().equals(this.getBeanDefinition());
return (getClass().equals(other.getClass()) &&
this.beanDefinition.equals(((CglibIdentitySupport) other).beanDefinition));
}
@Override
......@@ -183,6 +179,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
}
}
/**
* CGLIB callback for filtering method interception behavior.
*/
......@@ -190,7 +187,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class);
MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) {
super(beanDefinition);
}
......@@ -210,11 +206,12 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
else if (methodOverride instanceof ReplaceOverride) {
return METHOD_REPLACER;
}
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: "
+ methodOverride.getClass().getName());
throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " +
methodOverride.getClass().getName());
}
}
/**
* CGLIB MethodInterceptor to override methods, replacing them with an
* implementation that returns a bean looked up in the container.
......@@ -223,7 +220,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final BeanFactory owner;
LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
......@@ -233,10 +229,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
return this.owner.getBean(lo.getBeanName());
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
if (StringUtils.hasText(lo.getBeanName())) {
return this.owner.getBean(lo.getBeanName(), argsToUse);
}
else {
return this.owner.getBean(method.getReturnType(), argsToUse);
}
}
}
/**
* CGLIB MethodInterceptor to override methods, replacing them with a call
* to a generic MethodReplacer.
......@@ -245,7 +248,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final BeanFactory owner;
ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
......@@ -255,7 +257,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
// TODO could cache if a singleton for minor performance optimization
MethodReplacer mr = owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
return mr.reimplement(obj, method, args);
}
}
......
......@@ -17,8 +17,8 @@
package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
......@@ -34,20 +34,33 @@ public class LookupOverride extends MethodOverride {
private final String beanName;
private Method method;
/**
* Construct a new LookupOverride.
* @param methodName the name of the method to override.
* This method must have no arguments.
* @param beanName the name of the bean in the current BeanFactory
* that the overridden method should return
* @param methodName the name of the method to override
* @param beanName the name of the bean in the current {@code BeanFactory}
* that the overridden method should return (may be {@code null})
*/
public LookupOverride(String methodName, String beanName) {
super(methodName);
Assert.notNull(beanName, "Bean name must not be null");
this.beanName = beanName;
}
/**
* Construct a new LookupOverride.
* @param method the method to override
* @param beanName the name of the bean in the current {@code BeanFactory}
* that the overridden method should return (may be {@code null})
*/
public LookupOverride(Method method, String beanName) {
super(method.getName());
this.method = method;
this.beanName = beanName;
}
/**
* Return the name of the bean that should be returned by this method.
*/
......@@ -56,22 +69,33 @@ public class LookupOverride extends MethodOverride {
}
/**
* Match the method of the given name, with no parameters.
* Match the specified method by {@link Method} reference or method name.
* <p>For backwards compatibility reasons, in a scenario with overloaded
* non-abstract methods of the given name, only the no-arg variant of a
* method will be turned into a container-driven lookup method.
* <p>In case of a provided {@link Method}, only straight matches will
* be considered, usually demarcated by the {@code @Lookup} annotation.
*/
@Override
public boolean matches(Method method) {
return (method.getName().equals(getMethodName()) && method.getParameterTypes().length == 0);
if (this.method != null) {
return method.equals(this.method);
}
else {
return (method.getName().equals(getMethodName()) && (!isOverloaded() ||
Modifier.isAbstract(method.getModifiers()) || method.getParameterTypes().length == 0));
}
}
@Override
public String toString() {
return "LookupOverride for method '" + getMethodName() + "'; will return bean '" + this.beanName + "'";
}
@Override
public boolean equals(Object other) {
return (other instanceof LookupOverride && super.equals(other) &&
ObjectUtils.nullSafeEquals(this.beanName, ((LookupOverride) other).beanName));
if (!(other instanceof LookupOverride) || !super.equals(other)) {
return false;
}
LookupOverride that = (LookupOverride) other;
return (ObjectUtils.nullSafeEquals(this.method, that.method) &&
ObjectUtils.nullSafeEquals(this.beanName, that.beanName));
}
@Override
......@@ -79,4 +103,9 @@ public class LookupOverride extends MethodOverride {
return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName));
}
@Override
public String toString() {
return "LookupOverride for method '" + getMethodName() + "'";
}
}
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -52,6 +52,7 @@ public class ReplaceOverride extends MethodOverride {
this.methodReplacerBeanName = methodReplacerBeanName;
}
/**
* Return the name of the bean implementing MethodReplacer.
*/
......@@ -97,12 +98,6 @@ public class ReplaceOverride extends MethodOverride {
}
@Override
public String toString() {
return "Replace override for method '" + getMethodName() + "; will call bean '" +
this.methodReplacerBeanName + "'";
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ReplaceOverride) || !super.equals(other)) {
......@@ -121,4 +116,9 @@ public class ReplaceOverride extends MethodOverride {
return hashCode;
}
@Override
public String toString() {
return "Replace override for method '" + getMethodName() + "'";
}
}
......@@ -680,7 +680,12 @@
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the lookup method. This method must take no arguments.
The name of the lookup method. This method may have arguments which
will be passed on to the target constructor or factory method. Note
that for backwards compatibility reasons, in a scenario with overloaded
non-abstract methods of the given name, only the no-arg variant of a
method will be turned into a container-driven lookup method.
Consider using the @Lookup annotation for more specific demarcation.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
......@@ -688,9 +693,10 @@
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the bean in the current or ancestor factories that
the lookup method should resolve to. Often this bean will be a
the lookup method should resolve to. Usually this bean will be a
prototype, in which case the lookup method will return a distinct
instance on every invocation. This is useful for single-threaded objects.
instance on every invocation. If not specified, the lookup method's
return type will be used for a type-based lookup.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
......
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
* @author Karl Pietrzak
* @author Juergen Hoeller
*/
public class LookupAnnotationTests {
private DefaultListableBeanFactory beanFactory;
@Before
public void setUp() {
beanFactory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor aabpp = new AutowiredAnnotationBeanPostProcessor();
aabpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(aabpp);
beanFactory.registerBeanDefinition("abstractBean", new RootBeanDefinition(AbstractBean.class));
RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
tbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
beanFactory.registerBeanDefinition("testBean", tbd);
}
@Test
public void testWithoutConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
Object expected = bean.get();
assertEquals(TestBean.class, expected.getClass());
}
@Test
public void testWithOverloadedArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.get("haha");
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
}
@Test
public void testWithOneConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.getOneArgument("haha");
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
}
@Test
public void testWithTwoConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.getTwoArguments("haha", 72);
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
assertEquals(72, expected.getAge());
}
@Test
public void testWithThreeArgsShouldFail() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
try {
bean.getThreeArguments("name", 1, 2);
fail("TestBean does not have a three arg constructor so this should not have worked");
}
catch (AbstractMethodError ex) {
}
}
public static abstract class AbstractBean {
@Lookup
public abstract TestBean get();
@Lookup
public abstract TestBean get(String name); // overloaded
@Lookup
public abstract TestBean getOneArgument(String name);
@Lookup
public abstract TestBean getTwoArguments(String name, int age);
public abstract TestBean getThreeArguments(String name, int age, int anotherArg);
}
}
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.support;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
* @author Karl Pietrzak
* @author Juergen Hoeller
*/
public class LookupMethodTests {
private DefaultListableBeanFactory beanFactory;
@Before
public void setUp() {
beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass()));
}
@Test
public void testWithoutConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
Object expected = bean.get();
assertEquals(TestBean.class, expected.getClass());
}
@Test
public void testWithOverloadedArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.get("haha");
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
}
@Test
public void testWithOneConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.getOneArgument("haha");
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
}
@Test
public void testWithTwoConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
TestBean expected = bean.getTwoArguments("haha", 72);
assertEquals(TestBean.class, expected.getClass());
assertEquals("haha", expected.getName());
assertEquals(72, expected.getAge());
}
@Test
public void testWithThreeArgsShouldFail() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertNotNull(bean);
try {
bean.getThreeArguments("name", 1, 2);
fail("TestBean does not have a three arg constructor so this should not have worked");
}
catch (AbstractMethodError ex) {
}
}
public static abstract class AbstractBean {
public abstract TestBean get();
public abstract TestBean get(String name); // overloaded
public abstract TestBean getOneArgument(String name);
public abstract TestBean getTwoArguments(String name, int age);
public abstract TestBean getThreeArguments(String name, int age, int anotherArg);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
<bean id="abstractBean" class="org.springframework.beans.factory.support.LookupMethodTests$AbstractBean">
<lookup-method name="get"/> <!-- applying to overloaded methods, and based on return type since no bean name is given -->
<lookup-method name="getOneArgument" bean="testBean"/>
<lookup-method name="getTwoArguments" bean="testBean"/>
</bean>
<bean id="testBean" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"/>
</beans>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册