+ Eliminated UsageError/Validatable/MalformedConfigurationException in favor of existing Problem/ProblemReporter types

+ Pruned a number of attributes from the @Bean and @Configuration annotations
This commit is contained in:
Chris Beams 2009-03-07 03:22:22 +00:00
parent c41c64389f
commit 100ba6599f
16 changed files with 257 additions and 503 deletions

View File

@ -22,10 +22,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
/** /**
* Annotation to be applied to methods that create beans in a Spring context. The name of * Annotation to be applied to methods that create beans in a Spring context. The name of
@ -65,33 +61,39 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
@Documented @Documented
public @interface Bean { public @interface Bean {
/** // TODO:
* 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_INFRASTRUCTURE // * @see BeanDefinition#ROLE_APPLICATION
* @see BeanDefinition#ROLE_SUPPORT // * @see BeanDefinition#ROLE_INFRASTRUCTURE
* // * @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;
String[] name() default {};
/** // TODO: Prune aliases, favor name[]
* Bean aliases. // /**
*/ // * Bean aliases.
String[] aliases() default {}; // */
// String[] aliases() default {};
/** // TODO: favor @Scope
* Scope: whether the bean is a singleton, prototype or custom scope. Default is // /**
* singleton. // * Scope: whether the bean is a singleton, prototype or custom scope. Default is
*/ // * singleton.
String scope() default StandardScopes.SINGLETON; // */
// String scope() default StandardScopes.SINGLETON;
/** // TODO: prune autowiring?
* Bean autowire strategy. // /**
*/ // * Bean autowire strategy.
Autowire autowire() default Autowire.INHERITED; // */
// Autowire autowire() default Autowire.INHERITED;
// /** // /**
// * Bean lazy strategy. // * Bean lazy strategy.
@ -111,13 +113,14 @@ public @interface Bean {
* Bean init method name. Normally this is not needed, as the initialization (with * Bean init method name. Normally this is not needed, as the initialization (with
* parameterization) can be done directly through java code. * parameterization) can be done directly through java code.
*/ */
String initMethodName() default ""; String initMethod() default "";
/** /**
* Bean destroy method name. * Bean destroy method name.
*/ */
String destroyMethodName() default ""; String destroyMethod() default "";
// TODO: Prune DependencyCheck
// /** // /**
// * Bean dependency check strategy. // * Bean dependency check strategy.
// */ // */
@ -128,19 +131,23 @@ public @interface Bean {
*/ */
String[] dependsOn() default {}; String[] dependsOn() default {};
// TODO: Prune @Meta
// /** // /**
// * Metadata for the current bean. // * Metadata for the current bean.
// */ // */
// Meta[] meta() default { }; // Meta[] meta() default { };
/** // TODO: Prune allowOverriding
* Allow the bean to be overridden in another JavaConfig, XML or other non-Java // /**
* configuration. This is consistent with DefaultListableBeanFactory's // * Allow the bean to be overridden in another JavaConfig, XML or other non-Java
* allowBeanDefinitionOverriding property, which defaults to true. // * configuration. This is consistent with DefaultListableBeanFactory's
* // * allowBeanDefinitionOverriding property, which defaults to true.
* @return whether overriding of this bean is allowed // *
*/ // * @return whether overriding of this bean is allowed
boolean allowOverriding() default true; // */
// boolean allowOverriding() default true;
//
//String name() default "";
} }

View File

