diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanDefinitionRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanDefinitionRegistrar.java deleted file mode 100644 index eeef64639f3..00000000000 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanDefinitionRegistrar.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2009 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.config.java.support; - -import java.lang.reflect.Method; - -import org.springframework.beans.factory.support.BeanDefinitionRegistry; - - -/** - * Registers bean definition(s) for a particular method, usually based on its annotation - * metadata. - * - *

Constraints

Implementations must have only a default constructor, or explicitly - * declare a no-arg constructor. - * - * @see BeanMethod - * - * @author Chris Beams - */ -// TODO: SJC-242 document FactoryMethodHandler -// TODO: SJC-242 odd that the api here uses both ModelMethod and java.lang.reflect.Member -// TODO: SJC-242 document that there must be a no-arg ctor -interface BeanDefinitionRegistrar { - - /** - * Determines whether this registrar is capable of handling method. - */ - // TODO: rename to supports() in alignment with Validator nomenclature - boolean accepts(Method method); - - /** - * Registers any bean definitions for method with registry. - */ - void register(BeanMethod method, BeanDefinitionRegistry registry); - -} diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java index 71bd4204630..559f76171c6 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethod.java @@ -38,6 +38,10 @@ import org.springframework.util.Assert; * Represents a {@link Configuration} class method marked with the {@link Bean} annotation. * * @author Chris Beams + * @see ConfigurationClass + * @see ConfigurationModel + * @see ConfigurationParser + * @see ConfigurationModelBeanDefinitionReader */ final class BeanMethod implements BeanMetadataElement { diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethodInterceptor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethodInterceptor.java index 8b8b53c1b8f..9d94a8118c6 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethodInterceptor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanMethodInterceptor.java @@ -24,16 +24,12 @@ import net.sf.cglib.proxy.MethodProxy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.config.java.Bean; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.util.Assert; /** @@ -41,17 +37,16 @@ import org.springframework.util.Assert; * handling of bean semantics such as scoping and AOP proxying. * * @author Chris Beams - * @since 3.0 * @see Bean - * @see BeanRegistrar */ -class BeanMethodInterceptor implements BeanFactoryAware, MethodInterceptor { - protected final Log log = LogFactory.getLog(this.getClass()); - protected DefaultListableBeanFactory beanFactory; +class BeanMethodInterceptor implements MethodInterceptor { - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory); - this.beanFactory = (DefaultListableBeanFactory) beanFactory; + private static final Log log = LogFactory.getLog(BeanMethodInterceptor.class); + + private final DefaultListableBeanFactory beanFactory; + + public BeanMethodInterceptor(DefaultListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; } /** @@ -60,20 +55,18 @@ class BeanMethodInterceptor implements BeanFactoryAware, MethodInterceptor { */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - // determine the name of the bean - String beanName; + // by default the bean name is the name of the @Bean-annotated method + String beanName = method.getName(); + // check to see if the user has explicitly set the bean name Bean bean = method.getAnnotation(Bean.class); - if(bean != null && bean.name().length > 1) + if(bean != null && bean.name().length > 0) beanName = bean.name()[0]; - // if not, simply return the name of the method as the bean name - else - beanName = method.getName(); // determine whether this bean is a scoped-proxy Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); boolean isScopedProxy = (scope != null && scope.proxyMode() != ScopedProxyMode.NO); - String scopedBeanName = BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName); + String scopedBeanName = ConfigurationModelBeanDefinitionReader.resolveHiddenScopedProxyBeanName(beanName); if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) beanName = scopedBeanName; diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanRegistrar.java deleted file mode 100644 index 2ba7af8b64d..00000000000 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/BeanRegistrar.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2002-2009 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.config.java.support; - -import static java.lang.String.*; -import static org.springframework.util.StringUtils.*; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.aop.framework.autoproxy.AutoProxyUtils; -import org.springframework.aop.scope.ScopedProxyFactoryBean; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.config.java.Bean; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.util.Assert; - - -class BeanRegistrar implements BeanDefinitionRegistrar { - - private static final Log logger = LogFactory.getLog(BeanRegistrar.class); - - /** Prefix used when registering the target object for a scoped proxy. */ - private static final String TARGET_NAME_PREFIX = "scopedTarget."; - - /** - * Ensures that member is a method and is annotated (directly or indirectly) - * with {@link Bean @Bean}. - */ - public boolean accepts(Method method) { - return AnnotationUtils.findAnnotation(method, Bean.class) != null; - } - - public void register(BeanMethod method, BeanDefinitionRegistry registry) { - RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); - - ConfigurationClass configClass = method.getDeclaringClass(); - - beanDef.setFactoryBeanName(configClass.getBeanName()); - beanDef.setFactoryMethodName(method.getName()); - - Bean bean = method.getRequiredAnnotation(Bean.class); - - // TODO: prune defaults - //Configuration defaults = configClass.getMetadata(); - - // consider scoping - Scope scope = method.getAnnotation(Scope.class); - if(scope != null) - beanDef.setScope(scope.value()); - - // TODO: prune autowiring -// // consider autowiring -// if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire")) -// beanDef.setAutowireMode(bean.autowire().value()); -// else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class, -// "defaultAutowire")) -// beanDef.setAutowireMode(defaults.defaultAutowire().value()); - - // consider name and any aliases - ArrayList names = new ArrayList(Arrays.asList(bean.name())); - String beanName = (names.size() > 0) ? names.remove(0) : method.getName(); - for (String alias : bean.name()) - registry.registerAlias(beanName, alias); - - // has this already been overriden (i.e.: via XML)? - if (containsBeanDefinitionIncludingAncestry(beanName, registry)) { - BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry); - - // is the existing bean definition one that was created by JavaConfig? - if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { - // no -> then it's an external override, probably XML - - // TODO: Prune this -// // ensure that overriding is ok -// if (bean.allowOverriding() == false) { -// UsageError error = configClass.new IllegalBeanOverrideError(null, method); -// throw new MalformedConfigurationException(error); -// } - - // overriding is legal, return immediately - logger.info(format("Skipping loading bean definition for %s: a definition for bean " - + "'%s' already exists. This is likely due to an override in XML.", method, beanName)); - return; - } - } - - // TODO: re-enable for Lazy support - // // is this bean marked as primary for disambiguation? - // if (bean.primary() == Primary.TRUE) - // beanDef.setPrimary(true); - // - // // is this bean lazily instantiated? - // if ((bean.lazy() == Lazy.TRUE) - // || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE))) - // beanDef.setLazyInit(true); - - // does this bean have a custom init-method specified? - String initMethodName = bean.initMethod(); - if (hasText(initMethodName)) - beanDef.setInitMethodName(initMethodName); - - // does this bean have a custom destroy-method specified? - String destroyMethodName = bean.destroyMethod(); - if (hasText(destroyMethodName)) - beanDef.setDestroyMethodName(destroyMethodName); - - // is this method annotated with @Scope(scopedProxy=...)? - if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { - RootBeanDefinition targetDef = beanDef; - - // Create a scoped proxy definition for the original bean name, - // "hiding" the target bean in an internal target definition. - String targetBeanName = resolveHiddenScopedProxyBeanName(beanName); - RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); - scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); - - if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS) - targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); - // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we - // don't need to set it explicitly here. - else - scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); - - // The target bean should be ignored in favor of the scoped proxy. - targetDef.setAutowireCandidate(false); - - // Register the target bean as separate bean in the factory - registry.registerBeanDefinition(targetBeanName, targetDef); - - // replace the original bean definition with the target one - beanDef = scopedProxyDefinition; - } - - // TODO: re-enable for @Meta support - // does this bean method have any @Meta annotations? - // for (Meta meta : bean.meta()) - // beanDef.addMetadataAttribute(new BeanMetadataAttribute(meta.key(), - // meta.value())); - - if (bean.dependsOn().length > 0) - beanDef.setDependsOn(bean.dependsOn()); - - logger.info(format("Registering bean definition for @Bean method %s.%s()", - configClass.getName(), beanName)); - - registry.registerBeanDefinition(beanName, beanDef); - - } - - private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { - try { - getBeanDefinitionIncludingAncestry(beanName, registry); - return true; - } catch (NoSuchBeanDefinitionException ex) { - return false; - } - } - - private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { - if(!(registry instanceof ConfigurableListableBeanFactory)) { - return registry.getBeanDefinition(beanName); - } - - ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry; - - do { - if (clbf.containsBeanDefinition(beanName)) - return registry.getBeanDefinition(beanName); - - BeanFactory parent = clbf.getParentBeanFactory(); - if (parent == null) { - clbf = null; - } else if (parent instanceof ConfigurableListableBeanFactory) { - clbf = (ConfigurableListableBeanFactory) parent; - // TODO: re-enable - // } else if (parent instanceof AbstractApplicationContext) { - // clbf = ((AbstractApplicationContext) parent).getBeanFactory(); - } else { - throw new IllegalStateException("unknown parent type: " + parent.getClass().getName()); - } - } while (clbf != null); - - throw new NoSuchBeanDefinitionException(format("No bean definition matching name '%s' " - + "could be found in %s or its ancestry", beanName, registry)); - } - - /** - * Return the hidden name based on a scoped proxy bean name. - * - * @param originalBeanName the scope proxy bean name as declared in the - * Configuration-annotated class - * - * @return the internally-used hidden bean name - */ - public static String resolveHiddenScopedProxyBeanName(String originalBeanName) { - Assert.hasText(originalBeanName); - return TARGET_NAME_PREFIX.concat(originalBeanName); - } - -} - -/** - * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created - * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases - * where it's necessary to determine whether the bean definition was created externally - * (e.g. via XML). - */ -@SuppressWarnings("serial") -class ConfigurationClassBeanDefinition extends RootBeanDefinition { -} diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClass.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClass.java index a5fafab2049..41a4207a6b5 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClass.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClass.java @@ -29,7 +29,7 @@ import org.springframework.util.Assert; /** - * Abstract representation of a user-defined {@link Configuration @Configuration} class. + * Represents a user-defined {@link Configuration @Configuration} class. * Includes a set of {@link Bean} methods, including all such methods defined in the * ancestry of the class, in a 'flattened-out' manner. Note that each {@link BeanMethod} * representation contains source information about where it was originally detected diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassMethodVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassMethodVisitor.java index b22d9144a5e..f445aed2e65 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassMethodVisitor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassMethodVisitor.java @@ -27,15 +27,16 @@ import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.ClassAdapter; import org.springframework.asm.Label; import org.springframework.asm.MethodAdapter; +import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; import org.springframework.config.java.Bean; import org.springframework.config.java.Configuration; /** - * Visits a single method declared in a given {@link Configuration} class. Determines - * whether the method is a {@link Bean} method and if so, adds it to the - * {@link ConfigurationClass}. + * ASM {@link MethodVisitor} that visits a single method declared in a given + * {@link Configuration} class. Determines whether the method is a {@link Bean} + * method and if so, adds it to the {@link ConfigurationClass}. * * @author Chris Beams */ diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassVisitor.java index 8615ae82de1..ea83eca2c3e 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassVisitor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationClassVisitor.java @@ -25,6 +25,7 @@ import java.util.Stack; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.ClassAdapter; import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; import org.springframework.beans.factory.parsing.Location; @@ -36,10 +37,12 @@ import org.springframework.core.io.ClassPathResource; /** - * Visits a {@link Configuration} class, populating a {@link ConfigurationClass} instance - * with information gleaned along the way. + * ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a + * {@link ConfigurationClass} instance with information gleaned along the way. * * @author Chris Beams + * @see ConfigurationParser + * @see ConfigurationClass */ class ConfigurationClassVisitor extends ClassAdapter { diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationEnhancer.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationEnhancer.java index 97046c80d19..a916087b552 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationEnhancer.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationEnhancer.java @@ -17,29 +17,27 @@ package org.springframework.config.java.support; import static java.lang.String.*; import static org.springframework.config.java.support.Util.*; -import static org.springframework.util.Assert.*; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashSet; import net.sf.cglib.core.DefaultGeneratorStrategy; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import net.sf.cglib.proxy.NoOp; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.asm.ClassAdapter; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassWriter; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.config.java.Bean; import org.springframework.config.java.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; /** @@ -54,59 +52,30 @@ class ConfigurationEnhancer { private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class); - private final ArrayList> callbackTypes = new ArrayList>(); - - private final LinkedHashSet registrars = new LinkedHashSet(); - private final ArrayList callbackInstances = new ArrayList(); - - private final CallbackFilter callbackFilter = new CallbackFilter() { - public int accept(Method candidateMethod) { - Iterator iter = registrars.iterator(); - for (int i = 0; iter.hasNext(); i++) - if (iter.next().accepts(candidateMethod)) - return i; - - throw new IllegalStateException( - format("No registrar is capable of handling method [%s]. " - + "Perhaps you forgot to add a catch-all registrar?", - candidateMethod.getName())); - } - }; + private final ArrayList> callbackTypes = new ArrayList>(); + private final CallbackFilter callbackFilter; /** * Creates a new {@link ConfigurationEnhancer} instance. */ public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory) { - notNull(beanFactory, "beanFactory must be non-null"); - - registrars.add(new BeanRegistrar()); - BeanMethodInterceptor beanMethodInterceptor = new BeanMethodInterceptor(); - beanMethodInterceptor.setBeanFactory(beanFactory); - callbackInstances.add(beanMethodInterceptor); - - // add no-op default registrar and method interceptor - registrars.add(new BeanDefinitionRegistrar() { + Assert.notNull(beanFactory, "beanFactory must be non-null"); - public boolean accepts(Method method) { - return true; - } + callbackInstances.add(new BeanMethodInterceptor(beanFactory)); + callbackInstances.add(NoOp.INSTANCE); - public void register(BeanMethod method, BeanDefinitionRegistry registry) { - // no-op - } - }); - callbackInstances.add(new MethodInterceptor() { - - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - return null; - } - - }); - for (Callback callback : callbackInstances) callbackTypes.add(callback.getClass()); + + // set up the callback filter to return the index of the BeanMethodInterceptor when + // handling a @Bean-annotated method; otherwise, return index of the NoOp callback. + callbackFilter = new CallbackFilter() { + public int accept(Method candidateMethod) { + return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1; + } + }; } diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModel.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModel.java index c2ea8230a1d..d0312088621 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModel.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModel.java @@ -19,26 +19,22 @@ import static java.lang.String.*; import java.util.ArrayList; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.config.java.Configuration; /** - * An abstract representation of a set of user-provided "Configuration classes", usually but - * not necessarily annotated with {@link Configuration @Configuration}. The model is - * populated with a - * {@link org.springframework.config.java.support.ConfigurationParser} - * implementation which may be reflection-based or ASM-based. Once a model has been - * populated, it can then be rendered out to a set of BeanDefinitions. The model provides an - * important layer of indirection between the complexity of parsing a set of classes and the - * complexity of representing the contents of those classes as BeanDefinitions. - * - *

