resolved:

+ Provide @Primary annotation (SPR-5590)
+ Provide @Lazy annotation (SPR-5591)
+ Test @Bean initMethod/destroyMethod functionality (SPR-5592)
+ Test @Bean dependsOn functionality (SPR-5593)


git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@866 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Chris Beams 2009-03-28 22:21:50 +00:00
parent 33137847a3
commit eac93ebd59
9 changed files with 361 additions and 47 deletions

View File

@ -77,14 +77,19 @@ public @interface Bean {
/**
* The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly
* within the Bean method.
* within the body of a Bean-annotated method.
*/
String initMethod() default "";
/**
* The optional name of a method to call on the bean instance during upon closing
* the application context, for example a {@literal close()}
* method on a {@literal DataSource}.
* method on a {@literal DataSource}. The method must have no arguments, but may
* throw any exception.
* <p>Note: Only invoked on beans whose lifecycle is under the full control of the
* factory which is always the case for singletons, but not guaranteed
* for any other scope.
* see {@link org.springframework.context.ConfigurableApplicationContext#close()}
*/
String destroyMethod() default "";
@ -93,6 +98,8 @@ public @interface Bean {
* created by the container before this bean. Used infrequently in cases where a bean
* does not explicitly depend on another through properties or constructor arguments,
* but rather depends on the side effects of another bean's initialization.
* <p>Note: This attribute will not be inherited by child bean definitions,
* hence it needs to be specified per concrete bean definition.
*/
String[] dependsOn() default {};

View File

@ -85,10 +85,10 @@ final class BeanMethod implements BeanMetadataElement {
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
public <A extends Annotation> A getAnnotation(Class<A> annoType) {
for (Annotation anno : annotations)
if (anno.annotationType().equals(annoType))
return (T) anno;
return (A) anno;
return null;
}
@ -101,7 +101,9 @@ final class BeanMethod implements BeanMetadataElement {
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
T anno = getAnnotation(annoType);
Assert.notNull(anno, format("annotation %s not found on %s", annoType.getSimpleName(), this));
if(anno == null)
throw new IllegalStateException(
format("required annotation %s is not present on %s", annoType.getSimpleName(), this));
return anno;
}

View File

@ -55,13 +55,13 @@ import org.springframework.stereotype.Component;
* @see Bean
* @see Lazy
* @see Value
* @see org.springframework.context.annotation.support.ConfigurationClassPostProcessor;
* @see ConfigurationClassPostProcessor;
*/
@Component
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface Configuration {
}

View File

@ -18,6 +18,7 @@ package org.springframework.context.annotation;
import static java.lang.String.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
@ -43,7 +44,7 @@ final class ConfigurationClass extends ModelClass {
private String beanName;
private int modifiers;
private Configuration configurationAnnotation;
private HashSet<Annotation> annotations = new HashSet<Annotation>();
private HashSet<BeanMethod> methods = new HashSet<BeanMethod>();
private ConfigurationClass declaringClass;
@ -63,14 +64,38 @@ final class ConfigurationClass extends ModelClass {
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
this.modifiers = modifiers;
}
public Configuration getConfigurationAnnotation() {
return this.configurationAnnotation;
public void addAnnotation(Annotation annotation) {
this.annotations.add(annotation);
}
public void setConfigurationAnnotation(Configuration configAnno) {
Assert.notNull(configAnno, "configuration annotation must be non-null");
this.configurationAnnotation = configAnno;
/**
* @return the annotation on this class matching <var>annoType</var> or
* {@literal null} if not present.
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annoType) {
for (Annotation annotation : annotations)
if(annotation.annotationType().equals(annoType))
return (A) annotation;
return null;
}
/**
* @return the annotation on this class matching <var>annoType</var>
* @throws {@link IllegalStateException} if not present
* @see #getAnnotation(Class)
*/
public <A extends Annotation> A getRequiredAnnotation(Class<A> annoType) {
A anno = getAnnotation(annoType);
if(anno == null)
throw new IllegalStateException(
format("required annotation %s is not present on %s", annoType.getSimpleName(), this));
return anno;
}
public Set<BeanMethod> getBeanMethods() {
@ -93,7 +118,7 @@ final class ConfigurationClass extends ModelClass {
public void validate(ProblemReporter problemReporter) {
// configuration classes must be annotated with @Configuration
if (configurationAnnotation == null)
if (getAnnotation(Configuration.class) == null)
problemReporter.error(new NonAnnotatedConfigurationProblem());
// a configuration class may not be final (CGLIB limitation)
@ -113,9 +138,12 @@ final class ConfigurationClass extends ModelClass {
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((declaringClass == null) ? 0 : declaringClass.hashCode());
result = prime * result + ((beanName == null) ? 0 : beanName.hashCode());
result = prime * result + ((configurationAnnotation == null) ? 0 : configurationAnnotation.hashCode());
result = prime * result
+ ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result
+ ((beanName == null) ? 0 : beanName.hashCode());
result = prime * result
+ ((declaringClass == null) ? 0 : declaringClass.hashCode());
result = prime * result + ((methods == null) ? 0 : methods.hashCode());
result = prime * result + modifiers;
return result;
@ -130,20 +158,20 @@ final class ConfigurationClass extends ModelClass {
if (getClass() != obj.getClass())
return false;
ConfigurationClass other = (ConfigurationClass) obj;
if (declaringClass == null) {
if (other.declaringClass != null)
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!declaringClass.equals(other.declaringClass))
} else if (!annotations.equals(other.annotations))
return false;
if (beanName == null) {
if (other.beanName != null)
return false;
} else if (!beanName.equals(other.beanName))
return false;
if (configurationAnnotation == null) {
if (other.configurationAnnotation != null)
if (declaringClass == null) {
if (other.declaringClass != null)
return false;
} else if (!configurationAnnotation.equals(other.configurationAnnotation))
} else if (!declaringClass.equals(other.declaringClass))
return false;
if (methods == null) {
if (other.methods != null)

View File

@ -20,6 +20,7 @@ import static java.lang.String.*;
import static org.springframework.context.annotation.AsmUtils.*;
import static org.springframework.util.ClassUtils.*;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Stack;
@ -105,26 +106,27 @@ class ConfigurationClassVisitor extends ClassAdapter {
/**
* Visits a class level annotation on a {@link Configuration @Configuration} class.
* Accounts for all possible class-level annotations that are respected by JavaConfig
* including AspectJ's {@code @Aspect} annotation.
* <p>
* Upon encountering such an annotation, update the {@link #configClass} model object
* appropriately, and then return an {@link AnnotationVisitor} implementation that can
* populate the annotation appropriately with data.
*
* <p>Upon encountering such an annotation, updates the {@link #configClass} model
* object appropriately, and then returns an {@link AnnotationVisitor} implementation
* that can populate the annotation appropriately with its attribute data as parsed
* by ASM.
*
* @see MutableAnnotation
* @see Configuration
* @see Lazy
* @see Import
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoTypeName = convertAsmTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoClass = loadToolingSafeClass(annoTypeName, classLoader);
if (Configuration.class.getName().equals(annoTypeName)) {
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class, classLoader);
configClass.setConfigurationAnnotation(mutableConfiguration);
return new MutableAnnotationVisitor(mutableConfiguration, classLoader);
}
if (annoClass == null)
// annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation
return super.visitAnnotation(annoTypeDesc, visible);
if (Import.class.getName().equals(annoTypeName)) {
if (Import.class.equals(annoClass)) {
ImportStack importStack = ImportStackHolder.getImportStack();
if (!importStack.contains(configClass)) {
@ -135,7 +137,9 @@ class ConfigurationClassVisitor extends ClassAdapter {
problemReporter.error(new CircularImportProblem(configClass, importStack));
}
return super.visitAnnotation(annoTypeDesc, visible);
Annotation mutableAnnotation = createMutableAnnotation(annoClass, classLoader);
configClass.addAnnotation(mutableAnnotation);
return new MutableAnnotationVisitor(mutableAnnotation, classLoader);
}
/**
@ -187,7 +191,7 @@ class ConfigurationClassVisitor extends ClassAdapter {
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list
if (innerConfigClass.getConfigurationAnnotation() != null)
if (innerConfigClass.getAnnotation(Configuration.class) != null)
innerClasses.put(name, innerConfigClass);
}

View File

@ -155,15 +155,16 @@ class ConfigurationModelBeanDefinitionReader {
}
}
// 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);
if (method.getAnnotation(Primary.class) != null)
beanDef.setPrimary(true);
// is this bean to be instantiated lazily?
Lazy defaultLazy = configClass.getAnnotation(Lazy.class);
if (defaultLazy != null)
beanDef.setLazyInit(defaultLazy.value());
Lazy lazy = method.getAnnotation(Lazy.class);
if (lazy != null)
beanDef.setLazyInit(lazy.value());
// does this bean have a custom init-method specified?
String initMethodName = bean.initMethod();

View File

@ -0,0 +1,64 @@
/*
* 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.context.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;
/**
* Indicates whether a bean is to be lazily initialized.
*
* <p>May be used on any class directly or indirectly annotated with
* {@link org.springframework.stereotype.Component} or on methods annotated with
* {@link Bean}.
*
* <p>If this annotation is not present on a Component or Bean definition, eager
* initialization will occur. If present and set to {@literal true}, the
* Bean/Component will not be initialized until referenced by another bean or
* explicitly retrieved from the enclosing
* {@link org.springframework.beans.factory.BeanFactory}. If present and set to
* {@literal false}, the bean will be instantiated on startup by bean factories
* that perform eager initialization of singletons.
*
* <p>If Lazy is present on a {@link Configuration} class, this indicates that all
* {@link Bean} methods within that {@literal Configuration} should be lazily
* initialized. If Lazy is present and false on a Bean method within a
* Lazy-annotated Configuration class, this indicates overriding the 'default
* lazy' behavior and that the bean should be eagerly initialized.
*
* @author Chris Beams
* @since 3.0
* @see Primary
* @see Bean
* @see Configuration
* @see org.springframework.stereotype.Component
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* Whether lazy initialization should occur.
*/
boolean value() default true;
}

View File

@ -0,0 +1,51 @@
/*
* 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.context.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;
/**
* Indicates that a bean should be given preference when multiple candidates
* are qualified to autowire a single-valued dependency. If exactly one 'primary'
* bean exists among the candidates, it will be the autowired value.
*
* <p>May be used on any class directly or indirectly annotated with
* {@link org.springframework.stereotype.Component} or on methods annotated
* with {@link Bean}.
*
* <p>Using {@link Primary} at the class level has no effect unless component-scanning
* is being used. If a {@link Primary}-annotated class is declared via XML,
* {@link Primary} annotation metadata is ignored, and
* {@literal <bean primary="true|false"/>} is respected instead.
*
* @author Chris Beams
* @since 3.0
* @see Lazy
* @see Bean
* @see org.springframework.stereotype.Component
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}

View File

@ -0,0 +1,157 @@
/*
* 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.context.annotation.configuration;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
/**
* Unit tests proving that the various attributes available via the {@link Bean}
* annotation are correctly reflected in the {@link BeanDefinition} created when
* processing the {@link Configuration} class.
*
* <p>Also includes tests proving that using {@link Lazy} and {@link Primary}
* annotations in conjunction with Bean propagate their respective metadata
* correctly into the resulting BeanDefinition
*
* @author Chris Beams
*/
@SuppressWarnings("unused") // for unused @Bean methods in local classes
public class BeanAnnotationAttributePropagationTests {
@Test
public void initMethodMetadataIsPropagated() {
@Configuration class Config {
@Bean(initMethod="start") Object foo() { return null; }
}
assertEquals("init method name was not propagated",
"start", beanDef(Config.class).getInitMethodName());
}
@Test
public void destroyMethodMetadataIsPropagated() {
@Configuration class Config {
@Bean(destroyMethod="destroy") Object foo() { return null; }
}
assertEquals("destroy method name was not propagated",
"destroy", beanDef(Config.class).getDestroyMethodName());
}
@Test
public void dependsOnMetadataIsPropagated() {
@Configuration class Config {
@Bean(dependsOn={"bar", "baz"}) Object foo() { return null; }
}
assertArrayEquals("dependsOn metadata was not propagated",
new String[] {"bar", "baz"}, beanDef(Config.class).getDependsOn());
}
@Test
public void primaryMetadataIsPropagated() {
@Configuration class Config {
@Primary @Bean
Object foo() { return null; }
}
assertTrue("primary metadata was not propagated",
beanDef(Config.class).isPrimary());
}
@Test
public void primaryMetadataIsFalseByDefault() {
@Configuration class Config {
@Bean Object foo() { return null; }
}
assertFalse("@Bean methods should be non-primary by default",
beanDef(Config.class).isPrimary());
}
@Test
public void lazyMetadataIsPropagated() {
@Configuration class Config {
@Lazy @Bean
Object foo() { return null; }
}
assertTrue("lazy metadata was not propagated",
beanDef(Config.class).isLazyInit());
}
@Test
public void lazyMetadataIsFalseByDefault() {
@Configuration class Config {
@Bean Object foo() { return null; }
}
assertFalse("@Bean methods should be non-lazy by default",
beanDef(Config.class).isLazyInit());
}
@Test
public void defaultLazyConfigurationPropagatesToIndividualBeans() {
@Lazy @Configuration class Config {
@Bean Object foo() { return null; }
}
assertTrue("@Bean methods declared in a @Lazy @Configuration should be lazily instantiated",
beanDef(Config.class).isLazyInit());
}
@Test
public void eagerBeanOverridesDefaultLazyConfiguration() {
@Lazy @Configuration class Config {
@Lazy(false) @Bean Object foo() { return null; }
}
assertFalse("@Lazy(false) @Bean methods declared in a @Lazy @Configuration should be eagerly instantiated",
beanDef(Config.class).isLazyInit());
}
@Test
public void eagerConfigurationProducesEagerBeanDefinitions() {
@Lazy(false) @Configuration class Config { // will probably never happen, doesn't make much sense
@Bean Object foo() { return null; }
}
assertFalse("@Lazy(false) @Configuration should produce eager bean definitions",
beanDef(Config.class).isLazyInit());
}
private AbstractBeanDefinition beanDef(Class<?> configClass) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("config", new RootBeanDefinition(configClass));
new ConfigurationClassPostProcessor().postProcessBeanFactory(factory);
return (AbstractBeanDefinition) factory.getBeanDefinition("foo");
}
}