@ -16,16 +16,22 @@
package org.springframework.config.java; package org.springframework.config.java;
import static java.lang.String.*; import static java.lang.String.*;
import static org.springframework.config.java.StandardScopes.*;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
public final class BeanMethod implements Validatable { public final class BeanMethod {
private final String name; private final String name;
private final int modifiers; private final int modifiers;
@ -65,9 +71,8 @@ public final class BeanMethod implements Validatable {
} }
/** /**
* Returns the annotation on this method matching <var>annoType</var> or null * @return the annotation on this method matching <var>annoType</var> or
* IllegalStateException} if not present. * {@literal null} if not present.
*
* @see #getRequiredAnnotation(Class) * @see #getRequiredAnnotation(Class)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -80,9 +85,8 @@ public final class BeanMethod implements Validatable {
} }
/** /**
* Returns the annotation on this method matching <var>annoType</var> or throws * @return the annotation on this method matching <var>annoType</var>
* {@link IllegalStateException} if not present. * @throws {@link IllegalStateException} if not present
*
* @see #getAnnotation(Class) * @see #getAnnotation(Class)
*/ */
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) { public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
@ -96,7 +100,7 @@ public final class BeanMethod implements Validatable {
} }
/** /**
* Sets up bi-directional relationship between this method and its declaring class. * Set up a bi-directional relationship between this method and its declaring class.
* *
* @see ConfigurationClass#addMethod(BeanMethod) * @see ConfigurationClass#addMethod(BeanMethod)
*/ */
@ -116,22 +120,20 @@ public final class BeanMethod implements Validatable {
return lineNumber; return lineNumber;
} }
public void validate(List<UsageError> errors) { public void validate(ProblemReporter problemReporter) {
if (Modifier.isPrivate(getModifiers())) if (Modifier.isPrivate(getModifiers()))
errors.add(new PrivateMethodError()); problemReporter.error(new PrivateMethodError());
if (Modifier.isFinal(getModifiers())) if (Modifier.isFinal(getModifiers()))
errors.add(new FinalMethodError()); problemReporter.error(new FinalMethodError());
if (this.getAnnotation(ScopedProxy.class) == null) if (this.getAnnotation(ScopedProxy.class) == null)
return; return;
Bean bean =this.getRequiredAnnotation(Bean.class); Scope scope = this.getAnnotation(Scope.class);
if(scope == null || scope.equals(SINGLETON) || scope.equals(PROTOTYPE))
if (bean.scope().equals(StandardScopes.SINGLETON) problemReporter.error(new InvalidScopedProxyDeclarationError(this));
|| bean.scope().equals(StandardScopes.PROTOTYPE))
errors.add(new InvalidScopedProxyDeclarationError(this));
} }
@Override @Override
@ -181,27 +183,17 @@ public final class BeanMethod implements Validatable {
return true; return true;
} }
/** JavaConfigMethods must be visible (non-private) in order to accommodate CGLIB. */ /** {@link Bean} methods must be non-private in order to accommodate CGLIB. */
public class PrivateMethodError extends UsageError { public class PrivateMethodError extends Problem {
public PrivateMethodError() { public PrivateMethodError() {
super(getDeclaringClass(), getLineNumber()); super(format("method '%s' may not be private", getName()), new Location(new FileSystemResource("/dev/null")));
}
@Override
public String getDescription() {
return format("method '%s' may not be private", getName());
} }
} }
/** JavaConfigMethods must be extensible (non-final) in order to accommodate CGLIB. */ /** {@link Bean} methods must be non-final in order to accommodate CGLIB. */
public class FinalMethodError extends UsageError { public class FinalMethodError extends Problem {
public FinalMethodError() { public FinalMethodError() {
super(getDeclaringClass(), getLineNumber()); super(format("method '%s' may not be final. remove the final modifier to continue", getName()), new Location(new FileSystemResource("/dev/null")));
}
@Override
public String getDescription() {
return format("method '%s' may not be final - remove the final modifier to continue", getName());
} }
} }

View File

@ -4,6 +4,8 @@ import static java.lang.String.*;
import static org.springframework.util.StringUtils.*; import static org.springframework.util.StringUtils.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -15,6 +17,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
@ -42,20 +45,28 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
beanDef.setFactoryMethodName(method.getName()); beanDef.setFactoryMethodName(method.getName());
Bean bean = method.getRequiredAnnotation(Bean.class); Bean bean = method.getRequiredAnnotation(Bean.class);
Configuration defaults = configClass.getMetadata(); // TODO: prune defaults
//Configuration defaults = configClass.getMetadata();
// consider scoping // consider scoping
beanDef.setScope(bean.scope()); Scope scope = method.getAnnotation(Scope.class);
if(scope != null)
beanDef.setScope(scope.value());
// consider autowiring // TODO: prune autowiring
if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire")) // // consider autowiring
beanDef.setAutowireMode(bean.autowire().value()); // if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire"))
else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class, // beanDef.setAutowireMode(bean.autowire().value());
"defaultAutowire")) // else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class,
beanDef.setAutowireMode(defaults.defaultAutowire().value()); // "defaultAutowire"))
// beanDef.setAutowireMode(defaults.defaultAutowire().value());
String beanName = method.getName(); // consider name and any aliases
ArrayList<String> names = new ArrayList<String>(Arrays.asList(bean.name()));
String beanName = (names.size() > 0) ? names.remove(0) : method.getName();
for (String alias : bean.name())
registry.registerAlias(beanName, alias);
// has this already been overriden (i.e.: via XML)? // has this already been overriden (i.e.: via XML)?
if (containsBeanDefinitionIncludingAncestry(beanName, registry)) { if (containsBeanDefinitionIncludingAncestry(beanName, registry)) {
@ -65,11 +76,12 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
// no -> then it's an external override, probably XML // no -> then it's an external override, probably XML
// ensure that overriding is ok // TODO: Prune this
if (bean.allowOverriding() == false) { // // ensure that overriding is ok
UsageError error = configClass.new IllegalBeanOverrideError(null, method); // if (bean.allowOverriding() == false) {
throw new MalformedConfigurationException(error); // UsageError error = configClass.new IllegalBeanOverrideError(null, method);
} // throw new MalformedConfigurationException(error);
// }
// overriding is legal, return immediately // overriding is legal, return immediately
logger.info(format("Skipping loading bean definition for %s: a definition for bean " logger.info(format("Skipping loading bean definition for %s: a definition for bean "
@ -78,13 +90,6 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
} }
} }
// propagate this bean's 'role' attribute
beanDef.setRole(bean.role());
// consider aliases
for (String alias : bean.aliases())
registry.registerAlias(beanName, alias);
// TODO: re-enable for Lazy support // TODO: re-enable for Lazy support
// // is this bean marked as primary for disambiguation? // // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE) // if (bean.primary() == Primary.TRUE)
@ -96,12 +101,12 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
// beanDef.setLazyInit(true); // beanDef.setLazyInit(true);
// does this bean have a custom init-method specified? // does this bean have a custom init-method specified?
String initMethodName = bean.initMethodName(); String initMethodName = bean.initMethod();
if (hasText(initMethodName)) if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName); beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified? // does this bean have a custom destroy-method specified?
String destroyMethodName = bean.destroyMethodName(); String destroyMethodName = bean.destroyMethod();
if (hasText(destroyMethodName)) if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName); beanDef.setDestroyMethodName(destroyMethodName);

View File

@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -44,9 +42,13 @@ import org.springframework.stereotype.Component;
* fact that this annotation is meta-annotated with {@link Component @Component}. * fact that this annotation is meta-annotated with {@link Component @Component}.
* </p> * </p>
* *
* May be used in conjunction with {@link Lazy}
*
* @author Rod Johnson * @author Rod Johnson
* @author Chris Beams * @author Chris Beams
* @since 3.0 * @since 3.0
* @see Lazy
* @see Bean
*/ */
@Component @Component
@Target( { ElementType.TYPE }) @Target( { ElementType.TYPE })
@ -55,22 +57,25 @@ import org.springframework.stereotype.Component;
@Documented @Documented
public @interface Configuration { public @interface Configuration {
/** // TODO: consider pruning @Configuration(name[])
* Configuration name. Allow different variants, such as test, production etc. Default // /**
* will always match. // * Configuration name. Allow different variants, such as test, production etc. Default
* // * will always match.
* @return // *
*/ // * @return
String[] names() default ""; // */
// String[] name() default "";
/** // TODO: Prune defaultAutowire
* Specifies the default autowiring strategy. // /**
* // * Specifies the default autowiring strategy.
* @see Autowire // *
* @return // * @see Autowire
*/ // * @return
Autowire defaultAutowire() default Autowire.INHERITED; // */
// Autowire defaultAutowire() default Autowire.INHERITED;
// TODO: Prune DependencyCheck
// /** // /**
// * 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,
@ -81,6 +86,8 @@ public @interface Configuration {
// */ // */
// DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED; // DependencyCheck defaultDependencyCheck() default DependencyCheck.UNSPECIFIED;
// //
// TODO: Favor @Lazy at the @Configuration class level. Should have @Target(TYPE, METHOD)
// /** // /**
// * Bean instantiation strategy. By default, it is unspecified. // * Bean instantiation strategy. By default, it is unspecified.
// * // *
@ -89,14 +96,16 @@ public @interface Configuration {
// */ // */
// Lazy defaultLazy() default Lazy.UNSPECIFIED; // Lazy defaultLazy() default Lazy.UNSPECIFIED;
/** // TODO: prune useFactoryAspects
* 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;
/** // TODO: this is the default, and needs to be switched off at annotation-config
* Do we check {@link Required @Required} methods to make sure they've been called? // /**
*/ // * Do we check {@link Required @Required} methods to make sure they've been called?
boolean checkRequired() default false; // */
// boolean checkRequired() default false;
} }

View File

@ -21,9 +21,12 @@ import static java.lang.String.*;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import sun.security.x509.Extension; import sun.security.x509.Extension;
@ -47,7 +50,7 @@ import sun.security.x509.Extension;
*/ */
// 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 {
private String beanName; private String beanName;
@ -174,18 +177,18 @@ public final class ConfigurationClass extends ModelClass implements Validatable
return this; return this;
} }
public void validate(List<UsageError> errors) { public void validate(ProblemReporter problemReporter) {
// configuration classes must be annotated with @Configuration // configuration classes must be annotated with @Configuration
if (metadata == null) if (metadata == null)
errors.add(new NonAnnotatedConfigurationError()); problemReporter.error(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()); problemReporter.error(new FinalConfigurationError());
for (BeanMethod method : methods) for (BeanMethod method : methods)
method.validate(errors); method.validate(problemReporter);
} }
@ -248,94 +251,27 @@ public final class ConfigurationClass extends ModelClass implements Validatable
/** 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 Problem {
public NonAnnotatedConfigurationError() { public NonAnnotatedConfigurationError() {
super(ConfigurationClass.this, -1); super(
format("%s was provided as a Java Configuration class but was not annotated with @%s. "
+ "Update the class definition to continue.", getSimpleName(), Configuration.class
.getSimpleName()),
new Location(new FileSystemResource("/dev/null"))
);
} }
@Override
public String getDescription() {
return format("%s was provided as a Java Configuration class but was not annotated with @%s. "
+ "Update the class definition to continue.", getSimpleName(), Configuration.class
.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 Problem {
public FinalConfigurationError() { public FinalConfigurationError() {
super(ConfigurationClass.this, -1); super(
format("@%s class may not be final. Remove the final modifier to continue.",
Configuration.class.getSimpleName()),
new Location(new FileSystemResource("/dev/null"))
);
} }
@Override
public String getDescription() {
return format("@%s class may not be final. Remove the final modifier to continue.",
Configuration.class.getSimpleName());
}
}
public class InvalidPluginException extends UsageError {
private final Annotation invalidPluginAnnotation;
public InvalidPluginException(Annotation invalidPluginAnnotation) {
super(ConfigurationClass.this, -1);
this.invalidPluginAnnotation = invalidPluginAnnotation;
}
@Override
public String getDescription() {
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 another bean definition.
*
* @see Bean#allowOverriding()
*/
public class IllegalBeanOverrideError extends UsageError {
private final ConfigurationClass authoritativeClass;
private final BeanMethod finalMethodInQuestion;
/**
* Creates a new IllegalBeanOverrideError object.
*
* @param violatingClass class attempting an illegal override. null value signifies
* that the violating class is unknown or that there is no class to speak of
* (in the case of an XML bean definition doing the illegal overriding)
* @param finalMethodInQuestion the method that has been marked
* 'allowOverriding=false'
*/
public IllegalBeanOverrideError(ConfigurationClass violatingClass, BeanMethod finalMethodInQuestion) {
super(violatingClass, -1);
this.authoritativeClass = ConfigurationClass.this;
this.finalMethodInQuestion = finalMethodInQuestion;
}
@Override
public String getDescription() {
return format("Illegal attempt by '%s' to override bean definition originally "
+ "specified by %s.%s. Consider removing 'allowOverride=false' from original method.",
finalMethodInQuestion.getName(), authoritativeClass.getSimpleName(),
finalMethodInQuestion.getName());
}
}
public boolean hasMethod(String methodName) {
return getMethod(methodName) != null;
}
public BeanMethod getMethod(String methodName) {
for (BeanMethod method : methods)
if (methodName.equals(method.getName()))
return method;
return null;
} }
} }

View File

@ -18,7 +18,11 @@ package org.springframework.config.java;
import static java.lang.String.*; import static java.lang.String.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.io.FileSystemResource;
@ -39,7 +43,7 @@ import java.util.List;
* @author Chris Beams * @author Chris Beams
* @see org.springframework.config.java.internal.parsing.ConfigurationParser * @see org.springframework.config.java.internal.parsing.ConfigurationParser
*/ */
public final class ConfigurationModel implements Validatable { public final class ConfigurationModel {
/* 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>();
@ -89,35 +93,40 @@ public final class ConfigurationModel implements Validatable {
* Recurses through the model validating each object along the way and aggregating any * Recurses through the model validating each object along the way and aggregating any
* <var>errors</var>. * <var>errors</var>.
* *
* @see ConfigurationClass#validate(java.util.List) * @see ConfigurationClass#validate
* @see BeanMethod#validate(java.util.List) * @see BeanMethod#validate
* @see UsageError
*/ */
public void validate(List<UsageError> errors) { public void validate(ProblemReporter problemReporter) {
// user must specify at least one configuration // user must specify at least one configuration
if (configurationClasses.isEmpty()) if (configurationClasses.isEmpty())
errors.add(new EmptyModelError()); problemReporter.error(new EmptyModelError());
// check for any illegal @Bean overriding // TODO: prune this
ConfigurationClass[] allClasses = getAllConfigurationClasses(); // // check for any illegal @Bean overriding
for (int i = 0; i < allClasses.length; i++) { // ConfigurationClass[] allClasses = getAllConfigurationClasses();
for (BeanMethod method : allClasses[i].getMethods()) { // for (int i = 0; i < allClasses.length; i++) {
Bean bean = method.getAnnotation(Bean.class); // for (BeanMethod method : allClasses[i].getMethods()) {
// Bean bean = method.getAnnotation(Bean.class);
if (bean == null || bean.allowOverriding()) //
continue; // if (bean == null || bean.allowOverriding())
// continue;
for (int j = i + 1; j < allClasses.length; j++) //
if (allClasses[j].hasMethod(method.getName())) // for (int j = i + 1; j < allClasses.length; j++)
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method)); // if (allClasses[j].hasMethod(method.getName()))
} // problemReporter.error(
} // new Problem(
// allClasses[i].new IllegalBeanOverrideError(allClasses[j], method).getDescription(),
// new Location(new ClassPathResource(allClasses[i].getName().replace('.', '/').concat(".class")))
// )
// );
// }
// }
// each individual configuration class must be well-formed // each individual configuration class must be well-formed
// note that each configClass detects usage errors on its imports recursively // note that each configClass detects usage errors on its imports recursively
// note that each configClass will recursively process its respective methods // note that each configClass will recursively process its respective methods
for (ConfigurationClass configClass : configurationClasses) for (ConfigurationClass configClass : configurationClasses)
configClass.validate(errors); configClass.validate(problemReporter);
} }
@Override @Override
@ -151,15 +160,13 @@ public final class ConfigurationModel implements Validatable {
} }
public class EmptyModelError extends UsageError { public class EmptyModelError extends Problem {
public EmptyModelError() { public EmptyModelError() {
super(null, 0); super(
} format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName()),
@Override new Location(new FileSystemResource("/dev/null"))
public String getDescription() { );
return format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName());
} }
} }

View File

@ -15,19 +15,19 @@
*/ */
package org.springframework.config.java; package org.springframework.config.java;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.core.io.FileSystemResource;
public class InvalidScopedProxyDeclarationError extends UsageError {
private final BeanMethod method;
public class InvalidScopedProxyDeclarationError extends Problem {
public InvalidScopedProxyDeclarationError(BeanMethod method) { public InvalidScopedProxyDeclarationError(BeanMethod method) {
super(method.getDeclaringClass(), method.getLineNumber()); super(
this.method = method; String.format("method %s contains an invalid annotation declaration: @%s "
+ "cannot be used on a singleton/prototype bean", method.getName(), ScopedProxy.class
.getSimpleName()),
new Location(new FileSystemResource("/dev/null"))
);
} }
@Override
public String getDescription() {
return String.format("method %s contains an invalid annotation declaration: @%s "
+ "cannot be used on a singleton/prototype bean", method.getName(), ScopedProxy.class
.getSimpleName());
}
} }

View File

@ -1,75 +0,0 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.config.java;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* TODO: rename to UsageException / move outside .internal?
*
* @author Chris Beams
*/
@SuppressWarnings("serial")
public class MalformedConfigurationException extends RuntimeException {
private final List<? extends UsageError> errors;
public MalformedConfigurationException(String message) {
super(message);
this.errors = new ArrayList<UsageError>();
}
public MalformedConfigurationException(UsageError... errors) {
super(toString(errors));
this.errors = Arrays.asList(errors);
}
public boolean containsError(Class<? extends UsageError> errorType) {
for (UsageError error : errors)
if (error.getClass().isAssignableFrom(errorType))
return true;
return false;
}
/**
* Render a list of syntax errors as output suitable for diagnosis via System.err.
*/
private static String toString(UsageError... errors) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
if (errors.length == 1)
sb.append("A usage error has ");
else
sb.append(errors.length + " usage errors have ");
sb.append("been detected:\n");
for (int i = 0; i < errors.length; i++) {
sb.append(errors[i].toString());
if ((i + 1) < errors.length)
sb.append('\n');
}
return sb.toString();
}
}

View File

@ -1,73 +0,0 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.config.java;
/**
* Represents an invalid usage of JavaConfig constructs, e.g. a {@link Configuration} that
* 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 possible usage errors.
*
* @author Chris Beams
* @see MalformedConfigurationException
*/
public abstract class UsageError {
private final ModelClass clazz;
private final int lineNumber;
/**
* 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 error was not local to a single class.
* @param lineNumber line number on which this error was detected (useful for tooling
* integration)
*
* @see ModelClass#getSource()
*/
public UsageError(ModelClass modelClass, int lineNumber) {
this.clazz = modelClass;
this.lineNumber = lineNumber;
}
/**
* Human-readable description of this error suitable for console output or IDE tooling.
*/
public abstract String getDescription();
/**
* Same as {@link #getDescription()} but attributed with class and line number
* information. If modelClass constructor parameter was null, class and line number
* information will be omitted.
*/
public final String getAttributedDescription() {
if (clazz == null)
return getDescription();
return String.format("%s:%d: %s", clazz.getSource(), lineNumber, getDescription());
}
/**
* Delegates directly to {@link #getAttributedDescription()}.
*/
@Override
public String toString() {
return getAttributedDescription();
}
}

View File

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

View File

@ -174,6 +174,7 @@ public class ConfigurationEnhancer {
* @return original subclass instance unless superclass is annnotated with @Aspect, in * @return original subclass instance unless superclass is annnotated with @Aspect, in
* which case a subclass of the subclass is returned * which case a subclass of the subclass is returned
*/ */
// TODO: try to implement with modifications to AbstractAspectJAdvisorFactory#isAspect
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) { private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false; boolean superclassIsAnAspect = false;

View File

@ -16,11 +16,13 @@
package org.springframework.config.java.support; package org.springframework.config.java.support;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.Configuration; import org.springframework.config.java.Configuration;
import org.springframework.config.java.ConfigurationModel; import org.springframework.config.java.ConfigurationModel;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.internal.parsing.ConfigurationParser; import org.springframework.config.java.internal.parsing.ConfigurationParser;
@ -44,6 +46,14 @@ import org.springframework.config.java.internal.parsing.ConfigurationParser;
*/ */
public abstract class AbstractConfigurationClassProcessor { public abstract class AbstractConfigurationClassProcessor {
/**
* Used to register any problems detected with {@link Configuration} or {@link Bean}
* declarations. For instance, a Bean method marked as {@literal final} is illegal
* and would be reported as a problem. Defaults to {@link FailFastProblemReporter},
* but is overridable with {@link #setProblemReporter}
*/
private ProblemReporter problemReporter = new FailFastProblemReporter();
/** /**
* Populate and return a registry containing all {@link Configuration} bean definitions * Populate and return a registry containing all {@link Configuration} bean definitions
* to be processed. * to be processed.
@ -65,12 +75,27 @@ public abstract class AbstractConfigurationClassProcessor {
/** /**
* Validate the given model and handle any errors. Implementations may choose to throw * Validate the given model and handle any errors. Implementations may choose to throw
* {@link MalformedConfigurationException}, or in the case of tooling register problems * {@link BeanDefinitionParsingException}, or in the case of tooling register problems
* with the UI. * with the UI.
* @param configModel {@link ConfigurationModel} to validate * @param configModel {@link ConfigurationModel} to validate
*/ */
protected abstract void validateModel(ConfigurationModel configModel); protected abstract void validateModel(ConfigurationModel configModel);
/**
* Override the default {@link ProblemReporter}.
* @param problemReporter custom problem reporter
*/
protected final void setProblemReporter(ProblemReporter problemReporter) {
this.problemReporter = problemReporter;
}
/**
* Get the currently registered {@link ProblemReporter}.
*/
protected final ProblemReporter getProblemReporter() {
return problemReporter;
}
/** /**
* Build and validate a {@link ConfigurationModel} based on the registry of * Build and validate a {@link ConfigurationModel} based on the registry of
* {@link Configuration} classes provided by {@link #getConfigurationBeanDefinitions}, * {@link Configuration} classes provided by {@link #getConfigurationBeanDefinitions},

View File

@ -18,7 +18,6 @@ package org.springframework.config.java.support;
import static java.lang.String.*; import static java.lang.String.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -31,8 +30,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.Configuration; import org.springframework.config.java.Configuration;
import org.springframework.config.java.ConfigurationModel; import org.springframework.config.java.ConfigurationModel;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.UsageError;
import org.springframework.config.java.internal.enhancement.ConfigurationEnhancer; import org.springframework.config.java.internal.enhancement.ConfigurationEnhancer;
import org.springframework.config.java.internal.parsing.ConfigurationParser; import org.springframework.config.java.internal.parsing.ConfigurationParser;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -136,15 +133,12 @@ public class ConfigurationClassPostProcessor extends AbstractConfigurationClassP
} }
/** /**
* Validates the given <var>model</var>. * Validates the given <var>model</var>. Any problems found are delegated
* @throws MalformedConfigurationException if any errors are detected * to {@link #getProblemReporter()}.
*/ */
@Override @Override
protected void validateModel(ConfigurationModel model) { protected void validateModel(ConfigurationModel model) {
ArrayList<UsageError> errors = new ArrayList<UsageError>(); model.validate(this.getProblemReporter());
model.validate(errors);
if (errors.size() > 0)
throw new MalformedConfigurationException(errors.toArray(new UsageError[] {}));
} }
/** /**

View File

@ -19,12 +19,10 @@ import static java.lang.String.*;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.BeanMethod; import org.springframework.config.java.BeanMethod;
@ -96,18 +94,18 @@ class ConfigurationModelBeanDefinitionReader {
* @param beanDefs * @param beanDefs
*/ */
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) { private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
Configuration metadata = configClass.getMetadata();
// TODO: think about implications with annotation-config // TODO: prune support for @Required
if (metadata.checkRequired() == true) { // Configuration metadata = configClass.getMetadata();
RootBeanDefinition requiredAnnotationPostProcessor = new RootBeanDefinition(); // if (metadata.checkRequired() == true) {
Class<?> beanClass = RequiredAnnotationBeanPostProcessor.class; // RootBeanDefinition requiredAnnotationPostProcessor = new RootBeanDefinition();
String beanName = beanClass.getName() + "#0"; // Class<?> beanClass = RequiredAnnotationBeanPostProcessor.class;
requiredAnnotationPostProcessor.setBeanClass(beanClass); // String beanName = beanClass.getName() + "#0";
requiredAnnotationPostProcessor // requiredAnnotationPostProcessor.setBeanClass(beanClass);
.setResourceDescription("ensures @Required methods have been invoked"); // requiredAnnotationPostProcessor
registry.registerBeanDefinition(beanName, requiredAnnotationPostProcessor); // .setResourceDescription("ensures @Required methods have been invoked");
} // registry.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
// }
GenericBeanDefinition configBeanDef = new GenericBeanDefinition(); GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
configBeanDef.setBeanClassName(configClass.getName()); configBeanDef.setBeanClassName(configClass.getName());

View File

@ -11,9 +11,9 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.Configuration; import org.springframework.config.java.Configuration;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.StandardScopes; import org.springframework.config.java.StandardScopes;
import org.springframework.config.java.support.ConfigurationClassPostProcessor; import org.springframework.config.java.support.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import test.beans.ITestBean; import test.beans.ITestBean;
import test.beans.TestBean; import test.beans.TestBean;
@ -96,68 +96,10 @@ public class BasicTests {
return bar; return bar;
} }
@Bean(scope = StandardScopes.PROTOTYPE) @Bean @Scope(StandardScopes.PROTOTYPE)
public TestBean baz() { public TestBean baz() {
return new TestBean("bar"); return new TestBean("bar");
} }
} }
@Test
public void legalBeanOverriding() {
{
BeanFactory factory = initBeanFactory(ConfigWithBeanThatAllowsOverriding.class, ConfigWithBeanOverride.class);
TestBean testBean = factory.getBean("testBean", TestBean.class);
assertThat(testBean.getName(), equalTo("overridden"));
}
// now try it the other way around - order matters!
{
BeanFactory factory = initBeanFactory(ConfigWithBeanOverride.class, ConfigWithBeanThatAllowsOverriding.class);
TestBean testBean = factory.getBean("testBean", TestBean.class);
assertThat(testBean.getName(), equalTo("original"));
}
}
@Test(expected=MalformedConfigurationException.class)
public void illegalBeanOverriding() {
initBeanFactory(ConfigWithBeanThatDisallowsOverriding.class, ConfigWithBeanOverride.class);
}
@Test
public void illegalBeanOverriding2() {
// should be okay when the class that disallows overriding is the one doing the overriding
initBeanFactory(ConfigWithBeanOverride.class, ConfigWithBeanThatDisallowsOverriding.class);
}
@Configuration
static class ConfigWithBeanThatAllowsOverriding {
@Bean
public TestBean testBean() {
return new TestBean("original");
}
}
@Configuration
static class ConfigWithBeanThatDisallowsOverriding {
@Bean(allowOverriding = false)
public TestBean testBean() {
return new TestBean("original");
}
}
@Configuration
static class ConfigWithBeanOverride {
@Bean
public TestBean testBean() {
return new TestBean("overridden");
}
}
} }

View File

@ -24,11 +24,10 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.scope.ScopedObject; import org.springframework.aop.scope.ScopedObject;
import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.Configuration; import org.springframework.config.java.Configuration;
import org.springframework.config.java.InvalidScopedProxyDeclarationError;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.ScopedProxy; import org.springframework.config.java.ScopedProxy;
import org.springframework.config.java.StandardScopes; import org.springframework.config.java.StandardScopes;
import org.springframework.config.java.support.ConfigurationClassPostProcessor; import org.springframework.config.java.support.ConfigurationClassPostProcessor;
@ -118,8 +117,8 @@ public class ScopingTests {
try { try {
createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class); createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class);
fail("exception expected"); fail("exception expected");
} catch (MalformedConfigurationException ex) { } catch (BeanDefinitionParsingException ex) {
assertTrue(ex.containsError(InvalidScopedProxyDeclarationError.class)); assertTrue(ex.getMessage().contains("cannot be used on a singleton/prototype bean"));
} }
} }
@ -241,7 +240,8 @@ public class ScopingTests {
@Configuration @Configuration
static class ScopeTestConfiguration { static class ScopeTestConfiguration {
@Bean(scope = StandardScopes.SESSION) @Bean
@org.springframework.context.annotation.Scope(StandardScopes.SESSION)
@ScopedProxy @ScopedProxy
public Foo foo() { public Foo foo() {
return new Foo(); return new Foo();
@ -326,21 +326,24 @@ public class ScopingTests {
@Configuration @Configuration
public static class ScopedConfigurationClass { public static class ScopedConfigurationClass {
@Bean(scope = SCOPE) @Bean
@org.springframework.context.annotation.Scope(SCOPE)
public TestBean scopedClass() { public TestBean scopedClass() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);
return tb; return tb;
} }
@Bean(scope = SCOPE) @Bean
@org.springframework.context.annotation.Scope(SCOPE)
public ITestBean scopedInterface() { public ITestBean scopedInterface() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);
return tb; return tb;
} }
@Bean(scope = SCOPE) @Bean
@org.springframework.context.annotation.Scope(SCOPE)
@ScopedProxy(proxyTargetClass = false) @ScopedProxy(proxyTargetClass = false)
public ITestBean scopedProxyInterface() { public ITestBean scopedProxyInterface() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
@ -349,7 +352,8 @@ public class ScopingTests {
} }
@ScopedProxy @ScopedProxy
@Bean(scope = SCOPE) @Bean
@org.springframework.context.annotation.Scope(SCOPE)
public TestBean scopedProxyClass() { public TestBean scopedProxyClass() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);