+ Fleshed out, documented, tested and polished the ConfigurationPostProcessor implementation
+ Removed @FactoryMethod indirection and extension point in favor of direct processing of @Bean annotations
This commit is contained in:
parent
43b2a40343
commit
b985011b24
|
|
@ -12,7 +12,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
* <h3>Constraints</h3> Implementations must have only a default constructor, or explicitly
|
* <h3>Constraints</h3> Implementations must have only a default constructor, or explicitly
|
||||||
* declare a no-arg constructor.
|
* declare a no-arg constructor.
|
||||||
*
|
*
|
||||||
* @see FactoryMethod
|
|
||||||
* @see BeanMethod
|
* @see BeanMethod
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,13 @@
|
||||||
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.Util.*;
|
|
||||||
|
|
||||||
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.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import net.sf.cglib.proxy.Callback;
|
|
||||||
|
|
||||||
|
import org.springframework.config.java.ext.Bean;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,7 +35,6 @@ public final class BeanMethod implements Validatable {
|
||||||
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 FactoryMethod factoryAnno;
|
|
||||||
private transient final List<Validator> validators = new ArrayList<Validator>();
|
private transient final List<Validator> validators = new ArrayList<Validator>();
|
||||||
|
|
||||||
public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
|
public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
|
||||||
|
|
@ -47,11 +42,8 @@ public final class BeanMethod implements Validatable {
|
||||||
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)
|
|
||||||
factoryAnno = annotation.annotationType().getAnnotation(FactoryMethod.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
|
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
|
||||||
this.modifiers = modifiers;
|
this.modifiers = modifiers;
|
||||||
|
|
@ -140,30 +132,34 @@ public final class BeanMethod implements Validatable {
|
||||||
|
|
||||||
if (Modifier.isFinal(getModifiers()))
|
if (Modifier.isFinal(getModifiers()))
|
||||||
errors.add(new FinalMethodError());
|
errors.add(new FinalMethodError());
|
||||||
|
|
||||||
|
new BeanValidator().validate(this, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeanDefinitionRegistrar getRegistrar() {
|
// public BeanDefinitionRegistrar getRegistrar() {
|
||||||
return getInstance(factoryAnno.registrar());
|
// return getInstance(factoryAnno.registrar());
|
||||||
}
|
// }
|
||||||
|
|
||||||
public Set<Validator> getValidators() {
|
// public Set<Validator> getValidators() {
|
||||||
HashSet<Validator> validator = new HashSet<Validator>();
|
// HashSet<Validator> validators = new HashSet<Validator>();
|
||||||
|
//
|
||||||
for (Class<? extends Validator> validatorType : factoryAnno.validators())
|
//// for (Class<? extends Validator> validatorType : factoryAnno.validators())
|
||||||
validator.add(getInstance(validatorType));
|
//// validator.add(getInstance(validatorType));
|
||||||
|
//
|
||||||
return validator;
|
// validators.add(IllegalB)
|
||||||
}
|
//
|
||||||
|
// return validators;
|
||||||
public Callback getCallback() {
|
// }
|
||||||
Class<? extends Callback> callbackType = factoryAnno.interceptor();
|
|
||||||
|
|
||||||
if (callbackType.equals(NoOpInterceptor.class))
|
|
||||||
return NoOpInterceptor.INSTANCE;
|
|
||||||
|
|
||||||
return getInstance(callbackType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// public Callback getCallback() {
|
||||||
|
// Class<? extends Callback> callbackType = factoryAnno.interceptor();
|
||||||
|
//
|
||||||
|
// if (callbackType.equals(NoOpInterceptor.class))
|
||||||
|
// return NoOpInterceptor.INSTANCE;
|
||||||
|
//
|
||||||
|
// return getInstance(callbackType);
|
||||||
|
// }
|
||||||
|
//
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
|
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
|
||||||
|
|
@ -236,3 +232,31 @@ public final class BeanMethod implements Validatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects any user errors when declaring {@link Bean}-annotated methods.
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
*/
|
||||||
|
class BeanValidator implements Validator {
|
||||||
|
|
||||||
|
public boolean supports(Object object) {
|
||||||
|
return object instanceof BeanMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(Object object, List<UsageError> errors) {
|
||||||
|
BeanMethod method = (BeanMethod) 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import static java.lang.String.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.config.java.ext.Bean;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract representation of a set of user-provided "Configuration classes", usually but
|
* An abstract representation of a set of user-provided "Configuration classes", usually but
|
||||||
|
|
@ -106,23 +108,24 @@ public final class ConfigurationModel implements Validatable {
|
||||||
// cascade through model and allow handlers to register validators
|
// cascade through model and allow handlers to register validators
|
||||||
// depending on where they are registered (with the model, the class, or the method)
|
// depending on where they are registered (with the model, the class, or the method)
|
||||||
// they will be called directly or indirectly below
|
// they will be called directly or indirectly below
|
||||||
for (ConfigurationClass configClass : getAllConfigurationClasses()) {
|
// for (ConfigurationClass configClass : getAllConfigurationClasses()) {
|
||||||
for (BeanMethod method : configClass.getMethods()) {
|
// for (BeanMethod method : configClass.getMethods()) {
|
||||||
for (Validator validator : method.getValidators()) {
|
// for (Validator validator : method.getValidators()) {
|
||||||
if (validator.supports(method))
|
// if (validator.supports(method))
|
||||||
method.registerValidator(validator);
|
// method.registerValidator(validator);
|
||||||
// TODO: support class-level validation
|
// // TODO: support class-level validation
|
||||||
// if(validator.supports(configClass))
|
// // if(validator.supports(configClass))
|
||||||
// configClass.registerValidator(validator);
|
// // configClass.registerValidator(validator);
|
||||||
if (validator.supports(this))
|
// if (validator.supports(this))
|
||||||
this.registerValidator(validator);
|
// this.registerValidator(validator);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// process any validators registered directly with this model object
|
// process any validators registered directly with this model object
|
||||||
for (Validator validator : validators)
|
// for (Validator validator : validators)
|
||||||
validator.validate(this, errors);
|
// validator.validate(this, errors);
|
||||||
|
new IllegalBeanOverrideValidator().validate(this, errors);
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -175,3 +178,38 @@ public final class ConfigurationModel implements Validatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects any illegally-overridden {@link Bean} definitions within a particular
|
||||||
|
* {@link ConfigurationModel}
|
||||||
|
*
|
||||||
|
* @see Bean#allowOverriding()
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
*/
|
||||||
|
class IllegalBeanOverrideValidator implements Validator {
|
||||||
|
|
||||||
|
public boolean supports(Object object) {
|
||||||
|
return object instanceof ConfigurationModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(Object object, List<UsageError> errors) {
|
||||||
|
ConfigurationModel model = (ConfigurationModel) object;
|
||||||
|
|
||||||
|
ConfigurationClass[] allClasses = model.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +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.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
|
||||||
|
|
||||||
import org.springframework.config.java.ext.AbstractMethodInterceptor;
|
|
||||||
import org.springframework.config.java.ext.Bean;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Meta-annotation used to identify method annotations as producers of beans and/or values.
|
|
||||||
* Provides a model that's open for extension. i.e.: The {@link Bean} annotation is
|
|
||||||
* annotated as a {@link FactoryMethod}. In this same fashion, any custom annotation can be
|
|
||||||
* devised with its own semantics. It need only provide a custom registrar, interceptor and
|
|
||||||
* optionally, validators.
|
|
||||||
*
|
|
||||||
* @see Bean
|
|
||||||
* @see BeanDefinitionRegistrar
|
|
||||||
* @see AbstractMethodInterceptor
|
|
||||||
* @see Validator
|
|
||||||
*
|
|
||||||
* @author Chris Beams
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.ANNOTATION_TYPE)
|
|
||||||
@Documented
|
|
||||||
public @interface FactoryMethod {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies which registrar should be used to register bean definitions when processing
|
|
||||||
* this {@link FactoryMethod}.
|
|
||||||
*/
|
|
||||||
Class<? extends BeanDefinitionRegistrar> registrar();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies what interceptor should be used when processing this {@link FactoryMethod}.
|
|
||||||
* Defaults to {@link NoOpInterceptor} which does nothing.
|
|
||||||
*/
|
|
||||||
Class<? extends MethodInterceptor> interceptor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optionally specifies any {@link Validator} types capable of validating the syntax of
|
|
||||||
* this {@link FactoryMethod}. Usually used when a factory method may have multiple
|
|
||||||
* annotations such as {@link Bean} and {@link ScopedProxy}.
|
|
||||||
*/
|
|
||||||
Class<? extends Validator>[] validators() default {};
|
|
||||||
}
|
|
||||||
|
|
@ -106,7 +106,7 @@ public class Util {
|
||||||
* <p>
|
* <p>
|
||||||
* ASM class reading is used throughout JavaConfig, but there are certain cases where
|
* ASM class reading is used throughout JavaConfig, but there are certain cases where
|
||||||
* classloading cannot be avoided - specifically in cases where users define their own
|
* classloading cannot be avoided - specifically in cases where users define their own
|
||||||
* {@link Extension} or {@link FactoryMethod} annotations. This method should therefore be
|
* {@link Extension} annotations. This method should therefore be
|
||||||
* used sparingly but consistently where required.
|
* used sparingly but consistently where required.
|
||||||
* <p>
|
* <p>
|
||||||
* Because {@link ClassNotFoundException} is compensated for by returning null, callers
|
* Because {@link ClassNotFoundException} is compensated for by returning null, callers
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,12 @@ import java.lang.annotation.Inherited;
|
||||||
import java.lang.annotation.Retention;
|
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 java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||||
import org.springframework.config.java.BeanMethod;
|
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.config.java.ConfigurationClass;
|
|
||||||
import org.springframework.config.java.ConfigurationModel;
|
|
||||||
import org.springframework.config.java.FactoryMethod;
|
|
||||||
import org.springframework.config.java.Scopes;
|
import org.springframework.config.java.Scopes;
|
||||||
import org.springframework.config.java.UsageError;
|
|
||||||
import org.springframework.config.java.Validator;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -71,9 +64,6 @@ import org.springframework.config.java.Validator;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@Documented
|
@Documented
|
||||||
@FactoryMethod(registrar = BeanRegistrar.class,
|
|
||||||
interceptor = BeanMethodInterceptor.class,
|
|
||||||
validators = { BeanValidator.class, IllegalBeanOverrideValidator.class })
|
|
||||||
public @interface Bean {
|
public @interface Bean {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,66 +145,3 @@ public @interface Bean {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects any user errors when declaring {@link Bean}-annotated methods.
|
|
||||||
*
|
|
||||||
* @author Chris Beams
|
|
||||||
*/
|
|
||||||
class BeanValidator implements Validator {
|
|
||||||
|
|
||||||
public boolean supports(Object object) {
|
|
||||||
return object instanceof BeanMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validate(Object object, List<UsageError> errors) {
|
|
||||||
BeanMethod method = (BeanMethod) 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
|
|
||||||
* {@link ConfigurationModel}
|
|
||||||
*
|
|
||||||
* @see Bean#allowOverriding()
|
|
||||||
*
|
|
||||||
* @author Chris Beams
|
|
||||||
*/
|
|
||||||
class IllegalBeanOverrideValidator implements Validator {
|
|
||||||
|
|
||||||
public boolean supports(Object object) {
|
|
||||||
return object instanceof ConfigurationModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validate(Object object, List<UsageError> errors) {
|
|
||||||
ConfigurationModel model = (ConfigurationModel) object;
|
|
||||||
|
|
||||||
ConfigurationClass[] allClasses = model.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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,9 @@ public class ConfigurationEnhancer {
|
||||||
|
|
||||||
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
|
for (ConfigurationClass configClass : model.getAllConfigurationClasses()) {
|
||||||
for (BeanMethod method : configClass.getMethods()) {
|
for (BeanMethod method : configClass.getMethods()) {
|
||||||
registrars.add(method.getRegistrar());
|
registrars.add(new BeanRegistrar());
|
||||||
|
|
||||||
Callback callback = method.getCallback();
|
Callback callback = new BeanMethodInterceptor();
|
||||||
|
|
||||||
if (callback instanceof BeanFactoryAware)
|
if (callback instanceof BeanFactoryAware)
|
||||||
((BeanFactoryAware) callback).setBeanFactory(beanFactory);
|
((BeanFactoryAware) callback).setBeanFactory(beanFactory);
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,13 @@ import org.objectweb.asm.Opcodes;
|
||||||
import org.springframework.config.java.BeanMethod;
|
import org.springframework.config.java.BeanMethod;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.config.java.ConfigurationClass;
|
import org.springframework.config.java.ConfigurationClass;
|
||||||
import org.springframework.config.java.FactoryMethod;
|
|
||||||
import org.springframework.config.java.ModelClass;
|
import org.springframework.config.java.ModelClass;
|
||||||
import org.springframework.config.java.ext.Bean;
|
import org.springframework.config.java.ext.Bean;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visits a single method declared in a given {@link Configuration} class. Determines
|
* Visits a single method declared in a given {@link Configuration} class. Determines
|
||||||
* whether the method is a {@link FactoryMethod} method and if so, adds it to the
|
* whether the method is a {@link Bean} method and if so, adds it to the
|
||||||
* {@link ConfigurationClass}.
|
* {@link ConfigurationClass}.
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
|
@ -108,8 +107,7 @@ class ConfigurationClassMethodVisitor extends MethodAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses through all {@link #annotations} on this method in order to determine whether
|
* Parses through all {@link #annotations} on this method in order to determine whether
|
||||||
* it is a {@link FactoryMethod} method or not and if so adds it to the enclosing
|
* it is a {@link Bean} method or not and if so adds it to the enclosing {@link #configClass}.
|
||||||
* {@link #configClass}.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void visitEnd() {
|
public void visitEnd() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package org.springframework.config.java.support;
|
package org.springframework.config.java.support;
|
||||||
|
|
||||||
import static org.springframework.util.StringUtils.*;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.config.java.ConfigurationModel;
|
import org.springframework.config.java.ConfigurationModel;
|
||||||
|
|
@ -9,7 +7,6 @@ import org.springframework.config.java.internal.parsing.ConfigurationParser;
|
||||||
|
|
||||||
public abstract class AbstractConfigurationClassProcessor {
|
public abstract class AbstractConfigurationClassProcessor {
|
||||||
|
|
||||||
static String CGLIB_PACKAGE = "net.sf.cglib.proxy";
|
|
||||||
|
|
||||||
protected abstract BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs);
|
protected abstract BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs);
|
||||||
|
|
||||||
|
|
@ -23,14 +20,6 @@ public abstract class AbstractConfigurationClassProcessor {
|
||||||
if(configBeanDefs.getBeanDefinitionCount() == 0)
|
if(configBeanDefs.getBeanDefinitionCount() == 0)
|
||||||
return configBeanDefs; // nothing to do - don't waste any more cycles
|
return configBeanDefs; // nothing to do - don't waste any more cycles
|
||||||
|
|
||||||
// TODO: the location of this cglib check is temporary, pending removal of the
|
|
||||||
// @FactoryMethod meta-annotation indirection
|
|
||||||
if(Package.getPackage(CGLIB_PACKAGE) == null)
|
|
||||||
throw new RuntimeException("CGLIB is required to process @Configuration classes. " +
|
|
||||||
"Either add CGLIB v2.2.3 to the classpath or remove the following " +
|
|
||||||
"@Configuration bean definitions: ["
|
|
||||||
+ arrayToCommaDelimitedString(configBeanDefs.getBeanDefinitionNames()) + "]");
|
|
||||||
|
|
||||||
ConfigurationModel configModel = createConfigurationModelFor(configBeanDefs);
|
ConfigurationModel configModel = createConfigurationModelFor(configBeanDefs);
|
||||||
|
|
||||||
validateModel(configModel);
|
validateModel(configModel);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ import org.springframework.config.java.BeanMethod;
|
||||||
import org.springframework.config.java.Configuration;
|
import org.springframework.config.java.Configuration;
|
||||||
import org.springframework.config.java.ConfigurationClass;
|
import org.springframework.config.java.ConfigurationClass;
|
||||||
import org.springframework.config.java.ConfigurationModel;
|
import org.springframework.config.java.ConfigurationModel;
|
||||||
import org.springframework.config.java.FactoryMethod;
|
import org.springframework.config.java.ext.Bean;
|
||||||
|
import org.springframework.config.java.ext.BeanRegistrar;
|
||||||
import org.springframework.config.java.plugin.Extension;
|
import org.springframework.config.java.plugin.Extension;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
|
@ -75,7 +76,7 @@ class ConfigurationModelBeanDefinitionReader {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a particular {@link ConfigurationClass}, registering bean definitions for the
|
* Reads a particular {@link ConfigurationClass}, registering bean definitions for the
|
||||||
* class itself, all its {@link FactoryMethod} methods and all its {@link Extension}
|
* class itself, all its {@link Bean} methods and all its {@link Extension}
|
||||||
* annotations.
|
* annotations.
|
||||||
*/
|
*/
|
||||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||||
|
|
@ -137,11 +138,10 @@ class ConfigurationModelBeanDefinitionReader {
|
||||||
/**
|
/**
|
||||||
* Reads a particular {@link BeanMethod}, registering bean definitions with
|
* Reads a particular {@link BeanMethod}, registering bean definitions with
|
||||||
* {@link #registry} based on its contents.
|
* {@link #registry} based on its contents.
|
||||||
*
|
|
||||||
* @see FactoryMethod
|
|
||||||
*/
|
*/
|
||||||
private void loadBeanDefinitionsForModelMethod(BeanMethod method) {
|
private void loadBeanDefinitionsForModelMethod(BeanMethod method) {
|
||||||
method.getRegistrar().register(method, registry);
|
new BeanRegistrar().register(method, registry);
|
||||||
|
//method.getRegistrar().register(method, registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @SuppressWarnings("unchecked")
|
// @SuppressWarnings("unchecked")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2008 the original author or authors.
|
* Copyright 2002-2009 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -40,65 +40,93 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BeanFactoryPostProcessor} used for bootstrapping {@link Configuration
|
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||||
* @Configuration} beans. Usually used in conjunction with Spring XML files.
|
* {@link Configuration @Configuration} classes.
|
||||||
*/
|
*/
|
||||||
public class ConfigurationPostProcessor extends AbstractConfigurationClassProcessor
|
public class ConfigurationPostProcessor extends AbstractConfigurationClassProcessor
|
||||||
implements Ordered, BeanFactoryPostProcessor {
|
implements Ordered, BeanFactoryPostProcessor {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(ConfigurationPostProcessor.class);
|
private static final Log logger = LogFactory.getLog(ConfigurationPostProcessor.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A well-known class in the CGLIB API used when testing to see if CGLIB
|
||||||
|
* is present on the classpath. Package-private visibility allows for
|
||||||
|
* manipulation by tests.
|
||||||
|
* @see #assertCglibIsPresent(BeanDefinitionRegistry)
|
||||||
|
*/
|
||||||
|
static String CGLIB_TEST_CLASS = "net.sf.cglib.proxy.Callback";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for the calling BeanFactory
|
||||||
|
* @see #postProcessBeanFactory(ConfigurableListableBeanFactory)
|
||||||
|
*/
|
||||||
private DefaultListableBeanFactory beanFactory;
|
private DefaultListableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the order in which this {@link BeanPostProcessor} will be executed. Returns
|
* @return the order in which this {@link BeanPostProcessor} will be executed. Returns
|
||||||
* {@link Ordered#HIGHEST_PRECEDENCE}.
|
* {@link Ordered#HIGHEST_PRECEDENCE}.
|
||||||
*/
|
*/
|
||||||
public int getOrder() {
|
public int getOrder() {
|
||||||
return Ordered.HIGHEST_PRECEDENCE;
|
return Ordered.HIGHEST_PRECEDENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds {@link Configuration} bean definitions within <var>clBeanFactory</var>
|
||||||
|
* and processes them in order to register bean definitions for each Bean method
|
||||||
|
* found within; also prepares the the Configuration classes for servicing
|
||||||
|
* bean requests at runtime by replacing them with CGLIB-enhanced subclasses.
|
||||||
|
*/
|
||||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory clBeanFactory) throws BeansException {
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory clBeanFactory) throws BeansException {
|
||||||
Assert.isInstanceOf(DefaultListableBeanFactory.class, clBeanFactory);
|
Assert.isInstanceOf(DefaultListableBeanFactory.class, clBeanFactory);
|
||||||
beanFactory = (DefaultListableBeanFactory) clBeanFactory;
|
beanFactory = (DefaultListableBeanFactory) clBeanFactory;
|
||||||
|
|
||||||
BeanDefinitionRegistry factoryBeanDefs = processConfigBeanDefinitions();
|
BeanDefinitionRegistry factoryBeanDefs = processConfigBeanDefinitions();
|
||||||
|
|
||||||
for(String beanName : factoryBeanDefs.getBeanDefinitionNames())
|
for(String beanName : factoryBeanDefs.getBeanDefinitionNames())
|
||||||
beanFactory.registerBeanDefinition(beanName, factoryBeanDefs.getBeanDefinition(beanName));
|
beanFactory.registerBeanDefinition(beanName, factoryBeanDefs.getBeanDefinition(beanName));
|
||||||
|
|
||||||
enhanceConfigurationClasses();
|
enhanceConfigurationClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ConfigurationParser that uses the enclosing BeanFactory's
|
||||||
|
* classLoader to load all Configuration class artifacts.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected ConfigurationParser createConfigurationParser() {
|
protected ConfigurationParser createConfigurationParser() {
|
||||||
return new ConfigurationParser(beanFactory.getBeanClassLoader());
|
return new ConfigurationParser(beanFactory.getBeanClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return map of all non-abstract {@link BeanDefinition}s in the enclosing {@link #beanFactory}
|
* @return map of all non-abstract {@link BeanDefinition}s in the enclosing {@link #beanFactory}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs) {
|
protected BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs) {
|
||||||
|
|
||||||
BeanDefinitionRegistry configBeanDefs = new DefaultListableBeanFactory();
|
BeanDefinitionRegistry configBeanDefs = new DefaultListableBeanFactory();
|
||||||
|
|
||||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||||
|
|
||||||
if (beanDef.isAbstract() && !includeAbstractBeanDefs)
|
if (beanDef.isAbstract() && !includeAbstractBeanDefs)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (isConfigClass(beanDef))
|
if (isConfigClass(beanDef))
|
||||||
configBeanDefs.registerBeanDefinition(beanName, beanDef);
|
configBeanDefs.registerBeanDefinition(beanName, beanDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
return configBeanDefs;
|
return configBeanDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given <var>model</var>.
|
||||||
|
* @throws MalformedConfigurationException if any errors are detected
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void validateModel(ConfigurationModel model) {
|
protected void validateModel(ConfigurationModel model) {
|
||||||
ArrayList<UsageError> errors = new ArrayList<UsageError>();
|
ArrayList<UsageError> errors = new ArrayList<UsageError>();
|
||||||
|
|
@ -118,10 +146,12 @@ public class ConfigurationPostProcessor extends AbstractConfigurationClassProces
|
||||||
*/
|
*/
|
||||||
private void enhanceConfigurationClasses() {
|
private void enhanceConfigurationClasses() {
|
||||||
|
|
||||||
ConfigurationEnhancer enhancer = new ConfigurationEnhancer(beanFactory);
|
|
||||||
|
|
||||||
BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(true);
|
BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(true);
|
||||||
|
|
||||||
|
assertCglibIsPresent(configBeanDefs);
|
||||||
|
|
||||||
|
ConfigurationEnhancer enhancer = new ConfigurationEnhancer(beanFactory);
|
||||||
|
|
||||||
for(String beanName : configBeanDefs.getBeanDefinitionNames()) {
|
for(String beanName : configBeanDefs.getBeanDefinitionNames()) {
|
||||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||||
String configClassName = beanDef.getBeanClassName();
|
String configClassName = beanDef.getBeanClassName();
|
||||||
|
|
@ -129,23 +159,38 @@ public class ConfigurationPostProcessor extends AbstractConfigurationClassProces
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug(format("Replacing bean definition '%s' existing class name '%s' with enhanced class name '%s'",
|
logger.debug(format("Replacing bean definition '%s' existing class name '%s' with enhanced class name '%s'",
|
||||||
beanName, configClassName, enhancedClassName));
|
beanName, configClassName, enhancedClassName));
|
||||||
|
|
||||||
beanDef.setBeanClassName(enhancedClassName);
|
beanDef.setBeanClassName(enhancedClassName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the class for <var>beanDef</var> is a {@link Configuration}
|
* Tests for the presence of CGLIB on the classpath by trying to
|
||||||
* -annotated class. Returns false if <var>beanDef</var> has no class specified.
|
* classload {@link #CGLIB_TEST_CLASS}.
|
||||||
|
*/
|
||||||
|
private void assertCglibIsPresent(BeanDefinitionRegistry configBeanDefs) {
|
||||||
|
try {
|
||||||
|
Class.forName(CGLIB_TEST_CLASS);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException("CGLIB is required to process @Configuration classes. " +
|
||||||
|
"Either add CGLIB v2.2.3 to the classpath or remove the following " +
|
||||||
|
"@Configuration bean definitions: ["
|
||||||
|
+ StringUtils.arrayToCommaDelimitedString(configBeanDefs.getBeanDefinitionNames()) + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the BeanDefinition's beanClass is Configuration-annotated,
|
||||||
|
* false if no beanClass is specified.
|
||||||
*/
|
*/
|
||||||
private static boolean isConfigClass(BeanDefinition beanDef) {
|
private static boolean isConfigClass(BeanDefinition beanDef) {
|
||||||
|
|
||||||
String className = beanDef.getBeanClassName();
|
String className = beanDef.getBeanClassName();
|
||||||
|
|
||||||
if(className == null)
|
if(className == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(className);
|
MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(className);
|
||||||
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
|
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package org.springframework.config.java.support;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.config.java.Configuration;
|
||||||
|
import org.springframework.config.java.ext.Bean;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link ConfigurationPostProcessor}
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
*/
|
||||||
|
public class ConfigurationPostProcessorTests {
|
||||||
|
|
||||||
|
private static final String ORIG_CGLIB_TEST_CLASS = ConfigurationPostProcessor.CGLIB_TEST_CLASS;
|
||||||
|
private static final String BOGUS_CGLIB_TEST_CLASS = "a.bogus.class";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CGLIB is an optional dependency for Core Spring. If users attempt
|
||||||
|
* to use {@link Configuration} classes, they'll need it on the classpath;
|
||||||
|
* if Configuration classes are present in the bean factory and CGLIB
|
||||||
|
* is not present, an instructive exception should be thrown.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFailFastIfCglibNotPresent() {
|
||||||
|
@Configuration class Config {
|
||||||
|
public @Bean String name() { return "foo"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||||
|
|
||||||
|
factory.registerBeanDefinition("config1", rootBeanDefinition(Config.class).getBeanDefinition());
|
||||||
|
|
||||||
|
ConfigurationPostProcessor cpp = new ConfigurationPostProcessor();
|
||||||
|
|
||||||
|
// temporarily set the cglib test class to something bogus
|
||||||
|
ConfigurationPostProcessor.CGLIB_TEST_CLASS = BOGUS_CGLIB_TEST_CLASS;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cpp.postProcessBeanFactory(factory);
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
assertTrue(ex.getMessage().contains("CGLIB is required to process @Configuration classes"));
|
||||||
|
} finally {
|
||||||
|
ConfigurationPostProcessor.CGLIB_TEST_CLASS = ORIG_CGLIB_TEST_CLASS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to keep Spring's footprint as small as possible, CGLIB must
|
||||||
|
* not be required on the classpath unless the user is taking advantage
|
||||||
|
* of {@link Configuration} classes.
|
||||||
|
*
|
||||||
|
* This test will fail if any CGLIB classes are classloaded before the call
|
||||||
|
* to {@link ConfigurationPostProcessor#enhanceConfigurationClasses}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCglibClassesAreLoadedJustInTimeForEnhancement() throws Exception {
|
||||||
|
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
|
||||||
|
Field classesField = ClassLoader.class.getDeclaredField("classes");
|
||||||
|
classesField.setAccessible(true);
|
||||||
|
|
||||||
|
// first, remove any CGLIB classes that may have been loaded by other tests
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Vector<Class<?>> classes = (Vector<Class<?>>) classesField.get(classLoader);
|
||||||
|
|
||||||
|
Vector<Class<?>> cglibClassesAlreadyLoaded = new Vector<Class<?>>();
|
||||||
|
for(Class<?> loadedClass : classes)
|
||||||
|
if(loadedClass.getName().startsWith("net.sf.cglib"))
|
||||||
|
cglibClassesAlreadyLoaded.add(loadedClass);
|
||||||
|
|
||||||
|
for(Class<?> cglibClass : cglibClassesAlreadyLoaded)
|
||||||
|
classes.remove(cglibClass);
|
||||||
|
|
||||||
|
// now, execute a scenario where everything except enhancement occurs
|
||||||
|
// -- no CGLIB classes should get loaded!
|
||||||
|
testFailFastIfCglibNotPresent();
|
||||||
|
|
||||||
|
// test to ensure that indeed no CGLIB classes have been loaded
|
||||||
|
for(Class<?> loadedClass : classes)
|
||||||
|
if(loadedClass.getName().startsWith("net.sf.cglib"))
|
||||||
|
fail("CGLIB class should not have been eagerly loaded: " + loadedClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced {@link Configuration} classes are only necessary for respecting
|
||||||
|
* certain bean semantics, like singleton-scoping, scoped proxies, etc.
|
||||||
|
*
|
||||||
|
* Technically, {@link ConfigurationPostProcessor} could fail to enhance the
|
||||||
|
* registered Configuration classes, and many use cases would still work.
|
||||||
|
* Certain cases, however, like inter-bean singleton references would not.
|
||||||
|
* We test for such a case below, and in doing so prove that enhancement is
|
||||||
|
* working.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEnhancementIsPresentBecauseSingletonSemanticsAreRespected() {
|
||||||
|
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||||
|
beanFactory.registerBeanDefinition("config",
|
||||||
|
rootBeanDefinition(SingletonBeanConfig.class).getBeanDefinition());
|
||||||
|
new ConfigurationPostProcessor().postProcessBeanFactory(beanFactory);
|
||||||
|
Foo foo = (Foo) beanFactory.getBean("foo");
|
||||||
|
Bar bar = (Bar) beanFactory.getBean("bar");
|
||||||
|
assertThat(foo, sameInstance(bar.foo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class SingletonBeanConfig {
|
||||||
|
public @Bean Foo foo() {
|
||||||
|
return new Foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Bean Bar bar() {
|
||||||
|
return new Bar(foo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Foo { }
|
||||||
|
static class Bar {
|
||||||
|
final Foo foo;
|
||||||
|
public Bar(Foo foo) { this.foo = foo; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue