Formatting pass, primarily to align with Spring's convention of hard tab indentation.

This commit is contained in:
Chris Beams 2009-02-28 06:18:46 +00:00
parent f8270428df
commit f9918f9b2e
42 changed files with 3002 additions and 2788 deletions

View File

@ -29,70 +29,74 @@ import org.springframework.stereotype.Component;
/** /**
* Annotation indicating that a class is a "Java Configuration" class, meaning that it exposes one * Annotation indicating that a class is a "Java Configuration" class, meaning that it
* or more {@link Bean} methods. Holds similar information to that held in the default values of a * exposes one or more {@link Bean} methods. Holds similar information to that held in the
* bean factory; can generally be thought of as the JavaConfig equivalent of XML's 'beans' root * default values of a bean factory; can generally be thought of as the JavaConfig
* element. * equivalent of XML's 'beans' root element.
* *
* <p>Note however that the information here is not used to populate the defaults of the owning bean * <p>
* factory, which would affect other configurations. In the style of the Java configuration * Note however that the information here is not used to populate the defaults of the owning
* mechanism generally, each Java configuration class is kept isolated.</p> * bean factory, which would affect other configurations. In the style of the Java
* * configuration mechanism generally, each Java configuration class is kept isolated.
* <p>Configuration-annotated classes are also candidates for component scanning thanks to the fact * </p>
* that this annotation is meta-annotated with {@link Component @Component}.</p> *
* * <p>
* @author Rod Johnson * Configuration-annotated classes are also candidates for component scanning thanks to the
* @author Chris Beams * fact that this annotation is meta-annotated with {@link Component @Component}.
* </p>
*
* @author Rod Johnson
* @author Chris Beams
*/ */
@Component @Component
@Target({ ElementType.TYPE }) @Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Inherited @Inherited
@Documented @Documented
public @interface Configuration { public @interface Configuration {
/** /**
* Configuration name. Allow different variants, such as test, production * Configuration name. Allow different variants, such as test, production etc. Default
* etc. Default will always match. * will always match.
* @return *
*/ * @return
String[] names() default ""; */
String[] names() default "";
/** /**
* Specifies the default autowiring strategy. * Specifies the default autowiring strategy.
* *
* @see Autowire * @see Autowire
* @return * @return
*/ */
Autowire defaultAutowire() default Autowire.INHERITED; Autowire defaultAutowire() default Autowire.INHERITED;
// /** // /**
// * Dependency check strategy. By default, the dependency check is // * Dependency check strategy. By default, the dependency check is
// * unspecified, that is the default Spring option will apply. In most cases, // * unspecified, that is the default Spring option will apply. In most cases,
// * it means no dependency check will be done. // * it means no dependency check will be done.
// * // *
// * @see DependencyCheck // * @see DependencyCheck
// * @return // * @return
// */ // */
// DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED; // DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED;
// //
// /** // /**
// * Bean instantiation strategy. By default, it is unspecified. // * Bean instantiation strategy. By default, it is unspecified.
// * // *
// * @see Lazy // * @see Lazy
// * @return // * @return
// */ // */
// Lazy defaultLazy() default Lazy.UNSPECIFIED; // Lazy defaultLazy() default Lazy.UNSPECIFIED;
/** /**
* Do we autowire with aspects from the enclosing factory scope? * Do we autowire with aspects from the enclosing factory scope?
*/ */
boolean useFactoryAspects() default false; boolean useFactoryAspects() default false;
/** /**
* Do we check {@link Required @Required} methods to make sure they've been * Do we check {@link Required @Required} methods to make sure they've been called?
* called? */
*/ boolean checkRequired() default false;
boolean checkRequired() default false;
} }

View File

@ -30,308 +30,312 @@ import sun.security.x509.Extension;
/** /**
* Abstract representation of a user-defined {@link Configuration @Configuration} class. Includes a * Abstract representation of a user-defined {@link Configuration @Configuration} class.
* set of Bean methods, AutoBean methods, ExternalBean methods, ExternalValue methods, etc. Includes * Includes a set of Bean methods, AutoBean methods, ExternalBean methods, ExternalValue
* all such methods defined in the ancestry of the class, in a 'flattened-out' manner. Note that * methods, etc. Includes all such methods defined in the ancestry of the class, in a
* each BeanMethod representation does still contain source information about where it was * 'flattened-out' manner. Note that each BeanMethod representation does still contain
* originally detected (for the purpose of tooling with Spring IDE). * source information about where it was originally detected (for the purpose of tooling
* * with Spring IDE).
* <p>Like the rest of the {@link org.springframework.config.java.model model} package, *
* this class follows the fluent interface / builder pattern such that a model can be built up * <p>
* easily by method chaining.</p> * Like the rest of the {@link org.springframework.config.java.model model} package, this
* * class follows the fluent interface / builder pattern such that a model can be built up
* @author Chris Beams * easily by method chaining.
* </p>
*
* @author Chris Beams
*/ */
// TODO: SJC-242 update documentation in light of generalization changes // TODO: SJC-242 update documentation in light of generalization changes
// consider removing all refs to Bean, ExternalBean, etc. // consider removing all refs to Bean, ExternalBean, etc.
public final class ConfigurationClass extends ModelClass implements Validatable { public final class ConfigurationClass extends ModelClass implements Validatable {
private String beanName; private String beanName;
private int modifiers; private int modifiers;
private Configuration metadata; private Configuration metadata;
private HashSet<ModelMethod> methods = new HashSet<ModelMethod>(); private HashSet<ModelMethod> methods = new HashSet<ModelMethod>();
private HashSet<Annotation> pluginAnnotations = new HashSet<Annotation>();
private ConfigurationClass declaringClass; private HashSet<Annotation> pluginAnnotations = new HashSet<Annotation>();
public ConfigurationClass() { } private ConfigurationClass declaringClass;
// TODO: get rid of constructors used only for testing. put in testing util. public ConfigurationClass() {
/** }
* 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) { // TODO: get rid of constructors used only for testing. put in testing util.
this(name, null, metadata, 0); /**
} * 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, int modifiers) { ConfigurationClass(String name, Configuration metadata) {
this(name, null, defaultAnnotation(), modifiers); this(name, null, metadata, 0);
} }
private static Configuration defaultAnnotation() {
@Configuration class Prototype { }
return Prototype.class.getAnnotation(Configuration.class);
}
/** ConfigurationClass(String name, int modifiers) {
* Creates a new ConfigurationClass object. this(name, null, defaultAnnotation(), modifiers);
* }
* @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); private static Configuration defaultAnnotation() {
setMetadata(metadata); @Configuration
setModifiers(modifiers); class Prototype {
} }
return Prototype.class.getAnnotation(Configuration.class);
public ConfigurationClass addMethod(ModelMethod method) { }
method.setDeclaringClass(this);
methods.add(method);
return this;
}
public String getBeanName() { /**
return beanName == null ? getName() : beanName; * 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");
public ConfigurationClass setBeanName(String id) { setBeanName(id);
this.beanName = id; setMetadata(metadata);
return this; setModifiers(modifiers);
} }
public Set<ModelMethod> getMethods() {
return methods;
}
public Annotation[] getPluginAnnotations() { public ConfigurationClass addMethod(ModelMethod method) {
return pluginAnnotations.toArray(new Annotation[pluginAnnotations.size()]); method.setDeclaringClass(this);
} methods.add(method);
return this;
}
/** public String getBeanName() {
* Add a {@link Extension @Plugin}-annotated annotation to this configuration class. return beanName == null ? getName() : beanName;
* }
* @param pluginAnno type-level <code>Plugin</code> annotation
*/ public ConfigurationClass setBeanName(String id) {
public ConfigurationClass addPluginAnnotation(Annotation pluginAnno) { this.beanName = id;
pluginAnnotations.add(pluginAnno); return this;
return this; }
}
public Set<ModelMethod> getMethods() {
return methods;
}
public Annotation[] getPluginAnnotations() {
return pluginAnnotations.toArray(new Annotation[pluginAnnotations.size()]);
}
/**
* 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) { public ConfigurationClass setDeclaringClass(ConfigurationClass configurationClass) {
this.declaringClass = configurationClass; this.declaringClass = configurationClass;
return this; return this;
} }
public ConfigurationClass getDeclaringClass() { public ConfigurationClass getDeclaringClass() {
return declaringClass; return declaringClass;
} }
public int getModifiers() { public int getModifiers() {
return modifiers; return modifiers;
} }
public ConfigurationClass setModifiers(int modifiers) { public ConfigurationClass setModifiers(int modifiers) {
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative"); Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
this.modifiers = modifiers; this.modifiers = modifiers;
return this; return this;
} }
public Configuration getMetadata() { public Configuration getMetadata() {
return this.metadata; return this.metadata;
} }
public ConfigurationClass setMetadata(Configuration configAnno) { public ConfigurationClass setMetadata(Configuration configAnno) {
this.metadata = configAnno; this.metadata = configAnno;
return this; return this;
} }
public void validate(List<UsageError> errors) { public void validate(List<UsageError> errors) {
// configuration classes must be annotated with @Configuration // configuration classes must be annotated with @Configuration
if (metadata == null) if (metadata == null)
errors.add(new NonAnnotatedConfigurationError()); errors.add(new NonAnnotatedConfigurationError());
// a configuration class may not be final (CGLIB limitation) // a configuration class may not be final (CGLIB limitation)
if (Modifier.isFinal(modifiers)) if (Modifier.isFinal(modifiers))
errors.add(new FinalConfigurationError()); errors.add(new FinalConfigurationError());
for(ModelMethod method : methods) for (ModelMethod method : methods)
method.validate(errors); method.validate(errors);
} }
@Override @Override
public String toString() { public String toString() {
return format("%s; modifiers=%d; methods=%s", return format("%s; modifiers=%d; methods=%s", super.toString(), modifiers, methods);
super.toString(), modifiers, methods); }
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
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 + ((metadata == null) ? 0 : metadata.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()); result = prime * result + ((pluginAnnotations == null) ? 0 : pluginAnnotations.hashCode());
return result; return result;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj)
return true; return true;
if (!super.equals(obj)) if (!super.equals(obj))
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
ConfigurationClass other = (ConfigurationClass) obj; ConfigurationClass other = (ConfigurationClass) obj;
if (declaringClass == null) { if (declaringClass == null) {
if (other.declaringClass != null) if (other.declaringClass != null)
return false; return false;
} else if (!declaringClass.equals(other.declaringClass)) } else if (!declaringClass.equals(other.declaringClass))
return false; return false;
if (beanName == null) { if (beanName == null) {
if (other.beanName != null) if (other.beanName != null)
return false; return false;
} else if (!beanName.equals(other.beanName)) } else if (!beanName.equals(other.beanName))
return false; return false;
if (metadata == null) { if (metadata == null) {
if (other.metadata != null) if (other.metadata != null)
return false; return false;
} else if (!metadata.equals(other.metadata)) } else if (!metadata.equals(other.metadata))
return false; return false;
if (methods == null) { if (methods == null) {
if (other.methods != null) if (other.methods != null)
return false; return false;
} else if (!methods.equals(other.methods)) } else if (!methods.equals(other.methods))
return false; return false;
if (modifiers != other.modifiers) if (modifiers != other.modifiers)
return false; return false;
if (pluginAnnotations == null) { if (pluginAnnotations == null) {
if (other.pluginAnnotations != null) if (other.pluginAnnotations != null)
return false; return false;
} else if (!pluginAnnotations.equals(other.pluginAnnotations)) } else if (!pluginAnnotations.equals(other.pluginAnnotations))
return false; 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 NonAnnotatedConfigurationError extends UsageError { public class NonAnnotatedConfigurationError extends UsageError {
public NonAnnotatedConfigurationError() { public NonAnnotatedConfigurationError() {
super(ConfigurationClass.this, -1); super(ConfigurationClass.this, -1);
} }
@Override @Override
public String getDescription() { public String getDescription() {
return format("%s was provided as a Java Configuration class but was not annotated with @%s. " return format("%s was provided as a Java Configuration class but was not annotated with @%s. "
+ "Update the class definition to continue.", + "Update the class definition to continue.", getSimpleName(), Configuration.class
getSimpleName(), Configuration.class.getSimpleName()); .getSimpleName());
} }
} }
/** Configuration classes must be non-final to accommodate CGLIB subclassing. */ /** Configuration classes must be non-final to accommodate CGLIB subclassing. */
public class FinalConfigurationError extends UsageError { public class FinalConfigurationError extends UsageError {
public FinalConfigurationError() { public FinalConfigurationError() {
super(ConfigurationClass.this, -1); super(ConfigurationClass.this, -1);
} }
@Override @Override
public String getDescription() { public String getDescription() {
return format("@%s class may not be final. Remove the final modifier to continue.", return format("@%s class may not be final. Remove the final modifier to continue.",
Configuration.class.getSimpleName()); Configuration.class.getSimpleName());
} }
} }
public class InvalidPluginException extends UsageError { public class InvalidPluginException extends UsageError {
private final Annotation invalidPluginAnnotation; private final Annotation invalidPluginAnnotation;
public InvalidPluginException(Annotation invalidPluginAnnotation) { public InvalidPluginException(Annotation invalidPluginAnnotation) {
super(ConfigurationClass.this, -1); super(ConfigurationClass.this, -1);
this.invalidPluginAnnotation = invalidPluginAnnotation; this.invalidPluginAnnotation = invalidPluginAnnotation;
} }
@Override @Override
public String getDescription() { public String getDescription() {
return format("Annotation [%s] was not annotated with @Plugin", invalidPluginAnnotation); return format("Annotation [%s] was not annotated with @Plugin", invalidPluginAnnotation);
} }
} }
/** /**
* Error raised when a Bean marked as 'allowOverriding=false' is attempted to be overridden by * Error raised when a Bean marked as 'allowOverriding=false' is attempted to be
* another bean definition. * overridden by another bean definition.
* *
* @see Bean#allowOverriding() * @see Bean#allowOverriding()
*/ */
public class IllegalBeanOverrideError extends UsageError { public class IllegalBeanOverrideError extends UsageError {
private final ConfigurationClass authoritativeClass; private final ConfigurationClass authoritativeClass;
private final ModelMethod finalMethodInQuestion; private final ModelMethod finalMethodInQuestion;
/** /**
* Creates a new IllegalBeanOverrideError object. * Creates a new IllegalBeanOverrideError object.
* *
* @param violatingClass class attempting an illegal override. null value signifies * @param violatingClass class attempting an illegal override. null value signifies
* that the violating class is unknown or that there is no * that the violating class is unknown or that there is no class to speak of
* class to speak of (in the case of an XML bean definition * (in the case of an XML bean definition doing the illegal overriding)
* doing the illegal overriding) * @param finalMethodInQuestion the method that has been marked
* @param finalMethodInQuestion the method that has been marked 'allowOverriding=false' * 'allowOverriding=false'
*/ */
public IllegalBeanOverrideError(ConfigurationClass violatingClass, public IllegalBeanOverrideError(ConfigurationClass violatingClass, ModelMethod finalMethodInQuestion) {
ModelMethod finalMethodInQuestion) { super(violatingClass, -1);
super(violatingClass, -1); this.authoritativeClass = ConfigurationClass.this;
this.authoritativeClass = ConfigurationClass.this; this.finalMethodInQuestion = finalMethodInQuestion;
this.finalMethodInQuestion = finalMethodInQuestion; }
}
@Override @Override
public String getDescription() { public String getDescription() {
return format("Illegal attempt by '%s' to override bean definition originally " return format("Illegal attempt by '%s' to override bean definition originally "
+ "specified by %s.%s. Consider removing 'allowOverride=false' from original method.", + "specified by %s.%s. Consider removing 'allowOverride=false' from original method.",
finalMethodInQuestion.getName(), authoritativeClass.getSimpleName(), finalMethodInQuestion.getName(), authoritativeClass.getSimpleName(),
finalMethodInQuestion.getName()); finalMethodInQuestion.getName());
} }
} }
public boolean hasMethod(String methodName) { public boolean hasMethod(String methodName) {
return getMethod(methodName) != null; return getMethod(methodName) != null;
} }
public ModelMethod getMethod(String methodName) { public ModelMethod getMethod(String methodName) {
for(ModelMethod method : methods) for (ModelMethod method : methods)
if(methodName.equals(method.getName())) if (methodName.equals(method.getName()))
return method; return method;
return null; return null;
} }
} }

View File

@ -21,154 +21,157 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* An abstract representation of a set of user-provided "Configuration classes", usually but not * An abstract representation of a set of user-provided "Configuration classes", usually but
* necessarily annotated with {@link Configuration @Configuration}. The model is populated with a * not necessarily annotated with {@link Configuration @Configuration}. The model is
* {@link org.springframework.config.java.internal.parsing.ConfigurationParser} implementation which * populated with a
* may be reflection-based or ASM-based. Once a model has been populated, it can then be rendered * {@link org.springframework.config.java.internal.parsing.ConfigurationParser}
* out to a set of BeanDefinitions. The model provides an important layer of indirection between the * implementation which may be reflection-based or ASM-based. Once a model has been
* complexity of parsing a set of classes and the complexity of representing the contents of those * populated, it can then be rendered out to a set of BeanDefinitions. The model provides an
* classes as BeanDefinitions. * important layer of indirection between the complexity of parsing a set of classes and the
* * complexity of representing the contents of those classes as BeanDefinitions.
* <p>Interface follows the builder pattern for method chaining.</p> *
* * <p>
* @author Chris Beams * Interface follows the builder pattern for method chaining.
* @see org.springframework.config.java.internal.parsing.ConfigurationParser * </p>
*
* @author Chris Beams
* @see org.springframework.config.java.internal.parsing.ConfigurationParser
*/ */
public final class ConfigurationModel implements Validatable { public final class ConfigurationModel implements Validatable {
/* list is used because order and collection equality matters. */ /* list is used because order and collection equality matters. */
private final ArrayList<ConfigurationClass> configurationClasses = new ArrayList<ConfigurationClass>(); private final ArrayList<ConfigurationClass> configurationClasses = new ArrayList<ConfigurationClass>();
private final ArrayList<Validator> validators = new ArrayList<Validator>(); private final ArrayList<Validator> validators = new ArrayList<Validator>();
/** /**
* Add a {@link Configuration @Configuration} class to the model. Classes may be added at will * Add a {@link Configuration @Configuration} class to the model. Classes may be added
* and without any particular validation. Malformed classes will be caught and errors processed * at will and without any particular validation. Malformed classes will be caught and
* during {@link #validate() validation} * errors processed during {@link #validate() validation}
* *
* @param configurationClass user-supplied Configuration class * @param configurationClass user-supplied Configuration class
*/ */
public ConfigurationModel add(ConfigurationClass configurationClass) { public ConfigurationModel add(ConfigurationClass configurationClass) {
configurationClasses.add(configurationClass); configurationClasses.add(configurationClass);
return this; return this;
} }
public void registerValidator(Validator validator) {
validators.add(validator);
}
/** public void registerValidator(Validator validator) {
* Return configuration classes that have been directly added to this model. validators.add(validator);
* }
* @see #getAllConfigurationClasses()
*/
public ConfigurationClass[] getConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[] { });
}
// /** /**
// * Return all configuration classes, including all imported configuration classes. This method * Return configuration classes that have been directly added to this model.
// * should be generally preferred over {@link #getConfigurationClasses()} *
// * * @see #getAllConfigurationClasses()
// * @see #getConfigurationClasses() */
// */ public ConfigurationClass[] getConfigurationClasses() {
// public ConfigurationClass[] getAllConfigurationClasses() { return configurationClasses.toArray(new ConfigurationClass[] {});
// ArrayList<ConfigurationClass> allConfigClasses = new ArrayList<ConfigurationClass>(); }
//
// for (ConfigurationClass configClass : configurationClasses)
// allConfigClasses.addAll(configClass.getSelfAndAllImports());
//
// return allConfigClasses.toArray(new ConfigurationClass[allConfigClasses.size()]);
// }
public ConfigurationClass[] getAllConfigurationClasses() {
return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
}
/** // /**
* Recurses through the model validating each object along the way and aggregating any <var>errors</var>. // * Return all configuration classes, including all imported configuration classes.
* // This method
* @see ConfigurationClass#validate(java.util.List) // * should be generally preferred over {@link #getConfigurationClasses()}
* @see ModelMethod#validate(java.util.List) // *
* @see Validator // * @see #getConfigurationClasses()
* @see UsageError // */
*/ // public ConfigurationClass[] getAllConfigurationClasses() {
public void validate(List<UsageError> errors) { // ArrayList<ConfigurationClass> allConfigClasses = new ArrayList<ConfigurationClass>();
// user must specify at least one configuration //
if (configurationClasses.isEmpty()) // for (ConfigurationClass configClass : configurationClasses)
errors.add(new EmptyModelError()); // allConfigClasses.addAll(configClass.getSelfAndAllImports());
//
// cascade through model and allow handlers to register validators // return allConfigClasses.toArray(new ConfigurationClass[allConfigClasses.size()]);
// depending on where they are registered (with the model, the class, or the method) // }
// they will be called directly or indirectly below
for (ConfigurationClass configClass : getAllConfigurationClasses()) {
for(ModelMethod method : configClass.getMethods()) {
for(Validator validator : method.getValidators()) {
if(validator.supports(method))
method.registerValidator(validator);
// TODO: support class-level validation
// if(validator.supports(configClass))
// configClass.registerValidator(validator);
if(validator.supports(this))
this.registerValidator(validator);
}
}
}
// process any validators registered directly with this model object
for(Validator validator : validators)
validator.validate(this, errors);
// each individual configuration class must be well-formed public ConfigurationClass[] getAllConfigurationClasses() {
// note that each configClass detects usage errors on its imports recursively return configurationClasses.toArray(new ConfigurationClass[configurationClasses.size()]);
// note that each configClass will recursively process its respective methods }
for (ConfigurationClass configClass : configurationClasses)
configClass.validate(errors);
}
@Override /**
public String toString() { * Recurses through the model validating each object along the way and aggregating any
return format("%s: configurationClasses=%s", * <var>errors</var>.
getClass().getSimpleName(), configurationClasses); *
} * @see ConfigurationClass#validate(java.util.List)
* @see ModelMethod#validate(java.util.List)
* @see Validator
* @see UsageError
*/
public void validate(List<UsageError> errors) {
// user must specify at least one configuration
if (configurationClasses.isEmpty())
errors.add(new EmptyModelError());
@Override // cascade through model and allow handlers to register validators
public int hashCode() { // depending on where they are registered (with the model, the class, or the method)
final int prime = 31; // they will be called directly or indirectly below
int result = 1; for (ConfigurationClass configClass : getAllConfigurationClasses()) {
result = prime * result + ((configurationClasses == null) ? 0 : configurationClasses.hashCode()); for (ModelMethod method : configClass.getMethods()) {
return result; for (Validator validator : method.getValidators()) {
} if (validator.supports(method))
method.registerValidator(validator);
// TODO: support class-level validation
// if(validator.supports(configClass))
// configClass.registerValidator(validator);
if (validator.supports(this))
this.registerValidator(validator);
}
}
}
@Override // process any validators registered directly with this model object
public boolean equals(Object obj) { for (Validator validator : validators)
if (this == obj) validator.validate(this, errors);
return true;
if (obj == null) // each individual configuration class must be well-formed
return false; // note that each configClass detects usage errors on its imports recursively
if (getClass() != obj.getClass()) // note that each configClass will recursively process its respective methods
return false; for (ConfigurationClass configClass : configurationClasses)
ConfigurationModel other = (ConfigurationModel) obj; configClass.validate(errors);
if (configurationClasses == null) { }
if (other.configurationClasses != null)
return false; @Override
} else if (!configurationClasses.equals(other.configurationClasses)) public String toString() {
return false; return format("%s: configurationClasses=%s", getClass().getSimpleName(), configurationClasses);
return true; }
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((configurationClasses == null) ? 0 : configurationClasses.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ConfigurationModel other = (ConfigurationModel) obj;
if (configurationClasses == null) {
if (other.configurationClasses != null)
return false;
} else if (!configurationClasses.equals(other.configurationClasses))
return false;
return true;
}
public class EmptyModelError extends UsageError { public class EmptyModelError extends UsageError {
public EmptyModelError() { public EmptyModelError() {
super(null, 0); super(null, 0);
} }
@Override @Override
public String getDescription() { public String getDescription() {
return format("Configuration model was empty. Make sure at least one " return format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName()); + "@%s class has been specified.", Configuration.class.getSimpleName());
} }
} }
} }

View File

@ -24,9 +24,10 @@ import java.lang.annotation.Target;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.NoOp; import net.sf.cglib.proxy.NoOp;
/** /**
* Meta-annotation used to identify annotations as producers of beans and/or values. * Meta-annotation used to identify annotations as producers of beans and/or values.
* *
* @author Chris Beams * @author Chris Beams
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -34,22 +35,21 @@ import net.sf.cglib.proxy.NoOp;
@Documented @Documented
public @interface Factory { public @interface Factory {
/** /**
* Specifies which registrar (if any) should be used to register * Specifies which registrar (if any) should be used to register bean definitions for
* bean definitions for this {@link Factory} method. * this {@link Factory} method.
*/ */
Class<? extends BeanDefinitionRegistrar> registrarType(); Class<? extends BeanDefinitionRegistrar> registrarType();
/** /**
* Specifies what (if any) callback should be used when processing this {@link Factory} method. * Specifies what (if any) callback should be used when processing this {@link Factory}
* Defaults to CGLIB's {@link NoOp}, which does nothing. * method. Defaults to CGLIB's {@link NoOp}, which does nothing. TODO: rename
* TODO: rename (interceptorType)? to keep with the -or|-ar nomenclature * (interceptorType)? to keep with the -or|-ar nomenclature
*/ */
Class<? extends Callback> callbackType() default NoOp.class; Class<? extends Callback> callbackType() default NoOp.class;
/** /**
* TODO: document * TODO: document TODO: rename
* TODO: rename */
*/ Class<? extends Validator>[] validatorTypes() default {};
Class<? extends Validator>[] validatorTypes() default {};
} }

View File

@ -22,54 +22,54 @@ import java.util.List;
/** /**
* TODO: rename to UsageException / move outside .internal? * TODO: rename to UsageException / move outside .internal?
* *
* @author Chris Beams * @author Chris Beams
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class MalformedConfigurationException extends RuntimeException { public class MalformedConfigurationException extends RuntimeException {
private final List<? extends UsageError> errors; private final List<? extends UsageError> errors;
public MalformedConfigurationException(String message) { public MalformedConfigurationException(String message) {
super(message); super(message);
this.errors = new ArrayList<UsageError>(); this.errors = new ArrayList<UsageError>();
} }
public MalformedConfigurationException(UsageError... errors) { public MalformedConfigurationException(UsageError... errors) {
super(toString(errors)); super(toString(errors));
this.errors = Arrays.asList(errors); this.errors = Arrays.asList(errors);
} }
public boolean containsError(Class<? extends UsageError> errorType) { public boolean containsError(Class<? extends UsageError> errorType) {
for (UsageError error : errors) for (UsageError error : errors)
if (error.getClass().isAssignableFrom(errorType)) if (error.getClass().isAssignableFrom(errorType))
return true; return true;
return false; return false;
} }
/** /**
* Render a list of syntax errors as output suitable for diagnosis via System.err. * Render a list of syntax errors as output suitable for diagnosis via System.err.
*/ */
private static String toString(UsageError... errors) { private static String toString(UsageError... errors) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("\n"); sb.append("\n");
if (errors.length == 1) if (errors.length == 1)
sb.append("A usage error has "); sb.append("A usage error has ");
else else
sb.append(errors.length + " usage errors have "); sb.append(errors.length + " usage errors have ");
sb.append("been detected:\n"); sb.append("been detected:\n");
for (int i = 0; i < errors.length; i++) { for (int i = 0; i < errors.length; i++) {
sb.append(errors[i].toString()); sb.append(errors[i].toString());
if ((i + 1) < errors.length) if ((i + 1) < errors.length)
sb.append('\n'); sb.append('\n');
} }
return sb.toString(); return sb.toString();
} }
} }

View File

@ -21,139 +21,141 @@ import org.springframework.util.ClassUtils;
/** /**
* Abstract representation of a class, free from java reflection. * Abstract representation of a class, free from java reflection. Base class used within the
* Base class used within the internal JavaConfig metamodel for * internal JavaConfig metamodel for representing {@link Configuration} classes.
* representing {@link Configuration} classes. *
*
* @author Chris Beams * @author Chris Beams
*/ */
// TODO: Consider eliminating in favor of just ConfigurationClass // TODO: Consider eliminating in favor of just ConfigurationClass
public class ModelClass implements BeanMetadataElement { public class ModelClass implements BeanMetadataElement {
private String name; private String name;
private boolean isInterface; private boolean isInterface;
private String source; private String source;
/** /**
* Creates a new and empty ModelClass instance. * Creates a new and empty ModelClass instance.
*/ */
public ModelClass() { } public ModelClass() {
}
/** /**
* Creates a new ModelClass instance * Creates a new ModelClass instance
* *
* @param name fully-qualified name of the class being represented * @param name fully-qualified name of the class being represented
*/ */
public ModelClass(String name) { public ModelClass(String name) {
this(name, false); this(name, false);
} }
/** /**
* Creates a new ModelClass instance * Creates a new ModelClass instance
* *
* @param name fully-qualified name of the class being represented * @param name fully-qualified name of the class being represented
* @param isInterface whether the represented type is an interface * @param isInterface whether the represented type is an interface
*/ */
public ModelClass(String name, boolean isInterface) { public ModelClass(String name, boolean isInterface) {
this.name = name; this.name = name;
this.isInterface = isInterface; this.isInterface = isInterface;
} }
/** /**
* Returns the fully-qualified name of this class. * Returns the fully-qualified name of this class.
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/** /**
* Sets the fully-qualified name of this class. * Sets the fully-qualified name of this class.
*/ */
public void setName(String className) { public void setName(String className) {
this.name = className; this.name = className;
} }
/** /**
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
*/ */
public String getSimpleName() { public String getSimpleName() {
return name == null ? null : ClassUtils.getShortName(name); return name == null ? null : ClassUtils.getShortName(name);
} }
/** /**
* Returns whether the class represented by this ModelClass instance is an interface. * Returns whether the class represented by this ModelClass instance is an interface.
*/ */
public boolean isInterface() { public boolean isInterface() {
return isInterface; return isInterface;
} }
/** /**
* Signifies that this class is (true) or is not (false) an interface. * Signifies that this class is (true) or is not (false) an interface.
*/ */
public void setInterface(boolean isInterface) { public void setInterface(boolean isInterface) {
this.isInterface = isInterface; this.isInterface = isInterface;
} }
/** /**
* Returns a resource path-formatted representation of the .java * Returns a resource path-formatted representation of the .java file that declares this
* file that declares this class * class
*/ */
public String getSource() { public String getSource() {
return source; return source;
} }
/** /**
* Set the source location for this class. Must be a resource-path formatted string. * 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. * @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); Assert.isInstanceOf(String.class, source);
this.source = (String) source; this.source = (String) source;
} }
/** /**
* Given a ModelClass instance representing a class com.acme.Foo, this method will return * Given a ModelClass instance representing a class com.acme.Foo, this method will
* <pre> * return
* ModelClass: name=Foo *
* </pre> * <pre>
*/ * ModelClass: name=Foo
@Override * </pre>
public String toString() { */
return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName()); @Override
} public String toString() {
return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName());
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = (prime * result) + (isInterface ? 1231 : 1237); result = (prime * result) + (isInterface ? 1231 : 1237);
result = (prime * result) + ((name == null) ? 0 : name.hashCode()); result = (prime * result) + ((name == null) ? 0 : name.hashCode());
return result; return result;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj)
return true; return true;
if (obj == null) if (obj == null)
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
ModelClass other = (ModelClass) obj; ModelClass other = (ModelClass) obj;
if (isInterface != other.isInterface) if (isInterface != other.isInterface)
return false; return false;
if (name == null) { if (name == null) {
if (other.name != null) if (other.name != null)
return false; return false;
} else if (!name.equals(other.name)) } else if (!name.equals(other.name))
return false; return false;
return true; return true;
} }
} }

View File

@ -34,207 +34,206 @@ import org.springframework.util.Assert;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
public final class ModelMethod implements Validatable { public final class ModelMethod implements Validatable {
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 int lineNumber;
private transient Factory factoryAnno; private transient Factory factoryAnno;
private transient final List<Validator> validators = new ArrayList<Validator>(); private transient final List<Validator> validators = new ArrayList<Validator>();
public ModelMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) { public ModelMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
Assert.hasText(name); Assert.hasText(name);
this.name = name; this.name = name;
Assert.notNull(annotations); Assert.notNull(annotations);
for(Annotation annotation : annotations) { for (Annotation annotation : annotations) {
this.annotations.add(annotation); this.annotations.add(annotation);
if(factoryAnno == null) if (factoryAnno == null)
factoryAnno = annotation.annotationType().getAnnotation(Factory.class); factoryAnno = annotation.annotationType().getAnnotation(Factory.class);
} }
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
this.modifiers = modifiers;
Assert.notNull(returnType);
this.returnType = returnType;
}
public String getName() { Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
return name; this.modifiers = modifiers;
}
public ModelClass getReturnType() {
return returnType;
}
/** Assert.notNull(returnType);
* @see java.lang.reflect.Modifier this.returnType = returnType;
*/ }
public int getModifiers() {
return modifiers;
}
/** public String getName() {
* Returns the annotation on this method matching <var>annoType</var> or null return name;
* IllegalStateException} if not present. }
*
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
for(Annotation anno : annotations)
if(anno.annotationType().equals(annoType))
return (T) anno;
return null;
}
/** public ModelClass getReturnType() {
* Returns the annotation on this method matching <var>annoType</var> or throws return returnType;
* {@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(
format("annotation %s not found on %s", annoType.getSimpleName(), this));
return anno;
}
/** /**
* Sets up bi-directional relationship between this method and its declaring class. * @see java.lang.reflect.Modifier
* */
* @see ConfigurationClass#addMethod(ModelMethod) public int getModifiers() {
*/ return modifiers;
public void setDeclaringClass(ConfigurationClass declaringClass) { }
this.declaringClass = declaringClass;
}
public ConfigurationClass getDeclaringClass() { /**
return declaringClass; * Returns the annotation on this method matching <var>annoType</var> or null
} * IllegalStateException} if not present.
*
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
for (Annotation anno : annotations)
if (anno.annotationType().equals(annoType))
return (T) anno;
public void setLineNumber(int lineNumber) { return null;
this.lineNumber = lineNumber; }
}
public int getLineNumber() { /**
return lineNumber; * Returns the annotation on this method matching <var>annoType</var> or throws
} * {@link IllegalStateException} if not present.
*
public void registerValidator(Validator validator) { * @see #getAnnotation(Class)
validators.add(validator); */
} public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
T anno = getAnnotation(annoType);
public void validate(List<UsageError> errors) {
for(Validator validator : validators)
validator.validate(this, errors);
if (Modifier.isPrivate(getModifiers()))
errors.add(new PrivateMethodError());
if (Modifier.isFinal(getModifiers())) if (anno == null)
errors.add(new FinalMethodError()); throw new IllegalStateException(format("annotation %s not found on %s", annoType.getSimpleName(),
} this));
public BeanDefinitionRegistrar getRegistrar() { return anno;
return getInstance(factoryAnno.registrarType()); }
}
public Set<Validator> getValidators() { /**
HashSet<Validator> validator = new HashSet<Validator>(); * Sets up bi-directional relationship between this method and its declaring class.
*
for(Class<? extends Validator> validatorType : factoryAnno.validatorTypes()) * @see ConfigurationClass#addMethod(ModelMethod)
validator.add(getInstance(validatorType)); */
public void setDeclaringClass(ConfigurationClass declaringClass) {
return validator; this.declaringClass = declaringClass;
} }
public Callback getCallback() { public ConfigurationClass getDeclaringClass() {
Class<? extends Callback> callbackType = factoryAnno.callbackType(); return declaringClass;
}
if(callbackType.equals(NoOp.class))
return NoOp.INSTANCE;
return getInstance(callbackType);
}
@Override public void setLineNumber(int lineNumber) {
public String toString() { this.lineNumber = lineNumber;
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName(); }
return String.format("%s: name=%s; returnType=%s; modifiers=%d",
getClass().getSimpleName(), name, returnTypeName, modifiers);
}
@Override public int getLineNumber() {
public int hashCode() { return lineNumber;
final int prime = 31; }
int result = 1;
result = prime * result + ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result + modifiers;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override public void registerValidator(Validator validator) {
public boolean equals(Object obj) { validators.add(validator);
if (this == obj) }
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelMethod other = (ModelMethod) obj;
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!annotations.equals(other.annotations))
return false;
if (modifiers != other.modifiers)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (returnType == null) {
if (other.returnType != null)
return false;
} else if (!returnType.equals(other.returnType))
return false;
return true;
}
/** JavaConfigMethods must be visible (non-private) in order to accommodate CGLIB. */ public void validate(List<UsageError> errors) {
public class PrivateMethodError extends UsageError { for (Validator validator : validators)
public PrivateMethodError() { validator.validate(this, errors);
super(getDeclaringClass(), getLineNumber());
}
@Override if (Modifier.isPrivate(getModifiers()))
public String getDescription() { errors.add(new PrivateMethodError());
return format("method '%s' may not be private", getName());
}
}
/** JavaConfigMethods must be extensible (non-final) in order to accommodate CGLIB. */ if (Modifier.isFinal(getModifiers()))
public class FinalMethodError extends UsageError { errors.add(new FinalMethodError());
public FinalMethodError() { }
super(getDeclaringClass(), getLineNumber());
} public BeanDefinitionRegistrar getRegistrar() {
return getInstance(factoryAnno.registrarType());
}
public Set<Validator> getValidators() {
HashSet<Validator> validator = new HashSet<Validator>();
for (Class<? extends Validator> validatorType : factoryAnno.validatorTypes())
validator.add(getInstance(validatorType));
return validator;
}
public Callback getCallback() {
Class<? extends Callback> callbackType = factoryAnno.callbackType();
if (callbackType.equals(NoOp.class))
return NoOp.INSTANCE;
return getInstance(callbackType);
}
@Override
public String toString() {
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
return String.format("%s: name=%s; returnType=%s; modifiers=%d", getClass().getSimpleName(), name,
returnTypeName, modifiers);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result + modifiers;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelMethod other = (ModelMethod) obj;
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!annotations.equals(other.annotations))
return false;
if (modifiers != other.modifiers)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (returnType == null) {
if (other.returnType != null)
return false;
} else if (!returnType.equals(other.returnType))
return false;
return true;
}
/** JavaConfigMethods must be visible (non-private) in order to accommodate CGLIB. */
public class PrivateMethodError extends UsageError {
public PrivateMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be private", getName());
}
}
/** JavaConfigMethods must be extensible (non-final) in order to accommodate CGLIB. */
public class FinalMethodError extends UsageError {
public FinalMethodError() {
super(getDeclaringClass(), getLineNumber());
}
@Override
public String getDescription() {
return format("method '%s' may not be final - remove the final modifier to continue", getName());
}
}
@Override
public String getDescription() {
return format("method '%s' may not be final - remove the final modifier to continue",
getName());
}
}
} }

View File

@ -19,23 +19,24 @@ package org.springframework.config.java;
/** /**
* Enumerates the names of the scopes supported out of the box in Spring. * Enumerates the names of the scopes supported out of the box in Spring.
* <p> * <p>
* Not modeled as an actual java enum because annotations that accept a scope * Not modeled as an actual java enum because annotations that accept a scope attribute must
* attribute must allow for user-defined scope names. Given that java * allow for user-defined scope names. Given that java enums are not extensible, these must
* enums are not extensible, these must remain simple string constants. * remain simple string constants.
* *
* @author Chris Beams * @author Chris Beams
* @since 3.0 * @since 3.0
*/ */
public class Scopes { public class Scopes {
private Scopes() { }
public static final String SINGLETON = "singleton"; // see BeanDefinition.SCOPE_SINGLETON; private Scopes() {
}
public static final String PROTOTYPE = "prototype"; // see BeanDefinition.SCOPE_PROTOTYPE; public static final String SINGLETON = "singleton";
public static final String REQUEST = "request"; // see WebApplicationContext.SCOPE_REQUEST; public static final String PROTOTYPE = "prototype";
public static final String SESSION = "session"; // see WebApplicationContext.SCOPE_SESSION; public static final String REQUEST = "request";
public static final String SESSION = "session";
} }

View File

@ -16,57 +16,58 @@
package org.springframework.config.java; package org.springframework.config.java;
/** /**
* Represents an invalid usage of JavaConfig constructs, e.g. a {@link Configuration} that declares * Represents an invalid usage of JavaConfig constructs, e.g. a {@link Configuration} that
* no {@link Bean @Bean} methods, or declaring both {@link Bean @Bean} and * declares no {@link Bean @Bean} methods, or declaring both {@link Bean @Bean} and
* {@link ExternalBean @ExternalBean} on a single method. Explore the type hierarchy to discover all * {@link ExternalBean @ExternalBean} on a single method. Explore the type hierarchy to
* possible usage errors. * discover all possible usage errors.
* *
* @author Chris Beams * @author Chris Beams
* @see MalformedConfigurationException * @see MalformedConfigurationException
*/ */
public abstract class UsageError { public abstract class UsageError {
private final ModelClass clazz; private final ModelClass clazz;
private final int lineNumber; private final int lineNumber;
/** /**
* Create a new usage error, providing information about where the error was detected. * Create a new usage error, providing information about where the error was detected.
* *
* @param modelClass class in which this error was detected. Null value indicates that the * @param modelClass class in which this error was detected. Null value indicates that
* error was not local to a single class. * the error was not local to a single class.
* @param lineNumber line number on which this error was detected (useful for tooling integration) * @param lineNumber line number on which this error was detected (useful for tooling
* * integration)
* @see ModelClass#getSource() *
*/ * @see ModelClass#getSource()
public UsageError(ModelClass modelClass, int lineNumber) { */
this.clazz = modelClass; public UsageError(ModelClass modelClass, int lineNumber) {
this.lineNumber = lineNumber; this.clazz = modelClass;
} this.lineNumber = lineNumber;
}
/** /**
* Human-readable description of this error suitable for console output or IDE tooling. * Human-readable description of this error suitable for console output or IDE tooling.
*/ */
public abstract String getDescription(); public abstract String getDescription();
/** /**
* Same as {@link #getDescription()} but attributed with class and line number information. If * Same as {@link #getDescription()} but attributed with class and line number
* modelClass constructor parameter was null, class and line number information will be omitted. * information. If modelClass constructor parameter was null, class and line number
*/ * information will be omitted.
public final String getAttributedDescription() { */
if (clazz == null) public final String getAttributedDescription() {
return getDescription(); if (clazz == null)
return getDescription();
return String.format("%s:%d: %s", clazz.getSource(), lineNumber, getDescription()); return String.format("%s:%d: %s", clazz.getSource(), lineNumber, getDescription());
} }
/** /**
* Delegates directly to {@link #getAttributedDescription()}. * Delegates directly to {@link #getAttributedDescription()}.
*/ */
@Override @Override
public String toString() { public String toString() {
return getAttributedDescription(); return getAttributedDescription();
} }
} }

View File

@ -14,9 +14,10 @@ import org.springframework.util.ReflectionUtils;
import sun.security.x509.Extension; import sun.security.x509.Extension;
/** /**
* Misc utils * Misc utils
* *
* @author Chris Beams * @author Chris Beams
*/ */
// TODO: SJC-242 general - check cycles with s101 // TODO: SJC-242 general - check cycles with s101
@ -25,140 +26,142 @@ import sun.security.x509.Extension;
// TODO: SJC-242 rename, repackage, document // TODO: SJC-242 rename, repackage, document
public class Util { public class Util {
private static final Log log = LogFactory.getLog(Util.class); private static final Log log = LogFactory.getLog(Util.class);
private Util() { } private Util() {
}
/** /**
* Returns instance of type T by invoking its default or no-arg * Returns instance of type T by invoking its default or no-arg constructor.
* constructor. * <p>
* <p> * Any reflection-related issues are re-thrown as unchecked.
* Any reflection-related issues are re-thrown as unchecked. */
*/ public static <T> T getInstance(Class<? extends T> clazz) {
public static <T> T getInstance(Class<? extends T> clazz) { try {
try { Constructor<? extends T> noArgCtor = clazz.getDeclaredConstructor();
Constructor<? extends T> noArgCtor = clazz.getDeclaredConstructor(); ReflectionUtils.makeAccessible(noArgCtor);
ReflectionUtils.makeAccessible(noArgCtor); return noArgCtor.newInstance();
return noArgCtor.newInstance(); } catch (Exception ex) {
} catch (Exception ex) { ReflectionUtils.handleReflectionException(ex);
ReflectionUtils.handleReflectionException(ex); throw new IllegalStateException(format("Unexpected reflection exception - %s: %s", ex.getClass()
throw new IllegalStateException( .getName(), ex.getMessage()));
format("Unexpected reflection exception - %s: %s", }
ex.getClass().getName(), ex.getMessage())); }
}
}
/** /**
* Loads the specified class using the default class loader, gracefully handling any * Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown. This functionality is specifically * {@link ClassNotFoundException} that may be thrown. This functionality is specifically
* implemented to accomodate tooling (Spring IDE) concerns, where user-defined types will not be * implemented to accomodate tooling (Spring IDE) concerns, where user-defined types
* * will not be
* @param <T> type of class to be returned *
* @param fqClassName fully-qualified class name * @param <T> type of class to be returned
* * @param fqClassName fully-qualified class name
* @return newly loaded class instance, null if class could not be found *
* * @return newly loaded class instance, null if class could not be found
* @see #loadRequiredClass(String) *
* @see #loadToolingSafeClass(String) * @see #loadRequiredClass(String)
* @see ClassUtils#getDefaultClassLoader() * @see #loadToolingSafeClass(String)
*/ * @see ClassUtils#getDefaultClassLoader()
@SuppressWarnings("unchecked") */
public static <T> Class<? extends T> loadClass(String fqClassName) { @SuppressWarnings("unchecked")
try { public static <T> Class<? extends T> loadClass(String fqClassName) {
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName); try {
} catch (ClassNotFoundException ex) { return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
return null; } catch (ClassNotFoundException ex) {
} return null;
} }
}
/** /**
* Loads the specified class using the default class loader, rethrowing any * Loads the specified class using the default class loader, rethrowing any
* {@link ClassNotFoundException} as an unchecked exception. * {@link ClassNotFoundException} as an unchecked exception.
* *
* @param <T> type of class to be returned * @param <T> type of class to be returned
* @param fqClassName fully-qualified class name * @param fqClassName fully-qualified class name
* *
* @return newly loaded class instance * @return newly loaded class instance
* *
* @throws IllegalArgumentException if configClassName cannot be loaded. * @throws IllegalArgumentException if configClassName cannot be loaded.
* *
* @see #loadClass(String) * @see #loadClass(String)
* @see #loadToolingSafeClass(String) * @see #loadToolingSafeClass(String)
* @see ClassUtils#getDefaultClassLoader() * @see ClassUtils#getDefaultClassLoader()
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Class<? extends T> loadRequiredClass(String fqClassName) { public static <T> Class<? extends T> loadRequiredClass(String fqClassName) {
try { try {
return (Class<? extends T>)getDefaultClassLoader().loadClass(fqClassName); return (Class<? extends T>) getDefaultClassLoader().loadClass(fqClassName);
} catch (ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
throw new IllegalArgumentException( throw new IllegalArgumentException(format(
format("Class [%s] could not be loaded, check your CLASSPATH.", fqClassName), ex); "Class [%s] could not be loaded, check your CLASSPATH.", fqClassName), ex);
} }
} }
/** /**
* Loads the specified class using the default class loader, gracefully handling any * Loads the specified class using the default class loader, gracefully handling any
* {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging statement * {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging
* and return null. This functionality is specifically implemented to accomodate tooling * statement and return null. This functionality is specifically implemented to
* (Spring IDE) concerns, where user-defined types will not be available to the tooling. * accomodate tooling (Spring IDE) concerns, where user-defined types will not be
* <p> * available to the tooling.
* ASM class reading is used throughout JavaConfig, but there are certain cases where * <p>
* classloading cannot be avoided - specifically in cases where users define their own * ASM class reading is used throughout JavaConfig, but there are certain cases where
* {@link Extension} or {@link Factory} annotations. This method should therefore be used sparingly * classloading cannot be avoided - specifically in cases where users define their own
* but consistently where required. * {@link Extension} or {@link Factory} annotations. This method should therefore be
* <p> * used sparingly but consistently where required.
* Because {@link ClassNotFoundException} is compensated for by returning null, callers must * <p>
* take care to handle the null case appropriately. * Because {@link ClassNotFoundException} is compensated for by returning null, callers
* <p> * must take care to handle the null case appropriately.
* In cases where the WARN logging statement is not desired, use the {@link #loadClass(String)} * <p>
* method, which returns null but issues no logging statements. * In cases where the WARN logging statement is not desired, use the
* <p> * {@link #loadClass(String)} method, which returns null but issues no logging
* This method should only ever return null in the case of a user-defined type be processed at * statements.
* tooling time. Therefore, tooling may not be able to represent any custom annotation * <p>
* semantics, but JavaConfig itself will not have any problem loading and respecting them at * This method should only ever return null in the case of a user-defined type be
* actual runtime. * processed at tooling time. Therefore, tooling may not be able to represent any custom
* * annotation semantics, but JavaConfig itself will not have any problem loading and
* @param <T> type of class to be returned * respecting them at actual runtime.
* @param fqClassName fully-qualified class name *
* * @param <T> type of class to be returned
* @return newly loaded class, null if class could not be found. * @param fqClassName fully-qualified class name
* *
* @see #loadClass(String) * @return newly loaded class, null if class could not be found.
* @see #loadRequiredClass(String) *
* @see ClassUtils#getDefaultClassLoader() * @see #loadClass(String)
*/ * @see #loadRequiredClass(String)
@SuppressWarnings("unchecked") * @see ClassUtils#getDefaultClassLoader()
public static <T> Class<? extends T> loadToolingSafeClass(String fqClassName) { */
try { @SuppressWarnings("unchecked")
return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName); public static <T> Class<? extends T> loadToolingSafeClass(String fqClassName) {
} catch (ClassNotFoundException ex) { try {
log.warn(format("Unable to load class [%s], likely due to tooling-specific restrictions." return (Class<? extends T>) ClassUtils.getDefaultClassLoader().loadClass(fqClassName);
+ "Attempting to continue, but unexpected errors may occur", fqClassName), ex); } catch (ClassNotFoundException ex) {
return null; log.warn(format("Unable to load class [%s], likely due to tooling-specific restrictions."
} + "Attempting to continue, but unexpected errors may occur", fqClassName), ex);
} return null;
}
}
/** /**
* Uses the default ClassLoader to load <var>pathToClass</var>. Appends '.class' * Uses the default ClassLoader to load <var>pathToClass</var>. Appends '.class' to
* to pathToClass before attempting to load. * pathToClass before attempting to load.
* *
* @param pathToClass resource path to class, not including .class suffix. * @param pathToClass resource path to class, not including .class suffix. e.g.:
* e.g.: com/acme/MyClass * com/acme/MyClass
* *
* @return inputStream for <var>pathToClass</var> * @return inputStream for <var>pathToClass</var>
* *
* @throws RuntimeException if <var>pathToClass</var> does not exist * @throws RuntimeException if <var>pathToClass</var> does not exist
*/ */
public static InputStream getClassAsStream(String pathToClass) { public static InputStream getClassAsStream(String pathToClass) {
String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX; String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX;
InputStream is = ClassUtils.getDefaultClassLoader().getResourceAsStream(classFileName); InputStream is = ClassUtils.getDefaultClassLoader().getResourceAsStream(classFileName);
if (is == null) if (is == null)
throw new RuntimeException( throw new RuntimeException(new FileNotFoundException("Class file [" + classFileName
new FileNotFoundException("Class file [" + classFileName + "] not found")); + "] not found"));
return is; return is;
} }
} }

View File

@ -2,18 +2,19 @@ package org.springframework.config.java;
import java.util.List; import java.util.List;
/** /**
* Indicates a type is able to be validated for errors. * Indicates a type is able to be validated for errors.
* *
* @see Validator * @see Validator
* *
* @author Chris Beams * @author Chris Beams
*/ */
public interface Validatable { public interface Validatable {
/** /**
* Validates this object, adding any errors to the supplied list of <var>errors</var>. * Validates this object, adding any errors to the supplied list of <var>errors</var>.
*/ */
public void validate(List<UsageError> errors); public void validate(List<UsageError> errors);
} }

View File

@ -4,10 +4,10 @@ import java.util.List;
/** Marker interface */ /** Marker interface */
//TODO: SJC-242 document // TODO: SJC-242 document
//TODO: SJC-242 rename // TODO: SJC-242 rename
public interface Validator { public interface Validator {
boolean supports(Object object); boolean supports(Object object);
void validate(Object object, List<UsageError> errors); void validate(Object object, List<UsageError> errors);
} }

View File

@ -30,19 +30,19 @@ import org.springframework.util.Assert;
/** /**
* Base class for all {@link MethodInterceptor} implementations. * Base class for all {@link MethodInterceptor} implementations.
* *
* @author Chris Beams * @author Chris Beams
*/ */
public abstract class AbstractMethodInterceptor implements BeanFactoryAware, MethodInterceptor { public abstract class AbstractMethodInterceptor implements BeanFactoryAware, MethodInterceptor {
protected final Log log = LogFactory.getLog(this.getClass()); protected final Log log = LogFactory.getLog(this.getClass());
protected DefaultListableBeanFactory beanFactory; protected DefaultListableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory); Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
this.beanFactory = (DefaultListableBeanFactory) beanFactory; this.beanFactory = (DefaultListableBeanFactory) beanFactory;
} }
protected String getBeanName(Method method) { protected String getBeanName(Method method) {
return method.getName(); return method.getName();
} }
} }

View File

@ -37,181 +37,184 @@ import org.springframework.config.java.Validator;
/** /**
* Annotation to be applied to methods that create beans in a Spring context. The name of the bean * Annotation to be applied to methods that create beans in a Spring context. The name of
* is the method name. (It is also possible to specify aliases using the aliases array on this * the bean is the method name. (It is also possible to specify aliases using the aliases
* annotation.) * array on this annotation.)
* *
* <p>Contains information similar to that held in Spring's internal BeanDefinition metadata.</p> * <p>
* Contains information similar to that held in Spring's internal BeanDefinition metadata.
* </p>
*
* <p>
* Bean creation methods must be non-private (default, public or protected). Bean creation
* methods may throw any exception, which will be caught and handled by the Spring container
* on processing of the configuration class.<br>
* Bean creation methods must return an object type. The decision to return a class or an
* interface will be significant in the event of proxying. Bean methods that return
* interfaces will be proxied using dynamic proxies; those that return a class will require
* CGLIB or other subclass-based proxying. It is recommended to return an interface where
* possible, as this is also consistent with best practice around loose coupling.
* </p>
*
* <p>
* Bean creation methods may reference other bean creation methods by calling them directly,
* as follows. This ensures that references between beans are strongly typed:
* </p>
* *
* <p>Bean creation methods must be non-private (default, public or protected). Bean creation
* methods may throw any exception, which will be caught and handled by the Spring container on
* processing of the configuration class.<br>
* Bean creation methods must return an object type. The decision to return a class or an interface
* will be significant in the event of proxying. Bean methods that return interfaces will be proxied
* using dynamic proxies; those that return a class will require CGLIB or other subclass-based
* proxying. It is recommended to return an interface where possible, as this is also consistent
* with best practice around loose coupling.</p>
*
* <p>Bean creation methods may reference other bean creation methods by calling them directly, as
* follows. This ensures that references between beans are strongly typed:</p>
*
* @see Configuration * @see Configuration
* @see BeanNamingStrategy * @see BeanNamingStrategy
* *
* @author Rod Johnson * @author Rod Johnson
* @author Costin Leau * @author Costin Leau
* @author Chris Beams * @author Chris Beams
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Inherited @Inherited
@Documented @Documented
@Factory(registrarType=BeanRegistrar.class, @Factory(registrarType = BeanRegistrar.class, callbackType = BeanMethodInterceptor.class, validatorTypes = {
callbackType=BeanMethodInterceptor.class, BeanValidator.class, IllegalBeanOverrideValidator.class })
validatorTypes={BeanValidator.class, IllegalBeanOverrideValidator.class})
public @interface Bean { public @interface Bean {
/** /**
* Role this bean plays in the overall application configuration. * Role this bean plays in the overall application configuration.
* *
* @see BeanDefinition#ROLE_APPLICATION * @see BeanDefinition#ROLE_APPLICATION
* @see BeanDefinition#ROLE_INFRASTRUCTURE * @see BeanDefinition#ROLE_INFRASTRUCTURE
* @see BeanDefinition#ROLE_SUPPORT * @see BeanDefinition#ROLE_SUPPORT
* *
* @see AbstractBeanDefinition the 'role' field is assigned by default to ROLE_APPLICATION * @see AbstractBeanDefinition the 'role' field is assigned by default to
*/ * ROLE_APPLICATION
int role() default BeanDefinition.ROLE_APPLICATION; */
int role() default BeanDefinition.ROLE_APPLICATION;
/**
* Bean aliases.
*/
String[] aliases() default { };
/** /**
* Scope: whether the bean is a singleton, prototype or custom scope. * Bean aliases.
* Default is singleton. */
*/ String[] aliases() default {};
String scope() default Scopes.SINGLETON;
/** /**
* Bean autowire strategy. * Scope: whether the bean is a singleton, prototype or custom scope. Default is
*/ * singleton.
Autowire autowire() default Autowire.INHERITED; */
String scope() default Scopes.SINGLETON;
// /** /**
// * Bean lazy strategy. * Bean autowire strategy.
// */ */
// Lazy lazy() default Lazy.UNSPECIFIED; Autowire autowire() default Autowire.INHERITED;
//
// /**
// * A bean may be marked as primary, useful for disambiguation when looking
// * up beans by type.
// *
// * @see org.springframework.config.java.context.JavaConfigApplicationContext#getBean(Class);
// */
// Primary primary() default Primary.UNSPECIFIED;
/** // /**
* Bean init method name. Normally this is not needed, as the initialization // * Bean lazy strategy.
* (with parameterization) can be done directly through java code. // */
*/ // Lazy lazy() default Lazy.UNSPECIFIED;
String initMethodName() default ""; //
// /**
// * A bean may be marked as primary, useful for disambiguation when looking
// * up beans by type.
// *
// * @see
// org.springframework.config.java.context.JavaConfigApplicationContext#getBean(Class);
// */
// Primary primary() default Primary.UNSPECIFIED;
/** /**
* Bean destroy method name. * Bean init method name. Normally this is not needed, as the initialization (with
*/ * parameterization) can be done directly through java code.
String destroyMethodName() default ""; */
String initMethodName() default "";
// /** /**
// * Bean dependency check strategy. * Bean destroy method name.
// */ */
// DependencyCheck dependencyCheck() default DependencyCheck.UNSPECIFIED; String destroyMethodName() default "";
/** // /**
* Beans on which the current bean depends on. // * Bean dependency check strategy.
*/ // */
String[] dependsOn() default { }; // DependencyCheck dependencyCheck() default DependencyCheck.UNSPECIFIED;
// /** /**
// * Metadata for the current bean. * Beans on which the current bean depends on.
// */ */
// Meta[] meta() default { }; String[] dependsOn() default {};
/** // /**
* Allow the bean to be overridden in another JavaConfig, XML or other // * Metadata for the current bean.
* non-Java configuration. This is consistent with // */
* DefaultListableBeanFactory's allowBeanDefinitionOverriding property, // Meta[] meta() default { };
* which defaults to true.
* /**
* @return whether overriding of this bean is allowed * Allow the bean to be overridden in another JavaConfig, XML or other non-Java
*/ * configuration. This is consistent with DefaultListableBeanFactory's
boolean allowOverriding() default true; * allowBeanDefinitionOverriding property, which defaults to true.
*
* @return whether overriding of this bean is allowed
*/
boolean allowOverriding() default true;
} }
/** /**
* Detects any user errors when declaring {@link Bean}-annotated methods. * Detects any user errors when declaring {@link Bean}-annotated methods.
* *
* @author Chris Beams * @author Chris Beams
*/ */
class BeanValidator implements Validator { class BeanValidator implements Validator {
public boolean supports(Object object) { public boolean supports(Object object) {
return object instanceof ModelMethod; return object instanceof ModelMethod;
} }
public void validate(Object object, List<UsageError> errors) {
ModelMethod method = (ModelMethod) object;
// TODO: re-enable for @ScopedProxy support
// if (method.getAnnotation(ScopedProxy.class) == null)
// return;
//
// Bean bean = method.getRequiredAnnotation(Bean.class);
//
// if (bean.scope().equals(DefaultScopes.SINGLETON)
// || bean.scope().equals(DefaultScopes.PROTOTYPE))
// errors.add(new InvalidScopedProxyDeclarationError(method));
}
public void validate(Object object, List<UsageError> errors) {
ModelMethod method = (ModelMethod) object;
// TODO: re-enable for @ScopedProxy support
// if (method.getAnnotation(ScopedProxy.class) == null)
// return;
//
// Bean bean = method.getRequiredAnnotation(Bean.class);
//
// if (bean.scope().equals(DefaultScopes.SINGLETON)
// || bean.scope().equals(DefaultScopes.PROTOTYPE))
// errors.add(new InvalidScopedProxyDeclarationError(method));
}
} }
/** /**
* Detects any illegally-overridden {@link Bean} definitions within a particular * Detects any illegally-overridden {@link Bean} definitions within a particular
* {@link ConfigurationModel} * {@link ConfigurationModel}
* *
* @see Bean#allowOverriding() * @see Bean#allowOverriding()
* *
* @author Chris Beams * @author Chris Beams
*/ */
class IllegalBeanOverrideValidator implements Validator { class IllegalBeanOverrideValidator implements Validator {
public boolean supports(Object object) {
return object instanceof ConfigurationModel;
}
public void validate(Object object, List<UsageError> errors) { public boolean supports(Object object) {
ConfigurationModel model = (ConfigurationModel) object; return object instanceof ConfigurationModel;
}
ConfigurationClass[] allClasses = model.getAllConfigurationClasses();
for (int i = 0; i < allClasses.length; i++) {
for (ModelMethod 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++) public void validate(Object object, List<UsageError> errors) {
if (allClasses[j].hasMethod(method.getName())) ConfigurationModel model = (ConfigurationModel) object;
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method));
} ConfigurationClass[] allClasses = model.getAllConfigurationClasses();
}
} for (int i = 0; i < allClasses.length; i++) {
for (ModelMethod 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()))
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method));
}
}
}
} }

View File

@ -23,6 +23,7 @@ import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
/** /**
* Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
* handling of bean semantics such as scoping and AOP proxying. * handling of bean semantics such as scoping and AOP proxying.
@ -34,51 +35,54 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
*/ */
class BeanMethodInterceptor extends AbstractMethodInterceptor { class BeanMethodInterceptor extends AbstractMethodInterceptor {
/** /**
* Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the existence * Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the
* of this bean object. * existence of this bean object.
*/ */
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String beanName = getBeanName(method); String beanName = getBeanName(method);
// TODO: re-enable for @ScopedProxy support
// boolean isScopedProxy = (AnnotationUtils.findAnnotation(method, ScopedProxy.class) != null);
//
// String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
// beanName = scopedBeanName;
if (factoryContainsBean(beanName)) { // TODO: re-enable for @ScopedProxy support
// we have an already existing cached instance of this bean -> retrieve it // boolean isScopedProxy = (AnnotationUtils.findAnnotation(method,
Object cachedBean = beanFactory.getBean(beanName); // ScopedProxy.class) != null);
if (log.isInfoEnabled()) //
log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s", // String scopedBeanName =
cachedBean, method.getDeclaringClass().getSimpleName(), beanName)); // ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
// beanName = scopedBeanName;
return cachedBean; if (factoryContainsBean(beanName)) {
} // we have an already existing cached instance of this bean -> retrieve it
Object cachedBean = beanFactory.getBean(beanName);
if (log.isInfoEnabled())
log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s", cachedBean,
method.getDeclaringClass().getSimpleName(), beanName));
return proxy.invokeSuper(obj, args); return cachedBean;
} }
/** return proxy.invokeSuper(obj, args);
* 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 * Check the beanFactory to see whether the bean named <var>beanName</var> already
* create it for the first time by executing the logic in the corresponding Bean method. * exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
* <p> * we're in the middle of servicing the initial request for this bean. From JavaConfig's
* Said another way, this check repurposes {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} * perspective, this means that the bean does not actually yet exist, and that it is now
* to determine whether the container is calling this method or the user is calling this method. * our job to create it for the first time by executing the logic in the corresponding
* * Bean method.
* @param beanName name of bean to check for * <p>
* * Said another way, this check repurposes
* @return true if <var>beanName</var> already exists in beanFactory * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
*/ * the container is calling this method or the user is calling this method.
private boolean factoryContainsBean(String beanName) { *
return beanFactory.containsBean(beanName) * @param beanName name of bean to check for
&& !beanFactory.isCurrentlyInCreation(beanName); *
} * @return true if <var>beanName</var> already exists in beanFactory
*/
private boolean factoryContainsBean(String beanName) {
return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
}
} }

View File

@ -26,178 +26,183 @@ import org.springframework.util.Assert;
// TODO: SJC-242 document BeanHandler // TODO: SJC-242 document BeanHandler
// TODO: SJC-242 make package-private // TODO: SJC-242 make package-private
class BeanRegistrar implements BeanDefinitionRegistrar { class BeanRegistrar implements BeanDefinitionRegistrar {
private static final Log logger = LogFactory.getLog(BeanRegistrar.class);
/**
* Ensures that <var>member</var> is a method and is annotated (directly or indirectly)
* with {@link Bean @Bean}.
*/
public boolean accepts(Method method) {
return AnnotationUtils.findAnnotation(method, Bean.class) != null;
}
// TODO: SJC-242 method too long
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDef = new JavaConfigBeanDefinition();
ConfigurationClass configClass = method.getDeclaringClass();
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setFactoryMethodName(method.getName());
Bean bean = method.getRequiredAnnotation(Bean.class); private static final Log logger = LogFactory.getLog(BeanRegistrar.class);
Configuration defaults = configClass.getMetadata();
// consider scoping /**
beanDef.setScope(bean.scope()); * Ensures that <var>member</var> is a method and is annotated (directly or indirectly)
* with {@link Bean @Bean}.
*/
public boolean accepts(Method method) {
return AnnotationUtils.findAnnotation(method, Bean.class) != null;
}
// consider autowiring // TODO: SJC-242 method too long
if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire")) public void register(ModelMethod method, BeanDefinitionRegistry registry) {
beanDef.setAutowireMode(bean.autowire().value()); RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition();
else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class, "defaultAutowire"))
beanDef.setAutowireMode(defaults.defaultAutowire().value());
String beanName = method.getName(); ConfigurationClass configClass = method.getDeclaringClass();
// has this already been overriden (i.e.: via XML)? beanDef.setFactoryBeanName(configClass.getBeanName());
if (containsBeanDefinitionIncludingAncestry(beanName, registry)) { beanDef.setFactoryMethodName(method.getName());
BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry);
// is the existing bean definition one that was created by JavaConfig? Bean bean = method.getRequiredAnnotation(Bean.class);
if (!(existingBeanDef instanceof JavaConfigBeanDefinition)) {
// no -> then it's an external override, probably XML
// ensure that overriding is ok Configuration defaults = configClass.getMetadata();
if (bean.allowOverriding() == false) {
UsageError error = configClass.new IllegalBeanOverrideError(null, method);
throw new MalformedConfigurationException(error);
}
// overriding is legal, return immediately // consider scoping
logger.info(format("Skipping loading bean definition for %s: a definition for bean '%s' already exists. " beanDef.setScope(bean.scope());
+ "This is likely due to an override in XML.",
method, beanName));
return;
}
}
// propagate this bean's 'role' attribute // consider autowiring
beanDef.setRole(bean.role()); if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire"))
beanDef.setAutowireMode(bean.autowire().value());
else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class,
"defaultAutowire"))
beanDef.setAutowireMode(defaults.defaultAutowire().value());
// consider aliases String beanName = method.getName();
for (String alias : bean.aliases())
registry.registerAlias(beanName, alias);
// TODO: re-enable for Lazy support // has this already been overriden (i.e.: via XML)?
// // is this bean marked as primary for disambiguation? if (containsBeanDefinitionIncludingAncestry(beanName, registry)) {
// if (bean.primary() == Primary.TRUE) BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry);
// beanDef.setPrimary(true);
//
// // is this bean lazily instantiated?
// if ((bean.lazy() == Lazy.TRUE)
// || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE)))
// beanDef.setLazyInit(true);
// does this bean have a custom init-method specified? // is the existing bean definition one that was created by JavaConfig?
String initMethodName = bean.initMethodName(); if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
if (hasText(initMethodName)) // no -> then it's an external override, probably XML
beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified? // ensure that overriding is ok
String destroyMethodName = bean.destroyMethodName(); if (bean.allowOverriding() == false) {
if (hasText(destroyMethodName)) UsageError error = configClass.new IllegalBeanOverrideError(null, method);
beanDef.setDestroyMethodName(destroyMethodName); throw new MalformedConfigurationException(error);
}
// TODO: re-enable for @ScopedProxy support // overriding is legal, return immediately
// is this method annotated with @ScopedProxy? logger.info(format(
// ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class); "Skipping loading bean definition for %s: a definition for bean '%s' already exists. "
// if (scopedProxy != null) { + "This is likely due to an override in XML.", method, beanName));
// RootBeanDefinition targetDef = beanDef; return;
// }
// // Create a scoped proxy definition for the original bean name, }
// // "hiding" the target bean in an internal target definition.
// String targetBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
// scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
//
// if (scopedProxy.proxyTargetClass())
// targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// // don't need to set it explicitly here.
// else
// scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
//
// // The target bean should be ignored in favor of the scoped proxy.
// targetDef.setAutowireCandidate(false);
//
// // Register the target bean as separate bean in the factory
// registry.registerBeanDefinition(targetBeanName, targetDef);
//
// // replace the original bean definition with the target one
// beanDef = scopedProxyDefinition;
// }
// TODO: re-enable for @Meta support // propagate this bean's 'role' attribute
// does this bean method have any @Meta annotations? beanDef.setRole(bean.role());
// for (Meta meta : bean.meta())
// beanDef.addMetadataAttribute(new BeanMetadataAttribute(meta.key(), meta.value()));
if(bean.dependsOn().length > 0) // consider aliases
beanDef.setDependsOn(bean.dependsOn()); for (String alias : bean.aliases())
registry.registerAlias(beanName, alias);
logger.info(format("Registering bean definition for @Bean method %s.%s()", // TODO: re-enable for Lazy support
configClass.getName(), beanName)); // // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE)
registry.registerBeanDefinition(beanName, beanDef); // beanDef.setPrimary(true);
//
} // // is this bean lazily instantiated?
// if ((bean.lazy() == Lazy.TRUE)
private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { // || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE)))
try { // beanDef.setLazyInit(true);
getBeanDefinitionIncludingAncestry(beanName, registry);
return true;
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { // does this bean have a custom init-method specified?
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry); String initMethodName = bean.initMethodName();
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory)registry; if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName);
do {
if (clbf.containsBeanDefinition(beanName))
return registry.getBeanDefinition(beanName);
BeanFactory parent = clbf.getParentBeanFactory(); // does this bean have a custom destroy-method specified?
if (parent == null) { String destroyMethodName = bean.destroyMethodName();
clbf = null; if (hasText(destroyMethodName))
} else if (parent instanceof ConfigurableListableBeanFactory) { beanDef.setDestroyMethodName(destroyMethodName);
clbf = (ConfigurableListableBeanFactory) parent;
// TODO: re-enable
// } else if (parent instanceof AbstractApplicationContext) {
// clbf = ((AbstractApplicationContext) parent).getBeanFactory();
} else {
throw new IllegalStateException("unknown parent type: " + parent.getClass().getName());
}
} while (clbf != null);
throw new NoSuchBeanDefinitionException( // TODO: re-enable for @ScopedProxy support
format("No bean definition matching name '%s' " // is this method annotated with @ScopedProxy?
+ "could be found in %s or its ancestry", beanName, registry)); // ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class);
} // if (scopedProxy != null) {
// RootBeanDefinition targetDef = beanDef;
//
// // Create a scoped proxy definition for the original bean name,
// // "hiding" the target bean in an internal target definition.
// String targetBeanName =
// ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName);
// RootBeanDefinition scopedProxyDefinition = new
// RootBeanDefinition(ScopedProxyFactoryBean.class);
// scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName",
// targetBeanName);
//
// if (scopedProxy.proxyTargetClass())
// targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE,
// Boolean.TRUE);
// // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// // don't need to set it explicitly here.
// else
// scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass",
// Boolean.FALSE);
//
// // The target bean should be ignored in favor of the scoped proxy.
// targetDef.setAutowireCandidate(false);
//
// // Register the target bean as separate bean in the factory
// registry.registerBeanDefinition(targetBeanName, targetDef);
//
// // replace the original bean definition with the target one
// beanDef = scopedProxyDefinition;
// }
// TODO: re-enable for @Meta support
// does this bean method have any @Meta annotations?
// for (Meta meta : bean.meta())
// beanDef.addMetadataAttribute(new BeanMetadataAttribute(meta.key(),
// meta.value()));
if (bean.dependsOn().length > 0)
beanDef.setDependsOn(bean.dependsOn());
logger.info(format("Registering bean definition for @Bean method %s.%s()", configClass.getName(),
beanName));
registry.registerBeanDefinition(beanName, beanDef);
}
private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
try {
getBeanDefinitionIncludingAncestry(beanName, registry);
return true;
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry);
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry;
do {
if (clbf.containsBeanDefinition(beanName))
return registry.getBeanDefinition(beanName);
BeanFactory parent = clbf.getParentBeanFactory();
if (parent == null) {
clbf = null;
} else if (parent instanceof ConfigurableListableBeanFactory) {
clbf = (ConfigurableListableBeanFactory) parent;
// TODO: re-enable
// } else if (parent instanceof AbstractApplicationContext) {
// clbf = ((AbstractApplicationContext) parent).getBeanFactory();
} else {
throw new IllegalStateException("unknown parent type: " + parent.getClass().getName());
}
} while (clbf != null);
throw new NoSuchBeanDefinitionException(format("No bean definition matching name '%s' "
+ "could be found in %s or its ancestry", beanName, registry));
}
} }
/** /**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created
* created by JavaConfig as opposed to any other configuration source. Used in bean * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases
* overriding cases where it's necessary to determine whether the bean definition was created * where it's necessary to determine whether the bean definition was created externally
* externally (e.g. via XML). * (e.g. via XML).
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
// TODO: SJC-242 what to do about JavaConfigBeanDefinition? class ConfigurationClassBeanDefinition extends RootBeanDefinition {
class JavaConfigBeanDefinition extends RootBeanDefinition {
} }

View File

@ -24,93 +24,92 @@ import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
/** /**
* Transforms a class by adding bytecode for a class-level annotation. * Transforms a class by adding bytecode for a class-level annotation. Checks to ensure that
* Checks to ensure that the desired annotation is not already present * the desired annotation is not already present before adding. Used by
* before adding. Used by {@link ConfigurationEnhancer} to dynamically add * {@link ConfigurationEnhancer} to dynamically add an {@link org.aspectj.lang.Aspect}
* an {@link org.aspectj.lang.Aspect} annotation to an enhanced Configuration * annotation to an enhanced Configuration subclass.
* subclass.
* <p/> * <p/>
* This class was originally adapted from examples the ASM 3.0 documentation. * This class was originally adapted from examples the ASM 3.0 documentation.
* *
* @author Chris Beams * @author Chris Beams
*/ */
class AddAnnotationAdapter extends ClassAdapter { class AddAnnotationAdapter extends ClassAdapter {
private String annotationDesc; private String annotationDesc;
private boolean isAnnotationPresent; private boolean isAnnotationPresent;
/** /**
* Creates a new AddAnnotationAdapter instance. * Creates a new AddAnnotationAdapter instance.
* *
* @param cv the ClassVisitor delegate * @param cv the ClassVisitor delegate
* @param annotationDesc name of the annotation to be added * @param annotationDesc name of the annotation to be added (in type descriptor format)
* (in type descriptor format) */
*/ public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) {
public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) { super(cv);
super(cv); this.annotationDesc = annotationDesc;
this.annotationDesc = annotationDesc; }
}
/** /**
* Ensures that the version of the resulting class is Java 5 or better. * Ensures that the version of the resulting class is Java 5 or better.
*/ */
@Override @Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { public void visit(int version, int access, String name, String signature, String superName,
int v = (version & 0xFF) < Constants.V1_5 ? Constants.V1_5 : version; String[] interfaces) {
cv.visit(v, access, name, signature, superName, interfaces); int v = (version & 0xFF) < Constants.V1_5 ? Constants.V1_5 : version;
} cv.visit(v, access, name, signature, superName, interfaces);
}
/** /**
* Checks to ensure that the desired annotation is not already present. * Checks to ensure that the desired annotation is not already present.
*/ */
@Override @Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) { public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (visible && desc.equals(annotationDesc)) { if (visible && desc.equals(annotationDesc)) {
isAnnotationPresent = true; isAnnotationPresent = true;
} }
return cv.visitAnnotation(desc, visible); return cv.visitAnnotation(desc, visible);
} }
@Override @Override
public void visitInnerClass(String name, String outerName, String innerName, int access) { public void visitInnerClass(String name, String outerName, String innerName, int access) {
addAnnotation(); addAnnotation();
cv.visitInnerClass(name, outerName, innerName, access); cv.visitInnerClass(name, outerName, innerName, access);
} }
@Override @Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
addAnnotation(); addAnnotation();
return cv.visitField(access, name, desc, signature, value); return cv.visitField(access, name, desc, signature, value);
} }
@Override @Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { public MethodVisitor visitMethod(int access, String name, String desc, String signature,
addAnnotation(); String[] exceptions) {
return cv.visitMethod(access, name, desc, signature, exceptions); addAnnotation();
} return cv.visitMethod(access, name, desc, signature, exceptions);
}
/** /**
* Kicks off the process of actually adding the desired annotation. * Kicks off the process of actually adding the desired annotation.
* *
* @see #addAnnotation() * @see #addAnnotation()
*/ */
@Override @Override
public void visitEnd() { public void visitEnd() {
addAnnotation(); addAnnotation();
cv.visitEnd(); cv.visitEnd();
} }
/** /**
* Actually adds the desired annotation. * Actually adds the desired annotation.
*/ */
private void addAnnotation() { private void addAnnotation() {
if (!isAnnotationPresent) { if (!isAnnotationPresent) {
AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true); AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true);
if (av != null) { if (av != null) {
av.visitEnd(); av.visitEnd();
} }
isAnnotationPresent = true; isAnnotationPresent = true;
} }
} }
} }

View File

@ -46,7 +46,6 @@ import org.springframework.config.java.ConfigurationModel;
import org.springframework.config.java.ModelMethod; import org.springframework.config.java.ModelMethod;
/** /**
* Enhances {@link Configuration} classes by generating a CGLIB subclass capable of * Enhances {@link Configuration} classes by generating a CGLIB subclass capable of
* interacting with the Spring container to respect bean semantics. * interacting with the Spring container to respect bean semantics.
@ -57,176 +56,173 @@ import org.springframework.config.java.ModelMethod;
*/ */
public class ConfigurationEnhancer { public class ConfigurationEnhancer {
private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class); private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class);
private final ArrayList<Class<? extends Callback>> callbackTypes = private final ArrayList<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
new ArrayList<Class<? extends Callback>>();
private final LinkedHashSet<BeanDefinitionRegistrar> registrars =
new LinkedHashSet<BeanDefinitionRegistrar>();
private final ArrayList<Callback> callbackInstances =
new ArrayList<Callback>();
private final CallbackFilter callbackFilter =
new CallbackFilter() {
public int accept(Method candidateMethod) {
Iterator<BeanDefinitionRegistrar> iter = registrars.iterator();
for(int i=0; iter.hasNext(); i++)
if(iter.next().accepts(candidateMethod))
return i;
throw new IllegalStateException(format("No registrar is capable of " +
"handling method [%s]. Perhaps you forgot to add a catch-all registrar?",
candidateMethod.getName()));
}
};
/** private final LinkedHashSet<BeanDefinitionRegistrar> registrars = new LinkedHashSet<BeanDefinitionRegistrar>();
* Creates a new {@link ConfigurationEnhancer} instance.
*/ private final ArrayList<Callback> callbackInstances = new ArrayList<Callback>();
public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
notNull(beanFactory, "beanFactory must be non-null"); private final CallbackFilter callbackFilter = new CallbackFilter() {
notNull(model, "model must be non-null"); public int accept(Method candidateMethod) {
Iterator<BeanDefinitionRegistrar> iter = registrars.iterator();
populateRegistrarsAndCallbacks(beanFactory, model); for (int i = 0; iter.hasNext(); i++)
} if (iter.next().accepts(candidateMethod))
return i;
throw new IllegalStateException(format("No registrar is capable of "
+ "handling method [%s]. Perhaps you forgot to add a catch-all registrar?",
candidateMethod.getName()));
}
};
/** /**
* Reads the contents of {@code model} in order to populate {@link #registrars}, * Creates a new {@link ConfigurationEnhancer} instance.
* {@link #callbackInstances} and {@link #callbackTypes} appropriately. */
* public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
* @see #callbackFilter notNull(beanFactory, "beanFactory must be non-null");
*/ notNull(model, "model must be non-null");
private void populateRegistrarsAndCallbacks(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
for (ModelMethod method : configClass.getMethods()) {
registrars.add(method.getRegistrar());
Callback callback = method.getCallback();
if(callback instanceof BeanFactoryAware)
((BeanFactoryAware)callback).setBeanFactory(beanFactory);
callbackInstances.add(callback);
}
}
// register a 'catch-all' registrar
registrars.add(new BeanDefinitionRegistrar() {
public boolean accepts(Method method) { populateRegistrarsAndCallbacks(beanFactory, model);
return true; }
}
public void register(ModelMethod method, BeanDefinitionRegistry registry) {
// no-op
}
});
callbackInstances.add(NoOp.INSTANCE);
for(Callback callback : callbackInstances)
callbackTypes.add(callback.getClass());
}
/** /**
* Loads the specified class and generates a CGLIB subclass of it equipped with container-aware * Reads the contents of {@code model} in order to populate {@link #registrars},
* callbacks capable of respecting scoping and other bean semantics. * {@link #callbackInstances} and {@link #callbackTypes} appropriately.
* *
* @return fully-qualified name of the enhanced subclass * @see #callbackFilter
*/ */
public String enhance(String configClassName) { private void populateRegistrarsAndCallbacks(DefaultListableBeanFactory beanFactory,
if (log.isInfoEnabled()) ConfigurationModel model) {
log.info("Enhancing " + configClassName);
Class<?> superclass = loadRequiredClass(configClassName); for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
for (ModelMethod method : configClass.getMethods()) {
registrars.add(method.getRegistrar());
Class<?> subclass = createClass(newEnhancer(superclass), superclass); Callback callback = method.getCallback();
subclass = nestOneClassDeeperIfAspect(superclass, subclass);
if (log.isInfoEnabled()) if (callback instanceof BeanFactoryAware)
log.info(format("Successfully enhanced %s; enhanced class name is: %s", ((BeanFactoryAware) callback).setBeanFactory(beanFactory);
configClassName, subclass.getName()));
return subclass.getName(); callbackInstances.add(callback);
} }
}
/** // register a 'catch-all' registrar
* Creates a new CGLIB {@link Enhancer} instance. registrars.add(new BeanDefinitionRegistrar() {
*/
private Enhancer newEnhancer(Class<?> superclass) { public boolean accepts(Method method) {
Enhancer enhancer = new Enhancer(); return true;
}
// because callbackFilter and callbackTypes are dynamically populated
// there's no opportunity for caching. This does not appear to be causing public void register(ModelMethod method, BeanDefinitionRegistry registry) {
// any performance problem. // no-op
enhancer.setUseCache(false); }
});
enhancer.setSuperclass(superclass); callbackInstances.add(NoOp.INSTANCE);
enhancer.setUseFactory(false);
enhancer.setCallbackFilter(callbackFilter); for (Callback callback : callbackInstances)
enhancer.setCallbackTypes(callbackTypes.toArray(new Class<?>[]{})); callbackTypes.add(callback.getClass());
}
return enhancer;
}
/**
* Loads the specified class and generates a CGLIB subclass of it equipped with
* container-aware callbacks capable of respecting scoping and other bean semantics.
*
* @return fully-qualified name of the enhanced subclass
*/
public String enhance(String configClassName) {
if (log.isInfoEnabled())
log.info("Enhancing " + configClassName);
Class<?> superclass = loadRequiredClass(configClassName);
Class<?> subclass = createClass(newEnhancer(superclass), superclass);
subclass = nestOneClassDeeperIfAspect(superclass, subclass);
if (log.isInfoEnabled())
log.info(format("Successfully enhanced %s; enhanced class name is: %s", configClassName, subclass
.getName()));
return subclass.getName();
}
/**
* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> superclass) {
Enhancer enhancer = new Enhancer();
// 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<?>[] {}));
return enhancer;
}
/**
* Uses enhancer to generate a subclass of superclass, ensuring that
* {@link #callbackInstances} are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer, Class<?> superclass) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
return subclass;
}
/**
* Works around a constraint imposed by the AspectJ 5 annotation-style programming
* model. See comments inline for detail.
*
* @return original subclass instance unless superclass is annnotated with @Aspect, in
* which case a subclass of the subclass is returned
*/
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false;
// check for @Aspect by name rather than by class literal to avoid
// requiring AspectJ as a runtime dependency.
for (Annotation anno : superclass.getAnnotations())
if (anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect"))
superclassIsAnAspect = true;
if (!superclassIsAnAspect)
return origSubclass;
// the superclass is annotated with AspectJ's @Aspect.
// this means that we must create a subclass of the subclass
// in order to avoid some guard logic in Spring core that disallows
// extending a concrete aspect class.
Enhancer enhancer = newEnhancer(origSubclass);
enhancer.setStrategy(new DefaultGeneratorStrategy() {
@Override
protected byte[] transform(byte[] b) throws Exception {
ClassWriter writer = new ClassWriter(false);
ClassAdapter adapter = new AddAnnotationAdapter(writer,
"Lorg/aspectj/lang/annotation/Aspect;");
ClassReader reader = new ClassReader(b);
reader.accept(adapter, false);
return writer.toByteArray();
}
});
// create a subclass of the original subclass
Class<?> newSubclass = createClass(enhancer, origSubclass);
return newSubclass;
}
/**
* Uses enhancer to generate a subclass of superclass, ensuring that
* {@link #callbackInstances} are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer, Class<?> superclass) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
return subclass;
}
/**
* Works around a constraint imposed by the AspectJ 5 annotation-style programming model. See
* comments inline for detail.
*
* @return original subclass instance unless superclass is annnotated with @Aspect, in which
* case a subclass of the subclass is returned
*/
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false;
// check for @Aspect by name rather than by class literal to avoid
// requiring AspectJ as a runtime dependency.
for(Annotation anno : superclass.getAnnotations())
if(anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect"))
superclassIsAnAspect = true;
if(!superclassIsAnAspect)
return origSubclass;
// the superclass is annotated with AspectJ's @Aspect.
// this means that we must create a subclass of the subclass
// in order to avoid some guard logic in Spring core that disallows
// extending a concrete aspect class.
Enhancer enhancer = newEnhancer(origSubclass);
enhancer.setStrategy(new DefaultGeneratorStrategy() {
@Override
protected byte[] transform(byte[] b) throws Exception {
ClassWriter writer = new ClassWriter(false);
ClassAdapter adapter =
new AddAnnotationAdapter(writer, "Lorg/aspectj/lang/annotation/Aspect;");
ClassReader reader = new ClassReader(b);
reader.accept(adapter, false);
return writer.toByteArray();
}
});
// create a subclass of the original subclass
Class<?> newSubclass = createClass(enhancer, origSubclass);
return newSubclass;
}
} }

View File

@ -19,48 +19,47 @@ import org.objectweb.asm.AnnotationVisitor;
/** /**
* An empty AnnotationVisitor that delegates to another AnnotationVisitor. * An empty AnnotationVisitor that delegates to another AnnotationVisitor. This class can be
* This class can be used as a super class to quickly implement * used as a super class to quickly implement useful annotation adapter classes, just by
* useful annotation adapter classes, just by overriding the necessary * overriding the necessary methods. Note that for some reason, ASM doesn't provide this
* methods. Note that for some reason, ASM doesn't provide this class * class (it does provide MethodAdapter and ClassAdapter), thus we're following the general
* (it does provide MethodAdapter and ClassAdapter), thus we're following * pattern and adding our own here.
* the general pattern and adding our own here. *
*
* @author Chris Beams * @author Chris Beams
*/ */
class AnnotationAdapter implements AnnotationVisitor { class AnnotationAdapter implements AnnotationVisitor {
private AnnotationVisitor delegate; private AnnotationVisitor delegate;
/** /**
* Creates a new AnnotationAdapter instance that will delegate * Creates a new AnnotationAdapter instance that will delegate all its calls to
* all its calls to <var>delegate</var>. * <var>delegate</var>.
* *
* @param delegate In most cases, the delegate will simply be * @param delegate In most cases, the delegate will simply be
* {@link AsmUtils#EMPTY_VISITOR} * {@link AsmUtils#EMPTY_VISITOR}
*/ */
public AnnotationAdapter(AnnotationVisitor delegate) { public AnnotationAdapter(AnnotationVisitor delegate) {
this.delegate = delegate; this.delegate = delegate;
} }
public void visit(String arg0, Object arg1) { public void visit(String arg0, Object arg1) {
delegate.visit(arg0, arg1); delegate.visit(arg0, arg1);
} }
public AnnotationVisitor visitAnnotation(String arg0, String arg1) { public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
return delegate.visitAnnotation(arg0, arg1); return delegate.visitAnnotation(arg0, arg1);
} }
public AnnotationVisitor visitArray(String arg0) { public AnnotationVisitor visitArray(String arg0) {
return delegate.visitArray(arg0); return delegate.visitArray(arg0);
} }
public void visitEnum(String arg0, String arg1, String arg2) { public void visitEnum(String arg0, String arg1, String arg2) {
delegate.visitEnum(arg0, arg1, arg2); delegate.visitEnum(arg0, arg1, arg2);
} }
public void visitEnd() { public void visitEnd() {
delegate.visitEnd(); delegate.visitEnd();
} }
} }

View File

@ -30,108 +30,115 @@ import org.springframework.config.java.Util;
*/ */
class AsmUtils { class AsmUtils {
public static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor(); public static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor();
private static final Log log = LogFactory.getLog(AsmUtils.class); private static final Log log = LogFactory.getLog(AsmUtils.class);
/**
* @param className a standard, dot-delimeted, fully-qualified Java class name
* @return internal version of className, as per ASM guide section 2.1.2 "Internal Names"
*/
public static String convertClassNameToInternalName(String className) {
return className.replace('.', '/');
}
/**
* 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 convertTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
// TODO: SJC-242 should catch all possible cases. use case statement and switch on char
// TODO: SJC-242 converting from primitive to object here won't be intuitive to users
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("[[")) * @param className a standard, dot-delimeted, fully-qualified Java class name
internalName = typeDescriptor.substring(3); * @return internal version of className, as per ASM guide section 2.1.2
else if(typeDescriptor.startsWith("[")) * "Internal Names"
internalName = typeDescriptor.substring(2); */
else public static String convertClassNameToInternalName(String className) {
internalName = typeDescriptor.substring(1); return className.replace('.', '/');
}
// convert slashes to dots /**
String className = internalName.replace('/', '.'); * 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 convertTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
// and strip trailing semicolon (if present) // TODO: SJC-242 should catch all possible cases. use case statement and switch on
if(className.endsWith(";")) // char
className = className.substring(0, internalName.length()-1); // TODO: SJC-242 converting from primitive to object here won't be intuitive to
// users
if ("V".equals(typeDescriptor))
return Void.class.getName();
if ("I".equals(typeDescriptor))
return Integer.class.getName();
if ("Z".equals(typeDescriptor))
return Boolean.class.getName();
return className; // 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
* @param methodDescriptor see ASM guide section 2.1.4 String className = internalName.replace('/', '.');
*/
public static String getReturnTypeFromMethodDescriptor(String methodDescriptor) { // and strip trailing semicolon (if present)
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')')+1); if (className.endsWith(";"))
return convertTypeDescriptorToClassName(returnTypeDescriptor); className = className.substring(0, internalName.length() - 1);
}
return className;
/** }
* Creates a new ASM {@link ClassReader} for <var>pathToClass</var>. Appends '.class'
* to pathToClass before attempting to load. /**
* * @param methodDescriptor see ASM guide section 2.1.4
* @throws RuntimeException if <var>pathToClass</var>+.class cannot be found on the classpath */
* @throws RuntimeException if an IOException occurs when creating the new ClassReader public static String getReturnTypeFromMethodDescriptor(String methodDescriptor) {
*/ String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
public static ClassReader newClassReader(String pathToClass) { return convertTypeDescriptorToClassName(returnTypeDescriptor);
InputStream is = Util.getClassAsStream(pathToClass); }
return newClassReader(is);
} /**
* Creates a new ASM {@link ClassReader} for <var>pathToClass</var>. Appends '.class' to
/** * pathToClass before attempting to load.
* Convenience method that simply returns a new ASM {@link ClassReader} instance based on *
* the supplied <var>bytes</var> byte array. This method is exactly equivalent to calling * @throws RuntimeException if <var>pathToClass</var>+.class cannot be found on the
* new ClassReader(byte[]), and is mainly provided for symmetry with usage of * classpath
* {@link #newClassReader(InputStream)}. * @throws RuntimeException if an IOException occurs when creating the new ClassReader
* */
* @param bytes byte array that will be provided as input to the new ClassReader instance. public static ClassReader newClassReader(String pathToClass) {
* InputStream is = Util.getClassAsStream(pathToClass);
* @return return newClassReader(is);
*/ }
public static ClassReader newClassReader(byte[] bytes) {
return new ClassReader(bytes); /**
} * Convenience method that simply returns a new ASM {@link ClassReader} instance based
* on the supplied <var>bytes</var> byte array. This method is exactly equivalent to
/** * calling new ClassReader(byte[]), and is mainly provided for symmetry with usage of
* Convenience method that creates and returns a new ASM {@link ClassReader} for the given * {@link #newClassReader(InputStream)}.
* InputStream <var>is</var>, closing the InputStream after creating the ClassReader and rethrowing *
* any IOException thrown during ClassReader instantiation as an unchecked exception. Logs and ignores * @param bytes byte array that will be provided as input to the new ClassReader
* any IOException thrown when closing the InputStream. * instance.
* *
* @param is InputStream that will be provided to the new ClassReader instance. * @return
*/ */
public static ClassReader newClassReader(InputStream is) { public static ClassReader newClassReader(byte[] bytes) {
try { return new ClassReader(bytes);
return new ClassReader(is); }
} catch (IOException ex) {
throw new RuntimeException("An unexpected exception occurred while creating ASM ClassReader: " + ex); /**
} finally { * Convenience method that creates and returns a new ASM {@link ClassReader} for the
try { * given InputStream <var>is</var>, closing the InputStream after creating the
is.close(); * ClassReader and rethrowing any IOException thrown during ClassReader instantiation as
} catch (IOException ex) { * an unchecked exception. Logs and ignores any IOException thrown when closing the
log.error("Ignoring exception thrown while closing InputStream", ex); * InputStream.
} *
} * @param is InputStream that will be provided to the new ClassReader instance.
} */
public static ClassReader newClassReader(InputStream is) {
try {
return new ClassReader(is);
} catch (IOException ex) {
throw new RuntimeException("An unexpected exception occurred while creating ASM ClassReader: "
+ ex);
} finally {
try {
is.close();
} catch (IOException ex) {
log.error("Ignoring exception thrown while closing InputStream", ex);
}
}
}
} }

View File

@ -34,113 +34,114 @@ import org.springframework.config.java.Factory;
import org.springframework.config.java.ModelClass; import org.springframework.config.java.ModelClass;
import org.springframework.config.java.ModelMethod; import org.springframework.config.java.ModelMethod;
/** /**
* Visits a single method declared in a given {@link Configuration} class. Determines whether the * Visits a single method declared in a given {@link Configuration} class. Determines
* method is a {@link Factory} method and if so, adds it to the {@link ConfigurationClass}. * whether the method is a {@link Factory} method and if so, adds it to the
* {@link ConfigurationClass}.
* *
* @author Chris Beams * @author Chris Beams
*/ */
class ConfigurationClassMethodVisitor extends MethodAdapter { class ConfigurationClassMethodVisitor extends MethodAdapter {
private final ConfigurationClass configClass; private final ConfigurationClass configClass;
private final String methodName; private final String methodName;
private final int modifiers; private final int modifiers;
private final ModelClass returnType; private final ModelClass returnType;
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>(); private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
private boolean isModelMethod = false; private boolean isModelMethod = false;
private int lineNumber; private int lineNumber;
/** /**
* Creates a new {@link ConfigurationClassMethodVisitor} instance. * Creates a new {@link ConfigurationClassMethodVisitor} instance.
* *
* @param configClass model object to which this method will be added * @param configClass model object to which this method will be added
* @param methodName name of the method declared in the {@link Configuration} class * @param methodName name of the method declared in the {@link Configuration} class
* @param methodDescriptor ASM representation of the method signature * @param methodDescriptor ASM representation of the method signature
* @param modifiers modifiers for this method * @param modifiers modifiers for this method
*/ */
public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName, public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName,
String methodDescriptor, int modifiers) { String methodDescriptor, int modifiers) {
super(AsmUtils.EMPTY_VISITOR); super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass; this.configClass = configClass;
this.methodName = methodName; this.methodName = methodName;
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor); this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
this.modifiers = modifiers; this.modifiers = modifiers;
} }
/** /**
* Visits a single annotation on this method. Will be called once for each * Visits a single annotation on this method. Will be called once for each annotation
* annotation present (regardless of its RetentionPolicy). * present (regardless of its RetentionPolicy).
*/ */
@Override @Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoClassName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc); String annoClassName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoClass = loadToolingSafeClass(annoClassName);
if(annoClass == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation annotation = createMutableAnnotation(annoClass);
annotations.add(annotation);
return new MutableAnnotationVisitor(annotation);
}
/** Class<? extends Annotation> annoClass = loadToolingSafeClass(annoClassName);
* 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 Factory} method or not and if so adds it to the
* enclosing {@link #configClass}.
*/
@Override
public void visitEnd() {
for(Annotation anno : annotations) {
if(anno.annotationType().getAnnotation(Factory.class) != null) {
isModelMethod = true;
break;
}
}
if(!isModelMethod)
return;
Annotation[] annoArray = annotations.toArray(new Annotation[] { });
ModelMethod method = new ModelMethod(methodName, modifiers, returnType, annoArray);
method.setLineNumber(lineNumber);
configClass.addMethod(method);
}
/**
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
* that type is an interface.
*/
private static ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) {
final ModelClass returnType = new ModelClass(getReturnTypeFromMethodDescriptor(methodDescriptor));
// detect whether the return type is an interface if (annoClass == null)
newClassReader(convertClassNameToResourcePath(returnType.getName())).accept( return super.visitAnnotation(annoTypeDesc, visible);
new ClassAdapter(AsmUtils.EMPTY_VISITOR) {
@Override Annotation annotation = createMutableAnnotation(annoClass);
public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) {
returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE); annotations.add(annotation);
}
}, false); return new MutableAnnotationVisitor(annotation);
}
return returnType;
} /**
* 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 Factory} method or not and if so adds it to the enclosing
* {@link #configClass}.
*/
@Override
public void visitEnd() {
for (Annotation anno : annotations) {
if (anno.annotationType().getAnnotation(Factory.class) != null) {
isModelMethod = true;
break;
}
}
if (!isModelMethod)
return;
Annotation[] annoArray = annotations.toArray(new Annotation[] {});
ModelMethod method = new ModelMethod(methodName, modifiers, returnType, annoArray);
method.setLineNumber(lineNumber);
configClass.addMethod(method);
}
/**
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
* that type is an interface.
*/
private static ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) {
final ModelClass returnType = new ModelClass(getReturnTypeFromMethodDescriptor(methodDescriptor));
// detect whether the return type is an interface
newClassReader(convertClassNameToResourcePath(returnType.getName())).accept(
new ClassAdapter(AsmUtils.EMPTY_VISITOR) {
@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;
}
} }

View File

@ -37,198 +37,196 @@ import org.springframework.util.ClassUtils;
/** /**
* Visits a {@link Configuration} class, populating a {@link ConfigurationClass} instance with * Visits a {@link Configuration} class, populating a {@link ConfigurationClass} instance
* information gleaned along the way. * with information gleaned along the way.
* *
* @author Chris Beams * @author Chris Beams
*/ */
class ConfigurationClassVisitor extends ClassAdapter { class ConfigurationClassVisitor extends ClassAdapter {
private static final Log log = LogFactory.getLog(ConfigurationClassVisitor.class); private static final Log log = LogFactory.getLog(ConfigurationClassVisitor.class);
private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName()); private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName());
private final ConfigurationClass configClass; private final ConfigurationClass configClass;
private final ConfigurationModel model; private final ConfigurationModel model;
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;
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model) { public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model) {
super(AsmUtils.EMPTY_VISITOR); super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass; this.configClass = configClass;
this.model = model; this.model = model;
} }
public void setProcessInnerClasses(boolean processInnerClasses) { public void setProcessInnerClasses(boolean processInnerClasses) {
this.processInnerClasses = processInnerClasses; this.processInnerClasses = processInnerClasses;
} }
@Override @Override
public void visitSource(String sourceFile, String debug) { public void visitSource(String sourceFile, String debug) {
String resourcePath = String resourcePath = convertClassNameToResourcePath(configClass.getName()).substring(0,
convertClassNameToResourcePath(configClass.getName()) configClass.getName().lastIndexOf('.') + 1).concat(sourceFile);
.substring(0, configClass.getName().lastIndexOf('.')+1)
.concat(sourceFile);
configClass.setSource(resourcePath); configClass.setSource(resourcePath);
} }
@Override @Override
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3,
String superTypeDesc, String[] arg5) { String superTypeDesc, String[] arg5) {
visitSuperType(superTypeDesc); visitSuperType(superTypeDesc);
configClass.setName(convertResourcePathToClassName(classTypeDesc)); configClass.setName(convertResourcePathToClassName(classTypeDesc));
// ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions. // 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 // 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. // eliminated in order to comply with java.lang.reflect.Modifier values.
configClass.setModifiers(modifiers - Opcodes.ACC_SUPER); configClass.setModifiers(modifiers - Opcodes.ACC_SUPER);
} }
private void visitSuperType(String superTypeDesc) { private void visitSuperType(String superTypeDesc) {
// traverse up the type hierarchy unless the next ancestor is java.lang.Object // traverse up the type hierarchy unless the next ancestor is java.lang.Object
if(OBJECT_DESC.equals(superTypeDesc)) if (OBJECT_DESC.equals(superTypeDesc))
return; return;
ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model); ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model);
ClassReader reader = AsmUtils.newClassReader(superTypeDesc); ClassReader reader = AsmUtils.newClassReader(superTypeDesc);
reader.accept(visitor, false); reader.accept(visitor, false);
} }
/** /**
* Visits a class level annotation on a {@link Configuration @Configuration} class. * Visits a class level annotation on a {@link Configuration @Configuration} class.
* Accounts for all possible class-level annotations that are respected by JavaConfig * Accounts for all possible class-level annotations that are respected by JavaConfig
* including AspectJ's {@code @Aspect} annotation. * including AspectJ's {@code @Aspect} annotation.
* <p> * <p>
* Upon encountering such an annotation, update the {@link #configClass} model object * Upon encountering such an annotation, update the {@link #configClass} model object
* appropriately, and then return an {@link AnnotationVisitor} implementation that can * appropriately, and then return an {@link AnnotationVisitor} implementation that can
* populate the annotation appropriately with data. * populate the annotation appropriately with data.
* *
* @see MutableAnnotation * @see MutableAnnotation
*/ */
@Override @Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc); String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
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.setMetadata(mutableConfiguration);
return new MutableAnnotationVisitor(mutableConfiguration); return new MutableAnnotationVisitor(mutableConfiguration);
} }
// TODO: re-enable for @Import support // TODO: re-enable for @Import support
// 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); // throw new CircularImportException(configClass, importStack);
// //
// importStack.push(configClass); // importStack.push(configClass);
// //
// return new ImportAnnotationVisitor(model); // return new ImportAnnotationVisitor(model);
// } // }
// ------------------------------------- // -------------------------------------
// Detect @Plugin annotations // Detect @Plugin annotations
// ------------------------------------- // -------------------------------------
PluginAnnotationDetectingClassVisitor classVisitor = new PluginAnnotationDetectingClassVisitor(); PluginAnnotationDetectingClassVisitor classVisitor = new PluginAnnotationDetectingClassVisitor();
String className = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc); String className = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
String resourcePath = ClassUtils.convertClassNameToResourcePath(className); String resourcePath = ClassUtils.convertClassNameToResourcePath(className);
ClassReader reader = AsmUtils.newClassReader(resourcePath); ClassReader reader = AsmUtils.newClassReader(resourcePath);
reader.accept(classVisitor, false); reader.accept(classVisitor, false);
if(!classVisitor.hasPluginAnnotation()) if (!classVisitor.hasPluginAnnotation())
return super.visitAnnotation(annoTypeDesc, visible); return super.visitAnnotation(annoTypeDesc, visible);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if(annoType == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation pluginAnno = createMutableAnnotation(annoType);
configClass.addPluginAnnotation(pluginAnno);
return new MutableAnnotationVisitor(pluginAnno);
}
private static class PluginAnnotationDetectingClassVisitor extends ClassAdapter { Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
private boolean hasPluginAnnotation = false;
private final Extension pluginAnnotation = createMutableAnnotation(Extension.class);
public PluginAnnotationDetectingClassVisitor() { if (annoType == null)
super(AsmUtils.EMPTY_VISITOR); return super.visitAnnotation(annoTypeDesc, visible);
}
@Override Annotation pluginAnno = createMutableAnnotation(annoType);
public AnnotationVisitor visitAnnotation(String typeDesc, boolean arg1) { configClass.addPluginAnnotation(pluginAnno);
if(Extension.class.getName().equals(AsmUtils.convertTypeDescriptorToClassName(typeDesc))) { return new MutableAnnotationVisitor(pluginAnno);
hasPluginAnnotation = true; }
return new MutableAnnotationVisitor(pluginAnnotation);
}
return super.visitAnnotation(typeDesc, arg1);
}
public boolean hasPluginAnnotation() { private static class PluginAnnotationDetectingClassVisitor extends ClassAdapter {
return hasPluginAnnotation; private boolean hasPluginAnnotation = false;
} private final Extension pluginAnnotation = createMutableAnnotation(Extension.class);
public Extension getPluginAnnotation() { public PluginAnnotationDetectingClassVisitor() {
return pluginAnnotation; super(AsmUtils.EMPTY_VISITOR);
} }
}
/** @Override
* Delegates all {@link Configuration @Configuration} class method parsing to public AnnotationVisitor visitAnnotation(String typeDesc, boolean arg1) {
* {@link ConfigurationClassMethodVisitor}. if (Extension.class.getName().equals(AsmUtils.convertTypeDescriptorToClassName(typeDesc))) {
*/ hasPluginAnnotation = true;
@Override return new MutableAnnotationVisitor(pluginAnnotation);
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, }
String arg3, String[] arg4) { return super.visitAnnotation(typeDesc, arg1);
}
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers); public boolean hasPluginAnnotation() {
} return hasPluginAnnotation;
}
/** public Extension getPluginAnnotation() {
* Implementation deals with inner classes here even though it would have return pluginAnnotation;
* been more intuitive to deal with outer classes. Due to limitations in ASM }
* (resulting from limitations in the VM spec) we cannot directly look for outer classes }
* in all cases, so instead build up a model of {@link #innerClasses} and process
* declaring class logic in a kind of inverted manner.
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if(processInnerClasses == false)
return;
String innerClassName = convertResourcePathToClassName(name); /**
String configClassName = configClass.getName(); * Delegates all {@link Configuration @Configuration} class method parsing to
* {@link ConfigurationClassMethodVisitor}.
*/
@Override
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3,
String[] arg4) {
// if the innerClassName is equal to configClassName, we just return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers);
// ran into the outermost inner class look up the outer class }
// associated with this
if(innerClassName.equals(configClassName)) {
if(innerClasses.containsKey(outerName)) {
configClass.setDeclaringClass(innerClasses.get(outerName));
}
return;
}
ConfigurationClass innerConfigClass = new ConfigurationClass(); /**
* Implementation deals with inner classes here even though it would have been more
* intuitive to deal with outer classes. Due to limitations in ASM (resulting from
* limitations in the VM spec) we cannot directly look for outer classes in all cases,
* so instead build up a model of {@link #innerClasses} and process declaring class
* logic in a kind of inverted manner.
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (processInnerClasses == false)
return;
ConfigurationClassVisitor ccVisitor = String innerClassName = convertResourcePathToClassName(name);
new ConfigurationClassVisitor(innerConfigClass, new ConfigurationModel()); String configClassName = configClass.getName();
ccVisitor.setProcessInnerClasses(false);
ClassReader reader = AsmUtils.newClassReader(name); // if the innerClassName is equal to configClassName, we just
reader.accept(ccVisitor, false); // ran into the outermost inner class look up the outer class
// associated with this
if (innerClassName.equals(configClassName)) {
if (innerClasses.containsKey(outerName)) {
configClass.setDeclaringClass(innerClasses.get(outerName));
}
return;
}
if(innerClasses.containsKey(outerName)) ConfigurationClass innerConfigClass = new ConfigurationClass();
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list ConfigurationClassVisitor ccVisitor = new ConfigurationClassVisitor(innerConfigClass,
if(innerConfigClass.getMetadata() != null) new ConfigurationModel());
innerClasses.put(name, innerConfigClass); ccVisitor.setProcessInnerClasses(false);
}
ClassReader reader = AsmUtils.newClassReader(name);
reader.accept(ccVisitor, false);
if (innerClasses.containsKey(outerName))
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list
if (innerConfigClass.getMetadata() != null)
innerClasses.put(name, innerConfigClass);
}
} }

View File

@ -16,16 +16,15 @@
package org.springframework.config.java.internal.parsing; package org.springframework.config.java.internal.parsing;
/** /**
* Note: the visibility of this interface would be reduced to package-private * Note: the visibility of this interface would be reduced to package-private save for an
* save for an obscure restriction of JDK dynamic proxies. * obscure restriction of JDK dynamic proxies.
* {@link MutableAnnotationUtils#createMutableAnnotation(Class)} creates a proxy * {@link MutableAnnotationUtils#createMutableAnnotation(Class)} creates a proxy based on
* based on two interfaces: this one, and whatever annotation is currently being * two interfaces: this one, and whatever annotation is currently being parsed. The
* parsed. The restriction is that both interfaces may not be package-private if * restriction is that both interfaces may not be package-private if they are in separate
* they are in separate packages. In order to avoid unnecessarily restricting * packages. In order to avoid unnecessarily restricting the visibility options for
* the visibility options for user-defined annotations, this interface becomes * user-defined annotations, this interface becomes public. Because it is in the internal.*
* public. Because it is in the internal.* package, it won't pollute the public * package, it won't pollute the public API, but developers should take caution not to use
* API, but developers should take caution not to use this annotation outside * this annotation outside the internal.parsing package.
* the internal.parsing package.
* *
* @author Chris Beams * @author Chris Beams
*/ */

View File

@ -29,43 +29,43 @@ import org.objectweb.asm.AnnotationVisitor;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
class MutableAnnotationArrayVisitor extends AnnotationAdapter { class MutableAnnotationArrayVisitor extends AnnotationAdapter {
private static final Log log = LogFactory.getLog(MutableAnnotationArrayVisitor.class);
private final ArrayList<Object> values = new ArrayList<Object>();
private final MutableAnnotation mutableAnno;
private final String attribName;
public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName) { private static final Log log = LogFactory.getLog(MutableAnnotationArrayVisitor.class);
super(AsmUtils.EMPTY_VISITOR);
this.mutableAnno = mutableAnno; private final ArrayList<Object> values = new ArrayList<Object>();
this.attribName = attribName; private final MutableAnnotation mutableAnno;
} private final String attribName;
@Override public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName) {
public void visit(String na, Object value) { super(AsmUtils.EMPTY_VISITOR);
values.add(value);
}
@Override this.mutableAnno = mutableAnno;
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { this.attribName = attribName;
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc); }
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if(annoType == null)
return super.visitAnnotation(na, annoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
values.add(anno);
return new MutableAnnotationVisitor(anno);
}
@Override @Override
public void visitEnd() { public void visit(String na, Object value) {
Class<?> arrayType = mutableAnno.getAttributeType(attribName); values.add(value);
Object[] array = (Object[])Array.newInstance(arrayType.getComponentType(), 0); }
mutableAnno.setAttributeValue(attribName, values.toArray(array));
} @Override
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if (annoType == null)
return super.visitAnnotation(na, annoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
values.add(anno);
return new MutableAnnotationVisitor(anno);
}
@Override
public void visitEnd() {
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0);
mutableAnno.setAttributeValue(attribName, values.toArray(array));
}
} }

View File

@ -33,170 +33,173 @@ import org.springframework.util.StringUtils;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
final class MutableAnnotationInvocationHandler implements InvocationHandler { final class MutableAnnotationInvocationHandler implements InvocationHandler {
private final Class<? extends Annotation> annoType; private final Class<? extends Annotation> annoType;
private final HashMap<String, Object> attributes = new HashMap<String, Object>(); private final HashMap<String, Object> attributes = new HashMap<String, Object>();
private final HashMap<String, Class<?>> attributeTypes = new HashMap<String, Class<?>>(); private final HashMap<String, Class<?>> attributeTypes = new HashMap<String, Class<?>>();
public MutableAnnotationInvocationHandler(Class<? extends Annotation> annoType) { public MutableAnnotationInvocationHandler(Class<? extends Annotation> annoType) {
// pre-populate the attributes hash will all the names // pre-populate the attributes hash will all the names
// and default values of the attributes defined in 'annoType' // and default values of the attributes defined in 'annoType'
Method[] attribs = annoType.getDeclaredMethods(); Method[] attribs = annoType.getDeclaredMethods();
for(Method attrib : attribs) { for (Method attrib : attribs) {
this.attributes.put(attrib.getName(), getDefaultValue(annoType, attrib.getName())); this.attributes.put(attrib.getName(), getDefaultValue(annoType, attrib.getName()));
this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName()));
} }
this.annoType = annoType; this.annoType = annoType;
} }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Assert.isInstanceOf(Annotation.class, proxy); Assert.isInstanceOf(Annotation.class, proxy);
String methodName = method.getName(); String methodName = method.getName();
// first -> check to see if this method is an attribute on our annotation // first -> check to see if this method is an attribute on our annotation
if(attributes.containsKey(methodName)) if (attributes.containsKey(methodName))
return attributes.get(methodName); return attributes.get(methodName);
// second -> is it a method from java.lang.annotation.Annotation? // second -> is it a method from java.lang.annotation.Annotation?
if(methodName.equals("annotationType")) if (methodName.equals("annotationType"))
return annoType; return annoType;
// third -> is it a method from java.lang.Object? // third -> is it a method from java.lang.Object?
if(methodName.equals("toString")) if (methodName.equals("toString"))
return format("@%s(%s)", annoType.getName(), getAttribs()); return format("@%s(%s)", annoType.getName(), getAttribs());
if(methodName.equals("equals")) if (methodName.equals("equals"))
return isEqualTo(proxy, args[0]); return isEqualTo(proxy, args[0]);
if(methodName.equals("hashCode")) if (methodName.equals("hashCode"))
return calculateHashCode(proxy); return calculateHashCode(proxy);
// finally -> is it a method specified by MutableAnno? // finally -> is it a method specified by MutableAnno?
if(methodName.equals("setAttributeValue")) { if (methodName.equals("setAttributeValue")) {
attributes.put((String)args[0], args[1]); attributes.put((String) args[0], args[1]);
return null; // setAttributeValue has a 'void' return type return null; // setAttributeValue has a 'void' return type
} }
if(methodName.equals("getAttributeType")) if (methodName.equals("getAttributeType"))
return attributeTypes.get(args[0]); return attributeTypes.get(args[0]);
throw new UnsupportedOperationException("this proxy does not support method: " + methodName); throw new UnsupportedOperationException("this proxy does not support method: " + methodName);
} }
/** /**
* Conforms to the hashCode() specification for Annotation. * Conforms to the hashCode() specification for Annotation.
* *
* @see Annotation#hashCode() * @see Annotation#hashCode()
*/ */
private Object calculateHashCode(Object proxy) { private Object calculateHashCode(Object proxy) {
int sum = 0; int sum = 0;
for (String attribName : attributes.keySet()) { for (String attribName : attributes.keySet()) {
Object attribValue = attributes.get(attribName); Object attribValue = attributes.get(attribName);
final int attribNameHashCode = attribName.hashCode(); final int attribNameHashCode = attribName.hashCode();
final int attribValueHashCode; final int attribValueHashCode;
if (attribValue == null) if (attribValue == null)
// memberValue may be null when a mutable annotation is being added to a collection // memberValue may be null when a mutable annotation is being added to a
// and before it has actually been visited (and populated) by MutableAnnotationVisitor // collection
attribValueHashCode = 0; // and before it has actually been visited (and populated) by
else if (attribValue.getClass().isArray()) // MutableAnnotationVisitor
attribValueHashCode = Arrays.hashCode((Object[]) attribValue); attribValueHashCode = 0;
else else if (attribValue.getClass().isArray())
attribValueHashCode = attribValue.hashCode(); attribValueHashCode = Arrays.hashCode((Object[]) attribValue);
else
attribValueHashCode = attribValue.hashCode();
sum += (127 * attribNameHashCode) ^ attribValueHashCode; sum += (127 * attribNameHashCode) ^ attribValueHashCode;
} }
return sum; return sum;
} }
/** /**
* Compares <var>proxy</var> object and <var>other</var> object by comparing the return values * Compares <var>proxy</var> object and <var>other</var> object by comparing the return
* of the methods specified by their common {@link Annotation} ancestry. * values of the methods specified by their common {@link Annotation} ancestry.
* <p/> * <p/>
* <var>other</var> must be the same type as or a subtype of <var>proxy</var>. * <var>other</var> must be the same type as or a subtype of <var>proxy</var>. Will
* Will return false otherwise. * return false otherwise.
* <p/> * <p/>
* Eagerly returns true if {@code proxy} == <var>other</var></p> * Eagerly returns true if {@code proxy} == <var>other</var>
* <p/> * </p>
* Conforms strictly to the equals() specification for Annotation</p> * <p/>
* * Conforms strictly to the equals() specification for Annotation
* @see Annotation#equals(Object) * </p>
*/ *
private Object isEqualTo(Object proxy, Object other) { * @see Annotation#equals(Object)
if (proxy == other) */
return true; private Object isEqualTo(Object proxy, Object other) {
if (proxy == other)
return true;
if (other == null) if (other == null)
return false; return false;
if(!annoType.isAssignableFrom(other.getClass())) if (!annoType.isAssignableFrom(other.getClass()))
return false; return false;
for (String attribName : attributes.keySet()) { for (String attribName : attributes.keySet()) {
Object thisVal; Object thisVal;
Object thatVal; Object thatVal;
try { try {
thisVal = attributes.get(attribName); thisVal = attributes.get(attribName);
thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other); thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
if ((thisVal == null) && (thatVal != null)) if ((thisVal == null) && (thatVal != null))
return false; return false;
if ((thatVal == null) && (thisVal != null)) if ((thatVal == null) && (thisVal != null))
return false; return false;
if (thatVal.getClass().isArray()) { if (thatVal.getClass().isArray()) {
if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) { if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) {
return false; return false;
} }
} else if (thisVal instanceof Double) { } else if (thisVal instanceof Double) {
if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal))) if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal)))
return false; return false;
} else if (thisVal instanceof Float) { } else if (thisVal instanceof Float) {
if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal))) if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal)))
return false; return false;
} else if (!thisVal.equals(thatVal)) { } else if (!thisVal.equals(thatVal)) {
return false; return false;
} }
} }
return true; return true;
} }
private String getAttribs() { private String getAttribs() {
ArrayList<String> attribs = new ArrayList<String>(); ArrayList<String> attribs = new ArrayList<String>();
for (String attribName : attributes.keySet()) for (String attribName : attributes.keySet())
attribs.add(format("%s=%s", attribName, attributes.get(attribName))); attribs.add(format("%s=%s", attribName, attributes.get(attribName)));
return StringUtils.collectionToDelimitedString(attribs, ", "); return StringUtils.collectionToDelimitedString(attribs, ", ");
} }
/** /**
* Retrieve the type of the given annotation attribute. * Retrieve the type of the given annotation attribute.
*/ */
private static Class<?> getAttributeType(Class<? extends Annotation> annotationType, String attributeName) { private static Class<?> getAttributeType(Class<? extends Annotation> annotationType, String attributeName) {
Method method = null; Method method = null;
try { try {
method = annotationType.getDeclaredMethod(attributeName); method = annotationType.getDeclaredMethod(attributeName);
} } catch (Exception ex) {
catch (Exception ex) { ReflectionUtils.handleReflectionException(ex);
ReflectionUtils.handleReflectionException(ex); }
}
return method.getReturnType();
return method.getReturnType(); }
}
} }

View File

@ -22,22 +22,22 @@ import java.lang.reflect.Proxy;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
class MutableAnnotationUtils { class MutableAnnotationUtils {
/** /**
* Creates a {@link MutableAnnotation} for {@code annoType}. * Creates a {@link MutableAnnotation} for {@code annoType}. JDK dynamic proxies are
* JDK dynamic proxies are used, and the returned proxy implements * used, and the returned proxy implements both {@link MutableAnnotation} and annotation
* both {@link MutableAnnotation} and annotation type {@code A} * type {@code A}
* *
* @param <A> annotation type that must be supplied and returned * @param <A> annotation type that must be supplied and returned
* @param annoType type of annotation to create * @param annoType type of annotation to create
*/ */
public static <A extends Annotation> A createMutableAnnotation(Class<A> annoType) { public static <A extends Annotation> A createMutableAnnotation(Class<A> annoType) {
MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType); MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType);
ClassLoader classLoader = MutableAnnotationUtils.class.getClassLoader(); ClassLoader classLoader = MutableAnnotationUtils.class.getClassLoader();
Class<?>[] interfaces = new Class<?>[] {annoType, MutableAnnotation.class}; Class<?>[] interfaces = new Class<?>[] { annoType, MutableAnnotation.class };
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
A mutableAnno = (A) Proxy.newProxyInstance(classLoader, interfaces, handler); A mutableAnno = (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
return mutableAnno; return mutableAnno;
} }
} }

View File

@ -32,84 +32,85 @@ import org.springframework.util.Assert;
* Populates a given {@link MutableAnnotation} instance with its attributes. * Populates a given {@link MutableAnnotation} instance with its attributes.
*/ */
class MutableAnnotationVisitor implements AnnotationVisitor { class MutableAnnotationVisitor implements AnnotationVisitor {
private static final Log log = LogFactory.getLog(MutableAnnotationVisitor.class);
protected final MutableAnnotation mutableAnno; private static final Log log = LogFactory.getLog(MutableAnnotationVisitor.class);
/** protected final MutableAnnotation mutableAnno;
* Creates a new {@link MutableAnnotationVisitor} instance that will populate
* the the attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation}
* instead of {@link MutableAnnotation} to avoid the need for callers to typecast.
*
* @param mutableAnno {@link MutableAnnotation} instance to visit and populate
*
* @throws IllegalArgumentException if <var>mutableAnno</var> is not of type
* {@link MutableAnnotation}
*
* @see MutableAnnotationUtils#createMutableAnnotation(Class)
*/
public MutableAnnotationVisitor(Annotation mutableAnno) {
Assert.isInstanceOf(MutableAnnotation.class, mutableAnno, "annotation must be mutable");
this.mutableAnno = (MutableAnnotation)mutableAnno;
}
public AnnotationVisitor visitArray(final String attribName) { /**
return new MutableAnnotationArrayVisitor(mutableAnno, attribName); * Creates a new {@link MutableAnnotationVisitor} instance that will populate the the
} * attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation} instead of
* {@link MutableAnnotation} to avoid the need for callers to typecast.
*
* @param mutableAnno {@link MutableAnnotation} instance to visit and populate
*
* @throws IllegalArgumentException if <var>mutableAnno</var> is not of type
* {@link MutableAnnotation}
*
* @see MutableAnnotationUtils#createMutableAnnotation(Class)
*/
public MutableAnnotationVisitor(Annotation mutableAnno) {
Assert.isInstanceOf(MutableAnnotation.class, mutableAnno, "annotation must be mutable");
this.mutableAnno = (MutableAnnotation) mutableAnno;
}
public void visit(String attribName, Object attribValue) { public AnnotationVisitor visitArray(final String attribName) {
Class<?> attribReturnType = mutableAnno.getAttributeType(attribName); return new MutableAnnotationArrayVisitor(mutableAnno, attribName);
}
if (attribReturnType.equals(Class.class)) { public void visit(String attribName, Object attribValue) {
// the attribute type is Class -> load it and set it. Class<?> attribReturnType = mutableAnno.getAttributeType(attribName);
String fqClassName = ((Type) attribValue).getClassName();
Class<?> classVal = loadToolingSafeClass(fqClassName);
if(classVal == null)
return;
mutableAnno.setAttributeValue(attribName, classVal);
return;
}
// otherwise, assume the value can be set literally if (attribReturnType.equals(Class.class)) {
mutableAnno.setAttributeValue(attribName, attribValue); // the attribute type is Class -> load it and set it.
} String fqClassName = ((Type) attribValue).getClassName();
@SuppressWarnings("unchecked") Class<?> classVal = loadToolingSafeClass(fqClassName);
public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) {
String enumClassName = AsmUtils.convertTypeDescriptorToClassName(enumTypeDescriptor);
Class<? extends Enum> enumClass = loadToolingSafeClass(enumClassName); if (classVal == null)
return;
if(enumClass == null)
return;
Enum enumValue = Enum.valueOf(enumClass, strEnumValue); mutableAnno.setAttributeValue(attribName, classVal);
mutableAnno.setAttributeValue(attribName, enumValue); return;
} }
public AnnotationVisitor visitAnnotation(String attribName, String attribAnnoTypeDesc) { // otherwise, assume the value can be set literally
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(attribAnnoTypeDesc); mutableAnno.setAttributeValue(attribName, attribValue);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName); }
if(annoType == null)
return AsmUtils.EMPTY_VISITOR.visitAnnotation(attribName, attribAnnoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
try {
Field attribute = mutableAnno.getClass().getField(attribName);
attribute.set(mutableAnno, anno);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return new MutableAnnotationVisitor(anno);
}
public void visitEnd() { } @SuppressWarnings("unchecked")
public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) {
String enumClassName = AsmUtils.convertTypeDescriptorToClassName(enumTypeDescriptor);
Class<? extends Enum> enumClass = loadToolingSafeClass(enumClassName);
if (enumClass == null)
return;
Enum enumValue = Enum.valueOf(enumClass, strEnumValue);
mutableAnno.setAttributeValue(attribName, enumValue);
}
public AnnotationVisitor visitAnnotation(String attribName, String attribAnnoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(attribAnnoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName);
if (annoType == null)
return AsmUtils.EMPTY_VISITOR.visitAnnotation(attribName, attribAnnoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
try {
Field attribute = mutableAnno.getClass().getField(attribName);
attribute.set(mutableAnno, anno);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return new MutableAnnotationVisitor(anno);
}
public void visitEnd() {
}
} }

View File

@ -31,18 +31,18 @@ import org.springframework.core.Ordered;
@Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.ANNOTATION_TYPE)
@Inherited @Inherited
public @interface Extension { public @interface Extension {
/** /**
* The class that handles this plugin. * The class that handles this plugin.
*/ */
// TODO: SJC-242 rename to handlerType / handlerClass // TODO: SJC-242 rename to handlerType / handlerClass
Class<? extends ExtensionAnnotationBeanDefinitionRegistrar<?>> handler(); Class<? extends ExtensionAnnotationBeanDefinitionRegistrar<?>> handler();
/** /**
* The order in which this plugin will be processed * The order in which this plugin will be processed relative to others. Per the
* relative to others. Per the semantics of {@link Ordered}, * semantics of {@link Ordered}, lower integer values will be treated as higher
* lower integer values will be treated as higher priority. * priority.
* *
* @see Ordered * @see Ordered
*/ */
int order() default 0; int order() default 0;
} }

View File

@ -24,9 +24,9 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
* Registers bean definitions based on {@link Extension} metadata * Registers bean definitions based on {@link Extension} metadata
*/ */
public interface ExtensionAnnotationBeanDefinitionRegistrar<A extends Annotation> { public interface ExtensionAnnotationBeanDefinitionRegistrar<A extends Annotation> {
/** /**
* *
*/ */
void handle(A annotation, BeanDefinitionRegistry registry); void handle(A annotation, BeanDefinitionRegistry registry);
} }

View File

@ -15,7 +15,6 @@
*/ */
package org.springframework.config.java.support; package org.springframework.config.java.support;
import static java.lang.String.*; import static java.lang.String.*;
import static org.springframework.config.java.Util.*; import static org.springframework.config.java.Util.*;
@ -44,145 +43,146 @@ import org.springframework.core.io.Resource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Reads a given fully-populated {@link ConfigurationModel}, registering bean definitions with * Reads a given fully-populated {@link ConfigurationModel}, registering bean definitions
* the given {@link BeanDefinitionRegistry} based on its contents. * with the given {@link BeanDefinitionRegistry} based on its contents.
* <p> * <p>
* This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does not * This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does not
* implement/extend any of its artifacts as {@link ConfigurationModel} is not a * implement/extend any of its artifacts as {@link ConfigurationModel} is not a
* {@link Resource}. * {@link Resource}.
* *
* @author Chris Beams * @author Chris Beams
*/ */
class ConfigurationModelBeanDefinitionReader { class ConfigurationModelBeanDefinitionReader {
private static final Log log = LogFactory.getLog(ConfigurationModelBeanDefinitionReader.class); private static final Log log = LogFactory.getLog(ConfigurationModelBeanDefinitionReader.class);
private final DefaultListableBeanFactory beanFactory; private final DefaultListableBeanFactory beanFactory;
/** /**
* Creates a new {@link ConfigurationModelBeanDefinitionReader} instance. * Creates a new {@link ConfigurationModelBeanDefinitionReader} instance.
*/ */
public ConfigurationModelBeanDefinitionReader(DefaultListableBeanFactory beanFactory) { public ConfigurationModelBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory; this.beanFactory = beanFactory;
} }
/** /**
* Reads {@code model}, registering bean definitions with {@link #beanFactory} * Reads {@code model}, registering bean definitions with {@link #beanFactory} based on
* based on its contents. * its contents.
* *
* @return number of bean definitions generated * @return number of bean definitions generated
*/ */
public int loadBeanDefinitions(ConfigurationModel model) { public int loadBeanDefinitions(ConfigurationModel model) {
int initialBeanDefCount = beanFactory.getBeanDefinitionCount(); int initialBeanDefCount = beanFactory.getBeanDefinitionCount();
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) for (ConfigurationClass configClass : model.getAllConfigurationClasses())
loadBeanDefinitionsForConfigurationClass(configClass); loadBeanDefinitionsForConfigurationClass(configClass);
return beanFactory.getBeanDefinitionCount() - initialBeanDefCount; return beanFactory.getBeanDefinitionCount() - initialBeanDefCount;
} }
/** /**
* Reads a particular {@link ConfigurationClass}, registering bean definitions * Reads a particular {@link ConfigurationClass}, registering bean definitions for the
* for the class itself, all its {@link Factory} methods and all its {@link Extension} * class itself, all its {@link Factory} methods and all its {@link Extension}
* annotations. * annotations.
*/ */
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
doLoadBeanDefinitionForConfigurationClass(configClass); doLoadBeanDefinitionForConfigurationClass(configClass);
for (ModelMethod method : configClass.getMethods())
loadBeanDefinitionsForModelMethod(method);
Annotation[] pluginAnnotations = configClass.getPluginAnnotations(); for (ModelMethod method : configClass.getMethods())
Arrays.sort(pluginAnnotations, new PluginComparator()); loadBeanDefinitionsForModelMethod(method);
for (Annotation annotation : pluginAnnotations)
loadBeanDefinitionsForExtensionAnnotation(annotation);
}
/**
* Registers the {@link Configuration} class itself as a bean definition.
*/
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
Configuration metadata = configClass.getMetadata();
if (metadata.checkRequired() == true) {
RootBeanDefinition requiredAnnotationPostProcessor = new RootBeanDefinition();
Class<?> beanClass = RequiredAnnotationBeanPostProcessor.class;
String beanName = beanClass.getName() + "#0";
requiredAnnotationPostProcessor.setBeanClass(beanClass);
requiredAnnotationPostProcessor.setResourceDescription("ensures @Required methods have been invoked");
beanFactory.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
}
GenericBeanDefinition configBeanDef = new GenericBeanDefinition(); Annotation[] pluginAnnotations = configClass.getPluginAnnotations();
configBeanDef.setBeanClassName(configClass.getName()); Arrays.sort(pluginAnnotations, new PluginComparator());
for (Annotation annotation : pluginAnnotations)
loadBeanDefinitionsForExtensionAnnotation(annotation);
}
String configBeanName = configClass.getBeanName(); /**
* Registers the {@link Configuration} class itself as a bean definition.
*/
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
Configuration metadata = configClass.getMetadata();
// consider the case where it's already been defined (probably in XML) if (metadata.checkRequired() == true) {
// and potentially has PropertyValues and ConstructorArgs) RootBeanDefinition requiredAnnotationPostProcessor = new RootBeanDefinition();
if (beanFactory.containsBeanDefinition(configBeanName)) { Class<?> beanClass = RequiredAnnotationBeanPostProcessor.class;
if (log.isInfoEnabled()) String beanName = beanClass.getName() + "#0";
log.info(format("Copying property and constructor arg values from existing bean definition for " requiredAnnotationPostProcessor.setBeanClass(beanClass);
+ "@Configuration class %s to new bean definition", configBeanName)); requiredAnnotationPostProcessor
AbstractBeanDefinition existing = (AbstractBeanDefinition)beanFactory.getBeanDefinition(configBeanName); .setResourceDescription("ensures @Required methods have been invoked");
configBeanDef.setPropertyValues(existing.getPropertyValues()); beanFactory.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
configBeanDef.setConstructorArgumentValues(existing.getConstructorArgumentValues()); }
configBeanDef.setResource(existing.getResource());
}
if (log.isInfoEnabled()) GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
log.info(format("Registering bean definition for @Configuration class %s", configBeanName)); configBeanDef.setBeanClassName(configClass.getName());
beanFactory.registerBeanDefinition(configBeanName, configBeanDef); String configBeanName = configClass.getBeanName();
}
/**
* Reads a particular {@link ModelMethod}, registering bean definitions
* with {@link #beanFactory} based on its contents.
*
* @see Factory
*/
private void loadBeanDefinitionsForModelMethod(ModelMethod method) {
method.getRegistrar().register(method, beanFactory);
}
@SuppressWarnings("unchecked") // consider the case where it's already been defined (probably in XML)
private void loadBeanDefinitionsForExtensionAnnotation(Annotation anno) { // and potentially has PropertyValues and ConstructorArgs)
//ExtensionAnnotationUtils.getRegistrarFor(anno).registerBeanDefinitionsWith(beanFactory); if (beanFactory.containsBeanDefinition(configBeanName)) {
// there is a fixed assumption that in order for this annotation to have if (log.isInfoEnabled())
// been registered in the first place, it must be meta-annotated with @Plugin log.info(format(
// assert this as an invariant now "Copying property and constructor arg values from existing bean definition for "
Class<?> annoClass = anno.getClass(); + "@Configuration class %s to new bean definition", configBeanName));
Extension extensionAnno = AnnotationUtils.findAnnotation(annoClass, Extension.class); AbstractBeanDefinition existing = (AbstractBeanDefinition) beanFactory
Assert.isTrue(extensionAnno != null, .getBeanDefinition(configBeanName);
format("%s annotation is not annotated as a @%s", configBeanDef.setPropertyValues(existing.getPropertyValues());
annoClass, Extension.class.getSimpleName())); configBeanDef.setConstructorArgumentValues(existing.getConstructorArgumentValues());
configBeanDef.setResource(existing.getResource());
}
Class<? extends ExtensionAnnotationBeanDefinitionRegistrar> extHandlerClass = extensionAnno.handler(); if (log.isInfoEnabled())
log.info(format("Registering bean definition for @Configuration class %s", configBeanName));
ExtensionAnnotationBeanDefinitionRegistrar extHandler = getInstance(extHandlerClass);
extHandler.handle(anno, beanFactory);
}
private static class PluginComparator implements Comparator<Annotation> { beanFactory.registerBeanDefinition(configBeanName, configBeanDef);
public int compare(Annotation a1, Annotation a2) { }
Integer i1 = getOrder(a1);
Integer i2 = getOrder(a2);
return i1.compareTo(i2); /**
} * Reads a particular {@link ModelMethod}, registering bean definitions with
* {@link #beanFactory} based on its contents.
*
* @see Factory
*/
private void loadBeanDefinitionsForModelMethod(ModelMethod method) {
method.getRegistrar().register(method, beanFactory);
}
@SuppressWarnings("unchecked")
private void loadBeanDefinitionsForExtensionAnnotation(Annotation anno) {
// ExtensionAnnotationUtils.getRegistrarFor(anno).registerBeanDefinitionsWith(beanFactory);
// there is a fixed assumption that in order for this annotation to have
// been registered in the first place, it must be meta-annotated with @Plugin
// assert this as an invariant now
Class<?> annoClass = anno.getClass();
Extension extensionAnno = AnnotationUtils.findAnnotation(annoClass, Extension.class);
Assert.isTrue(extensionAnno != null, format("%s annotation is not annotated as a @%s", annoClass,
Extension.class.getSimpleName()));
Class<? extends ExtensionAnnotationBeanDefinitionRegistrar> extHandlerClass = extensionAnno.handler();
ExtensionAnnotationBeanDefinitionRegistrar extHandler = getInstance(extHandlerClass);
extHandler.handle(anno, beanFactory);
}
private static class PluginComparator implements Comparator<Annotation> {
public int compare(Annotation a1, Annotation a2) {
Integer i1 = getOrder(a1);
Integer i2 = getOrder(a2);
return i1.compareTo(i2);
}
private Integer getOrder(Annotation a) {
Extension plugin = a.annotationType().getAnnotation(Extension.class);
if (plugin == null)
throw new IllegalArgumentException("annotation was not annotated with @Plugin: "
+ a.annotationType());
return plugin.order();
}
}
private Integer getOrder(Annotation a) {
Extension plugin = a.annotationType().getAnnotation(Extension.class);
if(plugin == null)
throw new IllegalArgumentException(
"annotation was not annotated with @Plugin: " + a.annotationType());
return plugin.order();
}
}
} }

View File

@ -40,128 +40,133 @@ import org.springframework.util.ClassUtils;
/** /**
* {@link BeanFactoryPostProcessor} used for bootstrapping {@link Configuration @Configuration} * {@link BeanFactoryPostProcessor} used for bootstrapping {@link Configuration
* beans from Spring XML files. * @Configuration} beans from Spring XML files.
*/ */
public class ConfigurationPostProcessor implements Ordered, BeanFactoryPostProcessor { public class ConfigurationPostProcessor implements Ordered, BeanFactoryPostProcessor {
private static final Log logger = LogFactory.getLog(ConfigurationPostProcessor.class); private static final Log logger = LogFactory.getLog(ConfigurationPostProcessor.class);
/**
* Returns the order in which this {@link BeanPostProcessor} will be executed.
* Returns {@link Ordered#HIGHEST_PRECEDENCE}.
*/
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
/** /**
* Searches <var>beanFactory</var> for any {@link Configuration} classes in order * Returns the order in which this {@link BeanPostProcessor} will be executed. Returns
* to parse and enhance them. Also registers any {@link BeanPostProcessor} objects * {@link Ordered#HIGHEST_PRECEDENCE}.
* necessary to fulfill JavaConfig requirements. */
*/ public int getOrder() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory clBeanFactory) throws BeansException { return Ordered.HIGHEST_PRECEDENCE;
if(!(clBeanFactory instanceof DefaultListableBeanFactory)) }
throw new IllegalStateException("beanFactory must be of type "
+ DefaultListableBeanFactory.class.getSimpleName());
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) clBeanFactory;
ConfigurationModel model = new ConfigurationModel();
parseAnyConfigurationClasses(beanFactory, model);
enhanceAnyConfigurationClasses(beanFactory, model);
}
private void parseAnyConfigurationClasses(DefaultListableBeanFactory beanFactory, ConfigurationModel model) { /**
* Searches <var>beanFactory</var> for any {@link Configuration} classes in order to
// linked map is important for maintaining predictable ordering of configuration classes. * parse and enhance them. Also registers any {@link BeanPostProcessor} objects
// this is important in bean / value override situations. * necessary to fulfill JavaConfig requirements.
LinkedHashMap<String, ClassPathResource> configClassResources = new LinkedHashMap<String, ClassPathResource>(); */
public void postProcessBeanFactory(ConfigurableListableBeanFactory clBeanFactory) throws BeansException {
if (!(clBeanFactory instanceof DefaultListableBeanFactory))
throw new IllegalStateException("beanFactory must be of type "
+ DefaultListableBeanFactory.class.getSimpleName());
for (String beanName : beanFactory.getBeanDefinitionNames()) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) clBeanFactory;
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (beanDef.isAbstract())
continue;
if (isConfigClass(beanDef)) { ConfigurationModel model = new ConfigurationModel();
String path = ClassUtils.convertClassNameToResourcePath(beanDef.getBeanClassName());
configClassResources.put(beanName, new ClassPathResource(path));
}
}
ConfigurationModelBeanDefinitionReader modelBeanDefinitionReader = new ConfigurationModelBeanDefinitionReader(beanFactory);
ConfigurationParser parser = new ConfigurationParser(model);
for (String id : configClassResources.keySet())
parser.parse(configClassResources.get(id), id);
ArrayList<UsageError> errors = new ArrayList<UsageError>(); parseAnyConfigurationClasses(beanFactory, model);
model.validate(errors);
if (errors.size() > 0)
throw new MalformedConfigurationException(errors.toArray(new UsageError[] { }));
modelBeanDefinitionReader.loadBeanDefinitions(model); enhanceAnyConfigurationClasses(beanFactory, model);
} }
/**
* Post-processes a BeanFactory in search of Configuration class BeanDefinitions; any candidates
* are then enhanced by a {@link ConfigurationEnhancer}. Candidate status is determined by
* BeanDefinition attribute metadata.
*
* @author Chris Beams
* @see ConfigurationEnhancer
* @see BeanFactoryPostProcessor
*/
private void enhanceAnyConfigurationClasses(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
ConfigurationEnhancer enhancer = new ConfigurationEnhancer(beanFactory, model);
int configClassesEnhanced = 0; private void parseAnyConfigurationClasses(DefaultListableBeanFactory beanFactory, ConfigurationModel model) {
for (String beanName : beanFactory.getBeanDefinitionNames()) { // linked map is important for maintaining predictable ordering of configuration
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); // classes.
// this is important in bean / value override situations.
LinkedHashMap<String, ClassPathResource> configClassResources = new LinkedHashMap<String, ClassPathResource>();
if (!isConfigClass(beanDef)) for (String beanName : beanFactory.getBeanDefinitionNames()) {
continue; BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (beanDef.isAbstract())
continue;
String configClassName = beanDef.getBeanClassName(); if (isConfigClass(beanDef)) {
String path = ClassUtils.convertClassNameToResourcePath(beanDef.getBeanClassName());
configClassResources.put(beanName, new ClassPathResource(path));
}
}
String enhancedClassName = enhancer.enhance(configClassName); ConfigurationModelBeanDefinitionReader modelBeanDefinitionReader = new ConfigurationModelBeanDefinitionReader(
beanFactory);
ConfigurationParser parser = new ConfigurationParser(model);
if (logger.isDebugEnabled()) for (String id : configClassResources.keySet())
logger.debug(String.format("Replacing bean definition '%s' existing class name '%s' with enhanced class name '%s'", parser.parse(configClassResources.get(id), id);
beanName, configClassName, enhancedClassName));
beanDef.setBeanClassName(enhancedClassName); ArrayList<UsageError> errors = new ArrayList<UsageError>();
model.validate(errors);
if (errors.size() > 0)
throw new MalformedConfigurationException(errors.toArray(new UsageError[] {}));
configClassesEnhanced++; modelBeanDefinitionReader.loadBeanDefinitions(model);
} }
if (configClassesEnhanced == 0) /**
logger.warn("Found no @Configuration class BeanDefinitions within " + beanFactory); * Post-processes a BeanFactory in search of Configuration class BeanDefinitions; any
} * candidates are then enhanced by a {@link ConfigurationEnhancer}. Candidate status is
* determined by BeanDefinition attribute metadata.
*
* @author Chris Beams
* @see ConfigurationEnhancer
* @see BeanFactoryPostProcessor
*/
private void enhanceAnyConfigurationClasses(DefaultListableBeanFactory beanFactory,
ConfigurationModel model) {
/** ConfigurationEnhancer enhancer = new ConfigurationEnhancer(beanFactory, model);
* Determines whether the class for <var>beanDef</var> is a {@link Configuration}-annotated
* class. Returns false if <var>beanDef</var> has no class specified. int configClassesEnhanced = 0;
* <p>
* Note: the classloading used within should not be problematic or interfere with tooling in any for (String beanName : beanFactory.getBeanDefinitionNames()) {
* way. BeanFactoryPostProcessing happens only during actual runtime processing via BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
* {@link JavaConfigApplicationContext} or via XML using {@link ConfigurationPostProcessor}. In
* any case, tooling (Spring IDE) will hook in at a lower level than this class and if (!isConfigClass(beanDef))
* thus never encounter this classloading. Should this become problematic, it would not be continue;
* too difficult to replace the following with ASM logic that traverses the class hierarchy in
* order to find whether the class is directly or indirectly annotated with String configClassName = beanDef.getBeanClassName();
* {@link Configuration}.
*/ String enhancedClassName = enhancer.enhance(configClassName);
if (logger.isDebugEnabled())
logger
.debug(String
.format(
"Replacing bean definition '%s' existing class name '%s' with enhanced class name '%s'",
beanName, configClassName, enhancedClassName));
beanDef.setBeanClassName(enhancedClassName);
configClassesEnhanced++;
}
if (configClassesEnhanced == 0)
logger.warn("Found no @Configuration class BeanDefinitions within " + beanFactory);
}
/**
* Determines whether the class for <var>beanDef</var> is a {@link Configuration}
* -annotated class. Returns false if <var>beanDef</var> has no class specified.
* <p>
* Note: the classloading used within should not be problematic or interfere with
* tooling in any way. BeanFactoryPostProcessing happens only during actual runtime
* processing via {@link JavaConfigApplicationContext} or via XML using
* {@link ConfigurationPostProcessor}. In any case, tooling (Spring IDE) will hook in at
* a lower level than this class and thus never encounter this classloading. Should this
* become problematic, it would not be too difficult to replace the following with ASM
* logic that traverses the class hierarchy in order to find whether the class is
* directly or indirectly annotated with {@link Configuration}.
*/
private static boolean isConfigClass(BeanDefinition beanDef) { private static boolean isConfigClass(BeanDefinition beanDef) {
String className = beanDef.getBeanClassName(); String className = beanDef.getBeanClassName();
return className != null return className != null && loadRequiredClass(className).isAnnotationPresent(Configuration.class);
&& loadRequiredClass(className).isAnnotationPresent(Configuration.class);
} }
} }

View File

@ -2,13 +2,14 @@ package test.basic;
import org.junit.Before; import org.junit.Before;
public abstract class AbstractJavaConfigTests { public abstract class AbstractJavaConfigTests {
@Before @Before
public void setUp() { public void setUp() {
} }
//protected // protected
} }

View File

@ -13,49 +13,56 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.beans.Colour; import test.beans.Colour;
import test.beans.TestBean; import test.beans.TestBean;
public class AutowiredConfigurationTests { public class AutowiredConfigurationTests {
public @Test void test() { public @Test
void test() {
ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext( ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext(
AutowiredConfigurationTests.class.getSimpleName() + ".xml", AutowiredConfigurationTests.class.getSimpleName() + ".xml", AutowiredConfigurationTests.class);
AutowiredConfigurationTests.class);
assertThat(factory.getBean("colour", Colour.class), equalTo(Colour.RED)); assertThat(factory.getBean("colour", Colour.class), equalTo(Colour.RED));
assertThat(factory.getBean("testBean", TestBean.class).getName(), equalTo(Colour.RED.toString())); assertThat(factory.getBean("testBean", TestBean.class).getName(), equalTo(Colour.RED.toString()));
} }
@Configuration @Configuration
static class AutowiredConfig { static class AutowiredConfig {
private @Autowired Colour colour; private @Autowired
Colour colour;
public @Bean TestBean testBean() {
public @Bean
TestBean testBean() {
return new TestBean(colour.toString()); return new TestBean(colour.toString());
} }
} }
@Configuration @Configuration
static class ColorConfig { static class ColorConfig {
public @Bean Colour colour() { return Colour.RED; } public @Bean
Colour colour() {
return Colour.RED;
}
} }
public @Test
public @Test void testValueInjection() { void testValueInjection() {
System.setProperty("myProp", "foo"); System.setProperty("myProp", "foo");
ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext( ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext(
"ValueInjectionTests.xml", AutowiredConfigurationTests.class); "ValueInjectionTests.xml", AutowiredConfigurationTests.class);
TestBean testBean = factory.getBean("testBean", TestBean.class); TestBean testBean = factory.getBean("testBean", TestBean.class);
assertThat(testBean.getName(), equalTo("foo")); assertThat(testBean.getName(), equalTo("foo"));
} }
@Configuration @Configuration
static class ValueConfig { static class ValueConfig {
@Value("#{systemProperties.myProp}") @Value("#{systemProperties.myProp}")
private String name = "default"; private String name = "default";
public @Bean TestBean testBean() { public @Bean
TestBean testBean() {
return new TestBean(name); return new TestBean(name);
} }
} }

View File

@ -17,82 +17,87 @@ import org.springframework.config.java.support.ConfigurationPostProcessor;
import test.beans.ITestBean; import test.beans.ITestBean;
import test.beans.TestBean; import test.beans.TestBean;
public class BasicTests { public class BasicTests {
/** /**
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition} for * Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition} for
* each of the given {@link Configuration} <var>configClasses</var>, and then post-processes * each of the given {@link Configuration} <var>configClasses</var>, and then
* the factory using JavaConfig's {@link ConfigurationPostProcessor}. When complete, * post-processes the factory using JavaConfig's {@link ConfigurationPostProcessor}.
* the factory is ready to service requests for any {@link Bean} methods declared by * When complete, the factory is ready to service requests for any {@link Bean} methods
* <var>configClasses</var>. * declared by <var>configClasses</var>.
* *
* @param configClasses the {@link Configuration} classes under test. may be an empty list. * @param configClasses the {@link Configuration} classes under test. may be an empty
* list.
* *
* @return fully initialized and post-processed {@link BeanFactory} * @return fully initialized and post-processed {@link BeanFactory}
*/ */
private static BeanFactory initBeanFactory(Class<?>... configClasses) { private static BeanFactory initBeanFactory(Class<?>... configClasses) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
for(Class<?> configClass : configClasses) { for (Class<?> configClass : configClasses) {
String configBeanName = configClass.getName(); String configBeanName = configClass.getName();
factory.registerBeanDefinition(configBeanName, rootBeanDefinition(configClass).getBeanDefinition()); factory.registerBeanDefinition(configBeanName, rootBeanDefinition(configClass)
.getBeanDefinition());
} }
new ConfigurationPostProcessor().postProcessBeanFactory(factory); new ConfigurationPostProcessor().postProcessBeanFactory(factory);
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
return factory; return factory;
} }
@Test @Test
public void simplestPossibleConfiguration() { public void simplestPossibleConfiguration() {
BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class); BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class);
String stringBean = factory.getBean("stringBean", String.class); String stringBean = factory.getBean("stringBean", String.class);
assertThat(stringBean, equalTo("foo")); assertThat(stringBean, equalTo("foo"));
} }
@Configuration @Configuration
static class SimplestPossibleConfig { static class SimplestPossibleConfig {
public @Bean String stringBean() { public @Bean
String stringBean() {
return "foo"; return "foo";
} }
} }
@Test @Test
public void configurationWithPrototypeScopedBeans() { public void configurationWithPrototypeScopedBeans() {
BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class); BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class);
TestBean foo = factory.getBean("foo", TestBean.class); TestBean foo = factory.getBean("foo", TestBean.class);
ITestBean bar = factory.getBean("bar", ITestBean.class); ITestBean bar = factory.getBean("bar", ITestBean.class);
ITestBean baz = factory.getBean("baz", ITestBean.class); ITestBean baz = factory.getBean("baz", ITestBean.class);
assertThat(foo.getSpouse(), sameInstance(bar)); assertThat(foo.getSpouse(), sameInstance(bar));
assertThat(bar.getSpouse(), not(sameInstance(baz))); assertThat(bar.getSpouse(), not(sameInstance(baz)));
} }
@Configuration @Configuration
static class ConfigWithPrototypeBean { static class ConfigWithPrototypeBean {
public @Bean TestBean foo() { public @Bean
TestBean foo() {
TestBean foo = new TestBean("foo"); TestBean foo = new TestBean("foo");
foo.setSpouse(bar()); foo.setSpouse(bar());
return foo; return foo;
} }
public @Bean TestBean bar() { public @Bean
TestBean bar() {
TestBean bar = new TestBean("bar"); TestBean bar = new TestBean("bar");
bar.setSpouse(baz()); bar.setSpouse(baz());
return bar; return bar;
} }
@Bean(scope=Scopes.PROTOTYPE) @Bean(scope = Scopes.PROTOTYPE)
public TestBean baz() { public TestBean baz() {
return new TestBean("bar"); return new TestBean("bar");
} }
} }
} }

View File

@ -20,19 +20,21 @@ import org.springframework.core.enums.ShortCodedLabeledEnum;
/** /**
* TODO: JAVADOC * TODO: JAVADOC
* *
* @author Rob Harrop * @author Rob Harrop
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class Colour extends ShortCodedLabeledEnum { public class Colour extends ShortCodedLabeledEnum {
public static final Colour RED = new Colour(0, "RED"); public static final Colour RED = new Colour(0, "RED");
public static final Colour BLUE = new Colour(1, "BLUE"); public static final Colour BLUE = new Colour(1, "BLUE");
public static final Colour GREEN = new Colour(2, "GREEN"); public static final Colour GREEN = new Colour(2, "GREEN");
public static final Colour PURPLE = new Colour(3, "PURPLE"); public static final Colour PURPLE = new Colour(3, "PURPLE");
private Colour(int code, String label) { super(code, label); } private Colour(int code, String label) {
super(code, label);
}
} }

View File

@ -18,14 +18,20 @@ package test.beans;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
public class DependsOnTestBean { public class DependsOnTestBean {
public TestBean tb; public TestBean tb;
private int state; private int state;
public void setTestBean(TestBean tb) { this.tb = tb; } public void setTestBean(TestBean tb) {
this.tb = tb;
}
public int getState() { return state; } public int getState() {
return state;
}
public TestBean getTestBean() { return tb; } public TestBean getTestBean() {
return tb;
}
} }

View File

@ -19,6 +19,6 @@ package test.beans;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
public interface INestedTestBean { public interface INestedTestBean {
String getCompany(); String getCompany();
} }

View File

@ -19,6 +19,6 @@ package test.beans;
/** TODO: JAVADOC */ /** TODO: JAVADOC */
public interface IOther { public interface IOther {
void absquatulate(); void absquatulate();
} }

View File

@ -19,44 +19,44 @@ import java.io.IOException;
/** /**
* Interface used for test beans. Two methods are the same as on Person, but if * Interface used for test beans. Two methods are the same as on Person, but if this extends
* this extends person it breaks quite a few tests * person it breaks quite a few tests
* *
* @author Rod Johnson * @author Rod Johnson
*/ */
public interface ITestBean { public interface ITestBean {
int getAge(); int getAge();
void setAge(int age); void setAge(int age);
String getName(); String getName();
void setName(String name); void setName(String name);
ITestBean getSpouse(); ITestBean getSpouse();
void setSpouse(ITestBean spouse); void setSpouse(ITestBean spouse);
/** /**
* t null no error. * t null no error.
*/ */
void exceptional(Throwable t) throws Throwable; void exceptional(Throwable t) throws Throwable;
Object returnsThis(); Object returnsThis();
INestedTestBean getDoctor(); INestedTestBean getDoctor();
INestedTestBean getLawyer(); INestedTestBean getLawyer();
IndexedTestBean getNestedIndexedBean(); IndexedTestBean getNestedIndexedBean();
/** /**
* Increment the age by one. * Increment the age by one.
* *
* @return the previous age * @return the previous age
*/ */
int haveBirthday(); int haveBirthday();
void unreliableFileOperation() throws IOException; void unreliableFileOperation() throws IOException;
} }

View File

@ -27,89 +27,119 @@ import java.util.TreeSet;
/** /**
* TODO: JAVADOC * TODO: JAVADOC
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 11.11.2003 * @since 11.11.2003
*/ */
public class IndexedTestBean { public class IndexedTestBean {
private TestBean[] array; private TestBean[] array;
private Collection<TestBean> collection; private Collection<TestBean> collection;
private List<TestBean> list; private List<TestBean> list;
private Set<TestBean> set; private Set<TestBean> set;
private SortedSet<TestBean> sortedSet; private SortedSet<TestBean> sortedSet;
private Map<String, Object> map; private Map<String, Object> map;
private SortedMap<String, TestBean> sortedMap; private SortedMap<String, TestBean> sortedMap;
public IndexedTestBean() { this(true); } public IndexedTestBean() {
this(true);
}
public IndexedTestBean(boolean populate) { public IndexedTestBean(boolean populate) {
if (populate) { if (populate) {
populate(); populate();
} }
} }
public void populate() { public void populate() {
TestBean tb0 = new TestBean("name0", 0); TestBean tb0 = new TestBean("name0", 0);
TestBean tb1 = new TestBean("name1", 0); TestBean tb1 = new TestBean("name1", 0);
TestBean tb2 = new TestBean("name2", 0); TestBean tb2 = new TestBean("name2", 0);
TestBean tb3 = new TestBean("name3", 0); TestBean tb3 = new TestBean("name3", 0);
TestBean tb4 = new TestBean("name4", 0); TestBean tb4 = new TestBean("name4", 0);
TestBean tb5 = new TestBean("name5", 0); TestBean tb5 = new TestBean("name5", 0);
TestBean tb6 = new TestBean("name6", 0); TestBean tb6 = new TestBean("name6", 0);
TestBean tb7 = new TestBean("name7", 0); TestBean tb7 = new TestBean("name7", 0);
TestBean tbX = new TestBean("nameX", 0); TestBean tbX = new TestBean("nameX", 0);
TestBean tbY = new TestBean("nameY", 0); TestBean tbY = new TestBean("nameY", 0);
this.array = new TestBean[] { tb0, tb1 }; this.array = new TestBean[] { tb0, tb1 };
this.list = new ArrayList<TestBean>(); this.list = new ArrayList<TestBean>();
this.list.add(tb2); this.list.add(tb2);
this.list.add(tb3); this.list.add(tb3);
this.set = new TreeSet<TestBean>(); this.set = new TreeSet<TestBean>();
this.set.add(tb6); this.set.add(tb6);
this.set.add(tb7); this.set.add(tb7);
this.map = new HashMap<String, Object>(); this.map = new HashMap<String, Object>();
this.map.put("key1", tb4); this.map.put("key1", tb4);
this.map.put("key2", tb5); this.map.put("key2", tb5);
this.map.put("key.3", tb5); this.map.put("key.3", tb5);
List<TestBean> list = new ArrayList<TestBean>(); List<TestBean> list = new ArrayList<TestBean>();
list.add(tbX); list.add(tbX);
list.add(tbY); list.add(tbY);
this.map.put("key4", list); this.map.put("key4", list);
} }
public TestBean[] getArray() { return array; } public TestBean[] getArray() {
return array;
}
public void setArray(TestBean[] array) { this.array = array; } public void setArray(TestBean[] array) {
this.array = array;
}
public Collection<?> getCollection() { return collection; } public Collection<?> getCollection() {
return collection;
}
public void setCollection(Collection<TestBean> collection) { this.collection = collection; } public void setCollection(Collection<TestBean> collection) {
this.collection = collection;
}
public List<TestBean> getList() { return list; } public List<TestBean> getList() {
return list;
}
public void setList(List<TestBean> list) { this.list = list; } public void setList(List<TestBean> list) {
this.list = list;
}
public Set<TestBean> getSet() { return set; } public Set<TestBean> getSet() {
return set;
}
public void setSet(Set<TestBean> set) { this.set = set; } public void setSet(Set<TestBean> set) {
this.set = set;
}
public SortedSet<TestBean> getSortedSet() { return sortedSet; } public SortedSet<TestBean> getSortedSet() {
return sortedSet;
}
public void setSortedSet(SortedSet<TestBean> sortedSet) { this.sortedSet = sortedSet; } public void setSortedSet(SortedSet<TestBean> sortedSet) {
this.sortedSet = sortedSet;
}
public Map<String, Object> getMap() { return map; } public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) { this.map = map; } public void setMap(Map<String, Object> map) {
this.map = map;
}
public SortedMap<String, TestBean> getSortedMap() { return sortedMap; } public SortedMap<String, TestBean> getSortedMap() {
return sortedMap;
}
public void setSortedMap(SortedMap<String, TestBean> sortedMap) { this.sortedMap = sortedMap; } public void setSortedMap(SortedMap<String, TestBean> sortedMap) {
this.sortedMap = sortedMap;
}
} }

View File

@ -17,46 +17,47 @@ package test.beans;
/** /**
* Simple nested test bean used for testing bean factories, AOP framework etc. * Simple nested test bean used for testing bean factories, AOP framework etc.
* *
* @author Trevor D. Cook * @author Trevor D. Cook
* @since 30.09.2003 * @since 30.09.2003
*/ */
public class NestedTestBean implements INestedTestBean { public class NestedTestBean implements INestedTestBean {
private String company = ""; private String company = "";
public NestedTestBean() { } public NestedTestBean() {
}
public NestedTestBean(String company) { public NestedTestBean(String company) {
setCompany(company); setCompany(company);
} }
public void setCompany(String company) { public void setCompany(String company) {
this.company = ((company != null) ? company : ""); this.company = ((company != null) ? company : "");
} }
public String getCompany() { public String getCompany() {
return company; return company;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof NestedTestBean)) { if (!(obj instanceof NestedTestBean)) {
return false; return false;
} }
NestedTestBean ntb = (NestedTestBean) obj; NestedTestBean ntb = (NestedTestBean) obj;
return this.company.equals(ntb.company); return this.company.equals(ntb.company);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return this.company.hashCode(); return this.company.hashCode();
} }
@Override @Override
public String toString() { public String toString() {
return "NestedTestBean: " + this.company; return "NestedTestBean: " + this.company;
} }
} }

View File

@ -37,259 +37,383 @@ import java.util.Set;
/** /**
* Simple test bean used for testing bean factories, AOP framework etc. * Simple test bean used for testing bean factories, AOP framework etc.
* *
* @author Rod Johnson * @author Rod Johnson
* @since 15 April 2001 * @since 15 April 2001
*/ */
public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOther, Comparable<Object> { public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOther, Comparable<Object> {
private String beanName; private String beanName;
private String country; private String country;
private BeanFactory beanFactory; private BeanFactory beanFactory;
private boolean postProcessed; private boolean postProcessed;
private String name; private String name;
private String sex; private String sex;
private int age; private int age;
private boolean jedi; private boolean jedi;
private ITestBean spouse; private ITestBean spouse;
private String touchy; private String touchy;
private String[] stringArray; private String[] stringArray;
private Integer[] someIntegerArray; private Integer[] someIntegerArray;
private Date date = new Date(); private Date date = new Date();
private Float myFloat = new Float(0.0); private Float myFloat = new Float(0.0);
private Collection<?> friends = new LinkedList<Object>(); private Collection<?> friends = new LinkedList<Object>();
private Set<?> someSet = new HashSet<Object>(); private Set<?> someSet = new HashSet<Object>();
private Map<?, ?> someMap = new HashMap<Object, Object>(); private Map<?, ?> someMap = new HashMap<Object, Object>();
private List<?> someList = new ArrayList<Object>(); private List<?> someList = new ArrayList<Object>();
private Properties someProperties = new Properties(); private Properties someProperties = new Properties();
private INestedTestBean doctor = new NestedTestBean(); private INestedTestBean doctor = new NestedTestBean();
private INestedTestBean lawyer = new NestedTestBean(); private INestedTestBean lawyer = new NestedTestBean();
private IndexedTestBean nestedIndexedBean; private IndexedTestBean nestedIndexedBean;
private boolean destroyed; private boolean destroyed;
private Number someNumber; private Number someNumber;
private Colour favouriteColour; private Colour favouriteColour;
private Boolean someBoolean; private Boolean someBoolean;
private List<?> otherColours; private List<?> otherColours;
private List<?> pets; private List<?> pets;
public TestBean() { } public TestBean() {
}
public TestBean(String name) { this.name = name; } public TestBean(String name) {
this.name = name;
}
public TestBean(ITestBean spouse) { this.spouse = spouse; } public TestBean(ITestBean spouse) {
this.spouse = spouse;
}
public TestBean(String name, int age) { public TestBean(String name, int age) {
this.name = name; this.name = name;
this.age = age; this.age = age;
} }
public TestBean(ITestBean spouse, Properties someProperties) { public TestBean(ITestBean spouse, Properties someProperties) {
this.spouse = spouse; this.spouse = spouse;
this.someProperties = someProperties; this.someProperties = someProperties;
} }
public void setBeanName(String beanName) { this.beanName = beanName; } public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getBeanName() { return beanName; } public String getBeanName() {
return beanName;
}
public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public BeanFactory getBeanFactory() { return beanFactory; } public BeanFactory getBeanFactory() {
return beanFactory;
}
public void setPostProcessed(boolean postProcessed) { this.postProcessed = postProcessed; } public void setPostProcessed(boolean postProcessed) {
this.postProcessed = postProcessed;
}
public boolean isPostProcessed() { return postProcessed; } public boolean isPostProcessed() {
return postProcessed;
}
public String getName() { return name; } public String getName() {
return name;
}
public void setName(String name) { this.name = name; } public void setName(String name) {
this.name = name;
}
public String getSex() { return sex; } public String getSex() {
return sex;
}
public void setSex(String sex) { this.sex = sex; } public void setSex(String sex) {
this.sex = sex;
}
public int getAge() { return age; } public int getAge() {
return age;
}
public void setAge(int age) { this.age = age; } public void setAge(int age) {
this.age = age;
}
public boolean isJedi() { return jedi; } public boolean isJedi() {
return jedi;
}
public void setJedi(boolean jedi) { this.jedi = jedi; } public void setJedi(boolean jedi) {
this.jedi = jedi;
}
public ITestBean getSpouse() { return spouse; } public ITestBean getSpouse() {
return spouse;
}
public void setSpouse(ITestBean spouse) { this.spouse = spouse; } public void setSpouse(ITestBean spouse) {
this.spouse = spouse;
}
public String getTouchy() { return touchy; } public String getTouchy() {
return touchy;
}
public void setTouchy(String touchy) throws Exception { public void setTouchy(String touchy) throws Exception {
if (touchy.indexOf('.') != -1) { if (touchy.indexOf('.') != -1) {
throw new Exception("Can't contain a ."); throw new Exception("Can't contain a .");
} }
if (touchy.indexOf(',') != -1) { if (touchy.indexOf(',') != -1) {
throw new NumberFormatException("Number format exception: contains a ,"); throw new NumberFormatException("Number format exception: contains a ,");
} }
this.touchy = touchy; this.touchy = touchy;
} }
public String getCountry() { return country; } public String getCountry() {
return country;
}
public void setCountry(String country) { this.country = country; } public void setCountry(String country) {
this.country = country;
}
public String[] getStringArray() { return stringArray; } public String[] getStringArray() {
return stringArray;
}
public void setStringArray(String[] stringArray) { this.stringArray = stringArray; } public void setStringArray(String[] stringArray) {
this.stringArray = stringArray;
}
public Integer[] getSomeIntegerArray() { return someIntegerArray; } public Integer[] getSomeIntegerArray() {
return someIntegerArray;
}
public void setSomeIntegerArray(Integer[] someIntegerArray) { this.someIntegerArray = someIntegerArray; } public void setSomeIntegerArray(Integer[] someIntegerArray) {
this.someIntegerArray = someIntegerArray;
}
public Date getDate() { return date; } public Date getDate() {
return date;
}
public void setDate(Date date) { this.date = date; } public void setDate(Date date) {
this.date = date;
}
public Float getMyFloat() { return myFloat; } public Float getMyFloat() {
return myFloat;
}
public void setMyFloat(Float myFloat) { this.myFloat = myFloat; } public void setMyFloat(Float myFloat) {
this.myFloat = myFloat;
}
public Collection<?> getFriends() { return friends; } public Collection<?> getFriends() {
return friends;
}
public void setFriends(Collection<?> friends) { this.friends = friends; } public void setFriends(Collection<?> friends) {
this.friends = friends;
}
public Set<?> getSomeSet() { return someSet; } public Set<?> getSomeSet() {
return someSet;
public void setSomeSet(Set<?> someSet) { this.someSet = someSet; } }
public Map<?, ?> getSomeMap() { return someMap; } public void setSomeSet(Set<?> someSet) {
this.someSet = someSet;
public void setSomeMap(Map<?, ?> someMap) { this.someMap = someMap; } }
public List<?> getSomeList() { return someList; } public Map<?, ?> getSomeMap() {
return someMap;
public void setSomeList(List<?> someList) { this.someList = someList; } }
public Properties getSomeProperties() { return someProperties; } public void setSomeMap(Map<?, ?> someMap) {
this.someMap = someMap;
public void setSomeProperties(Properties someProperties) { this.someProperties = someProperties; } }
public INestedTestBean getDoctor() { return doctor; } public List<?> getSomeList() {
return someList;
public INestedTestBean getLawyer() { return lawyer; } }
public void setDoctor(INestedTestBean bean) { doctor = bean; } public void setSomeList(List<?> someList) {
this.someList = someList;
public void setLawyer(INestedTestBean bean) { lawyer = bean; } }
public Number getSomeNumber() { return someNumber; } public Properties getSomeProperties() {
return someProperties;
public void setSomeNumber(Number someNumber) { this.someNumber = someNumber; } }
public Colour getFavouriteColour() { return favouriteColour; } public void setSomeProperties(Properties someProperties) {
this.someProperties = someProperties;
public void setFavouriteColour(Colour favouriteColour) { this.favouriteColour = favouriteColour; } }
public Boolean getSomeBoolean() { return someBoolean; } public INestedTestBean getDoctor() {
return doctor;
public void setSomeBoolean(Boolean someBoolean) { this.someBoolean = someBoolean; } }
public IndexedTestBean getNestedIndexedBean() { return nestedIndexedBean; } public INestedTestBean getLawyer() {
return lawyer;
public void setNestedIndexedBean(IndexedTestBean nestedIndexedBean) { this.nestedIndexedBean = nestedIndexedBean; } }
public List<?> getOtherColours() { return otherColours; } public void setDoctor(INestedTestBean bean) {
doctor = bean;
public void setOtherColours(List<?> otherColours) { this.otherColours = otherColours; } }
public List<?> getPets() { return pets; } public void setLawyer(INestedTestBean bean) {
lawyer = bean;
public void setPets(List<?> pets) { this.pets = pets; } }
/** public Number getSomeNumber() {
* @see ITestBean#exceptional(Throwable) return someNumber;
*/ }
public void exceptional(Throwable t) throws Throwable {
if (t != null) { public void setSomeNumber(Number someNumber) {
throw t; this.someNumber = someNumber;
} }
}
public Colour getFavouriteColour() {
public void unreliableFileOperation() throws IOException { throw new IOException(); } return favouriteColour;
}
/**
* @see ITestBean#returnsThis() public void setFavouriteColour(Colour favouriteColour) {
*/ this.favouriteColour = favouriteColour;
public Object returnsThis() { return this; } }
/** public Boolean getSomeBoolean() {
* @see IOther#absquatulate() return someBoolean;
*/ }
public void absquatulate() { }
public void setSomeBoolean(Boolean someBoolean) {
public int haveBirthday() { return age++; } this.someBoolean = someBoolean;
}
public void destroy() { this.destroyed = true; }
public IndexedTestBean getNestedIndexedBean() {
public boolean wasDestroyed() { return destroyed; } return nestedIndexedBean;
}
@Override
public boolean equals(Object other) { public void setNestedIndexedBean(IndexedTestBean nestedIndexedBean) {
if (this == other) { this.nestedIndexedBean = nestedIndexedBean;
return true; }
}
public List<?> getOtherColours() {
if ((other == null) || !(other instanceof TestBean)) { return otherColours;
return false; }
}
public void setOtherColours(List<?> otherColours) {
TestBean tb2 = (TestBean) other; this.otherColours = otherColours;
return (ObjectUtils.nullSafeEquals(this.name, tb2.name) && (this.age == tb2.age)); }
}
public List<?> getPets() {
@Override return pets;
public int hashCode() { return this.age; } }
public int compareTo(Object other) { public void setPets(List<?> pets) {
if ((this.name != null) && (other instanceof TestBean)) { this.pets = pets;
return this.name.compareTo(((TestBean) other).getName()); }
}
/**
return 1; * @see ITestBean#exceptional(Throwable)
} */
public void exceptional(Throwable t) throws Throwable {
@Override if (t != null) {
public String toString() { throw t;
String s = "name=" + name + "; age=" + age + "; touchy=" + touchy; }
s += "; spouse={" + ((spouse != null) ? spouse.getName() : null) + "}"; }
return s;
} public void unreliableFileOperation() throws IOException {
throw new IOException();
}
/**
* @see ITestBean#returnsThis()
*/
public Object returnsThis() {
return this;
}
/**
* @see IOther#absquatulate()
*/
public void absquatulate() {
}
public int haveBirthday() {
return age++;
}
public void destroy() {
this.destroyed = true;
}
public boolean wasDestroyed() {
return destroyed;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if ((other == null) || !(other instanceof TestBean)) {
return false;
}
TestBean tb2 = (TestBean) other;
return (ObjectUtils.nullSafeEquals(this.name, tb2.name) && (this.age == tb2.age));
}
@Override
public int hashCode() {
return this.age;
}
public int compareTo(Object other) {
if ((this.name != null) && (other instanceof TestBean)) {
return this.name.compareTo(((TestBean) other).getName());
}
return 1;
}
@Override
public String toString() {
String s = "name=" + name + "; age=" + age + "; touchy=" + touchy;
s += "; spouse={" + ((spouse != null) ? spouse.getName() : null) + "}";
return s;
}
} }