+ Source attribution is now consistent across all registered Problems
+ Various pruning of dead code and polish
This commit is contained in:
parent
8b4ad4575c
commit
72fae2ea19
|
|
@ -23,24 +23,31 @@ import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanMetadataElement;
|
||||||
import org.springframework.beans.factory.parsing.Location;
|
import org.springframework.beans.factory.parsing.Location;
|
||||||
import org.springframework.beans.factory.parsing.Problem;
|
import org.springframework.beans.factory.parsing.Problem;
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.config.java.Bean;
|
import org.springframework.config.java.Bean;
|
||||||
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.context.annotation.ScopedProxyMode;
|
import org.springframework.context.annotation.ScopedProxyMode;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
final class BeanMethod {
|
/**
|
||||||
|
* Represents a {@link Configuration} class method marked with the {@link Bean} annotation.
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
*/
|
||||||
|
final class BeanMethod implements BeanMetadataElement {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int modifiers;
|
private final int modifiers;
|
||||||
private final ModelClass returnType;
|
private final ModelClass returnType;
|
||||||
private final List<Annotation> annotations = new ArrayList<Annotation>();
|
private final List<Annotation> annotations = new ArrayList<Annotation>();
|
||||||
|
|
||||||
private transient ConfigurationClass declaringClass;
|
private transient ConfigurationClass declaringClass;
|
||||||
private transient int lineNumber;
|
private transient Object source;
|
||||||
|
|
||||||
public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
|
public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
|
||||||
Assert.hasText(name);
|
Assert.hasText(name);
|
||||||
|
|
@ -104,7 +111,7 @@ final class BeanMethod {
|
||||||
/**
|
/**
|
||||||
* Set up a bi-directional relationship between this method and its declaring class.
|
* Set up a bi-directional relationship between this method and its declaring class.
|
||||||
*
|
*
|
||||||
* @see ConfigurationClass#addMethod(BeanMethod)
|
* @see ConfigurationClass#addBeanMethod(BeanMethod)
|
||||||
*/
|
*/
|
||||||
public void setDeclaringClass(ConfigurationClass declaringClass) {
|
public void setDeclaringClass(ConfigurationClass declaringClass) {
|
||||||
this.declaringClass = declaringClass;
|
this.declaringClass = declaringClass;
|
||||||
|
|
@ -114,12 +121,16 @@ final class BeanMethod {
|
||||||
return declaringClass;
|
return declaringClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLineNumber(int lineNumber) {
|
public void setSource(Object source) {
|
||||||
this.lineNumber = lineNumber;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLineNumber() {
|
public Object getSource() {
|
||||||
return lineNumber;
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getLocation() {
|
||||||
|
return new Location(declaringClass.getLocation().getResource(), getSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validate(ProblemReporter problemReporter) {
|
public void validate(ProblemReporter problemReporter) {
|
||||||
|
|
@ -188,7 +199,7 @@ final class BeanMethod {
|
||||||
public class PrivateMethodError extends Problem {
|
public class PrivateMethodError extends Problem {
|
||||||
public PrivateMethodError() {
|
public PrivateMethodError() {
|
||||||
super(format("method '%s' may not be private", getName()),
|
super(format("method '%s' may not be private", getName()),
|
||||||
new Location(new FileSystemResource("/dev/null")));
|
BeanMethod.this.getLocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,7 +207,7 @@ final class BeanMethod {
|
||||||
public class FinalMethodError extends Problem {
|
public class FinalMethodError extends Problem {
|
||||||
public FinalMethodError() {
|
public FinalMethodError() {
|
||||||
super(format("method '%s' may not be final. remove the final modifier to continue", getName()),
|
super(format("method '%s' may not be final. remove the final modifier to continue", getName()),
|
||||||
new Location(new FileSystemResource("/dev/null")));
|
BeanMethod.this.getLocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +215,7 @@ final class BeanMethod {
|
||||||
public InvalidScopedProxyDeclarationError(BeanMethod method) {
|
public InvalidScopedProxyDeclarationError(BeanMethod method) {
|
||||||
super(format("method %s contains an invalid annotation declaration: scoped proxies "
|
super(format("method %s contains an invalid annotation declaration: scoped proxies "
|
||||||
+ "cannot be created for singleton/prototype beans", method.getName()),
|
+ "cannot be created for singleton/prototype beans", method.getName()),
|
||||||
new Location(new FileSystemResource("/dev/null")));
|
BeanMethod.this.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,16 @@ package org.springframework.config.java.support;
|
||||||
|
|
||||||
import static java.lang.String.*;
|
import static java.lang.String.*;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.beans.factory.parsing.Location;
|
|
||||||
import org.springframework.beans.factory.parsing.Problem;
|
import org.springframework.beans.factory.parsing.Problem;
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.config.java.Bean;
|
import org.springframework.config.java.Bean;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import sun.security.x509.Extension;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract representation of a user-defined {@link Configuration @Configuration} class.
|
* Abstract representation of a user-defined {@link Configuration @Configuration} class.
|
||||||
|
|
@ -41,107 +36,51 @@ import sun.security.x509.Extension;
|
||||||
* (for the purpose of tooling with Spring IDE).
|
* (for the purpose of tooling with Spring IDE).
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
* @see ConfigurationModel
|
||||||
|
* @see BeanMethod
|
||||||
|
* @see ConfigurationParser
|
||||||
*/
|
*/
|
||||||
final class ConfigurationClass extends ModelClass {
|
final class ConfigurationClass extends ModelClass {
|
||||||
|
|
||||||
private String beanName;
|
private String beanName;
|
||||||
|
|
||||||
private int modifiers;
|
private int modifiers;
|
||||||
|
private Configuration configurationAnnotation;
|
||||||
private Configuration metadata;
|
|
||||||
|
|
||||||
private HashSet<BeanMethod> methods = new HashSet<BeanMethod>();
|
private HashSet<BeanMethod> methods = new HashSet<BeanMethod>();
|
||||||
|
|
||||||
private HashSet<Annotation> pluginAnnotations = new HashSet<Annotation>();
|
|
||||||
|
|
||||||
private ConfigurationClass declaringClass;
|
private ConfigurationClass declaringClass;
|
||||||
|
|
||||||
public ConfigurationClass() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: get rid of constructors used only for testing. put in testing util.
|
|
||||||
/**
|
|
||||||
* Creates a new ConfigurationClass named <var>className.</var>
|
|
||||||
*
|
|
||||||
* @param name fully-qualified Configuration class being represented
|
|
||||||
*
|
|
||||||
* @see #setClassName(String)
|
|
||||||
*/
|
|
||||||
ConfigurationClass(String name) {
|
|
||||||
this(name, null, defaultAnnotation(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurationClass(String name, Configuration metadata) {
|
|
||||||
this(name, null, metadata, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurationClass(String name, int modifiers) {
|
|
||||||
this(name, null, defaultAnnotation(), modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Configuration defaultAnnotation() {
|
|
||||||
@Configuration
|
|
||||||
class Prototype {
|
|
||||||
}
|
|
||||||
return Prototype.class.getAnnotation(Configuration.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ConfigurationClass object.
|
|
||||||
*
|
|
||||||
* @param name Fully qualified name of the class being represented
|
|
||||||
* @param id Bean name/id (if any) of this configuration class. used only in the case of
|
|
||||||
* XML integration where {@link Configuration} beans may have a user-specified
|
|
||||||
* id.
|
|
||||||
* @param metadata Configuration annotation resident on this class. May be null
|
|
||||||
* indicating that the user specified this class to be processed but failed to
|
|
||||||
* properly annotate it.
|
|
||||||
* @param modifiers Per {@link java.lang.reflect.Modifier}
|
|
||||||
*/
|
|
||||||
public ConfigurationClass(String name, String id, Configuration metadata, int modifiers) {
|
|
||||||
super(name);
|
|
||||||
Assert.hasText(name, "Configuration class name must have text");
|
|
||||||
|
|
||||||
setBeanName(id);
|
|
||||||
setMetadata(metadata);
|
|
||||||
setModifiers(modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurationClass addMethod(BeanMethod method) {
|
|
||||||
method.setDeclaringClass(this);
|
|
||||||
methods.add(method);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBeanName() {
|
public String getBeanName() {
|
||||||
return beanName == null ? getName() : beanName;
|
return beanName == null ? getName() : beanName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationClass setBeanName(String id) {
|
public void setBeanName(String id) {
|
||||||
this.beanName = id;
|
this.beanName = id;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<BeanMethod> getMethods() {
|
public int getModifiers() {
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiers(int modifiers) {
|
||||||
|
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Configuration getConfigurationAnnotation() {
|
||||||
|
return this.configurationAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfigurationAnnotation(Configuration configAnno) {
|
||||||
|
Assert.notNull(configAnno, "configuration annotation must be non-null");
|
||||||
|
this.configurationAnnotation = configAnno;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<BeanMethod> getBeanMethods() {
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Annotation[] getPluginAnnotations() {
|
public ConfigurationClass addBeanMethod(BeanMethod method) {
|
||||||
return pluginAnnotations.toArray(new Annotation[pluginAnnotations.size()]);
|
method.setDeclaringClass(this);
|
||||||
}
|
methods.add(method);
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a {@link Extension @Plugin}-annotated annotation to this configuration class.
|
|
||||||
*
|
|
||||||
* @param pluginAnno type-level <code>Plugin</code> annotation
|
|
||||||
*/
|
|
||||||
public ConfigurationClass addPluginAnnotation(Annotation pluginAnno) {
|
|
||||||
pluginAnnotations.add(pluginAnno);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurationClass setDeclaringClass(ConfigurationClass configurationClass) {
|
|
||||||
this.declaringClass = configurationClass;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,29 +88,13 @@ final class ConfigurationClass extends ModelClass {
|
||||||
return declaringClass;
|
return declaringClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getModifiers() {
|
public void setDeclaringClass(ConfigurationClass configurationClass) {
|
||||||
return modifiers;
|
this.declaringClass = configurationClass;
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurationClass setModifiers(int modifiers) {
|
|
||||||
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
|
|
||||||
this.modifiers = modifiers;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Configuration getMetadata() {
|
|
||||||
return this.metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurationClass setMetadata(Configuration configAnno) {
|
|
||||||
this.metadata = configAnno;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validate(ProblemReporter problemReporter) {
|
public void validate(ProblemReporter problemReporter) {
|
||||||
|
|
||||||
// configuration classes must be annotated with @Configuration
|
// configuration classes must be annotated with @Configuration
|
||||||
if (metadata == null)
|
if (configurationAnnotation == null)
|
||||||
problemReporter.error(new NonAnnotatedConfigurationProblem());
|
problemReporter.error(new NonAnnotatedConfigurationProblem());
|
||||||
|
|
||||||
// a configuration class may not be final (CGLIB limitation)
|
// a configuration class may not be final (CGLIB limitation)
|
||||||
|
|
@ -193,10 +116,9 @@ final class ConfigurationClass extends ModelClass {
|
||||||
int result = super.hashCode();
|
int result = super.hashCode();
|
||||||
result = prime * result + ((declaringClass == null) ? 0 : declaringClass.hashCode());
|
result = prime * result + ((declaringClass == null) ? 0 : declaringClass.hashCode());
|
||||||
result = prime * result + ((beanName == null) ? 0 : beanName.hashCode());
|
result = prime * result + ((beanName == null) ? 0 : beanName.hashCode());
|
||||||
result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
|
result = prime * result + ((configurationAnnotation == null) ? 0 : configurationAnnotation.hashCode());
|
||||||
result = prime * result + ((methods == null) ? 0 : methods.hashCode());
|
result = prime * result + ((methods == null) ? 0 : methods.hashCode());
|
||||||
result = prime * result + modifiers;
|
result = prime * result + modifiers;
|
||||||
result = prime * result + ((pluginAnnotations == null) ? 0 : pluginAnnotations.hashCode());
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,10 +141,10 @@ final class ConfigurationClass extends ModelClass {
|
||||||
return false;
|
return false;
|
||||||
} else if (!beanName.equals(other.beanName))
|
} else if (!beanName.equals(other.beanName))
|
||||||
return false;
|
return false;
|
||||||
if (metadata == null) {
|
if (configurationAnnotation == null) {
|
||||||
if (other.metadata != null)
|
if (other.configurationAnnotation != null)
|
||||||
return false;
|
return false;
|
||||||
} else if (!metadata.equals(other.metadata))
|
} else if (!configurationAnnotation.equals(other.configurationAnnotation))
|
||||||
return false;
|
return false;
|
||||||
if (methods == null) {
|
if (methods == null) {
|
||||||
if (other.methods != null)
|
if (other.methods != null)
|
||||||
|
|
@ -231,37 +153,30 @@ final class ConfigurationClass extends ModelClass {
|
||||||
return false;
|
return false;
|
||||||
if (modifiers != other.modifiers)
|
if (modifiers != other.modifiers)
|
||||||
return false;
|
return false;
|
||||||
if (pluginAnnotations == null) {
|
|
||||||
if (other.pluginAnnotations != null)
|
|
||||||
return false;
|
|
||||||
} else if (!pluginAnnotations.equals(other.pluginAnnotations))
|
|
||||||
return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */
|
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */
|
||||||
public class NonAnnotatedConfigurationProblem extends Problem {
|
class NonAnnotatedConfigurationProblem extends Problem {
|
||||||
|
|
||||||
public NonAnnotatedConfigurationProblem() {
|
NonAnnotatedConfigurationProblem() {
|
||||||
super(format("%s was specified as a @Configuration class but was not actually annotated " +
|
super(format("%s was specified as a @Configuration class but was not actually annotated " +
|
||||||
"with @Configuration. Annotate the class or do not attempt to process it.",
|
"with @Configuration. Annotate the class or do not attempt to process it.",
|
||||||
getSimpleName()),
|
getSimpleName()),
|
||||||
new Location(new FileSystemResource("/dev/null"))
|
ConfigurationClass.this.getLocation());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Configuration classes must be non-final to accommodate CGLIB subclassing. */
|
/** Configuration classes must be non-final to accommodate CGLIB subclassing. */
|
||||||
public class FinalConfigurationProblem extends Problem {
|
class FinalConfigurationProblem extends Problem {
|
||||||
|
|
||||||
public FinalConfigurationProblem() {
|
FinalConfigurationProblem() {
|
||||||
super(format("@Configuration class [%s] may not be final. Remove the final modifier to continue.",
|
super(format("@Configuration class [%s] may not be final. Remove the final modifier to continue.",
|
||||||
ConfigurationClass.this.getSimpleName()),
|
getSimpleName()),
|
||||||
new Location(new FileSystemResource("/dev/null"))
|
ConfigurationClass.this.getLocation());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ class ConfigurationClassMethodVisitor extends MethodAdapter {
|
||||||
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
|
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
private boolean isModelMethod = false;
|
|
||||||
private int lineNumber;
|
private int lineNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -104,24 +103,20 @@ class ConfigurationClassMethodVisitor extends MethodAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses through all {@link #annotations} on this method in order to determine whether
|
* Parses through all {@link #annotations} on this method in order to determine whether
|
||||||
* it is a {@link Bean} method or not and if so adds it to the enclosing {@link #configClass}.
|
* it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void visitEnd() {
|
public void visitEnd() {
|
||||||
for (Annotation anno : annotations) {
|
for (Annotation anno : annotations) {
|
||||||
if (anno.annotationType().equals(Bean.class)) {
|
if (Bean.class.equals(anno.annotationType())) {
|
||||||
isModelMethod = true;
|
// this method is annotated with @Bean -> add it to the ConfigurationClass model
|
||||||
|
Annotation[] annoArray = annotations.toArray(new Annotation[] {});
|
||||||
|
BeanMethod method = new BeanMethod(methodName, modifiers, returnType, annoArray);
|
||||||
|
method.setSource(lineNumber);
|
||||||
|
configClass.addBeanMethod(method);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isModelMethod)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Annotation[] annoArray = annotations.toArray(new Annotation[] {});
|
|
||||||
BeanMethod method = new BeanMethod(methodName, modifiers, returnType, annoArray);
|
|
||||||
method.setLineNumber(lineNumber);
|
|
||||||
configClass.addMethod(method);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,8 @@ package org.springframework.config.java.support;
|
||||||
|
|
||||||
import static java.lang.String.*;
|
import static java.lang.String.*;
|
||||||
import static org.springframework.config.java.support.MutableAnnotationUtils.*;
|
import static org.springframework.config.java.support.MutableAnnotationUtils.*;
|
||||||
import static org.springframework.config.java.support.Util.*;
|
|
||||||
import static org.springframework.util.ClassUtils.*;
|
import static org.springframework.util.ClassUtils.*;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
|
@ -29,13 +27,12 @@ import org.springframework.asm.ClassAdapter;
|
||||||
import org.springframework.asm.ClassReader;
|
import org.springframework.asm.ClassReader;
|
||||||
import org.springframework.asm.MethodVisitor;
|
import org.springframework.asm.MethodVisitor;
|
||||||
import org.springframework.asm.Opcodes;
|
import org.springframework.asm.Opcodes;
|
||||||
import org.springframework.asm.commons.EmptyVisitor;
|
|
||||||
import org.springframework.beans.factory.parsing.Location;
|
import org.springframework.beans.factory.parsing.Location;
|
||||||
import org.springframework.beans.factory.parsing.Problem;
|
import org.springframework.beans.factory.parsing.Problem;
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.config.java.Import;
|
import org.springframework.config.java.Import;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,10 +48,11 @@ class ConfigurationClassVisitor extends ClassAdapter {
|
||||||
private final ConfigurationClass configClass;
|
private final ConfigurationClass configClass;
|
||||||
private final ConfigurationModel model;
|
private final ConfigurationModel model;
|
||||||
private final ProblemReporter problemReporter;
|
private final ProblemReporter problemReporter;
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
private final HashMap<String, ConfigurationClass> innerClasses = new HashMap<String, ConfigurationClass>();
|
private final HashMap<String, ConfigurationClass> innerClasses = new HashMap<String, ConfigurationClass>();
|
||||||
|
|
||||||
private boolean processInnerClasses = true;
|
private boolean processInnerClasses = true;
|
||||||
private final ClassLoader classLoader;
|
|
||||||
|
|
||||||
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model,
|
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model,
|
||||||
ProblemReporter problemReporter, ClassLoader classLoader) {
|
ProblemReporter problemReporter, ClassLoader classLoader) {
|
||||||
|
|
@ -120,78 +118,24 @@ class ConfigurationClassVisitor extends ClassAdapter {
|
||||||
|
|
||||||
if (Configuration.class.getName().equals(annoTypeName)) {
|
if (Configuration.class.getName().equals(annoTypeName)) {
|
||||||
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class);
|
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class);
|
||||||
configClass.setMetadata(mutableConfiguration);
|
configClass.setConfigurationAnnotation(mutableConfiguration);
|
||||||
return new MutableAnnotationVisitor(mutableConfiguration, classLoader);
|
return new MutableAnnotationVisitor(mutableConfiguration, classLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Import.class.getName().equals(annoTypeName)) {
|
if (Import.class.getName().equals(annoTypeName)) {
|
||||||
ImportStack importStack = ImportStackHolder.getImportStack();
|
ImportStack importStack = ImportStackHolder.getImportStack();
|
||||||
|
|
||||||
if (importStack.contains(configClass)) {
|
if (!importStack.contains(configClass)) {
|
||||||
//throw new CircularImportException(configClass, importStack);
|
importStack.push(configClass);
|
||||||
problemReporter.error(new CircularImportProblem(configClass, importStack));
|
return new ImportAnnotationVisitor(model, problemReporter, classLoader);
|
||||||
return new EmptyVisitor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
importStack.push(configClass);
|
problemReporter.error(new CircularImportProblem(configClass, importStack));
|
||||||
|
|
||||||
return new ImportAnnotationVisitor(model, problemReporter, classLoader);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------
|
return super.visitAnnotation(annoTypeDesc, visible);
|
||||||
// Detect @Extension annotations
|
|
||||||
// -------------------------------------
|
|
||||||
PluginAnnotationDetectingClassVisitor classVisitor = new PluginAnnotationDetectingClassVisitor(classLoader);
|
|
||||||
|
|
||||||
String className = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
|
|
||||||
String resourcePath = ClassUtils.convertClassNameToResourcePath(className);
|
|
||||||
ClassReader reader = AsmUtils.newClassReader(resourcePath, classLoader);
|
|
||||||
reader.accept(classVisitor, false);
|
|
||||||
|
|
||||||
if (!classVisitor.hasPluginAnnotation())
|
|
||||||
return super.visitAnnotation(annoTypeDesc, visible);
|
|
||||||
*/
|
|
||||||
|
|
||||||
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName, classLoader);
|
|
||||||
|
|
||||||
if (annoType == null)
|
|
||||||
return super.visitAnnotation(annoTypeDesc, visible);
|
|
||||||
|
|
||||||
Annotation pluginAnno = createMutableAnnotation(annoType);
|
|
||||||
configClass.addPluginAnnotation(pluginAnno);
|
|
||||||
return new MutableAnnotationVisitor(pluginAnno, classLoader);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Support for @Extension annotation processing
|
|
||||||
private static class PluginAnnotationDetectingClassVisitor extends ClassAdapter {
|
|
||||||
private boolean hasPluginAnnotation = false;
|
|
||||||
private final Extension pluginAnnotation = createMutableAnnotation(Extension.class);
|
|
||||||
private final ClassLoader classLoader;
|
|
||||||
|
|
||||||
public PluginAnnotationDetectingClassVisitor(ClassLoader classLoader) {
|
|
||||||
super(AsmUtils.EMPTY_VISITOR);
|
|
||||||
this.classLoader = classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AnnotationVisitor visitAnnotation(String typeDesc, boolean arg1) {
|
|
||||||
if (Extension.class.getName().equals(AsmUtils.convertTypeDescriptorToClassName(typeDesc))) {
|
|
||||||
hasPluginAnnotation = true;
|
|
||||||
return new MutableAnnotationVisitor(pluginAnnotation, classLoader);
|
|
||||||
}
|
|
||||||
return super.visitAnnotation(typeDesc, arg1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPluginAnnotation() {
|
|
||||||
return hasPluginAnnotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Extension getPluginAnnotation() {
|
|
||||||
return pluginAnnotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates all {@link Configuration @Configuration} class method parsing to
|
* Delegates all {@link Configuration @Configuration} class method parsing to
|
||||||
* {@link ConfigurationClassMethodVisitor}.
|
* {@link ConfigurationClassMethodVisitor}.
|
||||||
|
|
@ -241,7 +185,7 @@ class ConfigurationClassVisitor extends ClassAdapter {
|
||||||
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
|
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
|
||||||
|
|
||||||
// is the inner class a @Configuration class? If so, add it to the list
|
// is the inner class a @Configuration class? If so, add it to the list
|
||||||
if (innerConfigClass.getMetadata() != null)
|
if (innerConfigClass.getConfigurationAnnotation() != null)
|
||||||
innerClasses.put(name, innerConfigClass);
|
innerClasses.put(name, innerConfigClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,13 +199,14 @@ class ConfigurationClassVisitor extends ClassAdapter {
|
||||||
*/
|
*/
|
||||||
class CircularImportProblem extends Problem {
|
class CircularImportProblem extends Problem {
|
||||||
|
|
||||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> currentImportStack) {
|
CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
|
||||||
super(format("A circular @Import has been detected: " +
|
super(format("A circular @Import has been detected: " +
|
||||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
||||||
"already present in the current import stack [%s]",
|
"already present in the current import stack [%s]",
|
||||||
currentImportStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
||||||
attemptedImport.getSimpleName(), currentImportStack),
|
attemptedImport.getSimpleName(), importStack),
|
||||||
new Location(new FileSystemResource("/dev/null"))
|
new Location(new ClassPathResource(convertClassNameToResourcePath(importStack.peek().getName())),
|
||||||
|
importStack.peek().getSource())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,8 @@ import static java.lang.String.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import org.springframework.beans.factory.parsing.Location;
|
|
||||||
import org.springframework.beans.factory.parsing.Problem;
|
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,31 +57,6 @@ final class ConfigurationModel {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return configuration classes that have been directly added to this model.
|
|
||||||
*
|
|
||||||
* @see #getAllConfigurationClasses()
|
|
||||||
*/
|
|
||||||
public ConfigurationClass[] getConfigurationClasses() {
|
|
||||||
return configurationClasses.toArray(new ConfigurationClass[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Return all configuration classes, including all imported configuration classes.
|
|
||||||
// This method
|
|
||||||
// * should be generally preferred over {@link #getConfigurationClasses()}
|
|
||||||
// *
|
|
||||||
// * @see #getConfigurationClasses()
|
|
||||||
// */
|
|
||||||
// public ConfigurationClass[] getAllConfigurationClasses() {
|
|
||||||
// ArrayList<ConfigurationClass> allConfigClasses = new ArrayList<ConfigurationClass>();
|
|
||||||
//
|
|
||||||
// for (ConfigurationClass configClass : configurationClasses)
|
|
||||||
// allConfigClasses.addAll(configClass.getSelfAndAllImports());
|
|
||||||
//
|
|
||||||
// return allConfigClasses.toArray(new ConfigurationClass[allConfigClasses.size()]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
public ConfigurationClass[] getAllConfigurationClasses() {
|
public ConfigurationClass[] getAllConfigurationClasses() {
|
||||||
return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
|
return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
|
||||||
}
|
}
|
||||||
|
|
@ -94,37 +66,8 @@ final class ConfigurationModel {
|
||||||
* <var>errors</var>.
|
* <var>errors</var>.
|
||||||
*
|
*
|
||||||
* @see ConfigurationClass#validate
|
* @see ConfigurationClass#validate
|
||||||
* @see BeanMethod#validate
|
|
||||||
*/
|
*/
|
||||||
public void validate(ProblemReporter problemReporter) {
|
public void validate(ProblemReporter problemReporter) {
|
||||||
// user must specify at least one configuration
|
|
||||||
if (configurationClasses.isEmpty())
|
|
||||||
problemReporter.error(new EmptyModelError());
|
|
||||||
|
|
||||||
// TODO: prune this
|
|
||||||
// // check for any illegal @Bean overriding
|
|
||||||
// ConfigurationClass[] allClasses = getAllConfigurationClasses();
|
|
||||||
// for (int i = 0; i < allClasses.length; i++) {
|
|
||||||
// for (BeanMethod method : allClasses[i].getMethods()) {
|
|
||||||
// Bean bean = method.getAnnotation(Bean.class);
|
|
||||||
//
|
|
||||||
// if (bean == null || bean.allowOverriding())
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// for (int j = i + 1; j < allClasses.length; j++)
|
|
||||||
// if (allClasses[j].hasMethod(method.getName()))
|
|
||||||
// problemReporter.error(
|
|
||||||
// new Problem(
|
|
||||||
// allClasses[i].new IllegalBeanOverrideError(allClasses[j], method).getDescription(),
|
|
||||||
// new Location(new ClassPathResource(allClasses[i].getName().replace('.', '/').concat(".class")))
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// each individual configuration class must be well-formed
|
|
||||||
// note that each configClass detects usage errors on its imports recursively
|
|
||||||
// note that each configClass will recursively process its respective methods
|
|
||||||
for (ConfigurationClass configClass : configurationClasses)
|
for (ConfigurationClass configClass : configurationClasses)
|
||||||
configClass.validate(problemReporter);
|
configClass.validate(problemReporter);
|
||||||
}
|
}
|
||||||
|
|
@ -159,13 +102,4 @@ final class ConfigurationModel {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class EmptyModelError extends Problem {
|
|
||||||
public EmptyModelError() {
|
|
||||||
super(format("Configuration model was empty. Make sure at least one "
|
|
||||||
+ "@%s class has been specified.", Configuration.class.getSimpleName()),
|
|
||||||
new Location(new FileSystemResource("/dev/null")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ class ConfigurationModelBeanDefinitionReader {
|
||||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||||
doLoadBeanDefinitionForConfigurationClass(configClass);
|
doLoadBeanDefinitionForConfigurationClass(configClass);
|
||||||
|
|
||||||
for (BeanMethod method : configClass.getMethods())
|
for (BeanMethod method : configClass.getBeanMethods())
|
||||||
loadBeanDefinitionsForModelMethod(method);
|
loadBeanDefinitionsForModelMethod(method);
|
||||||
|
|
||||||
// Annotation[] pluginAnnotations = configClass.getPluginAnnotations();
|
// Annotation[] pluginAnnotations = configClass.getPluginAnnotations();
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import org.springframework.util.ClassUtils;
|
||||||
* from the concern of registering {@link BeanDefinition} objects based on the content of
|
* from the concern of registering {@link BeanDefinition} objects based on the content of
|
||||||
* that model.
|
* that model.
|
||||||
*
|
*
|
||||||
* @see org.springframework.config.java.support.ConfigurationModel
|
|
||||||
* @see org.springframework.config.java.support.ConfigurationModelBeanDefinitionReader
|
|
||||||
*
|
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
* @since 3.0
|
||||||
|
* @see ConfigurationModel
|
||||||
|
* @see ConfigurationModelBeanDefinitionReader
|
||||||
*/
|
*/
|
||||||
public class ConfigurationParser {
|
public class ConfigurationParser {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,28 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.config.java.support;
|
package org.springframework.config.java.support;
|
||||||
|
|
||||||
|
import static org.springframework.util.ClassUtils.*;
|
||||||
|
|
||||||
import org.springframework.beans.BeanMetadataElement;
|
import org.springframework.beans.BeanMetadataElement;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.beans.factory.parsing.Location;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract representation of a class, free from java reflection. Base class used within the
|
* Representation of a class, free from java reflection,
|
||||||
* internal JavaConfig metamodel for representing {@link Configuration} classes.
|
* populated by {@link ConfigurationParser}.
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
* @see ConfigurationModel
|
||||||
|
* @see ConfigurationClass
|
||||||
|
* @see ConfigurationParser
|
||||||
*/
|
*/
|
||||||
class ModelClass implements BeanMetadataElement {
|
class ModelClass implements BeanMetadataElement {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private boolean isInterface;
|
private boolean isInterface;
|
||||||
private String source;
|
private transient Object source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new and empty ModelClass instance.
|
* Creates a new and empty ModelClass instance.
|
||||||
|
|
@ -98,8 +103,7 @@ class ModelClass implements BeanMetadataElement {
|
||||||
* Returns a resource path-formatted representation of the .java file that declares this
|
* Returns a resource path-formatted representation of the .java file that declares this
|
||||||
* class
|
* class
|
||||||
*/
|
*/
|
||||||
// TODO: return type should be Object here. Spring IDE will return a JDT representation...
|
public Object getSource() {
|
||||||
public String getSource() {
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,8 +113,11 @@ class ModelClass implements BeanMetadataElement {
|
||||||
* @param source resource path to the .java file that declares this class.
|
* @param source resource path to the .java file that declares this class.
|
||||||
*/
|
*/
|
||||||
public void setSource(Object source) {
|
public void setSource(Object source) {
|
||||||
Assert.isInstanceOf(String.class, source);
|
this.source = source;
|
||||||
this.source = (String) source;
|
}
|
||||||
|
|
||||||
|
public Location getLocation() {
|
||||||
|
return new Location(new ClassPathResource(convertClassNameToResourcePath(getName())), getSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -160,5 +167,3 @@ class ModelClass implements BeanMetadataElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider eliminating in favor of just ConfigurationClass
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,11 @@ import test.beans.TestBean;
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractCircularImportDetectionTests {
|
public abstract class AbstractCircularImportDetectionTests {
|
||||||
|
|
||||||
protected abstract ConfigurationParser newParser();
|
protected abstract ConfigurationParser newParser();
|
||||||
|
|
||||||
protected abstract String loadAsConfigurationSource(Class<?> clazz) throws Exception;
|
protected abstract String loadAsConfigurationSource(Class<?> clazz) throws Exception;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simpleCircularImportIsDetected() throws Exception {
|
public void simpleCircularImportIsDetected() throws Exception {
|
||||||
boolean threw = false;
|
boolean threw = false;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import org.junit.Test;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.config.java.Bean;
|
import org.springframework.config.java.Bean;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
|
|
@ -49,6 +50,17 @@ public class BasicTests {
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void testFinalBeanMethod() {
|
||||||
|
initBeanFactory(ConfigWithFinalBean.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class ConfigWithFinalBean {
|
||||||
|
public final @Bean TestBean testBean() {
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simplestPossibleConfiguration() {
|
public void simplestPossibleConfiguration() {
|
||||||
|
|
@ -61,8 +73,7 @@ public class BasicTests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class SimplestPossibleConfig {
|
static class SimplestPossibleConfig {
|
||||||
public @Bean
|
public @Bean String stringBean() {
|
||||||
String stringBean() {
|
|
||||||
return "foo";
|
return "foo";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,15 +93,13 @@ public class BasicTests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class ConfigWithPrototypeBean {
|
static class ConfigWithPrototypeBean {
|
||||||
public @Bean
|
public @Bean TestBean foo() {
|
||||||
TestBean foo() {
|
|
||||||
TestBean foo = new TestBean("foo");
|
TestBean foo = new TestBean("foo");
|
||||||
foo.setSpouse(bar());
|
foo.setSpouse(bar());
|
||||||
return foo;
|
return foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Bean
|
public @Bean TestBean bar() {
|
||||||
TestBean bar() {
|
|
||||||
TestBean bar = new TestBean("bar");
|
TestBean bar = new TestBean("bar");
|
||||||
bar.setSpouse(baz());
|
bar.setSpouse(baz());
|
||||||
return bar;
|
return bar;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue