diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java index 143411d31b6..a4e77a7d4bf 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java @@ -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. + *
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. + *
Note: This attribute will not be inherited by child bean definitions,
+ * hence it needs to be specified per concrete bean definition.
*/
String[] dependsOn() default {};
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java
index 9e2df803430..be9872be493 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java
@@ -85,10 +85,10 @@ final class BeanMethod implements BeanMetadataElement {
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
- public
- * 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.
+ *
+ * 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);
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java
index 3590ffe124b..9c1dba09a30 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java
@@ -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();
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java
new file mode 100644
index 00000000000..c42b4530402
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java
@@ -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.
+ *
+ * May be used on any class directly or indirectly annotated with
+ * {@link org.springframework.stereotype.Component} or on methods annotated with
+ * {@link Bean}.
+ *
+ * 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.
+ *
+ * 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;
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java
new file mode 100644
index 00000000000..2a29b92109a
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java
@@ -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.
+ *
+ * May be used on any class directly or indirectly annotated with
+ * {@link org.springframework.stereotype.Component} or on methods annotated
+ * with {@link Bean}.
+ *
+ * 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 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");
+ }
+
+}