- * Interface follows the builder pattern for method chaining. - *

+ * Represents the set of all user-defined {@link Configuration} classes. Once this model + * is populated using a {@link ConfigurationParser}, it can be rendered out to a set of + * {@link BeanDefinition} objects. This model provides an important layer of indirection + * between the complexity of parsing a set of classes and the complexity of representing + * the contents of those classes as BeanDefinitions. * * @author Chris Beams - * @see org.springframework.config.java.support.ConfigurationParser + * @see ConfigurationClass + * @see ConfigurationParser + * @see ConfigurationModelBeanDefinitionReader */ final class ConfigurationModel { diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModelBeanDefinitionReader.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModelBeanDefinitionReader.java index c58bb45c141..bc32a473760 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModelBeanDefinitionReader.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationModelBeanDefinitionReader.java @@ -16,17 +16,31 @@ package org.springframework.config.java.support; import static java.lang.String.*; +import static org.springframework.util.StringUtils.*; + +import java.util.ArrayList; +import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.autoproxy.AutoProxyUtils; +import org.springframework.aop.scope.ScopedProxyFactoryBean; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; import org.springframework.config.java.Bean; import org.springframework.config.java.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.io.Resource; +import org.springframework.util.Assert; /** @@ -38,6 +52,8 @@ import org.springframework.core.io.Resource; * {@link Resource}. * * @author Chris Beams + * @see ConfigurationModel + * @see AbstractConfigurationClassProcessor#processConfigBeanDefinitions() */ class ConfigurationModelBeanDefinitionReader { @@ -46,12 +62,6 @@ class ConfigurationModelBeanDefinitionReader { private BeanDefinitionRegistry registry; - /** - * Creates a new {@link ConfigurationModelBeanDefinitionReader} instance. - */ - public ConfigurationModelBeanDefinitionReader() { - } - /** * Reads {@code model}, registering bean definitions with {@link #registry} based on * its contents. @@ -60,7 +70,7 @@ class ConfigurationModelBeanDefinitionReader { */ public BeanDefinitionRegistry loadBeanDefinitions(ConfigurationModel model) { registry = new SimpleBeanDefinitionRegistry(); - + for (ConfigurationClass configClass : model.getAllConfigurationClasses()) loadBeanDefinitionsForConfigurationClass(configClass); @@ -76,11 +86,6 @@ class ConfigurationModelBeanDefinitionReader { for (BeanMethod method : configClass.getBeanMethods()) loadBeanDefinitionsForModelMethod(method); - -// Annotation[] pluginAnnotations = configClass.getPluginAnnotations(); -// Arrays.sort(pluginAnnotations, new PluginComparator()); -// for (Annotation annotation : pluginAnnotations) -// loadBeanDefinitionsForExtensionAnnotation(beanDefs, annotation); } /** @@ -117,40 +122,162 @@ class ConfigurationModelBeanDefinitionReader { * {@link #registry} based on its contents. */ private void loadBeanDefinitionsForModelMethod(BeanMethod method) { - new BeanRegistrar().register(method, registry); + RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); + + ConfigurationClass configClass = method.getDeclaringClass(); + + beanDef.setFactoryBeanName(configClass.getBeanName()); + beanDef.setFactoryMethodName(method.getName()); + + Bean bean = method.getRequiredAnnotation(Bean.class); + + // TODO: prune defaults + //Configuration defaults = configClass.getMetadata(); + + // consider scoping + Scope scope = method.getAnnotation(Scope.class); + if(scope != null) + beanDef.setScope(scope.value()); + + // consider name and any aliases + ArrayList names = new ArrayList(Arrays.asList(bean.name())); + String beanName = (names.size() > 0) ? names.remove(0) : method.getName(); + for (String alias : bean.name()) + registry.registerAlias(beanName, alias); + + // has this already been overriden (i.e.: via XML)? + if (containsBeanDefinitionIncludingAncestry(beanName, registry)) { + BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry); + + // is the existing bean definition one that was created by JavaConfig? + if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { + // no -> then it's an external override, probably XML + + // overriding is legal, return immediately + log.info(format("Skipping loading bean definition for %s: a definition for bean " + + "'%s' already exists. This is likely due to an override in XML.", method, beanName)); + return; + } + } + + // TODO: re-enable for Lazy support + // // is this bean marked as primary for disambiguation? + // if (bean.primary() == Primary.TRUE) + // beanDef.setPrimary(true); + // + // // is this bean lazily instantiated? + // if ((bean.lazy() == Lazy.TRUE) + // || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE))) + // beanDef.setLazyInit(true); + + // does this bean have a custom init-method specified? + String initMethodName = bean.initMethod(); + if (hasText(initMethodName)) + beanDef.setInitMethodName(initMethodName); + + // does this bean have a custom destroy-method specified? + String destroyMethodName = bean.destroyMethod(); + if (hasText(destroyMethodName)) + beanDef.setDestroyMethodName(destroyMethodName); + + // is this method annotated with @Scope(scopedProxy=...)? + if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { + RootBeanDefinition targetDef = beanDef; + + // Create a scoped proxy definition for the original bean name, + // "hiding" the target bean in an internal target definition. + String targetBeanName = resolveHiddenScopedProxyBeanName(beanName); + RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); + scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); + + if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS) + targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); + // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we + // don't need to set it explicitly here. + else + scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); + + // The target bean should be ignored in favor of the scoped proxy. + targetDef.setAutowireCandidate(false); + + // Register the target bean as separate bean in the factory + registry.registerBeanDefinition(targetBeanName, targetDef); + + // replace the original bean definition with the target one + beanDef = scopedProxyDefinition; + } + + if (bean.dependsOn().length > 0) + beanDef.setDependsOn(bean.dependsOn()); + + log.info(format("Registering bean definition for @Bean method %s.%s()", + configClass.getName(), beanName)); + + registry.registerBeanDefinition(beanName, beanDef); + } -// @SuppressWarnings("unchecked") -// private void loadBeanDefinitionsForExtensionAnnotation(Map beanDefs, Annotation anno) { -// // ExtensionAnnotationUtils.getRegistrarFor(anno).registerBeanDefinitionsWith(beanFactory); -// // there is a fixed assumption that in order for this annotation to have -// // been registered in the first place, it must be meta-annotated with @Plugin -// // assert this as an invariant now -// Class annoClass = anno.getClass(); -// Extension extensionAnno = AnnotationUtils.findAnnotation(annoClass, Extension.class); -// Assert.isTrue(extensionAnno != null, format("%s annotation is not annotated as a @%s", annoClass, -// Extension.class.getSimpleName())); -// -// Class extHandlerClass = extensionAnno.handler(); -// -// ExtensionAnnotationBeanDefinitionRegistrar extHandler = getInstance(extHandlerClass); -// extHandler.handle(anno, beanFactory); -// } -// -// private static class PluginComparator implements Comparator { -// public int compare(Annotation a1, Annotation a2) { -// Integer i1 = getOrder(a1); -// Integer i2 = getOrder(a2); -// return i1.compareTo(i2); -// } -// -// private Integer getOrder(Annotation a) { -// Extension plugin = a.annotationType().getAnnotation(Extension.class); -// if (plugin == null) -// throw new IllegalArgumentException("annotation was not annotated with @Plugin: " -// + a.annotationType()); -// return plugin.order(); -// } -// } + private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { + try { + getBeanDefinitionIncludingAncestry(beanName, registry); + return true; + } catch (NoSuchBeanDefinitionException ex) { + return false; + } + } + private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { + if(!(registry instanceof ConfigurableListableBeanFactory)) + return registry.getBeanDefinition(beanName); + + ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry; + + do { + if (clbf.containsBeanDefinition(beanName)) + return registry.getBeanDefinition(beanName); + + BeanFactory parent = clbf.getParentBeanFactory(); + if (parent == null) { + clbf = null; + } else if (parent instanceof ConfigurableListableBeanFactory) { + clbf = (ConfigurableListableBeanFactory) parent; + // TODO: re-enable + // } else if (parent instanceof AbstractApplicationContext) { + // clbf = ((AbstractApplicationContext) parent).getBeanFactory(); + } else { + throw new IllegalStateException("unknown parent type: " + parent.getClass().getName()); + } + } while (clbf != null); + + throw new NoSuchBeanDefinitionException( + format("No bean definition matching name '%s' " + + "could be found in %s or its ancestry", beanName, registry)); + } + + /** + * Return the hidden name based on a scoped proxy bean name. + * + * @param originalBeanName the scope proxy bean name as declared in the + * Configuration-annotated class + * + * @return the internally-used hidden bean name + */ + public static String resolveHiddenScopedProxyBeanName(String originalBeanName) { + Assert.hasText(originalBeanName); + return TARGET_NAME_PREFIX.concat(originalBeanName); + } + + /** Prefix used when registering the target object for a scoped proxy. */ + private static final String TARGET_NAME_PREFIX = "scopedTarget."; +} + + +/** + * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created + * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases + * where it's necessary to determine whether the bean definition was created externally + * (e.g. via XML). + */ +@SuppressWarnings("serial") +class ConfigurationClassBeanDefinition extends RootBeanDefinition { } diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationParser.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationParser.java index 96915e865f5..9f6323afdb5 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationParser.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ConfigurationParser.java @@ -46,9 +46,11 @@ public class ConfigurationParser { private final ClassLoader classLoader; /** - * Creates a new parser instance that will be used to populate model. - * @param model model to be populated by each successive call to - * {@link #parse(Object, String)} + * Creates a new {@link ConfigurationParser} instance that will be used to populate a + * {@link ConfigurationModel}. + * + * @param model model to be populated by each successive call to {@link #parse} + * @see #getConfigurationModel() */ public ConfigurationParser(ProblemReporter problemReporter, ClassLoader classLoader) { this.model = new ConfigurationModel(); @@ -77,6 +79,9 @@ public class ConfigurationParser { model.add(configClass); } + /** + * Returns the current {@link ConfigurationModel}, to be called after {@link #parse}. + */ public ConfigurationModel getConfigurationModel() { return model; } diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ImportStack.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ImportStack.java index 60e42cbf87d..70967bc0846 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ImportStack.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ImportStack.java @@ -25,7 +25,7 @@ import org.springframework.util.Assert; /** - * Stack used for detecting circular use of the {@link Import} annotation. + * {@link Stack} used for detecting circular use of the {@link Import} annotation. * * @author Chris Beams * @see Import diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java index ca4bed9a098..f3d33c2d55f 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/ModelClass.java @@ -24,7 +24,7 @@ import org.springframework.util.ClassUtils; /** - * Representation of a class, free from java reflection, + * Represents a class, free from java reflection, * populated by {@link ConfigurationParser}. * * @author Chris Beams diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationArrayVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationArrayVisitor.java index 2c89c7f4ae9..a31681513fb 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationArrayVisitor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationArrayVisitor.java @@ -25,6 +25,14 @@ import java.util.ArrayList; import org.springframework.asm.AnnotationVisitor; +/** + * ASM {@link AnnotationVisitor} that visits any annotation array values while populating + * a new {@link MutableAnnotation} instance. + * + * @author Chris Beams + * @see MutableAnnotation + * @see MutableAnnotationUtils + */ class MutableAnnotationArrayVisitor extends AnnotationAdapter { private final ArrayList values = new ArrayList(); diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationInvocationHandler.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationInvocationHandler.java index 9531ec4b145..d9e410dcfb4 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationInvocationHandler.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationInvocationHandler.java @@ -30,6 +30,14 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; +/** + * Handles calls to {@link MutableAnnotation} attribute methods at runtime. Essentially + * emulates what JDK annotation dynamic proxies do. + * + * @author Chris Beams + * @see MutableAnnotation + * @see MutableAnnotationUtils + */ final class MutableAnnotationInvocationHandler implements InvocationHandler { private final Class annoType; diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationVisitor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationVisitor.java index 3e4aa1143bb..be23fcac94b 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationVisitor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/support/MutableAnnotationVisitor.java @@ -27,7 +27,13 @@ import org.springframework.util.Assert; /** - * Populates a given {@link MutableAnnotation} instance with its attributes. + * ASM {@link AnnotationVisitor} that populates a given {@link MutableAnnotation} instance + * with its attributes. + * + * @author Chris Beams + * @see MutableAnnotation + * @see MutableAnnotationInvocationHandler + * @see MutableAnnotationUtils */ class MutableAnnotationVisitor implements AnnotationVisitor { diff --git a/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java index d20717a70c7..91e5fa9c4b2 100644 --- a/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java +++ b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/BeanMethodTests.java @@ -156,15 +156,10 @@ public class BeanMethodTests { @Test public void sessionInterfaceScopedProxiesAreLegal() { - Scope scope = PrototypeInterfaceProxy.class.getAnnotation(Scope.class); + Scope scope = SessionInterfaceProxy.class.getAnnotation(Scope.class); BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to prototype with scoped proxy"); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); - } + beanMethod.validate(problemReporter); // should validate without problems - it's legal } @Scope(value=SINGLETON, proxyMode=INTERFACES) diff --git a/org.springframework.config.java/src/test/java/test/basic/BasicTests.java b/org.springframework.config.java/src/test/java/test/basic/BasicTests.java index d9bf1428006..b2d55549c13 100644 --- a/org.springframework.config.java/src/test/java/test/basic/BasicTests.java +++ b/org.springframework.config.java/src/test/java/test/basic/BasicTests.java @@ -6,6 +6,7 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -39,8 +40,7 @@ public class BasicTests { for (Class configClass : configClasses) { String configBeanName = configClass.getName(); - factory.registerBeanDefinition(configBeanName, rootBeanDefinition(configClass) - .getBeanDefinition()); + factory.registerBeanDefinition(configBeanName, rootBeanDefinition(configClass).getBeanDefinition()); } new ConfigurationClassPostProcessor().postProcessBeanFactory(factory); @@ -50,6 +50,51 @@ public class BasicTests { return factory; } + @Test + public void customBeanNameIsRespected() { + BeanFactory factory = initBeanFactory(ConfigWithBeanWithCustomName.class); + assertSame(factory.getBean("customName"), ConfigWithBeanWithCustomName.testBean); + + // method name should not be registered + try { + factory.getBean("methodName"); + fail("bean should not have been registered with 'methodName'"); + } catch (NoSuchBeanDefinitionException ex) { /* expected */ } + } + + @Configuration + static class ConfigWithBeanWithCustomName { + static TestBean testBean = new TestBean(); + @Bean(name="customName") + public TestBean methodName() { + return testBean; + } + } + + @Test + public void aliasesAreRespected() { + BeanFactory factory = initBeanFactory(ConfigWithBeanWithAliases.class); + assertSame(factory.getBean("name1"), ConfigWithBeanWithAliases.testBean); + String[] aliases = factory.getAliases("name1"); + for(String alias : aliases) + assertSame(factory.getBean(alias), ConfigWithBeanWithAliases.testBean); + + // method name should not be registered + try { + factory.getBean("methodName"); + fail("bean should not have been registered with 'methodName'"); + } catch (NoSuchBeanDefinitionException ex) { /* expected */ } + } + + @Configuration + static class ConfigWithBeanWithAliases { + static TestBean testBean = new TestBean(); + @Bean(name={"name1", "alias1", "alias2", "alias3"}) + public TestBean methodName() { + return testBean; + } + } + @Test(expected=BeanDefinitionParsingException.class) public void testFinalBeanMethod() { initBeanFactory(ConfigWithFinalBean.class);