View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.util.HashSet;
20  import java.util.LinkedHashMap;
21  import java.util.LinkedHashSet;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.springframework.beans.factory.parsing.Location;
26  import org.springframework.beans.factory.parsing.Problem;
27  import org.springframework.beans.factory.parsing.ProblemReporter;
28  import org.springframework.beans.factory.support.BeanDefinitionReader;
29  import org.springframework.core.io.DescriptiveResource;
30  import org.springframework.core.io.Resource;
31  import org.springframework.core.type.AnnotationMetadata;
32  import org.springframework.core.type.StandardAnnotationMetadata;
33  import org.springframework.core.type.classreading.MetadataReader;
34  import org.springframework.util.Assert;
35  import org.springframework.util.ClassUtils;
36  
37  /**
38   * Represents a user-defined {@link Configuration @Configuration} class.
39   * Includes a set of {@link Bean} methods, including all such methods
40   * defined in the ancestry of the class, in a 'flattened-out' manner.
41   *
42   * @author Chris Beams
43   * @author Juergen Hoeller
44   * @author Phillip Webb
45   * @since 3.0
46   * @see BeanMethod
47   * @see ConfigurationClassParser
48   */
49  final class ConfigurationClass {
50  
51  	private final AnnotationMetadata metadata;
52  
53  	private final Resource resource;
54  
55  	private String beanName;
56  
57  	private final Set<ConfigurationClass> importedBy = new LinkedHashSet<ConfigurationClass>(1);
58  
59  	private final Set<BeanMethod> beanMethods = new LinkedHashSet<BeanMethod>();
60  
61  	private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
62  			new LinkedHashMap<String, Class<? extends BeanDefinitionReader>>();
63  
64  	private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
65  			new LinkedHashMap<ImportBeanDefinitionRegistrar, AnnotationMetadata>();
66  
67  	final Set<String> skippedBeanMethods = new HashSet<String>();
68  
69  
70  	/**
71  	 * Create a new {@link ConfigurationClass} with the given name.
72  	 * @param metadataReader reader used to parse the underlying {@link Class}
73  	 * @param beanName must not be {@code null}
74  	 * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
75  	 */
76  	public ConfigurationClass(MetadataReader metadataReader, String beanName) {
77  		Assert.hasText(beanName, "Bean name must not be null");
78  		this.metadata = metadataReader.getAnnotationMetadata();
79  		this.resource = metadataReader.getResource();
80  		this.beanName = beanName;
81  	}
82  
83  	/**
84  	 * Create a new {@link ConfigurationClass} representing a class that was imported
85  	 * using the {@link Import} annotation or automatically processed as a nested
86  	 * configuration class (if importedBy is not {@code null}).
87  	 * @param metadataReader reader used to parse the underlying {@link Class}
88  	 * @param importedBy the configuration class importing this one or {@code null}
89  	 * @since 3.1.1
90  	 */
91  	public ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
92  		this.metadata = metadataReader.getAnnotationMetadata();
93  		this.resource = metadataReader.getResource();
94  		this.importedBy.add(importedBy);
95  	}
96  
97  	/**
98  	 * Create a new {@link ConfigurationClass} with the given name.
99  	 * @param clazz the underlying {@link Class} to represent
100 	 * @param beanName name of the {@code @Configuration} class bean
101 	 * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
102 	 */
103 	public ConfigurationClass(Class<?> clazz, String beanName) {
104 		Assert.hasText(beanName, "Bean name must not be null");
105 		this.metadata = new StandardAnnotationMetadata(clazz, true);
106 		this.resource = new DescriptiveResource(clazz.toString());
107 		this.beanName = beanName;
108 	}
109 
110 	/**
111 	 * Create a new {@link ConfigurationClass} representing a class that was imported
112 	 * using the {@link Import} annotation or automatically processed as a nested
113 	 * configuration class (if imported is {@code true}).
114 	 * @param clazz the underlying {@link Class} to represent
115 	 * @param importedBy the configuration class importing this one or {@code null}
116 	 * @since 3.1.1
117 	 */
118 	public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
119 		this.metadata = new StandardAnnotationMetadata(clazz, true);
120 		this.resource = new DescriptiveResource(clazz.toString());
121 		this.importedBy.add(importedBy);
122 	}
123 
124 	/**
125 	 * Create a new {@link ConfigurationClass} with the given name.
126 	 * @param metadata the metadata for the underlying class to represent
127 	 * @param beanName name of the {@code @Configuration} class bean
128 	 * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
129 	 */
130 	public ConfigurationClass(AnnotationMetadata metadata, String beanName) {
131 		Assert.hasText(beanName, "Bean name must not be null");
132 		this.metadata = metadata;
133 		this.resource = new DescriptiveResource(metadata.getClassName());
134 		this.beanName = beanName;
135 	}
136 
137 
138 	public AnnotationMetadata getMetadata() {
139 		return this.metadata;
140 	}
141 
142 	public Resource getResource() {
143 		return this.resource;
144 	}
145 
146 	public String getSimpleName() {
147 		return ClassUtils.getShortName(getMetadata().getClassName());
148 	}
149 
150 	public void setBeanName(String beanName) {
151 		this.beanName = beanName;
152 	}
153 
154 	public String getBeanName() {
155 		return this.beanName;
156 	}
157 
158 	/**
159 	 * Return whether this configuration class was registered via @{@link Import} or
160 	 * automatically registered due to being nested within another configuration class.
161 	 * @since 3.1.1
162 	 * @see #getImportedBy()
163 	 */
164 	public boolean isImported() {
165 		return !this.importedBy.isEmpty();
166 	}
167 
168 	/**
169 	 * Merge the imported-by declarations from the given configuration class into this one.
170 	 * @since 4.0.5
171 	 */
172 	public void mergeImportedBy(ConfigurationClass otherConfigClass) {
173 		this.importedBy.addAll(otherConfigClass.importedBy);
174 	}
175 
176 	/**
177 	 * Return the configuration classes that imported this class,
178 	 * or an empty Set if this configuration was not imported.
179 	 * @since 4.0.5
180 	 * @see #isImported()
181 	 */
182 	public Set<ConfigurationClass> getImportedBy() {
183 		return this.importedBy;
184 	}
185 
186 	public void addBeanMethod(BeanMethod method) {
187 		this.beanMethods.add(method);
188 	}
189 
190 	public Set<BeanMethod> getBeanMethods() {
191 		return this.beanMethods;
192 	}
193 
194 	public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
195 		this.importedResources.put(importedResource, readerClass);
196 	}
197 
198 	public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
199 		this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
200 	}
201 
202 	public Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
203 		return this.importBeanDefinitionRegistrars;
204 	}
205 
206 	public Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
207 		return this.importedResources;
208 	}
209 
210 	public void validate(ProblemReporter problemReporter) {
211 		// A configuration class may not be final (CGLIB limitation)
212 		if (getMetadata().isAnnotated(Configuration.class.getName())) {
213 			if (getMetadata().isFinal()) {
214 				problemReporter.error(new FinalConfigurationProblem());
215 			}
216 		}
217 
218 		for (BeanMethod beanMethod : this.beanMethods) {
219 			beanMethod.validate(problemReporter);
220 		}
221 	}
222 
223 	@Override
224 	public boolean equals(Object other) {
225 		return (this == other || (other instanceof ConfigurationClass &&
226 				getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
227 	}
228 
229 	@Override
230 	public int hashCode() {
231 		return getMetadata().getClassName().hashCode();
232 	}
233 
234 	@Override
235 	public String toString() {
236 		return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource;
237 	}
238 
239 
240 	/**
241 	 * Configuration classes must be non-final to accommodate CGLIB subclassing.
242 	 */
243 	private class FinalConfigurationProblem extends Problem {
244 
245 		public FinalConfigurationProblem() {
246 			super(String.format("@Configuration class '%s' may not be final. Remove the final modifier to continue.",
247 					getSimpleName()), new Location(getResource(), getMetadata()));
248 		}
249 	}
250 
251 }