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

View File

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

View File

@ -4,6 +4,8 @@ import static java.lang.String.*;
import static org.springframework.util.StringUtils.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.logging.Log;
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.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AnnotationUtils;
@ -43,19 +46,27 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
Bean bean = method.getRequiredAnnotation(Bean.class);
Configuration defaults = configClass.getMetadata();
// TODO: prune defaults
//Configuration defaults = configClass.getMetadata();
// consider scoping
beanDef.setScope(bean.scope());
Scope scope = method.getAnnotation(Scope.class);
if(scope != null)
beanDef.setScope(scope.value());
// consider autowiring
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());
// TODO: prune autowiring
// // consider autowiring
// 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());
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)?
if (containsBeanDefinitionIncludingAncestry(beanName, registry)) {
@ -65,11 +76,12 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
// no -> then it's an external override, probably XML
// ensure that overriding is ok
if (bean.allowOverriding() == false) {
UsageError error = configClass.new IllegalBeanOverrideError(null, method);
throw new MalformedConfigurationException(error);
}
// TODO: Prune this
// // ensure that overriding is ok
// if (bean.allowOverriding() == false) {
// UsageError error = configClass.new IllegalBeanOverrideError(null, method);
// throw new MalformedConfigurationException(error);
// }
// overriding is legal, return immediately
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
// // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE)
@ -96,12 +101,12 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
// beanDef.setLazyInit(true);
// does this bean have a custom init-method specified?
String initMethodName = bean.initMethodName();
String initMethodName = bean.initMethod();
if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified?
String destroyMethodName = bean.destroyMethodName();
String destroyMethodName = bean.destroyMethod();
if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName);

View File

@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Required;
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}.
* </p>
*
* May be used in conjunction with {@link Lazy}
*
* @author Rod Johnson
* @author Chris Beams
* @since 3.0
* @see Lazy
* @see Bean
*/
@Component
@Target( { ElementType.TYPE })
@ -55,22 +57,25 @@ import org.springframework.stereotype.Component;
@Documented
public @interface Configuration {
/**
* Configuration name. Allow different variants, such as test, production etc. Default
* will always match.
*
* @return
*/
String[] names() default "";
// TODO: consider pruning @Configuration(name[])
// /**
// * Configuration name. Allow different variants, such as test, production etc. Default
// * will always match.
// *
// * @return
// */
// String[] name() default "";
/**
* Specifies the default autowiring strategy.
*
* @see Autowire
* @return
*/
Autowire defaultAutowire() default Autowire.INHERITED;
// TODO: Prune defaultAutowire
// /**
// * Specifies the default autowiring strategy.
// *
// * @see Autowire
// * @return
// */
// Autowire defaultAutowire() default Autowire.INHERITED;
// TODO: Prune DependencyCheck
// /**
// * Dependency check strategy. By default, the dependency check is
// * unspecified, that is the default Spring option will apply. In most cases,
@ -81,6 +86,8 @@ public @interface Configuration {
// */
// 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.
// *
@ -89,14 +96,16 @@ public @interface Configuration {
// */
// Lazy defaultLazy() default Lazy.UNSPECIFIED;
/**
* Do we autowire with aspects from the enclosing factory scope?
*/
boolean useFactoryAspects() default false;
// TODO: prune useFactoryAspects
// /**
// * Do we autowire with aspects from the enclosing factory scope?
// */
// boolean useFactoryAspects() default false;
/**
* Do we check {@link Required @Required} methods to make sure they've been called?
*/
boolean checkRequired() 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?
// */
// boolean checkRequired() default false;
}

View File

@ -21,9 +21,12 @@ import static java.lang.String.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
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 sun.security.x509.Extension;
@ -47,7 +50,7 @@ import sun.security.x509.Extension;
*/
// TODO: SJC-242 update documentation in light of generalization changes
// 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;
@ -174,18 +177,18 @@ public final class ConfigurationClass extends ModelClass implements Validatable
return this;
}
public void validate(List<UsageError> errors) {
public void validate(ProblemReporter problemReporter) {
// configuration classes must be annotated with @Configuration
if (metadata == null)
errors.add(new NonAnnotatedConfigurationError());
problemReporter.error(new NonAnnotatedConfigurationError());
// a configuration class may not be final (CGLIB limitation)
if (Modifier.isFinal(modifiers))
errors.add(new FinalConfigurationError());
problemReporter.error(new FinalConfigurationError());
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}. */
public class NonAnnotatedConfigurationError extends UsageError {
public class NonAnnotatedConfigurationError extends Problem {
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. */
public class FinalConfigurationError extends UsageError {
public class FinalConfigurationError extends Problem {
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 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
* @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. */
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
* <var>errors</var>.
*
* @see ConfigurationClass#validate(java.util.List)
* @see BeanMethod#validate(java.util.List)
* @see UsageError
* @see ConfigurationClass#validate
* @see BeanMethod#validate
*/
public void validate(List<UsageError> errors) {
public void validate(ProblemReporter problemReporter) {
// user must specify at least one configuration
if (configurationClasses.isEmpty())
errors.add(new EmptyModelError());
problemReporter.error(new EmptyModelError());
// check for any illegal @Bean overriding
ConfigurationClass[] allClasses = getAllConfigurationClasses();
for (int i = 0; i < allClasses.length; i++) {
for (BeanMethod method : allClasses[i].getMethods()) {
Bean bean = method.getAnnotation(Bean.class);
if (bean == null || bean.allowOverriding())
continue;
for (int j = i + 1; j < allClasses.length; j++)
if (allClasses[j].hasMethod(method.getName()))
errors.add(allClasses[i].new IllegalBeanOverrideError(allClasses[j], method));
}
}
// TODO: prune this
// // check for any illegal @Bean overriding
// ConfigurationClass[] allClasses = getAllConfigurationClasses();
// for (int i = 0; i < allClasses.length; i++) {
// for (BeanMethod method : allClasses[i].getMethods()) {
// Bean bean = method.getAnnotation(Bean.class);
//
// if (bean == null || bean.allowOverriding())
// continue;
//
// for (int j = i + 1; j < allClasses.length; j++)
// if (allClasses[j].hasMethod(method.getName()))
// problemReporter.error(
// new Problem(
// allClasses[i].new IllegalBeanOverrideError(allClasses[j], method).getDescription(),
// new Location(new ClassPathResource(allClasses[i].getName().replace('.', '/').concat(".class")))
// )
// );
// }
// }
// each individual configuration class must be well-formed
// note that each configClass detects usage errors on its imports recursively
// note that each configClass will recursively process its respective methods
for (ConfigurationClass configClass : configurationClasses)
configClass.validate(errors);
configClass.validate(problemReporter);
}
@Override
@ -151,15 +160,13 @@ public final class ConfigurationModel implements Validatable {
}
public class EmptyModelError extends UsageError {
public class EmptyModelError extends Problem {
public EmptyModelError() {
super(null, 0);
}
@Override
public String getDescription() {
return format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName());
super(
format("Configuration model was empty. Make sure at least one "
+ "@%s class has been specified.", Configuration.class.getSimpleName()),
new Location(new FileSystemResource("/dev/null"))
);
}
}

View File

@ -15,19 +15,19 @@
*/
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) {
super(method.getDeclaringClass(), method.getLineNumber());
this.method = method;
super(
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
* 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) {
boolean superclassIsAnAspect = false;

View File

@ -16,11 +16,13 @@
package org.springframework.config.java.support;
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.config.java.Bean;
import org.springframework.config.java.Configuration;
import org.springframework.config.java.ConfigurationModel;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.internal.parsing.ConfigurationParser;
@ -44,6 +46,14 @@ import org.springframework.config.java.internal.parsing.ConfigurationParser;
*/
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
* to be processed.
@ -65,12 +75,27 @@ public abstract class AbstractConfigurationClassProcessor {
/**
* 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.
* @param configModel {@link ConfigurationModel} to validate
*/
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
* {@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 java.io.IOException;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
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.Configuration;
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.parsing.ConfigurationParser;
import org.springframework.core.Ordered;
@ -136,15 +133,12 @@ public class ConfigurationClassPostProcessor extends AbstractConfigurationClassP
}
/**
* Validates the given <var>model</var>.
* @throws MalformedConfigurationException if any errors are detected
* Validates the given <var>model</var>. Any problems found are delegated
* to {@link #getProblemReporter()}.
*/
@Override
protected void validateModel(ConfigurationModel model) {
ArrayList<UsageError> errors = new ArrayList<UsageError>();
model.validate(errors);
if (errors.size() > 0)
throw new MalformedConfigurationException(errors.toArray(new UsageError[] {}));
model.validate(this.getProblemReporter());
}
/**

View File

@ -19,12 +19,10 @@ import static java.lang.String.*;
import org.apache.commons.logging.Log;
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.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.config.java.Bean;
import org.springframework.config.java.BeanMethod;
@ -96,18 +94,18 @@ class ConfigurationModelBeanDefinitionReader {
* @param beanDefs
*/
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
Configuration metadata = configClass.getMetadata();
// TODO: think about implications with annotation-config
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");
registry.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
}
// TODO: prune support for @Required
// 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");
// registry.registerBeanDefinition(beanName, requiredAnnotationPostProcessor);
// }
GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
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.config.java.Bean;
import org.springframework.config.java.Configuration;
import org.springframework.config.java.MalformedConfigurationException;
import org.springframework.config.java.StandardScopes;
import org.springframework.config.java.support.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import test.beans.ITestBean;
import test.beans.TestBean;
@ -96,68 +96,10 @@ public class BasicTests {
return bar;
}
@Bean(scope = StandardScopes.PROTOTYPE)
@Bean @Scope(StandardScopes.PROTOTYPE)
public TestBean baz() {
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.springframework.aop.scope.ScopedObject;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.config.java.Bean;
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.StandardScopes;
import org.springframework.config.java.support.ConfigurationClassPostProcessor;
@ -118,8 +117,8 @@ public class ScopingTests {
try {
createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class);
fail("exception expected");
} catch (MalformedConfigurationException ex) {
assertTrue(ex.containsError(InvalidScopedProxyDeclarationError.class));
} catch (BeanDefinitionParsingException ex) {
assertTrue(ex.getMessage().contains("cannot be used on a singleton/prototype bean"));
}
}
@ -241,7 +240,8 @@ public class ScopingTests {
@Configuration
static class ScopeTestConfiguration {
@Bean(scope = StandardScopes.SESSION)
@Bean
@org.springframework.context.annotation.Scope(StandardScopes.SESSION)
@ScopedProxy
public Foo foo() {
return new Foo();
@ -326,21 +326,24 @@ public class ScopingTests {
@Configuration
public static class ScopedConfigurationClass {
@Bean(scope = SCOPE)
@Bean
@org.springframework.context.annotation.Scope(SCOPE)
public TestBean scopedClass() {
TestBean tb = new TestBean();
tb.setName(flag);
return tb;
}
@Bean(scope = SCOPE)
@Bean
@org.springframework.context.annotation.Scope(SCOPE)
public ITestBean scopedInterface() {
TestBean tb = new TestBean();
tb.setName(flag);
return tb;
}
@Bean(scope = SCOPE)
@Bean
@org.springframework.context.annotation.Scope(SCOPE)
@ScopedProxy(proxyTargetClass = false)
public ITestBean scopedProxyInterface() {
TestBean tb = new TestBean();
@ -349,7 +352,8 @@ public class ScopingTests {
}
@ScopedProxy
@Bean(scope = SCOPE)
@Bean
@org.springframework.context.annotation.Scope(SCOPE)
public TestBean scopedProxyClass() {
TestBean tb = new TestBean();
tb.setName(flag);