@Configuration parsing fully relies on Spring's MetadataReader abstraction now
This commit is contained in:
parent
37e1333a41
commit
b5d21108da
|
|
@ -18,7 +18,6 @@ package org.springframework.context.annotation;
|
|||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
|
@ -27,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that a class declares one or more {@link Bean} methods and may be processed
|
||||
* by the Spring container to generate bean definitions and service requests for those beans
|
||||
|
|
@ -59,7 +57,6 @@ import org.springframework.stereotype.Component;
|
|||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Component
|
||||
public @interface Configuration {
|
||||
|
|
|
|||
|
|
@ -16,20 +16,19 @@
|
|||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.core.io.DescriptiveResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -45,75 +44,42 @@ import org.springframework.util.ClassUtils;
|
|||
* @see ConfigurationClassMethod
|
||||
* @see ConfigurationClassParser
|
||||
*/
|
||||
final class ConfigurationClass implements BeanMetadataElement {
|
||||
final class ConfigurationClass {
|
||||
|
||||
private String name;
|
||||
private final AnnotationMetadata metadata;
|
||||
|
||||
private ConfigurationClass declaringClass;
|
||||
|
||||
private Object source;
|
||||
private final Resource resource;
|
||||
|
||||
private String beanName;
|
||||
|
||||
private int modifiers;
|
||||
|
||||
private final Set<Annotation> annotations = new HashSet<Annotation>();
|
||||
|
||||
private final Set<ConfigurationClassMethod> methods = new LinkedHashSet<ConfigurationClassMethod>();
|
||||
|
||||
private final Map<String, Integer> overloadedMethodMap = new LinkedHashMap<String, Integer>();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the fully-qualified name of this class.
|
||||
*/
|
||||
public void setName(String className) {
|
||||
this.name = className;
|
||||
public ConfigurationClass(MetadataReader metadataReader, String beanName) {
|
||||
this.metadata = metadataReader.getAnnotationMetadata();
|
||||
this.resource = metadataReader.getResource();
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified name of this class.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
public ConfigurationClass(Class clazz, String beanName) {
|
||||
this.metadata = new StandardAnnotationMetadata(clazz);
|
||||
this.resource = new DescriptiveResource(clazz.toString());
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
||||
public AnnotationMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return name == null ? null : ClassUtils.getShortName(name);
|
||||
}
|
||||
|
||||
public void setDeclaringClass(ConfigurationClass configurationClass) {
|
||||
this.declaringClass = configurationClass;
|
||||
}
|
||||
|
||||
public ConfigurationClass getDeclaringClass() {
|
||||
return declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source location for this class. Must be a resource-path formatted string.
|
||||
* @param source resource path to the .java file that declares this class.
|
||||
*/
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a resource path-formatted representation of the .java file that declares this
|
||||
* class
|
||||
*/
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (getName() == null) {
|
||||
throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()");
|
||||
}
|
||||
return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource());
|
||||
return ClassUtils.getShortName(getMetadata().getClassName());
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
|
|
@ -121,71 +87,29 @@ final class ConfigurationClass implements BeanMetadataElement {
|
|||
}
|
||||
|
||||
public String getBeanName() {
|
||||
return beanName;
|
||||
}
|
||||
|
||||
public void setModifiers(int modifiers) {
|
||||
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
|
||||
this.modifiers = modifiers;
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
public void addAnnotation(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(
|
||||
String.format("Required annotation %s is not present on %s", annoType.getSimpleName(), this));
|
||||
}
|
||||
return anno;
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
public ConfigurationClass addMethod(ConfigurationClassMethod method) {
|
||||
method.setDeclaringClass(this);
|
||||
methods.add(method);
|
||||
Integer count = overloadedMethodMap.get(method.getName());
|
||||
this.methods.add(method);
|
||||
String name = method.getMetadata().getMethodName();
|
||||
Integer count = this.overloadedMethodMap.get(name);
|
||||
if (count != null) {
|
||||
overloadedMethodMap.put(method.getName(), count + 1);
|
||||
this.overloadedMethodMap.put(name, count + 1);
|
||||
}
|
||||
else {
|
||||
overloadedMethodMap.put(method.getName(), 1);
|
||||
this.overloadedMethodMap.put(name, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<ConfigurationClassMethod> getBeanMethods() {
|
||||
return methods;
|
||||
public Set<ConfigurationClassMethod> getConfigurationMethods() {
|
||||
return this.methods;
|
||||
}
|
||||
|
||||
public void validate(ProblemReporter problemReporter) {
|
||||
// No overloading of factory methods allowed
|
||||
for (Map.Entry<String, Integer> entry : overloadedMethodMap.entrySet()) {
|
||||
for (Map.Entry<String, Integer> entry : this.overloadedMethodMap.entrySet()) {
|
||||
String methodName = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (count > 1) {
|
||||
|
|
@ -194,11 +118,11 @@ final class ConfigurationClass implements BeanMetadataElement {
|
|||
}
|
||||
|
||||
// A configuration class may not be final (CGLIB limitation)
|
||||
if (getAnnotation(Configuration.class) != null) {
|
||||
if (Modifier.isFinal(modifiers)) {
|
||||
if (getMetadata().hasAnnotation(Configuration.class.getName())) {
|
||||
if (getMetadata().isFinal()) {
|
||||
problemReporter.error(new FinalConfigurationProblem());
|
||||
}
|
||||
for (ConfigurationClassMethod method : methods) {
|
||||
for (ConfigurationClassMethod method : this.methods) {
|
||||
method.validate(problemReporter);
|
||||
}
|
||||
}
|
||||
|
|
@ -210,7 +134,7 @@ final class ConfigurationClass implements BeanMetadataElement {
|
|||
|
||||
public FinalConfigurationProblem() {
|
||||
super(String.format("@Configuration class '%s' may not be final. Remove the final modifier to continue.",
|
||||
getSimpleName()), ConfigurationClass.this.getLocation());
|
||||
getSimpleName()), new Location(getResource()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +145,8 @@ final class ConfigurationClass implements BeanMetadataElement {
|
|||
public OverloadedMethodProblem(String methodName, int count) {
|
||||
super(String.format("@Configuration class '%s' has %s overloaded factory methods of name '%s'. " +
|
||||
"Only one factory method of the same name allowed.",
|
||||
getSimpleName(), count, methodName), ConfigurationClass.this.getLocation());
|
||||
getSimpleName(), count, methodName), new Location(getResource()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +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.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Interface used when dynamically creating mutable instances of annotations associated
|
||||
* with {@link Configuration} class processing. This functionality is necessary given
|
||||
* that parsing of Configuration classes is done with ASM. Annotation metadata (including
|
||||
* attributes) is parsed from the class files, and instances of those annotations are
|
||||
* then created using this interface and its associated utilities. The annotation
|
||||
* instances are attached to the configuration model objects at runtime, namely
|
||||
* {@link ConfigurationClassMethod}. This approach is better than the alternative of
|
||||
* creating fine-grained model representations of all annotations and attributes.
|
||||
* It is better to simply attach annotation instances and read them as needed.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassAnnotationVisitor
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
public interface ConfigurationClassAnnotation extends Annotation {
|
||||
|
||||
void setAttributeValue(String attribName, Object attribValue);
|
||||
|
||||
Class<?> getAttributeType(String attributeName);
|
||||
|
||||
}
|
||||
|
|
@ -1,167 +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.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} that populates a given {@link ConfigurationClassAnnotation} instance
|
||||
* with its attributes.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassAnnotation
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
class ConfigurationClassAnnotationVisitor implements AnnotationVisitor {
|
||||
|
||||
protected final ConfigurationClassAnnotation mutableAnno;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigurationClassAnnotationVisitor} instance that will populate the the
|
||||
* attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation} instead of
|
||||
* {@link ConfigurationClassAnnotation} to avoid the need for callers to typecast.
|
||||
* @param mutableAnno {@link ConfigurationClassAnnotation} instance to visit and populate
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
public ConfigurationClassAnnotationVisitor(ConfigurationClassAnnotation mutableAnno, ClassLoader classLoader) {
|
||||
this.mutableAnno = mutableAnno;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void visit(String attribName, Object attribValue) {
|
||||
Class<?> attribReturnType = mutableAnno.getAttributeType(attribName);
|
||||
if (attribReturnType.equals(Class.class)) {
|
||||
// the attribute type is Class -> load it and set it.
|
||||
String className = ((Type) attribValue).getClassName();
|
||||
try {
|
||||
Class<?> classVal = classLoader.loadClass(className);
|
||||
if (classVal != null) {
|
||||
mutableAnno.setAttributeValue(attribName, classVal);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Cannot resolve attribute type [" + className + "]", ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// otherwise, assume the value can be set literally
|
||||
mutableAnno.setAttributeValue(attribName, attribValue);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) {
|
||||
String enumTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor);
|
||||
try {
|
||||
Class<? extends Enum> enumType = (Class<? extends Enum>) classLoader.loadClass(enumTypeName);
|
||||
if (enumType == null) {
|
||||
return;
|
||||
}
|
||||
Enum enumValue = Enum.valueOf(enumType, strEnumValue);
|
||||
mutableAnno.setAttributeValue(attribName, enumValue);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Cannot resolve enum type [" + enumTypeName + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String attribName, String annoTypeDesc) {
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
try {
|
||||
Field attribute = mutableAnno.getClass().getField(attribName);
|
||||
attribute.set(mutableAnno, anno);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not reflectively set annotation field", ex);
|
||||
}
|
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(final String attribName) {
|
||||
return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} that visits any annotation array values while populating
|
||||
* a new {@link ConfigurationClassAnnotation} instance.
|
||||
*/
|
||||
private static class MutableAnnotationArrayVisitor implements AnnotationVisitor {
|
||||
|
||||
private final List<Object> values = new ArrayList<Object>();
|
||||
|
||||
private final ConfigurationClassAnnotation mutableAnno;
|
||||
|
||||
private final String attribName;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public MutableAnnotationArrayVisitor(ConfigurationClassAnnotation mutableAnno, String attribName, ClassLoader classLoader) {
|
||||
this.mutableAnno = mutableAnno;
|
||||
this.attribName = attribName;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void visit(String na, Object value) {
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
values.add(anno);
|
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String s) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
|
||||
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0);
|
||||
mutableAnno.setAttributeValue(attribName, values.toArray(array));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
|
@ -35,6 +36,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
|
@ -78,7 +80,7 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
*/
|
||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||
doLoadBeanDefinitionForConfigurationClass(configClass);
|
||||
for (ConfigurationClassMethod method : configClass.getBeanMethods()) {
|
||||
for (ConfigurationClassMethod method : configClass.getConfigurationMethods()) {
|
||||
loadBeanDefinitionsForModelMethod(method);
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +91,7 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
|
||||
if (configClass.getBeanName() == null) {
|
||||
GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
|
||||
configBeanDef.setBeanClassName(configClass.getName());
|
||||
configBeanDef.setBeanClassName(configClass.getMetadata().getClassName());
|
||||
String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, registry);
|
||||
configClass.setBeanName(configBeanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
|
@ -103,24 +105,26 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
* {@link #registry} based on its contents.
|
||||
*/
|
||||
private void loadBeanDefinitionsForModelMethod(ConfigurationClassMethod method) {
|
||||
MethodMetadata metadata = method.getMetadata();
|
||||
|
||||
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition();
|
||||
ConfigurationClass configClass = method.getDeclaringClass();
|
||||
beanDef.setFactoryBeanName(configClass.getBeanName());
|
||||
beanDef.setUniqueFactoryMethodName(method.getName());
|
||||
beanDef.setUniqueFactoryMethodName(metadata.getMethodName());
|
||||
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
// consider name and any aliases
|
||||
Bean bean = method.getRequiredAnnotation(Bean.class);
|
||||
List<String> names = new ArrayList<String>(Arrays.asList(bean.name()));
|
||||
String beanName = (names.size() > 0) ? names.remove(0) : method.getName();
|
||||
for (String alias : bean.name()) {
|
||||
Map<String, Object> beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName());
|
||||
List<String> names = new ArrayList<String>(Arrays.asList((String[]) beanAttributes.get("name")));
|
||||
String beanName = (names.size() > 0 ? names.remove(0) : method.getMetadata().getMethodName());
|
||||
for (String alias : names) {
|
||||
registry.registerAlias(beanName, alias);
|
||||
}
|
||||
|
||||
// has this already been overriden (i.e.: via XML)?
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName);
|
||||
// is the existing bean definition one that was created by JavaConfig?
|
||||
// is the existing bean definition one that was created from a configuration class?
|
||||
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
|
||||
// no -> then it's an external override, probably XML
|
||||
// overriding is legal, return immediately
|
||||
|
|
@ -132,48 +136,46 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
}
|
||||
}
|
||||
|
||||
if (method.getAnnotation(Primary.class) != null) {
|
||||
if (metadata.hasAnnotation(Primary.class.getName())) {
|
||||
beanDef.setPrimary(true);
|
||||
}
|
||||
|
||||
// is this bean to be instantiated lazily?
|
||||
Lazy lazy = method.getAnnotation(Lazy.class);
|
||||
if (lazy != null) {
|
||||
beanDef.setLazyInit(lazy.value());
|
||||
if (metadata.hasAnnotation(Lazy.class.getName())) {
|
||||
beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value"));
|
||||
}
|
||||
else {
|
||||
Lazy defaultLazy = configClass.getAnnotation(Lazy.class);
|
||||
if (defaultLazy != null) {
|
||||
beanDef.setLazyInit(defaultLazy.value());
|
||||
else if (configClass.getMetadata().hasAnnotation(Lazy.class.getName())){
|
||||
beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"));
|
||||
}
|
||||
|
||||
if (metadata.hasAnnotation(DependsOn.class.getName())) {
|
||||
String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value");
|
||||
if (dependsOn.length > 0) {
|
||||
beanDef.setDependsOn(dependsOn);
|
||||
}
|
||||
}
|
||||
|
||||
DependsOn dependsOn = method.getAnnotation(DependsOn.class);
|
||||
if (dependsOn != null && dependsOn.value().length > 0) {
|
||||
beanDef.setDependsOn(dependsOn.value());
|
||||
}
|
||||
|
||||
Autowire autowire = bean.autowire();
|
||||
Autowire autowire = (Autowire) beanAttributes.get("autowire");
|
||||
if (autowire.isAutowire()) {
|
||||
beanDef.setAutowireMode(autowire.value());
|
||||
}
|
||||
|
||||
String initMethodName = bean.initMethod();
|
||||
String initMethodName = (String) beanAttributes.get("initMethod");
|
||||
if (StringUtils.hasText(initMethodName)) {
|
||||
beanDef.setInitMethodName(initMethodName);
|
||||
}
|
||||
|
||||
String destroyMethodName = bean.destroyMethod();
|
||||
String destroyMethodName = (String) beanAttributes.get("destroyMethod");
|
||||
if (StringUtils.hasText(destroyMethodName)) {
|
||||
beanDef.setDestroyMethodName(destroyMethodName);
|
||||
}
|
||||
|
||||
// consider scoping
|
||||
Scope scope = method.getAnnotation(Scope.class);
|
||||
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
|
||||
if (scope != null) {
|
||||
beanDef.setScope(scope.value());
|
||||
proxyMode = scope.proxyMode();
|
||||
if (metadata.hasAnnotation(Scope.class.getName())) {
|
||||
Map<String, Object> scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName());
|
||||
beanDef.setScope((String) scopeAttributes.get("value"));
|
||||
proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode");
|
||||
if (proxyMode == ScopedProxyMode.DEFAULT) {
|
||||
proxyMode = ScopedProxyMode.NO;
|
||||
}
|
||||
|
|
@ -188,7 +190,7 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getName(), beanName));
|
||||
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName));
|
||||
}
|
||||
|
||||
registry.registerBeanDefinition(beanName, beanDefToRegister);
|
||||
|
|
@ -196,11 +198,11 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
|
||||
* created from a configuration class 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.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private class ConfigurationClassBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
public ConfigurationClassBeanDefinition() {
|
||||
|
|
|
|||
|
|
@ -60,17 +60,14 @@ class ConfigurationClassEnhancer {
|
|||
*/
|
||||
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
|
||||
callbackInstances.add(new BeanMethodInterceptor(beanFactory));
|
||||
callbackInstances.add(NoOp.INSTANCE);
|
||||
|
||||
for (Callback callback : callbackInstances) {
|
||||
callbackTypes.add(callback.getClass());
|
||||
this.callbackInstances.add(new BeanMethodInterceptor(beanFactory));
|
||||
this.callbackInstances.add(NoOp.INSTANCE);
|
||||
for (Callback callback : this.callbackInstances) {
|
||||
this.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() {
|
||||
this.callbackFilter = new CallbackFilter() {
|
||||
public int accept(Method candidateMethod) {
|
||||
return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1;
|
||||
}
|
||||
|
|
@ -84,12 +81,12 @@ class ConfigurationClassEnhancer {
|
|||
* @return fully-qualified name of the enhanced subclass
|
||||
*/
|
||||
public Class enhance(Class configClass) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Enhancing " + configClass.getName());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Enhancing " + configClass.getName());
|
||||
}
|
||||
Class<?> enhancedClass = createClass(newEnhancer(configClass));
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
configClass.getName(), enhancedClass.getName()));
|
||||
}
|
||||
return enhancedClass;
|
||||
|
|
@ -100,17 +97,14 @@ class ConfigurationClassEnhancer {
|
|||
*/
|
||||
private Enhancer newEnhancer(Class<?> superclass) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
|
||||
// because callbackFilter and callbackTypes are dynamically populated
|
||||
// Because callbackFilter and callbackTypes are dynamically populated
|
||||
// there's no opportunity for caching. This does not appear to be causing
|
||||
// any performance problem.
|
||||
enhancer.setUseCache(false);
|
||||
|
||||
enhancer.setSuperclass(superclass);
|
||||
enhancer.setUseFactory(false);
|
||||
enhancer.setCallbackFilter(callbackFilter);
|
||||
enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()]));
|
||||
|
||||
enhancer.setCallbackFilter(this.callbackFilter);
|
||||
enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()]));
|
||||
return enhancer;
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +114,7 @@ class ConfigurationClassEnhancer {
|
|||
*/
|
||||
private Class<?> createClass(Enhancer enhancer) {
|
||||
Class<?> subclass = enhancer.createClass();
|
||||
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[callbackInstances.size()]));
|
||||
Enhancer.registerCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()]));
|
||||
return subclass;
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +136,6 @@ class ConfigurationClassEnhancer {
|
|||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
|
||||
* existence of this bean object.
|
||||
|
|
@ -161,7 +154,7 @@ class ConfigurationClassEnhancer {
|
|||
Scope scope = AnnotationUtils.findAnnotation(method, Scope.class);
|
||||
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
|
||||
String scopedBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
|
||||
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
|
||||
if (this.beanFactory.isCurrentlyInCreation(scopedBeanName)) {
|
||||
beanName = scopedBeanName;
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +163,7 @@ class ConfigurationClassEnhancer {
|
|||
// container for already cached instances
|
||||
if (factoryContainsBean(beanName)) {
|
||||
// we have an already existing cached instance of this bean -> retrieve it
|
||||
Object cachedBean = beanFactory.getBean(beanName);
|
||||
Object cachedBean = this.beanFactory.getBean(beanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Returning cached object [%s] for @Bean method %s.%s",
|
||||
cachedBean, method.getDeclaringClass().getSimpleName(), beanName));
|
||||
|
|
@ -185,18 +178,18 @@ class ConfigurationClassEnhancer {
|
|||
/**
|
||||
* Check the beanFactory to see whether the bean named <var>beanName</var> already
|
||||
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
|
||||
* we're in the middle of servicing the initial request for this bean. From JavaConfig's
|
||||
* perspective, this means that the bean does not actually yet exist, and that it is now
|
||||
* our job to create it for the first time by executing the logic in the corresponding
|
||||
* Bean method.
|
||||
* we're in the middle of servicing the initial request for this bean. From an enhanced
|
||||
* factory method's perspective, this means that the bean does not actually yet exist,
|
||||
* and that it is now our job to create it for the first time by executing the logic
|
||||
* in the corresponding factory method.
|
||||
* <p>Said another way, this check repurposes
|
||||
* {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
|
||||
* the container is calling this method or the user is calling this method.
|
||||
* @param beanName name of bean to check for
|
||||
* @return true if <var>beanName</var> already exists in the factory
|
||||
* @return whether <var>beanName</var> already exists in the factory
|
||||
*/
|
||||
private boolean factoryContainsBean(String beanName) {
|
||||
return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
|
||||
return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,10 @@
|
|||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
|
||||
/**
|
||||
* Represents a {@link Configuration} class method marked with the {@link Bean} annotation.
|
||||
|
|
@ -38,213 +31,46 @@ import org.springframework.core.io.ClassPathResource;
|
|||
* @see ConfigurationClassParser
|
||||
* @see ConfigurationClassBeanDefinitionReader
|
||||
*/
|
||||
final class ConfigurationClassMethod implements BeanMetadataElement {
|
||||
final class ConfigurationClassMethod {
|
||||
|
||||
private final String name;
|
||||
private final MethodMetadata metadata;
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
private final ReturnType returnType;
|
||||
|
||||
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
|
||||
|
||||
private transient ConfigurationClass declaringClass;
|
||||
|
||||
private transient Object source;
|
||||
private final ConfigurationClass declaringClass;
|
||||
|
||||
|
||||
public ConfigurationClassMethod(String name, int modifiers, ReturnType returnType, Annotation... annotations) {
|
||||
Assert.hasText(name);
|
||||
this.name = name;
|
||||
|
||||
Assert.notNull(annotations);
|
||||
for (Annotation annotation : annotations) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
|
||||
this.modifiers = modifiers;
|
||||
|
||||
Assert.notNull(returnType);
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ReturnType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.reflect.Modifier
|
||||
*/
|
||||
public int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this method 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 anno : annotations)
|
||||
if (anno.annotationType().equals(annoType))
|
||||
return (A) anno;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this method matching <var>annoType</var>
|
||||
* @throws {@link IllegalStateException} if not present
|
||||
* @see #getAnnotation(Class)
|
||||
*/
|
||||
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
|
||||
T anno = getAnnotation(annoType);
|
||||
if (anno == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("required annotation %s is not present on %s", annoType.getSimpleName(), this));
|
||||
}
|
||||
return anno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a bi-directional relationship between this method and its declaring class.
|
||||
*
|
||||
* @see ConfigurationClass#addMethod(ConfigurationClassMethod)
|
||||
*/
|
||||
public void setDeclaringClass(ConfigurationClass declaringClass) {
|
||||
public ConfigurationClassMethod(MethodMetadata metadata, ConfigurationClass declaringClass) {
|
||||
this.metadata = metadata;
|
||||
this.declaringClass = declaringClass;
|
||||
}
|
||||
|
||||
|
||||
public MethodMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public ConfigurationClass getDeclaringClass() {
|
||||
return declaringClass;
|
||||
return this.declaringClass;
|
||||
}
|
||||
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (declaringClass == null) {
|
||||
throw new IllegalStateException(
|
||||
"declaringClass property is null. Call setDeclaringClass() before calling getLocation()");
|
||||
}
|
||||
return new Location(declaringClass.getLocation().getResource(), getSource());
|
||||
public Location getResourceLocation() {
|
||||
return new Location(this.declaringClass.getResource(), this.metadata.getMethodName());
|
||||
}
|
||||
|
||||
public void validate(ProblemReporter problemReporter) {
|
||||
if (Modifier.isPrivate(getModifiers())) {
|
||||
problemReporter.error(new PrivateMethodError());
|
||||
}
|
||||
if (Modifier.isFinal(getModifiers())) {
|
||||
problemReporter.error(new FinalMethodError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ReturnType implements BeanMetadataElement {
|
||||
|
||||
private String name;
|
||||
|
||||
private boolean isInterface;
|
||||
|
||||
private transient Object source;
|
||||
|
||||
|
||||
public ReturnType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified name of this class.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fully-qualified name of this class.
|
||||
*/
|
||||
public void setName(String className) {
|
||||
this.name = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return name == null ? null : ClassUtils.getShortName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class represented by this ModelClass instance is an interface.
|
||||
*/
|
||||
public boolean isInterface() {
|
||||
return isInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies that this class is (true) or is not (false) an interface.
|
||||
*/
|
||||
public void setInterface(boolean isInterface) {
|
||||
this.isInterface = isInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a resource path-formatted representation of the .java file that declares this
|
||||
* class
|
||||
*/
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source location for this class. Must be a resource-path formatted string.
|
||||
*
|
||||
* @param source resource path to the .java file that declares this class.
|
||||
*/
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (getName() == null) {
|
||||
throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()");
|
||||
}
|
||||
return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource());
|
||||
if (this.declaringClass.getMetadata().hasAnnotation(Configuration.class.getName()) && !getMetadata().isOverridable()) {
|
||||
problemReporter.error(new NonOverridableMethodError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Bean} methods must be non-private in order to accommodate CGLIB.
|
||||
* {@link Bean} methods must be overridable in order to accommodate CGLIB.
|
||||
*/
|
||||
private class PrivateMethodError extends Problem {
|
||||
private class NonOverridableMethodError extends Problem {
|
||||
|
||||
public PrivateMethodError() {
|
||||
super(String.format("Method '%s' must not be private; increase the method's visibility to continue",
|
||||
getName()), ConfigurationClassMethod.this.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Bean} methods must be non-final in order to accommodate CGLIB.
|
||||
*/
|
||||
private class FinalMethodError extends Problem {
|
||||
|
||||
public FinalMethodError() {
|
||||
super(String.format("Method '%s' must not be final; remove the final modifier to continue",
|
||||
getName()), ConfigurationClassMethod.this.getLocation());
|
||||
public NonOverridableMethodError() {
|
||||
super(String.format("Method '%s' must not be private, final or static; change the method's modifiers to continue",
|
||||
getMetadata().getMethodName()), getResourceLocation());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,23 @@
|
|||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReader;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
|
||||
/**
|
||||
* Parses a {@link Configuration} class definition, populating a configuration model.
|
||||
|
|
@ -41,19 +51,20 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
|||
*/
|
||||
class ConfigurationClassParser {
|
||||
|
||||
private final SimpleMetadataReaderFactory metadataReaderFactory;
|
||||
private final MetadataReaderFactory metadataReaderFactory;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final Stack<ConfigurationClass> importStack = new ImportStack();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used to populate a
|
||||
* configuration model.
|
||||
* @param model model to be populated by each successive call to {@link #parse}
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used
|
||||
* to populate a configuration model.
|
||||
*/
|
||||
public ConfigurationClassParser(SimpleMetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
|
||||
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.problemReporter = problemReporter;
|
||||
this.model = new LinkedHashSet<ConfigurationClass>();
|
||||
|
|
@ -67,17 +78,80 @@ class ConfigurationClassParser {
|
|||
* (assumes that this configuration class was configured via XML)
|
||||
*/
|
||||
public void parse(String className, String beanName) throws IOException {
|
||||
SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
ConfigurationClass configClass = new ConfigurationClass();
|
||||
configClass.setBeanName(beanName);
|
||||
reader.getClassReader().accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory), false);
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
processConfigurationClass(new ConfigurationClass(reader, beanName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the specified {@link Configuration @Configuration} class.
|
||||
* @param clazz the Clazz to parse
|
||||
* @param beanName may be null, but if populated represents the bean id
|
||||
* (assumes that this configuration class was configured via XML)
|
||||
*/
|
||||
public void parse(Class clazz, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(clazz, beanName));
|
||||
}
|
||||
|
||||
|
||||
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
|
||||
AnnotationMetadata metadata = configClass.getMetadata();
|
||||
while (metadata != null) {
|
||||
doProcessConfigurationClass(configClass, metadata);
|
||||
String superClassName = metadata.getSuperClassName();
|
||||
if (superClassName != null && !Object.class.getName().equals(superClassName)) {
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
|
||||
metadata = new StandardAnnotationMetadata(clazz.getSuperclass());
|
||||
}
|
||||
else {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName);
|
||||
metadata = reader.getAnnotationMetadata();
|
||||
}
|
||||
}
|
||||
else {
|
||||
metadata = null;
|
||||
}
|
||||
}
|
||||
model.add(configClass);
|
||||
}
|
||||
|
||||
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
||||
if (metadata.hasAnnotation(Import.class.getName())) {
|
||||
processImport(configClass, (String[]) metadata.getAnnotationAttributes(Import.class.getName()).get("value"));
|
||||
}
|
||||
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(Bean.class.getName());
|
||||
for (MethodMetadata methodMetadata : methods) {
|
||||
configClass.addMethod(new ConfigurationClassMethod(methodMetadata, configClass));
|
||||
}
|
||||
}
|
||||
|
||||
public void processImport(ConfigurationClass configClass, String[] classesToImport) throws IOException {
|
||||
if (this.importStack.contains(configClass)) {
|
||||
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
|
||||
}
|
||||
else {
|
||||
this.importStack.push(configClass);
|
||||
for (String classToImport : classesToImport) {
|
||||
processClassToImport(classToImport);
|
||||
}
|
||||
this.importStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void processClassToImport(String classToImport) throws IOException {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport);
|
||||
AnnotationMetadata metadata = reader.getAnnotationMetadata();
|
||||
if (!metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
this.problemReporter.error(
|
||||
new NonAnnotatedConfigurationProblem(metadata.getClassName(), reader.getResource()));
|
||||
}
|
||||
else {
|
||||
processConfigurationClass(new ConfigurationClass(reader, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through the model validating each {@link ConfigurationClass}.
|
||||
* @param problemReporter {@link ProblemReporter} against which any validation errors
|
||||
* will be registered
|
||||
* @see ConfigurationClass#validate
|
||||
*/
|
||||
public void validate() {
|
||||
|
|
@ -90,4 +164,78 @@ class ConfigurationClassParser {
|
|||
return this.model;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class ImportStack extends Stack<ConfigurationClass> {
|
||||
|
||||
/**
|
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
|
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of
|
||||
* type ConfigurationClass.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object elem) {
|
||||
ConfigurationClass configClass = (ConfigurationClass) elem;
|
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
|
||||
public int compare(ConfigurationClass first, ConfigurationClass second) {
|
||||
return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1;
|
||||
}
|
||||
};
|
||||
return (Collections.binarySearch(this, configClass, comparator) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stack containing (in order)
|
||||
* <ol>
|
||||
* <li>com.acme.Foo</li>
|
||||
* <li>com.acme.Bar</li>
|
||||
* <li>com.acme.Baz</li>
|
||||
* </ol>
|
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Iterator<ConfigurationClass> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
builder.append(iterator.next().getSimpleName());
|
||||
if (iterator.hasNext()) {
|
||||
builder.append("->");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configuration classes must be annotated with {@link Configuration @Configuration}.
|
||||
*/
|
||||
private static class NonAnnotatedConfigurationProblem extends Problem {
|
||||
|
||||
public NonAnnotatedConfigurationProblem(String className, Resource resource) {
|
||||
super(String.format("%s was imported as a @Configuration class but was not actually annotated " +
|
||||
"with @Configuration. Annotate the class or do not attempt to process it.", className),
|
||||
new Location(resource));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Problem} registered upon detection of a circular {@link Import}.
|
||||
*/
|
||||
private static class CircularImportProblem extends Problem {
|
||||
|
||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
|
||||
super(String.format("A circular @Import has been detected: " +
|
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
||||
"already present in the current import stack [%s]",
|
||||
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
||||
attemptedImport.getSimpleName(), importStack),
|
||||
new Location(importStack.peek().getResource())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,23 +35,23 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
|||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||
* {@link Configuration @Configuration} classes.
|
||||
* <p>
|
||||
* Registered by default when using {@literal <context:annotation-config/>} or
|
||||
*
|
||||
* <p>Registered by default when using {@literal <context:annotation-config/>} or
|
||||
* {@literal <context:component-scan/>}. Otherwise, may be declared manually as
|
||||
* with any other BeanFactoryPostProcessor.
|
||||
* <p>
|
||||
* This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it's important
|
||||
*
|
||||
* <p>This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it is important
|
||||
* that any {@link Bean} methods declared in Configuration classes have their
|
||||
* respective bean definitions registered before any other BeanFactoryPostProcessor
|
||||
* executes.
|
||||
|
|
@ -62,32 +62,33 @@ import org.springframework.util.ClassUtils;
|
|||
*/
|
||||
public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware {
|
||||
|
||||
public static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
|
||||
|
||||
private static final String CONFIGURATION_CLASS_FULL = "full";
|
||||
|
||||
private static final String CONFIGURATION_CLASS_LITE = "lite";
|
||||
|
||||
|
||||
/** Whether the CGLIB2 library is present on the classpath */
|
||||
private static final boolean cglibAvailable = ClassUtils.isPresent(
|
||||
"net.sf.cglib.proxy.Enhancer", ConfigurationClassPostProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ConfigurationClassPostProcessor.class);
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Used to register any problems detected with {@link Configuration} or {@link Bean}
|
||||
* declarations. For instance, a Bean method marked as {@literal final} is illegal
|
||||
* and would be reported as a problem. Defaults to {@link FailFastProblemReporter},
|
||||
* but is overridable with {@link #setProblemReporter}
|
||||
*/
|
||||
private ProblemReporter problemReporter = new FailFastProblemReporter();
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
|
||||
private SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
|
||||
|
||||
|
||||
/**
|
||||
* Override the default {@link ProblemReporter}.
|
||||
* @param problemReporter custom problem reporter
|
||||
* Set the {@link ProblemReporter} to use.
|
||||
* <p>Used to register any problems detected with {@link Configuration} or {@link Bean}
|
||||
* declarations. For instance, a Bean method marked as {@literal final} is illegal
|
||||
* and would be reported as a problem. Defaults to {@link FailFastProblemReporter}.
|
||||
*/
|
||||
public void setProblemReporter(ProblemReporter problemReporter) {
|
||||
this.problemReporter = problemReporter;
|
||||
|
|
@ -116,34 +117,38 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
|||
enhanceConfigurationClasses(beanFactory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build and validate a configuration model based on the registry of
|
||||
* {@link Configuration} classes.
|
||||
*/
|
||||
protected final void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
|
||||
Set<BeanDefinitionHolder> configBeanDefs = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
for (String beanName : registry.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
|
||||
if (checkConfigurationClassBeanDefinition(beanDef)) {
|
||||
configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
if (checkConfigurationClassCandidate(beanDef)) {
|
||||
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
}
|
||||
}
|
||||
|
||||
// Return immediately if no @Configuration classes were found
|
||||
if (configBeanDefs.isEmpty()) {
|
||||
if (configCandidates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate a new configuration model by parsing each @Configuration classes
|
||||
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter);
|
||||
for (BeanDefinitionHolder holder : configBeanDefs) {
|
||||
String beanClassName = holder.getBeanDefinition().getBeanClassName();
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
parser.parse(beanClassName, holder.getBeanName());
|
||||
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
|
||||
parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
|
||||
}
|
||||
else {
|
||||
parser.parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class [" + beanClassName + "]", ex);
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
|
||||
}
|
||||
}
|
||||
parser.validate();
|
||||
|
|
@ -152,6 +157,42 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
|||
new ConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getModel());
|
||||
}
|
||||
|
||||
private boolean checkConfigurationClassCandidate(BeanDefinition beanDef) {
|
||||
AnnotationMetadata metadata;
|
||||
|
||||
// Check already loaded Class if present...
|
||||
// since we possibly can't even load the class file for this Class.
|
||||
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
|
||||
Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
|
||||
metadata = new StandardAnnotationMetadata(beanClass);
|
||||
}
|
||||
else {
|
||||
String className = beanDef.getBeanClassName();
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
metadata = metadataReader.getAnnotationMetadata();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not find class file for introspecting factory methods: " + className, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
|
||||
return true;
|
||||
}
|
||||
else if (metadata.hasAnnotation(Component.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
|
||||
* any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
|
||||
|
|
@ -162,7 +203,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
|||
Set<BeanDefinitionHolder> configBeanDefs = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||
if ("full".equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) {
|
||||
if (CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) {
|
||||
configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
}
|
||||
}
|
||||
|
|
@ -197,48 +238,4 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the BeanDefinition's beanClass (or its ancestry) is
|
||||
* {@link Configuration}-annotated, false if no beanClass is specified.
|
||||
*/
|
||||
private boolean checkConfigurationClassBeanDefinition(BeanDefinition beanDef) {
|
||||
// accommodating SPR-5655
|
||||
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
|
||||
if (AnnotationUtils.findAnnotation(
|
||||
((AbstractBeanDefinition) beanDef).getBeanClass(), Configuration.class) != null) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
|
||||
return true;
|
||||
}
|
||||
else if (AnnotationUtils.findAnnotation(
|
||||
((AbstractBeanDefinition) beanDef).getBeanClass(), Component.class) != null) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String className = beanDef.getBeanClassName();
|
||||
while (className != null && !(className.equals(Object.class.getName()))) {
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
|
||||
if (metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
|
||||
return true;
|
||||
}
|
||||
if (metadata.hasAnnotation(Component.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
|
||||
return true;
|
||||
}
|
||||
className = metadata.getSuperClassName();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load class file [" + className + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,282 +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.context.annotation;
|
||||
|
||||
import static java.lang.String.*;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Various utility methods commonly used when interacting with ASM, classloading
|
||||
* and creating {@link ConfigurationClassAnnotation} instances.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.0
|
||||
*/
|
||||
class ConfigurationClassReaderUtils {
|
||||
|
||||
/**
|
||||
* Convert a type descriptor to a classname suitable for classloading with
|
||||
* Class.forName().
|
||||
*
|
||||
* @param typeDescriptor see ASM guide section 2.1.3
|
||||
*/
|
||||
public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) {
|
||||
final String internalName; // See ASM guide section 2.1.2
|
||||
|
||||
if ("V".equals(typeDescriptor))
|
||||
return Void.class.getName();
|
||||
if ("I".equals(typeDescriptor))
|
||||
return Integer.class.getName();
|
||||
if ("Z".equals(typeDescriptor))
|
||||
return Boolean.class.getName();
|
||||
|
||||
// strip the leading array/object/primitive identifier
|
||||
if (typeDescriptor.startsWith("[["))
|
||||
internalName = typeDescriptor.substring(3);
|
||||
else if (typeDescriptor.startsWith("["))
|
||||
internalName = typeDescriptor.substring(2);
|
||||
else
|
||||
internalName = typeDescriptor.substring(1);
|
||||
|
||||
// convert slashes to dots
|
||||
String className = internalName.replace('/', '.');
|
||||
|
||||
// and strip trailing semicolon (if present)
|
||||
if (className.endsWith(";"))
|
||||
className = className.substring(0, internalName.length() - 1);
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param methodDescriptor see ASM guide section 2.1.4
|
||||
*/
|
||||
public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) {
|
||||
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
|
||||
return convertAsmTypeDescriptorToClassName(returnTypeDescriptor);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Class<? extends Annotation> loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) {
|
||||
String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc);
|
||||
try {
|
||||
return (Class<? extends Annotation>) classLoader.loadClass(annoTypeName);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Could not load annotation type [" + annoTypeName + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ConfigurationClassAnnotation} for {@code annoType}. JDK dynamic proxies are used,
|
||||
* and the returned proxy implements both {@link ConfigurationClassAnnotation} and the annotation type.
|
||||
* @param annoType annotation type that must be supplied and returned
|
||||
* @param annoType type of annotation to create
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ConfigurationClassAnnotation createMutableAnnotation(Class<? extends Annotation> annoType, ClassLoader classLoader) {
|
||||
MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType);
|
||||
Class<?>[] interfaces = new Class<?>[] {annoType, ConfigurationClassAnnotation.class};
|
||||
return (ConfigurationClassAnnotation) Proxy.newProxyInstance(classLoader, interfaces, handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles calls to {@link ConfigurationClassAnnotation} attribute methods at runtime. Essentially
|
||||
* emulates what JDK annotation dynamic proxies do.
|
||||
*/
|
||||
private static final class MutableAnnotationInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Class<? extends Annotation> annoType;
|
||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
private final Map<String, Class<?>> attributeTypes = new HashMap<String, Class<?>>();
|
||||
|
||||
public MutableAnnotationInvocationHandler(Class<? extends Annotation> annoType) {
|
||||
// pre-populate the attributes hash will all the names
|
||||
// and default values of the attributes defined in 'annoType'
|
||||
Method[] attribs = annoType.getDeclaredMethods();
|
||||
for (Method attrib : attribs) {
|
||||
this.attributes.put(attrib.getName(), AnnotationUtils.getDefaultValue(annoType, attrib.getName()));
|
||||
this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName()));
|
||||
}
|
||||
|
||||
this.annoType = annoType;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
Assert.isInstanceOf(Annotation.class, proxy);
|
||||
|
||||
String methodName = method.getName();
|
||||
|
||||
// first -> check to see if this method is an attribute on our annotation
|
||||
if (attributes.containsKey(methodName))
|
||||
return attributes.get(methodName);
|
||||
|
||||
|
||||
// second -> is it a method from java.lang.annotation.Annotation?
|
||||
if (methodName.equals("annotationType"))
|
||||
return annoType;
|
||||
|
||||
|
||||
// third -> is it a method from java.lang.Object?
|
||||
if (methodName.equals("toString"))
|
||||
return format("@%s(%s)", annoType.getName(), getAttribs());
|
||||
|
||||
if (methodName.equals("equals"))
|
||||
return isEqualTo(proxy, args[0]);
|
||||
|
||||
if (methodName.equals("hashCode"))
|
||||
return calculateHashCode(proxy);
|
||||
|
||||
|
||||
// finally -> is it a method specified by MutableAnno?
|
||||
if (methodName.equals("setAttributeValue")) {
|
||||
attributes.put((String) args[0], args[1]);
|
||||
return null; // setAttributeValue has a 'void' return type
|
||||
}
|
||||
|
||||
if (methodName.equals("getAttributeType"))
|
||||
return attributeTypes.get(args[0]);
|
||||
|
||||
throw new UnsupportedOperationException("this proxy does not support method: " + methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conforms to the hashCode() specification for Annotation.
|
||||
*
|
||||
* @see Annotation#hashCode()
|
||||
*/
|
||||
private Object calculateHashCode(Object proxy) {
|
||||
int sum = 0;
|
||||
|
||||
for (String attribName : attributes.keySet()) {
|
||||
Object attribValue = attributes.get(attribName);
|
||||
|
||||
final int attribNameHashCode = attribName.hashCode();
|
||||
final int attribValueHashCode;
|
||||
|
||||
if (attribValue == null)
|
||||
// memberValue may be null when a mutable annotation is being added to a
|
||||
// collection
|
||||
// and before it has actually been visited (and populated) by
|
||||
// MutableAnnotationVisitor
|
||||
attribValueHashCode = 0;
|
||||
else if (attribValue.getClass().isArray())
|
||||
attribValueHashCode = Arrays.hashCode((Object[]) attribValue);
|
||||
else
|
||||
attribValueHashCode = attribValue.hashCode();
|
||||
|
||||
sum += (127 * attribNameHashCode) ^ attribValueHashCode;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares <var>proxy</var> object and <var>other</var> object by comparing the return
|
||||
* values of the methods specified by their common {@link Annotation} ancestry.
|
||||
* <p/>
|
||||
* <var>other</var> must be the same type as or a subtype of <var>proxy</var>. Will
|
||||
* return false otherwise.
|
||||
* <p/>
|
||||
* Eagerly returns true if {@code proxy} == <var>other</var>
|
||||
* </p>
|
||||
* <p/>
|
||||
* Conforms strictly to the equals() specification for Annotation
|
||||
* </p>
|
||||
*
|
||||
* @see Annotation#equals(Object)
|
||||
*/
|
||||
private Object isEqualTo(Object proxy, Object other) {
|
||||
if (proxy == other)
|
||||
return true;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (!annoType.isAssignableFrom(other.getClass()))
|
||||
return false;
|
||||
|
||||
for (String attribName : attributes.keySet()) {
|
||||
Object thisVal;
|
||||
Object thatVal;
|
||||
|
||||
try {
|
||||
thisVal = attributes.get(attribName);
|
||||
thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
if ((thisVal == null) && (thatVal != null))
|
||||
return false;
|
||||
|
||||
if ((thatVal == null) && (thisVal != null))
|
||||
return false;
|
||||
|
||||
if (thatVal.getClass().isArray()) {
|
||||
if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) {
|
||||
return false;
|
||||
}
|
||||
} else if (thisVal instanceof Double) {
|
||||
if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal)))
|
||||
return false;
|
||||
} else if (thisVal instanceof Float) {
|
||||
if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal)))
|
||||
return false;
|
||||
} else if (!thisVal.equals(thatVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getAttribs() {
|
||||
List<String> attribs = new ArrayList<String>();
|
||||
for (String attribName : attributes.keySet()) {
|
||||
attribs.add(format("%s=%s", attribName, attributes.get(attribName)));
|
||||
}
|
||||
return StringUtils.collectionToDelimitedString(attribs, ", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the given annotation attribute.
|
||||
*/
|
||||
private static Class<?> getAttributeType(Class<? extends Annotation> annotationType, String attributeName) {
|
||||
try {
|
||||
return annotationType.getDeclaredMethod(attributeName).getReturnType();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not introspect return type", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,445 +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.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.Attribute;
|
||||
import org.springframework.asm.ClassAdapter;
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.asm.FieldVisitor;
|
||||
import org.springframework.asm.Label;
|
||||
import org.springframework.asm.MethodAdapter;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReader;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a
|
||||
* {@link ConfigurationClass} instance with information gleaned along the way.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassParser
|
||||
* @see ConfigurationClass
|
||||
*/
|
||||
class ConfigurationClassVisitor implements ClassVisitor {
|
||||
|
||||
private static final String OBJECT_DESC = ClassUtils.convertClassNameToResourcePath(Object.class.getName());
|
||||
|
||||
private final ConfigurationClass configClass;
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final SimpleMetadataReaderFactory metadataReaderFactory;
|
||||
|
||||
private final Stack<ConfigurationClass> importStack;
|
||||
|
||||
|
||||
public ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model,
|
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory) {
|
||||
|
||||
this.configClass = configClass;
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.importStack = new ImportStack();
|
||||
}
|
||||
|
||||
private ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model,
|
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory,
|
||||
Stack<ConfigurationClass> importStack) {
|
||||
this.configClass = configClass;
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.importStack = importStack;
|
||||
}
|
||||
|
||||
|
||||
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, String superTypeDesc, String[] arg5) {
|
||||
visitSuperType(superTypeDesc);
|
||||
|
||||
configClass.setName(ClassUtils.convertResourcePathToClassName(classTypeDesc));
|
||||
|
||||
// ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions.
|
||||
// Unknown as to why (JavaDoc is silent on the matter), but it should be
|
||||
// eliminated in order to comply with java.lang.reflect.Modifier values.
|
||||
configClass.setModifiers(modifiers - Opcodes.ACC_SUPER);
|
||||
}
|
||||
|
||||
private void visitSuperType(String superTypeDesc) {
|
||||
// traverse up the type hierarchy unless the next ancestor is java.lang.Object
|
||||
if (OBJECT_DESC.equals(superTypeDesc)) {
|
||||
return;
|
||||
}
|
||||
ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack);
|
||||
String superClassName = ClassUtils.convertResourcePathToClassName(superTypeDesc);
|
||||
try {
|
||||
SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName);
|
||||
reader.getClassReader().accept(visitor, false);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean super class [" + superClassName + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitSource(String sourceFile, String debug) {
|
||||
String resourcePath =
|
||||
ClassUtils.convertClassNameToResourcePath(configClass.getName())
|
||||
.substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile);
|
||||
|
||||
configClass.setSource(resourcePath);
|
||||
}
|
||||
|
||||
public void visitOuterClass(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a class level annotation on a {@link Configuration @Configuration} class.
|
||||
* <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 ConfigurationClassAnnotation
|
||||
* @see Configuration
|
||||
* @see Lazy
|
||||
* @see Import
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
|
||||
ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader();
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
// annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
if (Import.class.equals(annoClass)) {
|
||||
if (!importStack.contains(configClass)) {
|
||||
importStack.push(configClass);
|
||||
return new ImportAnnotationVisitor(model, problemReporter, classLoader);
|
||||
}
|
||||
problemReporter.error(new CircularImportProblem(configClass, importStack));
|
||||
}
|
||||
|
||||
ConfigurationClassAnnotation mutableAnnotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
configClass.addAnnotation(mutableAnnotation);
|
||||
return new ConfigurationClassAnnotationVisitor(mutableAnnotation, classLoader);
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attribute) {
|
||||
}
|
||||
|
||||
public void visitInnerClass(String s, String s1, String s2, int i) {
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates all {@link Configuration @Configuration} class method parsing to
|
||||
* {@link ConfigurationClassMethodVisitor}.
|
||||
*/
|
||||
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3, String[] arg4) {
|
||||
ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader();
|
||||
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
private class ConfigurationClassMethodVisitor extends MethodAdapter {
|
||||
|
||||
private final ConfigurationClass configClass;
|
||||
private final String methodName;
|
||||
private final int modifiers;
|
||||
private final ConfigurationClassMethod.ReturnType returnType;
|
||||
private final List<Annotation> annotations = new ArrayList<Annotation>();
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private int lineNumber;
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassMethodVisitor} instance.
|
||||
* @param configClass model object to which this method will be added
|
||||
* @param methodName name of the method declared in the {@link Configuration} class
|
||||
* @param methodDescriptor ASM representation of the method signature
|
||||
* @param modifiers modifiers for this method
|
||||
*/
|
||||
public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName,
|
||||
String methodDescriptor, int modifiers, ClassLoader classLoader) {
|
||||
super(new EmptyVisitor());
|
||||
this.configClass = configClass;
|
||||
this.methodName = methodName;
|
||||
this.classLoader = classLoader;
|
||||
this.modifiers = modifiers;
|
||||
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a single annotation on this method. Will be called once for each annotation
|
||||
* present (regardless of its RetentionPolicy).
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
return super.visitAnnotation(annoTypeDesc, visible);
|
||||
}
|
||||
ConfigurationClassAnnotation annotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
annotations.add(annotation);
|
||||
return new ConfigurationClassAnnotationVisitor(annotation, classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the line number of this method within its declaring class. In reality, this
|
||||
* number is always inaccurate - <var>lineNo</var> represents the line number of the
|
||||
* first instruction in this method. Method declaration line numbers are not in any way
|
||||
* tracked in the bytecode. Any tooling or output that reads this value will have to
|
||||
* compensate and estimate where the actual method declaration is.
|
||||
*/
|
||||
@Override
|
||||
public void visitLineNumber(int lineNo, Label start) {
|
||||
this.lineNumber = lineNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses through all {@link #annotations} on this method in order to determine whether
|
||||
* it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}.
|
||||
*/
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
for (Annotation anno : annotations) {
|
||||
if (Bean.class.equals(anno.annotationType())) {
|
||||
// this method is annotated with @Bean -> add it to the ConfigurationClass model
|
||||
Annotation[] annoArray = annotations.toArray(new Annotation[annotations.size()]);
|
||||
ConfigurationClassMethod method = new ConfigurationClassMethod(methodName, modifiers, returnType, annoArray);
|
||||
method.setSource(lineNumber);
|
||||
configClass.addMethod(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
|
||||
* that type is an interface.
|
||||
*/
|
||||
private ConfigurationClassMethod.ReturnType initReturnTypeFromMethodDescriptor(String methodDescriptor) {
|
||||
final ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType(ConfigurationClassReaderUtils.getReturnTypeFromAsmMethodDescriptor(methodDescriptor));
|
||||
// detect whether the return type is an interface
|
||||
try {
|
||||
metadataReaderFactory.getMetadataReader(returnType.getName()).getClassReader().accept(
|
||||
new ClassAdapter(new EmptyVisitor()) {
|
||||
@Override
|
||||
public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) {
|
||||
returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE);
|
||||
}
|
||||
}, false);
|
||||
return returnType;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean return type [" + returnType.getName() + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation
|
||||
* for all its specified classes and then one by one processes each class with a new
|
||||
* {@link ConfigurationClassVisitor}.
|
||||
*/
|
||||
private class ImportAnnotationVisitor implements AnnotationVisitor{
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final List<String> classesToImport = new ArrayList<String>();
|
||||
|
||||
public ImportAnnotationVisitor(
|
||||
Set<ConfigurationClass> model, ProblemReporter problemReporter, ClassLoader classLoader) {
|
||||
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
}
|
||||
|
||||
public void visit(String s, Object o) {
|
||||
}
|
||||
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String s, String s1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String attribName) {
|
||||
Assert.isTrue("value".equals(attribName));
|
||||
return new AnnotationVisitor() {
|
||||
public void visit(String na, Object type) {
|
||||
Assert.isInstanceOf(Type.class, type);
|
||||
classesToImport.add(((Type) type).getClassName());
|
||||
}
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
public AnnotationVisitor visitAnnotation(String s, String s1) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public AnnotationVisitor visitArray(String s) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public void visitEnd() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
for (String classToImport : classesToImport) {
|
||||
processClassToImport(classToImport);
|
||||
}
|
||||
importStack.pop();
|
||||
}
|
||||
|
||||
private void processClassToImport(String classToImport) {
|
||||
ConfigurationClass configClass = new ConfigurationClass();
|
||||
try {
|
||||
ClassReader reader = metadataReaderFactory.getMetadataReader(classToImport).getClassReader();
|
||||
reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack), false);
|
||||
if (configClass.getAnnotation(Configuration.class) == null) {
|
||||
problemReporter.error(new NonAnnotatedConfigurationProblem(configClass.getName(), configClass.getLocation()));
|
||||
}
|
||||
else {
|
||||
model.add(configClass);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load imported configuration class [" + classToImport + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class ImportStack extends Stack<ConfigurationClass> {
|
||||
|
||||
/**
|
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
|
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of
|
||||
* type ConfigurationClass.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object elem) {
|
||||
if (!(elem instanceof ConfigurationClass)) {
|
||||
return false;
|
||||
}
|
||||
ConfigurationClass configClass = (ConfigurationClass) elem;
|
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
|
||||
public int compare(ConfigurationClass first, ConfigurationClass second) {
|
||||
return first.getName().equals(second.getName()) ? 0 : 1;
|
||||
}
|
||||
};
|
||||
return (Collections.binarySearch(this, configClass, comparator) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stack containing (in order)
|
||||
* <ol>
|
||||
* <li>com.acme.Foo</li>
|
||||
* <li>com.acme.Bar</li>
|
||||
* <li>com.acme.Baz</li>
|
||||
* </ol>
|
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string.
|
||||
*/
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Iterator<ConfigurationClass> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
builder.append(iterator.next().getSimpleName());
|
||||
if (iterator.hasNext()) {
|
||||
builder.append("->");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */
|
||||
private static class NonAnnotatedConfigurationProblem extends Problem {
|
||||
|
||||
public NonAnnotatedConfigurationProblem(String className, Location location) {
|
||||
super(String.format("%s was imported as a @Configuration class but was not actually annotated " +
|
||||
"with @Configuration. Annotate the class or do not attempt to process it.", className), location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Problem} registered upon detection of a circular {@link Import}.
|
||||
* @see Import
|
||||
*/
|
||||
private static class CircularImportProblem extends Problem {
|
||||
|
||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
|
||||
super(String.format("A circular @Import has been detected: " +
|
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
||||
"already present in the current import stack [%s]",
|
||||
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
||||
attemptedImport.getSimpleName(), importStack),
|
||||
new Location(new ClassPathResource(
|
||||
ClassUtils.convertClassNameToResourcePath(importStack.peek().getName())),
|
||||
importStack.peek().getSource())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,137 +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.context.annotation;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import static org.springframework.context.annotation.ConfigurationClassReaderUtils.*;
|
||||
import static org.springframework.context.annotation.ScopedProxyMode.*;
|
||||
import static org.springframework.context.annotation.StandardScopes.*;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class BeanMethodTests {
|
||||
|
||||
private ProblemReporter problemReporter = new FailFastProblemReporter();
|
||||
private String beanName = "foo";
|
||||
private Bean beanAnno = (Bean) createMutableAnnotation(Bean.class, ClassUtils.getDefaultClassLoader());
|
||||
private ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType("FooType");
|
||||
private ConfigurationClass declaringClass = new ConfigurationClass();
|
||||
{ declaringClass.setName("test.Config"); }
|
||||
|
||||
@Test
|
||||
public void testWellFormedMethod() {
|
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno);
|
||||
|
||||
assertThat(beanMethod.getName(), sameInstance(beanName));
|
||||
assertThat(beanMethod.getModifiers(), equalTo(0));
|
||||
assertThat(beanMethod.getReturnType(), sameInstance(returnType));
|
||||
assertThat(beanMethod.getAnnotation(Bean.class), sameInstance(beanAnno));
|
||||
assertThat(beanMethod.getAnnotation(Override.class), nullValue());
|
||||
assertThat(beanMethod.getRequiredAnnotation(Bean.class), sameInstance(beanAnno));
|
||||
try {
|
||||
beanMethod.getRequiredAnnotation(Override.class);
|
||||
fail("expected IllegalStateException ex");
|
||||
} catch (IllegalStateException ex) { /* expected */ }
|
||||
|
||||
// must call setDeclaringClass() before calling getLocation()
|
||||
try {
|
||||
beanMethod.getLocation();
|
||||
fail("expected IllegalStateException ex");
|
||||
} catch (IllegalStateException ex) { /* expected */ }
|
||||
|
||||
|
||||
beanMethod.setDeclaringClass(declaringClass);
|
||||
assertThat(beanMethod.getDeclaringClass(), sameInstance(declaringClass));
|
||||
|
||||
beanMethod.setSource(12); // indicating a line number
|
||||
assertEquals(beanMethod.getSource(), 12);
|
||||
|
||||
Location location = beanMethod.getLocation();
|
||||
assertEquals(location.getResource(), new ClassPathResource("test/Config"));
|
||||
assertEquals(location.getSource(), 12);
|
||||
|
||||
// should validate without throwing as this is a well-formed method
|
||||
beanMethod.validate(problemReporter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalMethodsAreIllegal() {
|
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.FINAL, returnType, beanAnno);
|
||||
beanMethod.setDeclaringClass(declaringClass);
|
||||
try {
|
||||
beanMethod.validate(problemReporter);
|
||||
fail("should have failed due to final bean method");
|
||||
} catch (BeanDefinitionParsingException ex) {
|
||||
assertTrue(ex.getMessage().contains("remove the final modifier"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateMethodsAreIllegal() {
|
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.PRIVATE, returnType, beanAnno);
|
||||
beanMethod.setDeclaringClass(declaringClass);
|
||||
try {
|
||||
beanMethod.validate(problemReporter);
|
||||
fail("should have failed due to private bean method");
|
||||
} catch (BeanDefinitionParsingException ex) {
|
||||
assertTrue(ex.getMessage().contains("increase the method's visibility"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singletonsSansProxyAreLegal() {
|
||||
Scope scope = SingletonNoProxy.class.getAnnotation(Scope.class);
|
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope);
|
||||
beanMethod.setDeclaringClass(declaringClass);
|
||||
beanMethod.validate(problemReporter); // should validate without problems - it's legal
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionInterfaceScopedProxiesAreLegal() {
|
||||
Scope scope = SessionInterfaceProxy.class.getAnnotation(Scope.class);
|
||||
ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope);
|
||||
beanMethod.setDeclaringClass(declaringClass);
|
||||
beanMethod.validate(problemReporter); // should validate without problems - it's legal
|
||||
}
|
||||
|
||||
@Scope(value=SINGLETON, proxyMode=INTERFACES)
|
||||
private class SingletonInterfaceProxy { }
|
||||
|
||||
@Scope(value=SINGLETON, proxyMode=TARGET_CLASS)
|
||||
private class SingletonTargetClassProxy { }
|
||||
|
||||
@Scope(value=SINGLETON, proxyMode=NO)
|
||||
private class SingletonNoProxy { }
|
||||
|
||||
@Scope(value=PROTOTYPE, proxyMode=INTERFACES)
|
||||
private class PrototypeInterfaceProxy { }
|
||||
|
||||
@Scope(value=SESSION, proxyMode=INTERFACES)
|
||||
private class SessionInterfaceProxy { }
|
||||
}
|
||||
|
|
@ -34,12 +34,10 @@ import org.springframework.core.io.ClassPathResource;
|
|||
import test.beans.Colour;
|
||||
import test.beans.TestBean;
|
||||
|
||||
|
||||
/**
|
||||
* System tests covering use of {@link Autowired} and {@link Value} within
|
||||
* {@link Configuration} classes.
|
||||
*
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AutowiredConfigurationTests {
|
||||
|
|
@ -73,6 +71,7 @@ public class AutowiredConfigurationTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Autowired} constructors are not supported on {@link Configuration} classes
|
||||
* due to CGLIB constraints
|
||||
|
|
@ -96,6 +95,7 @@ public class AutowiredConfigurationTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testValueInjection() {
|
||||
System.setProperty("myProp", "foo");
|
||||
|
|
@ -119,6 +119,7 @@ public class AutowiredConfigurationTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCustomProperties() {
|
||||
ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext(
|
||||
|
|
@ -143,4 +144,5 @@ public class AutowiredConfigurationTests {
|
|||
return new TestBean(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,15 +74,6 @@ public class ConfigurationClassProcessingTests {
|
|||
} 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);
|
||||
|
|
@ -98,27 +89,11 @@ public class ConfigurationClassProcessingTests {
|
|||
} 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);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithFinalBean {
|
||||
public final @Bean TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simplestPossibleConfiguration() {
|
||||
BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class);
|
||||
|
|
@ -139,6 +114,24 @@ public class ConfigurationClassProcessingTests {
|
|||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithBeanWithCustomName {
|
||||
static TestBean testBean = new TestBean();
|
||||
@Bean(name="customName")
|
||||
public TestBean methodName() {
|
||||
return testBean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithFinalBean {
|
||||
public final @Bean TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class SimplestPossibleConfig {
|
||||
public @Bean String stringBean() {
|
||||
|
|
@ -147,6 +140,16 @@ public class ConfigurationClassProcessingTests {
|
|||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithBeanWithAliases {
|
||||
static TestBean testBean = new TestBean();
|
||||
@Bean(name={"name1", "alias1", "alias2", "alias3"})
|
||||
public TestBean methodName() {
|
||||
return testBean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithPrototypeBean {
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ package org.springframework.context.annotation.configuration;
|
|||
import java.lang.annotation.Inherited;
|
||||
|
||||
import org.junit.Test;
|
||||
import test.beans.TestBean;
|
||||
|
||||
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 test.beans.TestBean;
|
||||
|
||||
/**
|
||||
* Tests that polymorphic Configuration classes need not explicitly redeclare the
|
||||
* {@link Configuration} annotation. This respects the {@link Inherited} nature
|
||||
|
|
@ -37,7 +37,7 @@ import test.beans.TestBean;
|
|||
public class PolymorphicConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void subclassNeedNotDeclareConfigurationAnnotation() {
|
||||
public void beanMethodsDetectedOnSuperClass() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(Config.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
|
|
@ -56,6 +56,7 @@ public class PolymorphicConfigurationTests {
|
|||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config extends SuperConfig {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -231,19 +231,46 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as a Map.
|
||||
* Retrieve the given annotation's attributes as a Map,
|
||||
* preserving all attribute types as-is.
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
* @return the Map of annotation attributes, with attribute names as keys
|
||||
* and corresponding attribute values as values
|
||||
*/
|
||||
public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
|
||||
return getAnnotationAttributes(annotation, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as a Map.
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
* @param filterClasses whether to turn Class references into Strings
|
||||
* (for compatibility with {@link org.springframework.core.type.AnnotationMetadata}
|
||||
* or to preserve them as Class references
|
||||
* @return the Map of annotation attributes, with attribute names as keys
|
||||
* and corresponding attribute values as values
|
||||
*/
|
||||
public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean filterClasses) {
|
||||
Map<String, Object> attrs = new HashMap<String, Object>();
|
||||
Method[] methods = annotation.annotationType().getDeclaredMethods();
|
||||
for (int j = 0; j < methods.length; j++) {
|
||||
Method method = methods[j];
|
||||
for (Method method : methods) {
|
||||
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
|
||||
try {
|
||||
attrs.put(method.getName(), method.invoke(annotation));
|
||||
Object value = method.invoke(annotation);
|
||||
if (filterClasses) {
|
||||
if (value instanceof Class) {
|
||||
value = ((Class) value).getName();
|
||||
}
|
||||
else if (value instanceof Class[]) {
|
||||
Class[] clazzArray = (Class[]) value;
|
||||
String[] newValue = new String[clazzArray.length];
|
||||
for (int i = 0; i < clazzArray.length; i++) {
|
||||
newValue[i] = clazzArray[i].getName();
|
||||
}
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
attrs.put(method.getName(), value);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
|
||||
|
|
|
|||
|
|
@ -45,9 +45,20 @@ public interface AnnotationMetadata extends ClassMetadata {
|
|||
*/
|
||||
boolean hasAnnotation(String annotationType);
|
||||
|
||||
/**
|
||||
* Retrieve the attributes of the annotation of the given type,
|
||||
* if any (i.e. if defined on the underlying class).
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return a Map of attributes, with the attribute name as key (e.g. "value")
|
||||
* and the defined attribute value as Map value. This return value will be
|
||||
* <code>null</code> if no matching annotation is defined.
|
||||
*/
|
||||
Map<String, Object> getAnnotationAttributes(String annotationType);
|
||||
|
||||
/**
|
||||
* Return the names of all meta-annotation types defined on the
|
||||
* given annotation type of the underlying class.
|
||||
* @param annotationType the meta-annotation type to look for
|
||||
* @return the meta-annotation type names
|
||||
*/
|
||||
Set<String> getMetaAnnotationTypes(String annotationType);
|
||||
|
|
@ -61,26 +72,20 @@ public interface AnnotationMetadata extends ClassMetadata {
|
|||
boolean hasMetaAnnotation(String metaAnnotationType);
|
||||
|
||||
/**
|
||||
* Retrieve the attributes of the annotation of the given type,
|
||||
* if any (i.e. if defined on the underlying class).
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return a Map of attributes, with the attribute name as key
|
||||
* (e.g. "value") and the defined attribute value as Map value.
|
||||
* This return value will be <code>null</code> if no matching
|
||||
* annotation is defined.
|
||||
* Retrieve the method metadata for all methods that are annotated
|
||||
* with at least one annotation type.
|
||||
* @return a Set of {@link MethodMetadata} for methods that have annotations.
|
||||
* The return value will be an empty set if no annotated methods are found.
|
||||
*/
|
||||
Map<String, Object> getAnnotationAttributes(String annotationType);
|
||||
|
||||
|
||||
// TODO return null would be more consistent with other methods if no match is found
|
||||
Set<MethodMetadata> getAnnotatedMethods();
|
||||
|
||||
/**
|
||||
* Retrieve the method meta-data for all methods that have the
|
||||
* Retrieve the method metadata for all methods that have the
|
||||
* given annotation type.
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return a Set of (@link MethodMetadata) for methods that
|
||||
* have a matching annotation. The return value will be an
|
||||
* empty set if no methods match the annotation type.
|
||||
* @return a Set of {@link MethodMetadata} for methods that have a matching
|
||||
* annotation. The return value will be an empty set if no methods match
|
||||
* the annotation type.
|
||||
*/
|
||||
Set<MethodMetadata> getAnnotatedMethods(String annotationType);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -49,6 +49,11 @@ public interface ClassMetadata {
|
|||
*/
|
||||
boolean isConcrete();
|
||||
|
||||
/**
|
||||
* Return whether the underlying class is marked as 'final'.
|
||||
*/
|
||||
boolean isFinal();
|
||||
|
||||
/**
|
||||
* Determine whether the underlying class is independent,
|
||||
* i.e. whether it is a top-level class or a nested class
|
||||
|
|
|
|||
|
|
@ -20,41 +20,60 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Interface that defines abstract access to the annotations of a specific
|
||||
* class, in a form that does not require that class to be loaded yet.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see StandardMethodMetadata
|
||||
* @see AnnotationMetadata#getAnnotatedMethods
|
||||
*/
|
||||
public interface MethodMetadata {
|
||||
|
||||
int getModifiers();
|
||||
|
||||
boolean isStatic();
|
||||
|
||||
/**
|
||||
* Return the name of the method.
|
||||
*/
|
||||
String getMethodName();
|
||||
|
||||
//TODO does the method return type have a generic wildcard or generic type parameters?
|
||||
|
||||
// annotation metadata
|
||||
|
||||
Set<String> getAnnotationTypes();
|
||||
|
||||
boolean hasAnnotation(String annotationType);
|
||||
|
||||
Map<String, Object> getAnnotationAttributes(String annotationType);
|
||||
|
||||
/**
|
||||
* Determine whether the underlying class has an annotation that
|
||||
* is itself annotated with the meta-annotation of the given type.
|
||||
* @param metaAnnotationType the meta-annotation type to look for
|
||||
* @return whether a matching meta-annotation is defined
|
||||
*/
|
||||
boolean hasMetaAnnotation(String metaAnnotationType);
|
||||
|
||||
/**
|
||||
* Return the names of all meta-annotation types defined on the
|
||||
* given annotation type of the underlying class.
|
||||
* @return the meta-annotation type names
|
||||
*/
|
||||
Set<String> getMetaAnnotationTypes(String annotationType);
|
||||
|
||||
Set<String> getAnnotationTypesWithMetaAnnotation(String qualifierClassName);
|
||||
/**
|
||||
* Return whether the underlying method is declared as 'static'.
|
||||
*/
|
||||
boolean isStatic();
|
||||
|
||||
/**
|
||||
* Return whether the underlying method is marked as 'final'.
|
||||
*/
|
||||
boolean isFinal();
|
||||
|
||||
/**
|
||||
* Return whether the underlying method is overridable,
|
||||
* i.e. not marked as static, final or private.
|
||||
*/
|
||||
boolean isOverridable();
|
||||
|
||||
/**
|
||||
* Return the names of all annotation types defined on the underlying method.
|
||||
* @return the annotation type names, or an empty Set if none found
|
||||
*/
|
||||
Set<String> getAnnotationTypes();
|
||||
|
||||
/**
|
||||
* Determine whether the underlying method has an annotation of the given
|
||||
* type defined.
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return whether a matching annotation is defined
|
||||
*/
|
||||
boolean hasAnnotation(String annotationType);
|
||||
|
||||
/**
|
||||
* Retrieve the attributes of the annotation of the given type,
|
||||
* if any (i.e. if defined on the underlying method).
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return a Map of attributes, with the attribute name as key (e.g. "value")
|
||||
* and the defined attribute value as Map value. This return value will be
|
||||
* <code>null</code> if no matching annotation is defined.
|
||||
*/
|
||||
Map<String, Object> getAnnotationAttributes(String annotationType);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
*/
|
||||
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
|
||||
|
||||
/**
|
||||
* Create a new StandardAnnotationMetadata wrapper for the given Class.
|
||||
* @param introspectedClass the Class to introspect
|
||||
*/
|
||||
public StandardAnnotationMetadata(Class introspectedClass) {
|
||||
super(introspectedClass);
|
||||
}
|
||||
|
|
@ -59,6 +63,16 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||
return false;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedClass().getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
return AnnotationUtils.getAnnotationAttributes(ann, true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedClass().getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
|
|
@ -87,25 +101,25 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
|
|||
return false;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedClass().getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
return AnnotationUtils.getAnnotationAttributes(ann);
|
||||
public Set<MethodMetadata> getAnnotatedMethods() {
|
||||
Method[] methods = getIntrospectedClass().getDeclaredMethods();
|
||||
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
||||
for (Method method : methods) {
|
||||
if (method.getAnnotations().length > 0) {
|
||||
annotatedMethods.add(new StandardMethodMetadata(method));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return annotatedMethods;
|
||||
}
|
||||
|
||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
|
||||
Method[] methods = getIntrospectedClass().getMethods();
|
||||
Method[] methods = getIntrospectedClass().getDeclaredMethods();
|
||||
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
||||
for (Method method : methods) {
|
||||
Annotation[] methodAnnotations = method.getAnnotations();
|
||||
for (Annotation ann : methodAnnotations) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
MethodMetadata mm = new StandardMethodMetadata(method);
|
||||
annotatedMethods.add(mm);
|
||||
annotatedMethods.add(new StandardMethodMetadata(method));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -18,6 +18,8 @@ package org.springframework.core.type;
|
|||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ClassMetadata} implementation that uses standard reflection
|
||||
* to introspect a given <code>Class</code>.
|
||||
|
|
@ -30,57 +32,69 @@ public class StandardClassMetadata implements ClassMetadata {
|
|||
private final Class introspectedClass;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new StandardClassMetadata wrapper for the given Class.
|
||||
* @param introspectedClass the Class to introspect
|
||||
*/
|
||||
public StandardClassMetadata(Class introspectedClass) {
|
||||
Assert.notNull(introspectedClass, "Class must not be null");
|
||||
this.introspectedClass = introspectedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying Class.
|
||||
*/
|
||||
public final Class getIntrospectedClass() {
|
||||
return this.introspectedClass;
|
||||
}
|
||||
|
||||
|
||||
public String getClassName() {
|
||||
return getIntrospectedClass().getName();
|
||||
return this.introspectedClass.getName();
|
||||
}
|
||||
|
||||
public boolean isInterface() {
|
||||
return getIntrospectedClass().isInterface();
|
||||
return this.introspectedClass.isInterface();
|
||||
}
|
||||
|
||||
public boolean isAbstract() {
|
||||
return Modifier.isAbstract(getIntrospectedClass().getModifiers());
|
||||
return Modifier.isAbstract(this.introspectedClass.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isConcrete() {
|
||||
return !(isInterface() || isAbstract());
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return Modifier.isFinal(this.introspectedClass.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isIndependent() {
|
||||
return (!hasEnclosingClass() ||
|
||||
(getIntrospectedClass().getDeclaringClass() != null &&
|
||||
Modifier.isStatic(getIntrospectedClass().getModifiers())));
|
||||
(this.introspectedClass.getDeclaringClass() != null &&
|
||||
Modifier.isStatic(this.introspectedClass.getModifiers())));
|
||||
}
|
||||
|
||||
public boolean hasEnclosingClass() {
|
||||
return (getIntrospectedClass().getEnclosingClass() != null);
|
||||
return (this.introspectedClass.getEnclosingClass() != null);
|
||||
}
|
||||
|
||||
public String getEnclosingClassName() {
|
||||
Class enclosingClass = getIntrospectedClass().getEnclosingClass();
|
||||
Class enclosingClass = this.introspectedClass.getEnclosingClass();
|
||||
return (enclosingClass != null ? enclosingClass.getName() : null);
|
||||
}
|
||||
|
||||
public boolean hasSuperClass() {
|
||||
return (getIntrospectedClass().getSuperclass() != null);
|
||||
return (this.introspectedClass.getSuperclass() != null);
|
||||
}
|
||||
|
||||
public String getSuperClassName() {
|
||||
Class superClass = getIntrospectedClass().getSuperclass();
|
||||
Class superClass = this.introspectedClass.getSuperclass();
|
||||
return (superClass != null ? superClass.getName() : null);
|
||||
}
|
||||
|
||||
public String[] getInterfaceNames() {
|
||||
Class[] ifcs = getIntrospectedClass().getInterfaces();
|
||||
Class[] ifcs = this.introspectedClass.getInterfaces();
|
||||
String[] ifcNames = new String[ifcs.length];
|
||||
for (int i = 0; i < ifcs.length; i++) {
|
||||
ifcNames[i] = ifcs[i].getName();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.core.type;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -28,52 +27,62 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link MethodMetadata} implementation that uses standard reflection
|
||||
* to introspect a given <code>Method</code>.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class StandardMethodMetadata implements MethodMetadata {
|
||||
|
||||
private final Method introspectedMethod;
|
||||
|
||||
public StandardMethodMetadata(Method method) {
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
introspectedMethod = method;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new StandardMethodMetadata wrapper for the given Method.
|
||||
* @param introspectedMethod the Method to introspect
|
||||
*/
|
||||
public StandardMethodMetadata(Method introspectedMethod) {
|
||||
Assert.notNull(introspectedMethod, "Method must not be null");
|
||||
this.introspectedMethod = introspectedMethod;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the underlying Method.
|
||||
*/
|
||||
public final Method getIntrospectedMethod() {
|
||||
return this.introspectedMethod;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedMethod().getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
return AnnotationUtils.getAnnotationAttributes(ann);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
public String getMethodName() {
|
||||
return this.introspectedMethod.getName();
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(this.introspectedMethod.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return Modifier.isFinal(this.introspectedMethod.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isOverridable() {
|
||||
return (!isStatic() && !isFinal() && !Modifier.isPrivate(this.introspectedMethod.getModifiers()));
|
||||
}
|
||||
|
||||
public Set<String> getAnnotationTypes() {
|
||||
Set<String> types = new HashSet<String>();
|
||||
Annotation[] anns = getIntrospectedMethod().getAnnotations();
|
||||
Annotation[] anns = this.introspectedMethod.getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
types.add(ann.annotationType().getName());
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return introspectedMethod.getName();
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
return introspectedMethod.getModifiers();
|
||||
}
|
||||
|
||||
public boolean hasAnnotation(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedMethod().getAnnotations();
|
||||
Annotation[] anns = this.introspectedMethod.getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
return true;
|
||||
|
|
@ -82,44 +91,14 @@ public class StandardMethodMetadata implements MethodMetadata {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(getIntrospectedMethod().getModifiers());
|
||||
}
|
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) {
|
||||
Annotation[] anns = getIntrospectedMethod().getAnnotations();
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
Annotation[] anns = this.introspectedMethod.getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType().getName().equals(annotationType)) {
|
||||
Set<String> types = new HashSet<String>();
|
||||
Annotation[] metaAnns = ann.annotationType().getAnnotations();
|
||||
for (Annotation meta : metaAnns) {
|
||||
types.add(meta.annotationType().getName());
|
||||
}
|
||||
return types;
|
||||
return AnnotationUtils.getAnnotationAttributes(ann, true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasMetaAnnotation(String metaAnnotationType) {
|
||||
//TODO can refactor into shared (utility) method with StandardAnnotationMetadata
|
||||
Annotation[] anns = getIntrospectedMethod().getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
Annotation[] metaAnns = ann.annotationType().getAnnotations();
|
||||
for (Annotation meta : metaAnns) {
|
||||
if (meta.annotationType().getName().equals(metaAnnotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<String> getAnnotationTypesWithMetaAnnotation(String qualifierClassName) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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.core.type.classreading;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* ASM visitor which looks for the annotations defined on a class or method.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
final class AnnotationAttributesReadingVisitor implements AnnotationVisitor {
|
||||
|
||||
private final String annotationType;
|
||||
|
||||
private final Map<String, Map<String, Object>> annotationMap;
|
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
|
||||
|
||||
|
||||
public AnnotationAttributesReadingVisitor(
|
||||
String annotationType, Map<String, Map<String, Object>> attributesMap,
|
||||
Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) {
|
||||
|
||||
this.annotationType = annotationType;
|
||||
this.annotationMap = attributesMap;
|
||||
this.metaAnnotationMap = metaAnnotationMap;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
public void visit(String name, Object value) {
|
||||
Object valueToUse = value;
|
||||
if (valueToUse instanceof Type) {
|
||||
valueToUse = ((Type) value).getClassName();
|
||||
}
|
||||
this.attributes.put(name, valueToUse);
|
||||
}
|
||||
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
Object valueToUse = value;
|
||||
try {
|
||||
Class<?> enumType = this.classLoader.loadClass(Type.getType(desc).getClassName());
|
||||
Field enumConstant = ReflectionUtils.findField(enumType, value);
|
||||
if (enumConstant != null) {
|
||||
valueToUse = enumConstant.get(null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
}
|
||||
this.attributes.put(name, valueToUse);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(final String attrName) {
|
||||
return new AnnotationVisitor() {
|
||||
public void visit(String name, Object value) {
|
||||
Object newValue = value;
|
||||
if (newValue instanceof Type) {
|
||||
newValue = ((Type) value).getClassName();
|
||||
}
|
||||
Object existingValue = attributes.get(attrName);
|
||||
if (existingValue != null) {
|
||||
newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
|
||||
}
|
||||
else {
|
||||
Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1);
|
||||
newArray[0] = newValue;
|
||||
newValue = newArray;
|
||||
}
|
||||
attributes.put(attrName, newValue);
|
||||
}
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
}
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public void visitEnd() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
try {
|
||||
Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
|
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods();
|
||||
for (Method annotationAttribute : annotationAttributes) {
|
||||
String attributeName = annotationAttribute.getName();
|
||||
Object defaultValue = annotationAttribute.getDefaultValue();
|
||||
if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
|
||||
this.attributes.put(attributeName, defaultValue);
|
||||
}
|
||||
}
|
||||
// Register annotations that the annotation type is annotated with.
|
||||
if (this.metaAnnotationMap != null) {
|
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations();
|
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>();
|
||||
for (Annotation metaAnnotation : metaAnnotations) {
|
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
|
||||
}
|
||||
this.metaAnnotationMap.put(this.annotationType, metaAnnotationTypeNames);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Class not found - can't determine meta-annotations.
|
||||
}
|
||||
this.annotationMap.put(this.annotationType, this.attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,12 +16,7 @@
|
|||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
|
|
@ -30,11 +25,8 @@ import java.util.Set;
|
|||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* ASM class visitor which looks for the class name and implemented types as
|
||||
|
|
@ -45,9 +37,9 @@ import org.springframework.util.ReflectionUtils;
|
|||
* @author Mark Fisher
|
||||
* @since 2.5
|
||||
*/
|
||||
class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
|
||||
final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
|
||||
|
||||
private final Map<String, Map<String, Object>> attributesMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
private final Map<String, Map<String, Object>> annotationMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>();
|
||||
|
||||
|
|
@ -63,109 +55,28 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple
|
|||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
MethodMetadataReadingVisitor md = new MethodMetadataReadingVisitor(classLoader, name, access);
|
||||
methodMetadataSet.add(md);
|
||||
return md;
|
||||
MethodMetadataReadingVisitor mm = new MethodMetadataReadingVisitor(name, access, this.classLoader);
|
||||
this.methodMetadataSet.add(mm);
|
||||
return mm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
|
||||
final String className = Type.getType(desc).getClassName();
|
||||
final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
|
||||
return new AnnotationVisitor() {
|
||||
public void visit(String name, Object value) {
|
||||
// Explicitly defined annotation attribute value.
|
||||
Object valueToUse = value;
|
||||
if (value instanceof Type) {
|
||||
try {
|
||||
valueToUse = classLoader.loadClass(((Type) value).getClassName());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
}
|
||||
}
|
||||
attributes.put(name, valueToUse);
|
||||
}
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
Object valueToUse = value;
|
||||
try {
|
||||
Class<?> enumType = classLoader.loadClass(Type.getType(desc).getClassName());
|
||||
Field enumConstant = ReflectionUtils.findField(enumType, value);
|
||||
if (enumConstant != null) {
|
||||
valueToUse = enumConstant.get(null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
}
|
||||
attributes.put(name, valueToUse);
|
||||
}
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public AnnotationVisitor visitArray(final String attrName) {
|
||||
return new AnnotationVisitor() {
|
||||
public void visit(String name, Object value) {
|
||||
Object newValue = value;
|
||||
Object existingValue = attributes.get(attrName);
|
||||
if (existingValue != null) {
|
||||
newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
|
||||
}
|
||||
else {
|
||||
Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1);
|
||||
newArray[0] = newValue;
|
||||
newValue = newArray;
|
||||
}
|
||||
attributes.put(attrName, newValue);
|
||||
}
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
}
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public void visitEnd() {
|
||||
}
|
||||
};
|
||||
}
|
||||
public void visitEnd() {
|
||||
try {
|
||||
Class<?> annotationClass = classLoader.loadClass(className);
|
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods();
|
||||
for (Method annotationAttribute : annotationAttributes) {
|
||||
String attributeName = annotationAttribute.getName();
|
||||
Object defaultValue = annotationAttribute.getDefaultValue();
|
||||
if (defaultValue != null && !attributes.containsKey(attributeName)) {
|
||||
attributes.put(attributeName, defaultValue);
|
||||
}
|
||||
}
|
||||
// Register annotations that the annotation type is annotated with.
|
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations();
|
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>();
|
||||
for (Annotation metaAnnotation : metaAnnotations) {
|
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
|
||||
}
|
||||
metaAnnotationMap.put(className, metaAnnotationTypeNames);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Class not found - can't determine meta-annotations.
|
||||
}
|
||||
attributesMap.put(className, attributes);
|
||||
|
||||
}
|
||||
};
|
||||
String className = Type.getType(desc).getClassName();
|
||||
return new AnnotationAttributesReadingVisitor(className, this.annotationMap, this.metaAnnotationMap, this.classLoader);
|
||||
}
|
||||
|
||||
|
||||
public Set<String> getAnnotationTypes() {
|
||||
return this.attributesMap.keySet();
|
||||
return this.annotationMap.keySet();
|
||||
}
|
||||
|
||||
public boolean hasAnnotation(String annotationType) {
|
||||
return this.attributesMap.containsKey(annotationType);
|
||||
return this.annotationMap.containsKey(annotationType);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
return this.annotationMap.get(annotationType);
|
||||
}
|
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) {
|
||||
|
|
@ -182,17 +93,20 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
return this.attributesMap.get(annotationType);
|
||||
public Set<MethodMetadata> getAnnotatedMethods() {
|
||||
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
||||
for (MethodMetadata method : this.methodMetadataSet) {
|
||||
if (!method.getAnnotationTypes().isEmpty()) {
|
||||
annotatedMethods.add(method);
|
||||
}
|
||||
}
|
||||
return annotatedMethods;
|
||||
}
|
||||
|
||||
|
||||
public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
|
||||
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
|
||||
for (MethodMetadata method : methodMetadataSet) {
|
||||
if (method.hasAnnotation(annotationType))
|
||||
{
|
||||
for (MethodMetadata method : this.methodMetadataSet) {
|
||||
if (method.hasAnnotation(annotationType)) {
|
||||
annotatedMethods.add(method);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -33,7 +33,7 @@ import org.springframework.core.io.ResourceLoader;
|
|||
*/
|
||||
public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory {
|
||||
|
||||
private final Map<Resource, SimpleMetadataReader> classReaderCache = new HashMap<Resource, SimpleMetadataReader>();
|
||||
private final Map<Resource, MetadataReader> classReaderCache = new HashMap<Resource, MetadataReader>();
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -62,9 +62,9 @@ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory {
|
|||
|
||||
|
||||
@Override
|
||||
public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException {
|
||||
public MetadataReader getMetadataReader(Resource resource) throws IOException {
|
||||
synchronized (this.classReaderCache) {
|
||||
SimpleMetadataReader metadataReader = this.classReaderCache.get(resource);
|
||||
MetadataReader metadataReader = this.classReaderCache.get(resource);
|
||||
if (metadataReader == null) {
|
||||
metadataReader = super.getMetadataReader(resource);
|
||||
this.classReaderCache.put(resource, metadataReader);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata {
|
|||
|
||||
private boolean isAbstract;
|
||||
|
||||
private boolean isFinal;
|
||||
|
||||
private String enclosingClassName;
|
||||
|
||||
private boolean independentInnerClass;
|
||||
|
|
@ -58,6 +60,7 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata {
|
|||
this.className = ClassUtils.convertResourcePathToClassName(name);
|
||||
this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
|
||||
this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0);
|
||||
this.isFinal = ((access & Opcodes.ACC_FINAL) != 0);
|
||||
if (supername != null) {
|
||||
this.superClassName = ClassUtils.convertResourcePathToClassName(supername);
|
||||
}
|
||||
|
|
@ -122,6 +125,10 @@ class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata {
|
|||
return !(this.isInterface || this.isAbstract);
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return this.isFinal;
|
||||
}
|
||||
|
||||
public boolean isIndependent() {
|
||||
return (this.enclosingClassName == null || this.independentInnerClass);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
|
||||
|
|
@ -28,13 +29,19 @@ import org.springframework.core.type.ClassMetadata;
|
|||
*/
|
||||
public interface MetadataReader {
|
||||
|
||||
/**
|
||||
* Return the resource reference for the class file.
|
||||
*/
|
||||
Resource getResource();
|
||||
|
||||
/**
|
||||
* Read basic class metadata for the underlying class.
|
||||
*/
|
||||
ClassMetadata getClassMetadata();
|
||||
|
||||
/**
|
||||
* Read full annotation metadata for the underlying class.
|
||||
* Read full annotation metadata for the underlying class,
|
||||
* including metadata for annotated methods.
|
||||
*/
|
||||
AnnotationMetadata getAnnotationMetadata();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,7 @@
|
|||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
|
@ -32,139 +26,68 @@ import org.springframework.asm.Opcodes;
|
|||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* ASM method visitor which looks for the annotations defined on the method,
|
||||
* exposing them through the {@link org.springframework.core.type.MethodMetadata}
|
||||
* interface.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata {
|
||||
final class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata {
|
||||
|
||||
private ClassLoader classLoader;
|
||||
private final String name;
|
||||
|
||||
private String name;
|
||||
private final int access;
|
||||
|
||||
private int access;
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private boolean isStatic;
|
||||
|
||||
private final Map<String, Map<String, Object>> attributesMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>();
|
||||
private final Map<String, Map<String, Object>> annotationMap = new LinkedHashMap<String, Map<String, Object>>();
|
||||
|
||||
|
||||
public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) {
|
||||
public MethodMetadataReadingVisitor(String name, int access, ClassLoader classLoader) {
|
||||
super(new EmptyVisitor());
|
||||
this.classLoader = classLoader;
|
||||
this.name = name;
|
||||
this.access = access;
|
||||
this.isStatic = ((access & Opcodes.ACC_STATIC) != 0);
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
return this.attributesMap.get(annotationType);
|
||||
public String getMethodName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return ((this.access & Opcodes.ACC_STATIC) != 0);
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return ((this.access & Opcodes.ACC_FINAL) != 0);
|
||||
}
|
||||
|
||||
public boolean isOverridable() {
|
||||
return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0));
|
||||
}
|
||||
|
||||
public Set<String> getAnnotationTypes() {
|
||||
return this.attributesMap.keySet();
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
return access;
|
||||
return this.annotationMap.keySet();
|
||||
}
|
||||
|
||||
public boolean hasAnnotation(String annotationType) {
|
||||
return this.attributesMap.containsKey(annotationType);
|
||||
return this.annotationMap.containsKey(annotationType);
|
||||
}
|
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) {
|
||||
return this.metaAnnotationMap.get(annotationType);
|
||||
}
|
||||
|
||||
public boolean hasMetaAnnotation(String metaAnnotationType) {
|
||||
Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
|
||||
for (Set<String> metaTypes : allMetaTypes) {
|
||||
if (metaTypes.contains(metaAnnotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return this.isStatic;
|
||||
}
|
||||
|
||||
public Set<String> getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) {
|
||||
Set<String> annotationTypes = new LinkedHashSet<String>();
|
||||
for (Map.Entry<String, Set<String>> entry : metaAnnotationMap.entrySet()) {
|
||||
String attributeType = entry.getKey();
|
||||
Set<String> metaAttributes = entry.getValue();
|
||||
if (metaAttributes.contains(metaAnnotationType)) {
|
||||
annotationTypes.add(attributeType);
|
||||
}
|
||||
}
|
||||
return annotationTypes;
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
return this.annotationMap.get(annotationType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
|
||||
final String className = Type.getType(desc).getClassName();
|
||||
final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
|
||||
return new EmptyVisitor() {
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
// Explicitly defined annotation attribute value.
|
||||
attributes.put(name, value);
|
||||
}
|
||||
@Override
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
Object valueToUse = value;
|
||||
try {
|
||||
Class<?> enumType = classLoader.loadClass(Type.getType(desc).getClassName());
|
||||
Field enumConstant = ReflectionUtils.findField(enumType, value);
|
||||
if (enumConstant != null) {
|
||||
valueToUse = enumConstant.get(null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
}
|
||||
attributes.put(name, valueToUse);
|
||||
}
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
try {
|
||||
Class<?> annotationClass = classLoader.loadClass(className);
|
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods();
|
||||
for (Method annotationAttribute : annotationAttributes) {
|
||||
String attributeName = annotationAttribute.getName();
|
||||
Object defaultValue = annotationAttribute.getDefaultValue();
|
||||
if (defaultValue != null && !attributes.containsKey(attributeName)) {
|
||||
attributes.put(attributeName, defaultValue);
|
||||
}
|
||||
}
|
||||
// Register annotations that the annotation type is annotated with.
|
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations();
|
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>();
|
||||
for (Annotation metaAnnotation : metaAnnotations) {
|
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
|
||||
}
|
||||
metaAnnotationMap.put(className, metaAnnotationTypeNames);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Class not found
|
||||
}
|
||||
attributesMap.put(className, attributes);
|
||||
}
|
||||
};
|
||||
String className = Type.getType(desc).getClassName();
|
||||
return new AnnotationAttributesReadingVisitor(className, this.annotationMap, null, this.classLoader);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
|
||||
|
|
@ -30,7 +34,9 @@ import org.springframework.core.type.ClassMetadata;
|
|||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
public class SimpleMetadataReader implements MetadataReader {
|
||||
final class SimpleMetadataReader implements MetadataReader {
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
private final ClassReader classReader;
|
||||
|
||||
|
|
@ -41,27 +47,23 @@ public class SimpleMetadataReader implements MetadataReader {
|
|||
private AnnotationMetadata annotationMetadata;
|
||||
|
||||
|
||||
SimpleMetadataReader(ClassReader classReader, ClassLoader classLoader) {
|
||||
this.classReader = classReader;
|
||||
SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
|
||||
this.resource = resource;
|
||||
InputStream is = this.resource.getInputStream();
|
||||
try {
|
||||
this.classReader = new ClassReader(is);
|
||||
}
|
||||
finally {
|
||||
is.close();
|
||||
}
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the underlying ASM ClassReader.
|
||||
*/
|
||||
public final ClassReader getClassReader() {
|
||||
return this.classReader;
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying ClassLoader.
|
||||
*/
|
||||
public final ClassLoader getClassLoader() {
|
||||
return this.classLoader;
|
||||
}
|
||||
|
||||
|
||||
public ClassMetadata getClassMetadata() {
|
||||
if (this.classMetadata == null) {
|
||||
ClassMetadataReadingVisitor visitor = new ClassMetadataReadingVisitor();
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
|
@ -72,20 +70,14 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
|
|||
}
|
||||
|
||||
|
||||
public SimpleMetadataReader getMetadataReader(String className) throws IOException {
|
||||
public MetadataReader getMetadataReader(String className) throws IOException {
|
||||
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
|
||||
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
|
||||
return getMetadataReader(this.resourceLoader.getResource(resourcePath));
|
||||
}
|
||||
|
||||
public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException {
|
||||
InputStream is = resource.getInputStream();
|
||||
try {
|
||||
return new SimpleMetadataReader(new ClassReader(is), this.resourceLoader.getClassLoader());
|
||||
}
|
||||
finally {
|
||||
is.close();
|
||||
}
|
||||
public MetadataReader getMetadataReader(Resource resource) throws IOException {
|
||||
return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue