+ Moving .config.java module -> .context

This commit is contained in:
Chris Beams
2009-03-23 04:48:04 +00:00
parent 63b5c48461
commit cd50e45645
62 changed files with 137 additions and 25 deletions

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2009 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.context.annotation;
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 org.springframework.context.annotation.Scope;
/**
* Indicates that a method produces a bean to be managed by the Spring container. The
* names and semantics of the attributes to this annotation are intentionally similar
* to those of the {@literal <bean/>} element in the Spring XML schema. Deviations are
* as follows:
*
* <p>The Bean annotation does not provide attributes for scope, primary or lazy. Rather,
* it should be used in conjunction with {@link Scope}, {@link Primary} and {@link Lazy}
* annotations to acheive the same semantics.
*
* <p>While a {@link #name()} attribute is available, the default strategy for determining
* the name of a bean is to use the name of the Bean method. This is convenient and
* intuitive, but if explicit naming is desired, the {@link #name()} attribute may be used.
* Also note that {@link #name()} accepts an array of strings. This is in order to allow
* for specifying multiple names (aka aliases) for a single bean.
*
* <h3>Constraints</h3>
* <ul>
* <li>Bean methods are valid only when declared within a {@link Configuration}-annotated class
* <li>Bean methods must be non-void, non-final, non-private
* <li>Bean methods may not accept any arguments
* <li>Bean methods may throw any exception, which will be caught and handled
* by the Spring container on processing of the declaring {@link Configuration} class.
* </ul>
*
* <h3>Usage</h3>
* <p>Bean methods may reference other Bean methods by calling them directly. This ensures
* that references between beans are strongly typed and navigable. So called 'inter-bean
* references' are guaranteed to respect scoping and AOP semantics.
*
* @author Rod Johnson
* @author Costin Leau
* @author Chris Beams
* @since 3.0
* @see Configuration
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
/**
* The name of this bean, or if plural, aliases for this bean. If left unspecified
* the name of the bean is the name of the annotated method. If specified, the method
* name is ignored.
*/
String[] name() default {};
/**
* The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly
* within the Bean method.
*/
String initMethod() default "";
/**
* The optional name of a method to call on the bean instance during upon closing
* the application context, for example a {@literal close()}
* method on a {@literal DataSource}.
*/
String destroyMethod() default "";
/**
* Beans on which the current bean depends. Any beans specified are guaranteed to be
* created by the container before this bean. Used infrequently in cases where a bean
* does not explicitly depend on another through properties or constructor arguments,
* but rather depends on the side effects of another bean's initialization.
*/
String[] dependsOn() default {};
}
// TODO: test @Lazy @Bean
// TODO: test @Primary @Bean
// TODO: test Bean alias scenarios
// TODO: test init/destroy method scenarios
// TODO: test dependsOn

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2009 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.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
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.Autowired;
import org.springframework.stereotype.Component;
/**
* Indicates that a class declares one or more {@link Bean} methods and may be processed
* by the Spring container to generate bean definitions and service requests for those beans
* at runtime.
*
* <p>Configuration is itself annotated as a {@link Component}, therefore Configuration
* classes are candidates for component-scanning and may also take advantage of
* {@link Autowire} at the field and method and constructor level.
*
* <p>May be used in conjunction with the {@link Lazy} annotation to indicate that all Bean
* methods declared within this class are by default lazily initialized.
*
* <h3>Constraints</h3>
* <ul>
* <li>Configuration classes must be non-final
* <li>Configuration classes must be non-local (may not be declared within a method)
* <li>Configuration classes must have a default/no-arg constructor or an
* {@link Autowired} constructor
* </ul>
*
*
* @author Rod Johnson
* @author Chris Beams
* @since 3.0
* @see org.springframework.context.annotation.support.ConfigurationClassPostProcessor;
* @see Bean
* @see Lazy
*/
@Component
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Configuration {
}
// TODO: test constructor autowiring<br>
// TODO: test private Configuration classes<br>
// TODO: test @Lazy @Configuration<br>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2009 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.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that allows one {@link Configuration} class to import another Configuration,
* and thereby all its {@link Bean} definitions.
*
* <p>Provides functionality equivalent to the {@literal <import/>} element in Spring XML.
*
* @author Chris Beams
* @since 3.0
* @see Configuration
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Import {
/**
* The {@link Configuration} class or classes to import.
*/
Class<?>[] value();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2009 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.context.annotation;
/**
* Enumerates the names of the scopes supported out of the box in Spring.
*
* <p>Not modeled as an actual java enum because annotations that accept a scope attribute
* must allow for user-defined scope names. Given that java enums are not extensible, these
* must remain simple string constants.
*
* @author Chris Beams
* @since 3.0
*/
public class StandardScopes {
private StandardScopes() { }
public static final String SINGLETON = "singleton";
public static final String PROTOTYPE = "prototype";
public static final String REQUEST = "request";
public static final String SESSION = "session";
}
// TODO: move StandardScopes to appropriate package

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Abstract superclass for processing {@link Configuration}-annotated classes and registering
* bean definitions based on {@link Bean}-annotated methods within those classes.
*
* <p>Provides template method {@link #processConfigBeanDefinitions()} that orchestrates calling each
* of several abstract methods to be overriden by concrete implementations that allow for
* customizing how {@link Configuration} classes are found ({@link #getConfigurationBeanDefinitions}),
* customizing the creation of a {@link ConfigurationParser} ({@link #createConfigurationParser}),
* and customizing {@link ConfigurationModel} validation logic ({@link #validateModel}).
*
* <p>This class was expressly designed with tooling in mind. Spring IDE will maintain it's
* own implementation of this class but still take advantage of the generic parsing algorithm
* defined here by {@link #processConfigBeanDefinitions()}.
*
* @author Chris Beams
* @since 3.0
* @see Configuration
* @see ConfigurationClassPostProcessor
*/
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.
*
* @param includeAbstractBeanDefs whether abstract Configuration bean definitions should
* be included in the resulting BeanDefinitionRegistry. Usually false, but called as true
* during the enhancement phase.
* @see #processConfigBeanDefinitions()
*/
protected abstract BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs);
/**
* Create and return a new {@link ConfigurationParser}, allowing for customization of
* type (ASM/JDT/Reflection) as well as providing specialized ClassLoader during
* construction.
* @see #processConfigBeanDefinitions()
*/
protected abstract ConfigurationParser createConfigurationParser();
/**
* 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},
* then, based on the content of that model, create and register bean definitions
* against a new {@link BeanDefinitionRegistry}, then return the registry.
*
* @return registry containing one bean definition per {@link Bean} method declared
* within the Configuration classes
*/
protected final BeanDefinitionRegistry processConfigBeanDefinitions() {
BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(false);
// return an empty registry immediately if no @Configuration classes were found
if(configBeanDefs.getBeanDefinitionCount() == 0)
return configBeanDefs;
// populate a new ConfigurationModel by parsing each @Configuration classes
ConfigurationParser parser = createConfigurationParser();
for(String beanName : configBeanDefs.getBeanDefinitionNames()) {
BeanDefinition beanDef = configBeanDefs.getBeanDefinition(beanName);
String className = beanDef.getBeanClassName();
parser.parse(className, beanName);
}
ConfigurationModel configModel = parser.getConfigurationModel();
configModel.validate(problemReporter);
// read the model and create bean definitions based on its content
return new ConfigurationModelBeanDefinitionReader().loadBeanDefinitions(configModel);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import static org.springframework.context.annotation.StandardScopes.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import org.springframework.beans.BeanMetadataElement;
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.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.util.Assert;
/**
* Represents a {@link Configuration} class method marked with the {@link Bean} annotation.
*
* @author Chris Beams
* @see ConfigurationClass
* @see ConfigurationModel
* @see ConfigurationParser
* @see ConfigurationModelBeanDefinitionReader
*/
final class BeanMethod implements BeanMetadataElement {
private final String name;
private final int modifiers;
private final ModelClass returnType;
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
private transient ConfigurationClass declaringClass;
private transient Object source;
public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) {
Assert.hasText(name);
this.name = name;
Assert.notNull(annotations);
for (Annotation annotation : annotations)
this.annotations.add(annotation);
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
this.modifiers = modifiers;
Assert.notNull(returnType);
this.returnType = returnType;
}
public String getName() {
return name;
}
public ModelClass getReturnType() {
return returnType;
}
/**
* @see java.lang.reflect.Modifier
*/
public int getModifiers() {
return modifiers;
}
/**
* @return the annotation on this method matching <var>annoType</var> or
* {@literal null} if not present.
* @see #getRequiredAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annoType) {
for (Annotation anno : annotations)
if (anno.annotationType().equals(annoType))
return (T) anno;
return null;
}
/**
* @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) {
T anno = getAnnotation(annoType);
Assert.notNull(anno, format("annotation %s not found on %s", annoType.getSimpleName(), this));
return anno;
}
/**
* Set up a bi-directional relationship between this method and its declaring class.
*
* @see ConfigurationClass#addBeanMethod(BeanMethod)
*/
public void setDeclaringClass(ConfigurationClass declaringClass) {
this.declaringClass = declaringClass;
}
public ConfigurationClass getDeclaringClass() {
return declaringClass;
}
public void setSource(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
public Location getLocation() {
if (declaringClass == null)
throw new IllegalStateException(
"declaringClass property is null. Call setDeclaringClass() before calling getLocation()");
return new Location(declaringClass.getLocation().getResource(), getSource());
}
public void validate(ProblemReporter problemReporter) {
if (Modifier.isPrivate(getModifiers()))
problemReporter.error(new PrivateMethodError());
if (Modifier.isFinal(getModifiers()))
problemReporter.error(new FinalMethodError());
Scope scope = this.getAnnotation(Scope.class);
if(scope != null
&& scope.proxyMode() != ScopedProxyMode.NO
&& (scope.value().equals(SINGLETON) || scope.value().equals(PROTOTYPE)))
problemReporter.error(new InvalidScopedProxyDeclarationError(this));
}
@Override
public String toString() {
String returnTypeName = returnType == null ? "<unknown>" : returnType.getSimpleName();
return format("%s: name=%s; returnType=%s; modifiers=%d",
getClass().getSimpleName(), name, returnTypeName, modifiers);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((annotations == null) ? 0 : annotations.hashCode());
result = prime * result + modifiers;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BeanMethod other = (BeanMethod) obj;
if (annotations == null) {
if (other.annotations != null)
return false;
} else if (!annotations.equals(other.annotations))
return false;
if (modifiers != other.modifiers)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (returnType == null) {
if (other.returnType != null)
return false;
} else if (!returnType.equals(other.returnType))
return false;
return true;
}
/** {@link Bean} methods must be non-private in order to accommodate CGLIB. */
class PrivateMethodError extends Problem {
PrivateMethodError() {
super(format("Method '%s' may not be private; increase the method's visibility to continue", getName()),
BeanMethod.this.getLocation());
}
}
/** {@link Bean} methods must be non-final in order to accommodate CGLIB. */
class FinalMethodError extends Problem {
FinalMethodError() {
super(format("Method '%s' may not be final; remove the final modifier to continue", getName()),
BeanMethod.this.getLocation());
}
}
class InvalidScopedProxyDeclarationError extends Problem {
InvalidScopedProxyDeclarationError(BeanMethod method) {
super(format("Method %s contains an invalid annotation declaration: scoped proxies "
+ "cannot be created for singleton/prototype beans", method.getName()),
BeanMethod.this.getLocation());
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.annotation.AnnotationUtils;
/**
* Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
* handling of bean semantics such as scoping and AOP proxying.
*
* @author Chris Beams
* @see Bean
* @see ConfigurationEnhancer
*/
class BeanMethodInterceptor implements MethodInterceptor {
private static final Log log = LogFactory.getLog(BeanMethodInterceptor.class);
private final DefaultListableBeanFactory beanFactory;
public BeanMethodInterceptor(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the
* existence of this bean object.
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// by default the bean name is the name of the @Bean-annotated method
String beanName = method.getName();
// check to see if the user has explicitly set the bean name
Bean bean = method.getAnnotation(Bean.class);
if(bean != null && bean.name().length > 0)
beanName = bean.name()[0];
// determine whether this bean is a scoped-proxy
Scope scope = AnnotationUtils.findAnnotation(method, Scope.class);
boolean isScopedProxy = (scope != null && scope.proxyMode() != ScopedProxyMode.NO);
String scopedBeanName = ConfigurationModelBeanDefinitionReader.resolveHiddenScopedProxyBeanName(beanName);
if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
beanName = scopedBeanName;
// to handle the case of an inter-bean method reference, we must explicitly check the
// container for already cached instances
if (factoryContainsBean(beanName)) {
// we have an already existing cached instance of this bean -> retrieve it
Object cachedBean = beanFactory.getBean(beanName);
if (log.isInfoEnabled())
log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s",
cachedBean, method.getDeclaringClass().getSimpleName(), beanName));
return cachedBean;
}
// actually create and return the bean
return proxy.invokeSuper(obj, args);
}
/**
* Check the beanFactory to see whether the bean named <var>beanName</var> already
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
* we're in the middle of servicing the initial request for this bean. From JavaConfig's
* perspective, this means that the bean does not actually yet exist, and that it is now
* our job to create it for the first time by executing the logic in the corresponding
* Bean method.
* <p>
* Said another way, this check repurposes
* {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
* the container is calling this method or the user is calling this method.
*
* @param beanName name of bean to check for
*
* @return true if <var>beanName</var> already exists in beanFactory
*/
private boolean factoryContainsBean(String beanName) {
return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
/**
* Represents a user-defined {@link Configuration @Configuration} class.
* Includes a set of {@link Bean} methods, including all such methods defined in the
* ancestry of the class, in a 'flattened-out' manner. Note that each {@link BeanMethod}
* representation contains source information about where it was originally detected
* (for the purpose of tooling with Spring IDE).
*
* @author Chris Beams
* @see ConfigurationModel
* @see BeanMethod
* @see ConfigurationParser
*/
final class ConfigurationClass extends ModelClass {
private String beanName;
private int modifiers;
private Configuration configurationAnnotation;
private HashSet<BeanMethod> methods = new HashSet<BeanMethod>();
private ConfigurationClass declaringClass;
public String getBeanName() {
return beanName == null ? getName() : beanName;
}
public void setBeanName(String id) {
this.beanName = id;
}
public int getModifiers() {
return modifiers;
}
public void setModifiers(int modifiers) {
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative");
this.modifiers = modifiers;
}
public Configuration getConfigurationAnnotation() {
return this.configurationAnnotation;
}
public void setConfigurationAnnotation(Configuration configAnno) {
Assert.notNull(configAnno, "configuration annotation must be non-null");
this.configurationAnnotation = configAnno;
}
public Set<BeanMethod> getBeanMethods() {
return methods;
}
public ConfigurationClass addBeanMethod(BeanMethod method) {
method.setDeclaringClass(this);
methods.add(method);
return this;
}
public ConfigurationClass getDeclaringClass() {
return declaringClass;
}
public void setDeclaringClass(ConfigurationClass configurationClass) {
this.declaringClass = configurationClass;
}
public void validate(ProblemReporter problemReporter) {
// configuration classes must be annotated with @Configuration
if (configurationAnnotation == null)
problemReporter.error(new NonAnnotatedConfigurationProblem());
// a configuration class may not be final (CGLIB limitation)
if (Modifier.isFinal(modifiers))
problemReporter.error(new FinalConfigurationProblem());
for (BeanMethod method : methods)
method.validate(problemReporter);
}
@Override
public String toString() {
return format("%s; modifiers=%d; methods=%s", super.toString(), modifiers, methods);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((declaringClass == null) ? 0 : declaringClass.hashCode());
result = prime * result + ((beanName == null) ? 0 : beanName.hashCode());
result = prime * result + ((configurationAnnotation == null) ? 0 : configurationAnnotation.hashCode());
result = prime * result + ((methods == null) ? 0 : methods.hashCode());
result = prime * result + modifiers;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
ConfigurationClass other = (ConfigurationClass) obj;
if (declaringClass == null) {
if (other.declaringClass != null)
return false;
} else if (!declaringClass.equals(other.declaringClass))
return false;
if (beanName == null) {
if (other.beanName != null)
return false;
} else if (!beanName.equals(other.beanName))
return false;
if (configurationAnnotation == null) {
if (other.configurationAnnotation != null)
return false;
} else if (!configurationAnnotation.equals(other.configurationAnnotation))
return false;
if (methods == null) {
if (other.methods != null)
return false;
} else if (!methods.equals(other.methods))
return false;
if (modifiers != other.modifiers)
return false;
return true;
}
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */
class NonAnnotatedConfigurationProblem extends Problem {
NonAnnotatedConfigurationProblem() {
super(format("%s was specified as a @Configuration class but was not actually annotated " +
"with @Configuration. Annotate the class or do not attempt to process it.",
getSimpleName()),
ConfigurationClass.this.getLocation());
}
}
/** Configuration classes must be non-final to accommodate CGLIB subclassing. */
class FinalConfigurationProblem extends Problem {
FinalConfigurationProblem() {
super(format("@Configuration class [%s] may not be final. Remove the final modifier to continue.",
getSimpleName()),
ConfigurationClass.this.getLocation());
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static org.springframework.context.annotation.support.AsmUtils.*;
import static org.springframework.context.annotation.support.MutableAnnotationUtils.*;
import static org.springframework.context.annotation.support.Util.*;
import static org.springframework.util.ClassUtils.*;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.ClassAdapter;
import org.springframework.asm.Label;
import org.springframework.asm.MethodAdapter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ASM {@link MethodVisitor} that visits a single method declared in a given
* {@link Configuration} class. Determines whether the method is a {@link Bean}
* method and if so, adds it to the {@link ConfigurationClass}.
*
* @author Chris Beams
*/
class ConfigurationClassMethodVisitor extends MethodAdapter {
private final ConfigurationClass configClass;
private final String methodName;
private final int modifiers;
private final ModelClass returnType;
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
private final ClassLoader classLoader;
private int lineNumber;
/**
* Creates a new {@link ConfigurationClassMethodVisitor} instance.
*
* @param configClass model object to which this method will be added
* @param methodName name of the method declared in the {@link Configuration} class
* @param methodDescriptor ASM representation of the method signature
* @param modifiers modifiers for this method
*/
public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName,
String methodDescriptor, int modifiers, ClassLoader classLoader) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.methodName = methodName;
this.classLoader = classLoader;
this.modifiers = modifiers;
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
}
/**
* Visits a single annotation on this method. Will be called once for each annotation
* present (regardless of its RetentionPolicy).
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoClassName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoClass = loadToolingSafeClass(annoClassName, classLoader);
if (annoClass == null)
return super.visitAnnotation(annoTypeDesc, visible);
Annotation annotation = createMutableAnnotation(annoClass);
annotations.add(annotation);
return new MutableAnnotationVisitor(annotation, classLoader);
}
/**
* Provides the line number of this method within its declaring class. In reality, this
* number is always inaccurate - <var>lineNo</var> represents the line number of the
* first instruction in this method. Method declaration line numbers are not in any way
* tracked in the bytecode. Any tooling or output that reads this value will have to
* compensate and estimate where the actual method declaration is.
*/
@Override
public void visitLineNumber(int lineNo, Label start) {
this.lineNumber = lineNo;
}
/**
* Parses through all {@link #annotations} on this method in order to determine whether
* it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}.
*/
@Override
public void visitEnd() {
for (Annotation anno : annotations) {
if (Bean.class.equals(anno.annotationType())) {
// this method is annotated with @Bean -> add it to the ConfigurationClass model
Annotation[] annoArray = annotations.toArray(new Annotation[] {});
BeanMethod method = new BeanMethod(methodName, modifiers, returnType, annoArray);
method.setSource(lineNumber);
configClass.addBeanMethod(method);
break;
}
}
}
/**
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
* that type is an interface.
*/
private ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) {
final ModelClass returnType = new ModelClass(getReturnTypeFromMethodDescriptor(methodDescriptor));
// detect whether the return type is an interface
newClassReader(convertClassNameToResourcePath(returnType.getName()), classLoader).accept(
new ClassAdapter(AsmUtils.EMPTY_VISITOR) {
@Override
public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) {
returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE);
}
}, false);
return returnType;
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
* {@link Configuration @Configuration} classes.
* <p>
* Registered by default when using {@literal <context:annotation-config/>} or
* {@literal <context:component-scan/>}. Otherwise, may be declared manually as
* with any other BeanFactoryPostProcessor.
* <p>
* This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it's important
* that any {@link Bean} methods declared in Configuration classes have their
* respective bean definitions registered before any other BeanFactoryPostProcessor
* executes.
*
* @author Chris Beams
* @since 3.0
*/
public class ConfigurationClassPostProcessor extends AbstractConfigurationClassProcessor
implements Ordered, BeanFactoryPostProcessor {
private static final Log logger = LogFactory.getLog(ConfigurationClassPostProcessor.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;
/**
* @return {@link Ordered#HIGHEST_PRECEDENCE}.
*/
public int getOrder() {
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 {
Assert.isInstanceOf(DefaultListableBeanFactory.class, clBeanFactory);
beanFactory = (DefaultListableBeanFactory) clBeanFactory;
BeanDefinitionRegistry factoryBeanDefs = processConfigBeanDefinitions();
for(String beanName : factoryBeanDefs.getBeanDefinitionNames())
beanFactory.registerBeanDefinition(beanName, factoryBeanDefs.getBeanDefinition(beanName));
enhanceConfigurationClasses();
}
/**
* @return a ConfigurationParser that uses the enclosing BeanFactory's
* ClassLoader to load all Configuration class artifacts.
*/
@Override
protected ConfigurationParser createConfigurationParser() {
return new ConfigurationParser(this.getProblemReporter(), beanFactory.getBeanClassLoader());
}
/**
* @return map of all non-abstract {@link BeanDefinition}s in the
* enclosing {@link #beanFactory}
*/
@Override
protected BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs) {
BeanDefinitionRegistry configBeanDefs = new DefaultListableBeanFactory();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (beanDef.isAbstract() && !includeAbstractBeanDefs)
continue;
if (isConfigurationClassBeanDefinition(beanDef))
configBeanDefs.registerBeanDefinition(beanName, beanDef);
}
return configBeanDefs;
}
/**
* Post-processes a BeanFactory in search of Configuration class BeanDefinitions; any
* candidates are then enhanced by a {@link ConfigurationEnhancer}. Candidate status is
* determined by BeanDefinition attribute metadata.
*
* @see ConfigurationEnhancer
* @see BeanFactoryPostProcessor
*/
private void enhanceConfigurationClasses() {
BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(true);
assertCglibIsPresent(configBeanDefs);
ConfigurationEnhancer enhancer = new ConfigurationEnhancer(beanFactory);
for(String beanName : configBeanDefs.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
String configClassName = beanDef.getBeanClassName();
String enhancedClassName = enhancer.enhance(configClassName);
if (logger.isDebugEnabled())
logger.debug(format("Replacing bean definition '%s' existing class name '%s' "
+ "with enhanced class name '%s'", beanName, configClassName, enhancedClassName));
beanDef.setBeanClassName(enhancedClassName);
}
}
/**
* Tests for the presence of CGLIB on the classpath by trying to
* classload {@link #CGLIB_TEST_CLASS}.
* @throws IllegalStateException if CGLIB is not present.
*/
private void assertCglibIsPresent(BeanDefinitionRegistry configBeanDefs) {
try {
Class.forName(CGLIB_TEST_CLASS);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("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 (or its ancestry) is
* {@link Configuration}-annotated, false if no beanClass is specified.
*/
private static boolean isConfigurationClassBeanDefinition(BeanDefinition beanDef) {
String className = beanDef.getBeanClassName();
while (className != null && !(className.equals(Object.class.getName()))) {
try {
MetadataReader metadataReader =
new SimpleMetadataReaderFactory().getMetadataReader(className);
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
ClassMetadata classMetadata = metadataReader.getClassMetadata();
if (annotationMetadata.hasAnnotation(Configuration.class.getName()))
return true;
className = classMetadata.getSuperClassName();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return false;
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import static org.springframework.context.annotation.support.MutableAnnotationUtils.*;
import static org.springframework.util.ClassUtils.*;
import java.util.HashMap;
import java.util.Stack;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.ClassAdapter;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
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.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
/**
* ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a
* {@link ConfigurationClass} instance with information gleaned along the way.
*
* @author Chris Beams
* @see ConfigurationParser
* @see ConfigurationClass
*/
class ConfigurationClassVisitor extends ClassAdapter {
private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName());
private final ConfigurationClass configClass;
private final ConfigurationModel model;
private final ProblemReporter problemReporter;
private final ClassLoader classLoader;
private final HashMap<String, ConfigurationClass> innerClasses = new HashMap<String, ConfigurationClass>();
private boolean processInnerClasses = true;
public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model,
ProblemReporter problemReporter, ClassLoader classLoader) {
super(AsmUtils.EMPTY_VISITOR);
this.configClass = configClass;
this.model = model;
this.problemReporter = problemReporter;
this.classLoader = classLoader;
}
public void setProcessInnerClasses(boolean processInnerClasses) {
this.processInnerClasses = processInnerClasses;
}
@Override
public void visitSource(String sourceFile, String debug) {
String resourcePath =
convertClassNameToResourcePath(configClass.getName())
.substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile);
configClass.setSource(resourcePath);
}
@Override
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3,
String superTypeDesc, String[] arg5) {
visitSuperType(superTypeDesc);
configClass.setName(convertResourcePathToClassName(classTypeDesc));
// ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions.
// Unknown as to why (JavaDoc is silent on the matter), but it should be
// eliminated in order to comply with java.lang.reflect.Modifier values.
configClass.setModifiers(modifiers - Opcodes.ACC_SUPER);
}
private void visitSuperType(String superTypeDesc) {
// traverse up the type hierarchy unless the next ancestor is java.lang.Object
if (OBJECT_DESC.equals(superTypeDesc))
return;
ConfigurationClassVisitor visitor =
new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader);
ClassReader reader = AsmUtils.newClassReader(superTypeDesc, classLoader);
reader.accept(visitor, false);
}
/**
* Visits a class level annotation on a {@link Configuration @Configuration} class.
* Accounts for all possible class-level annotations that are respected by JavaConfig
* including AspectJ's {@code @Aspect} annotation.
* <p>
* Upon encountering such an annotation, update the {@link #configClass} model object
* appropriately, and then return an {@link AnnotationVisitor} implementation that can
* populate the annotation appropriately with data.
*
* @see MutableAnnotation
*/
@Override
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
if (Configuration.class.getName().equals(annoTypeName)) {
Configuration mutableConfiguration = createMutableAnnotation(Configuration.class);
configClass.setConfigurationAnnotation(mutableConfiguration);
return new MutableAnnotationVisitor(mutableConfiguration, classLoader);
}
if (Import.class.getName().equals(annoTypeName)) {
ImportStack importStack = ImportStackHolder.getImportStack();
if (!importStack.contains(configClass)) {
importStack.push(configClass);
return new ImportAnnotationVisitor(model, problemReporter, classLoader);
}
problemReporter.error(new CircularImportProblem(configClass, importStack));
}
return super.visitAnnotation(annoTypeDesc, visible);
}
/**
* Delegates all {@link Configuration @Configuration} class method parsing to
* {@link ConfigurationClassMethodVisitor}.
*/
@Override
public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3,
String[] arg4) {
return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader);
}
/**
* Implementation deals with inner classes here even though it would have been more
* intuitive to deal with outer classes. Due to limitations in ASM (resulting from
* limitations in the VM spec) we cannot directly look for outer classes in all cases,
* so instead build up a model of {@link #innerClasses} and process declaring class
* logic in a kind of inverted manner.
*/
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (processInnerClasses == false)
return;
String innerClassName = convertResourcePathToClassName(name);
String configClassName = configClass.getName();
// if the innerClassName is equal to configClassName, we just
// ran into the outermost inner class look up the outer class
// associated with this
if (innerClassName.equals(configClassName)) {
if (innerClasses.containsKey(outerName)) {
configClass.setDeclaringClass(innerClasses.get(outerName));
}
return;
}
ConfigurationClass innerConfigClass = new ConfigurationClass();
ConfigurationClassVisitor ccVisitor =
new ConfigurationClassVisitor(innerConfigClass, new ConfigurationModel(), problemReporter, classLoader);
ccVisitor.setProcessInnerClasses(false);
ClassReader reader = AsmUtils.newClassReader(name, classLoader);
reader.accept(ccVisitor, false);
if (innerClasses.containsKey(outerName))
innerConfigClass.setDeclaringClass(innerClasses.get(outerName));
// is the inner class a @Configuration class? If so, add it to the list
if (innerConfigClass.getConfigurationAnnotation() != null)
innerClasses.put(name, innerConfigClass);
}
/**
* {@link Problem} registered upon detection of a circular {@link Import}.
*
* @see Import
* @see ImportStack
* @see ImportStackHolder
*/
class CircularImportProblem extends Problem {
CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
super(format("A circular @Import has been detected: " +
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
"already present in the current import stack [%s]",
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
attemptedImport.getSimpleName(), importStack),
new Location(new ClassPathResource(convertClassNameToResourcePath(importStack.peek().getName())),
importStack.peek().getSource())
);
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import static org.springframework.context.annotation.support.Util.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import net.sf.cglib.core.DefaultGeneratorStrategy;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.asm.ClassAdapter;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassWriter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
/**
* Enhances {@link Configuration} classes by generating a CGLIB subclass capable of
* interacting with the Spring container to respect bean semantics.
*
* @see #enhance(String)
*
* @author Chris Beams
* @see ConfigurationClassPostProcessor
*/
class ConfigurationEnhancer {
private static final Log log = LogFactory.getLog(ConfigurationEnhancer.class);
private final ArrayList<Callback> callbackInstances = new ArrayList<Callback>();
private final ArrayList<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
private final CallbackFilter callbackFilter;
/**
* Creates a new {@link ConfigurationEnhancer} instance.
*/
public ConfigurationEnhancer(DefaultListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory must be non-null");
callbackInstances.add(new BeanMethodInterceptor(beanFactory));
callbackInstances.add(NoOp.INSTANCE);
for (Callback callback : callbackInstances)
callbackTypes.add(callback.getClass());
// set up the callback filter to return the index of the BeanMethodInterceptor when
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
callbackFilter = new CallbackFilter() {
public int accept(Method candidateMethod) {
return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1;
}
};
}
/**
* Loads the specified class and generates a CGLIB subclass of it equipped with
* container-aware callbacks capable of respecting scoping and other bean semantics.
*
* @return fully-qualified name of the enhanced subclass
*/
public String enhance(String configClassName) {
if (log.isInfoEnabled())
log.info("Enhancing " + configClassName);
Class<?> superclass = loadRequiredClass(configClassName);
Class<?> subclass = createClass(newEnhancer(superclass), superclass);
subclass = nestOneClassDeeperIfAspect(superclass, subclass);
if (log.isInfoEnabled())
log.info(format("Successfully enhanced %s; enhanced class name is: %s",
configClassName, subclass.getName()));
return subclass.getName();
}
/**
* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> superclass) {
Enhancer enhancer = new Enhancer();
// because callbackFilter and callbackTypes are dynamically populated
// there's no opportunity for caching. This does not appear to be causing
// any performance problem.
enhancer.setUseCache(false);
enhancer.setSuperclass(superclass);
enhancer.setUseFactory(false);
enhancer.setCallbackFilter(callbackFilter);
enhancer.setCallbackTypes(callbackTypes.toArray(new Class<?>[] {}));
return enhancer;
}
/**
* Uses enhancer to generate a subclass of superclass, ensuring that
* {@link #callbackInstances} are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer, Class<?> superclass) {
Class<?> subclass = enhancer.createClass();
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {}));
return subclass;
}
/**
* Works around a constraint imposed by the AspectJ 5 annotation-style programming
* model. See comments inline for detail.
*
* @return original subclass instance unless superclass is annnotated with @Aspect, in
* which case a subclass of the subclass is returned
*/
// TODO: try to implement with modifications to AbstractAspectJAdvisorFactory#isAspect
private Class<?> nestOneClassDeeperIfAspect(Class<?> superclass, Class<?> origSubclass) {
boolean superclassIsAnAspect = false;
// check for @Aspect by name rather than by class literal to avoid
// requiring AspectJ as a runtime dependency.
for (Annotation anno : superclass.getAnnotations())
if (anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect"))
superclassIsAnAspect = true;
if (!superclassIsAnAspect)
return origSubclass;
// the superclass is annotated with AspectJ's @Aspect.
// this means that we must create a subclass of the subclass
// in order to avoid some guard logic in Spring core that disallows
// extending a concrete aspect class.
Enhancer enhancer = newEnhancer(origSubclass);
enhancer.setStrategy(new DefaultGeneratorStrategy() {
@Override
protected byte[] transform(byte[] b) throws Exception {
ClassWriter writer = new ClassWriter(false);
ClassAdapter adapter = new AddAnnotationAdapter(writer, "Lorg/aspectj/lang/annotation/Aspect;");
ClassReader reader = new ClassReader(b);
reader.accept(adapter, false);
return writer.toByteArray();
}
});
// create a subclass of the original subclass
Class<?> newSubclass = createClass(enhancer, origSubclass);
return newSubclass;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import java.util.LinkedHashSet;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Configuration;
/**
* Represents the set of all user-defined {@link Configuration} classes. Once this model
* is populated using a {@link ConfigurationParser}, it can be rendered out to a set of
* {@link BeanDefinition} objects. This model provides an important layer of indirection
* between the complexity of parsing a set of classes and the complexity of representing
* the contents of those classes as BeanDefinitions.
*
* @author Chris Beams
* @see ConfigurationClass
* @see ConfigurationParser
* @see ConfigurationModelBeanDefinitionReader
*/
@SuppressWarnings("serial")
final class ConfigurationModel extends LinkedHashSet<ConfigurationClass> {
/**
* Recurses through the model validating each {@link ConfigurationClass}.
*
* @param problemReporter {@link ProblemReporter} against which any validation errors
* will be registered
* @see ConfigurationClass#validate
*/
public void validate(ProblemReporter problemReporter) {
for (ConfigurationClass configClass : this)
configClass.validate(problemReporter);
}
@Override
public String toString() {
return format("%s containing @Configuration classes: %s", getClass().getSimpleName(), super.toString());
}
}

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import static org.springframework.util.StringUtils.*;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Reads a given fully-populated {@link ConfigurationModel}, registering bean definitions
* with the given {@link BeanDefinitionRegistry} based on its contents.
* <p>
* This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does not
* implement/extend any of its artifacts as {@link ConfigurationModel} is not a
* {@link Resource}.
*
* @author Chris Beams
* @see ConfigurationModel
* @see AbstractConfigurationClassProcessor#processConfigBeanDefinitions()
*/
class ConfigurationModelBeanDefinitionReader {
private static final Log log = LogFactory.getLog(ConfigurationModelBeanDefinitionReader.class);
private BeanDefinitionRegistry registry;
/**
* Reads {@code model}, registering bean definitions with {@link #registry} based on
* its contents.
*
* @return number of bean definitions generated
*/
public BeanDefinitionRegistry loadBeanDefinitions(ConfigurationModel configurationModel) {
registry = new SimpleBeanDefinitionRegistry();
for (ConfigurationClass configClass : configurationModel)
loadBeanDefinitionsForConfigurationClass(configClass);
return registry;
}
/**
* Reads a particular {@link ConfigurationClass}, registering bean definitions for the
* class itself, all its {@link Bean} methods
*/
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
doLoadBeanDefinitionForConfigurationClass(configClass);
for (BeanMethod method : configClass.getBeanMethods())
loadBeanDefinitionsForModelMethod(method);
}
/**
* Registers the {@link Configuration} class itself as a bean definition.
* @param beanDefs
*/
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
configBeanDef.setBeanClassName(configClass.getName());
String configBeanName = configClass.getBeanName();
// consider the case where it's already been defined (probably in XML)
// and potentially has PropertyValues and ConstructorArgs)
if (registry.containsBeanDefinition(configBeanName)) {
if (log.isInfoEnabled())
log.info(format("Copying property and constructor arg values from existing bean definition for "
+ "@Configuration class %s to new bean definition", configBeanName));
AbstractBeanDefinition existing = (AbstractBeanDefinition) registry.getBeanDefinition(configBeanName);
configBeanDef.setPropertyValues(existing.getPropertyValues());
configBeanDef.setConstructorArgumentValues(existing.getConstructorArgumentValues());
configBeanDef.setResource(existing.getResource());
}
if (log.isInfoEnabled())
log.info(format("Registering bean definition for @Configuration class %s", configBeanName));
registry.registerBeanDefinition(configBeanName, configBeanDef);
}
/**
* Reads a particular {@link BeanMethod}, registering bean definitions with
* {@link #registry} based on its contents.
*/
private void loadBeanDefinitionsForModelMethod(BeanMethod method) {
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition();
ConfigurationClass configClass = method.getDeclaringClass();
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setFactoryMethodName(method.getName());
Bean bean = method.getRequiredAnnotation(Bean.class);
// TODO: prune defaults
//Configuration defaults = configClass.getMetadata();
// consider scoping
Scope scope = method.getAnnotation(Scope.class);
if(scope != null)
beanDef.setScope(scope.value());
// 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)) {
BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry);
// is the existing bean definition one that was created by JavaConfig?
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
// no -> then it's an external override, probably XML
// overriding is legal, return immediately
log.info(format("Skipping loading bean definition for %s: a definition for bean "
+ "'%s' already exists. This is likely due to an override in XML.", method, beanName));
return;
}
}
// TODO: re-enable for Lazy support
// // is this bean marked as primary for disambiguation?
// if (bean.primary() == Primary.TRUE)
// beanDef.setPrimary(true);
//
// // is this bean lazily instantiated?
// if ((bean.lazy() == Lazy.TRUE)
// || ((bean.lazy() == Lazy.UNSPECIFIED) && (defaults.defaultLazy() == Lazy.TRUE)))
// beanDef.setLazyInit(true);
// does this bean have a custom init-method specified?
String initMethodName = bean.initMethod();
if (hasText(initMethodName))
beanDef.setInitMethodName(initMethodName);
// does this bean have a custom destroy-method specified?
String destroyMethodName = bean.destroyMethod();
if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName);
// is this method annotated with @Scope(scopedProxy=...)?
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
RootBeanDefinition targetDef = beanDef;
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
String targetBeanName = resolveHiddenScopedProxyBeanName(beanName);
RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS)
targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// don't need to set it explicitly here.
else
scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
// The target bean should be ignored in favor of the scoped proxy.
targetDef.setAutowireCandidate(false);
// Register the target bean as separate bean in the factory
registry.registerBeanDefinition(targetBeanName, targetDef);
// replace the original bean definition with the target one
beanDef = scopedProxyDefinition;
}
if (bean.dependsOn().length > 0)
beanDef.setDependsOn(bean.dependsOn());
log.info(format("Registering bean definition for @Bean method %s.%s()",
configClass.getName(), beanName));
registry.registerBeanDefinition(beanName, beanDef);
}
private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
try {
getBeanDefinitionIncludingAncestry(beanName, registry);
return true;
} catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) {
if(!(registry instanceof ConfigurableListableBeanFactory))
return registry.getBeanDefinition(beanName);
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry;
do {
if (clbf.containsBeanDefinition(beanName))
return registry.getBeanDefinition(beanName);
BeanFactory parent = clbf.getParentBeanFactory();
if (parent == null) {
clbf = null;
} else if (parent instanceof ConfigurableListableBeanFactory) {
clbf = (ConfigurableListableBeanFactory) parent;
// TODO: re-enable
// } else if (parent instanceof AbstractApplicationContext) {
// clbf = ((AbstractApplicationContext) parent).getBeanFactory();
} else {
throw new IllegalStateException("unknown parent type: " + parent.getClass().getName());
}
} while (clbf != null);
throw new NoSuchBeanDefinitionException(
format("No bean definition matching name '%s' " +
"could be found in %s or its ancestry", beanName, registry));
}
/**
* Return the <i>hidden</i> name based on a scoped proxy bean name.
*
* @param originalBeanName the scope proxy bean name as declared in the
* Configuration-annotated class
*
* @return the internally-used <i>hidden</i> bean name
*/
public static String resolveHiddenScopedProxyBeanName(String originalBeanName) {
Assert.hasText(originalBeanName);
return TARGET_NAME_PREFIX.concat(originalBeanName);
}
/** Prefix used when registering the target object for a scoped proxy. */
private static final String TARGET_NAME_PREFIX = "scopedTarget.";
}
/**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition created
* by JavaConfig as opposed to any other configuration source. Used in bean overriding cases
* where it's necessary to determine whether the bean definition was created externally
* (e.g. via XML).
*/
@SuppressWarnings("serial")
class ConfigurationClassBeanDefinition extends RootBeanDefinition {
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import org.springframework.asm.ClassReader;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
/**
* Parses a {@link Configuration} class definition, populating a {@link ConfigurationModel}.
* This ASM-based implementation avoids reflection and eager classloading in order to
* interoperate effectively with tooling (Spring IDE) and OSGi environments.
* <p>
* This class helps separate the concern of parsing the structure of a Configuration class
* from the concern of registering {@link BeanDefinition} objects based on the content of
* that model.
*
* @author Chris Beams
* @since 3.0
* @see ConfigurationModel
* @see ConfigurationModelBeanDefinitionReader
*/
public class ConfigurationParser {
/**
* Model to be populated during calls to {@link #parse(Object, String)}
*/
private final ConfigurationModel model;
private final ProblemReporter problemReporter;
private final ClassLoader classLoader;
/**
* Creates a new {@link ConfigurationParser} instance that will be used to populate a
* {@link ConfigurationModel}.
*
* @param model model to be populated by each successive call to {@link #parse}
* @see #getConfigurationModel()
*/
public ConfigurationParser(ProblemReporter problemReporter, ClassLoader classLoader) {
this.model = new ConfigurationModel();
this.problemReporter = problemReporter;
this.classLoader = classLoader;
}
/**
* Parse the {@link Configuration @Configuration} class encapsulated by
* <var>configurationSource</var>.
*
* @param configurationSource reader for Configuration class being parsed
* @param configurationId may be null, but if populated represents the bean id (assumes
* that this configuration class was configured via XML)
*/
public void parse(String className, String configurationId) {
String resourcePath = ClassUtils.convertClassNameToResourcePath(className);
ClassReader configClassReader = AsmUtils.newClassReader(Util.getClassAsStream(resourcePath, classLoader));
ConfigurationClass configClass = new ConfigurationClass();
configClass.setBeanName(configurationId);
configClassReader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader), false);
model.add(configClass);
}
/**
* Returns the current {@link ConfigurationModel}, to be called after {@link #parse}.
*/
public ConfigurationModel getConfigurationModel() {
return model;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static java.lang.String.*;
import static org.springframework.context.annotation.support.AsmUtils.*;
import static org.springframework.util.ClassUtils.*;
import java.util.ArrayList;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Type;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Import;
import org.springframework.util.Assert;
/**
* ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation
* for all its specified classes and then one by one processes each class with a new
* {@link ConfigurationClassVisitor}.
*
* @author Chris Beams
* @see Import
* @see ImportStack
* @see ImportStackHolder
* @see ConfigurationClassVisitor
*/
class ImportAnnotationVisitor extends AnnotationAdapter {
private final ArrayList<String> classesToImport = new ArrayList<String>();
private final ConfigurationModel model;
private final ProblemReporter problemReporter;
private final ClassLoader classLoader;
public ImportAnnotationVisitor(ConfigurationModel model, ProblemReporter problemReporter, ClassLoader classLoader) {
super(AsmUtils.EMPTY_VISITOR);
this.model = model;
this.problemReporter = problemReporter;
this.classLoader = classLoader;
}
@Override
public AnnotationVisitor visitArray(String attribName) {
Assert.isTrue("value".equals(attribName),
format("expected 'value' attribute, got unknown '%s' attribute", attribName));
return new AnnotationAdapter(AsmUtils.EMPTY_VISITOR) {
@Override
public void visit(String na, Object type) {
Assert.isInstanceOf(Type.class, type);
classesToImport.add(((Type) type).getClassName());
}
};
}
@Override
public void visitEnd() {
for (String classToImport : classesToImport)
processClassToImport(classToImport);
ImportStackHolder.getImportStack().pop();
}
private void processClassToImport(String classToImport) {
ConfigurationClass configClass = new ConfigurationClass();
ClassReader reader = newClassReader(convertClassNameToResourcePath(classToImport), classLoader);
reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader), false);
model.add(configClass);
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Stack;
import org.springframework.context.annotation.Import;
import org.springframework.util.Assert;
/**
* {@link Stack} used for detecting circular use of the {@link Import} annotation.
*
* @author Chris Beams
* @see Import
* @see ImportStackHolder
* @see CircularImportException
*/
@SuppressWarnings("serial")
class ImportStack extends Stack<ConfigurationClass> {
/**
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
* exists within this stack that has the same name as <var>elem</var>. Elem must be of
* type ConfigurationClass.
*/
@Override
public boolean contains(Object elem) {
Assert.isInstanceOf(ConfigurationClass.class, elem);
ConfigurationClass configClass = (ConfigurationClass) elem;
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
public int compare(ConfigurationClass first, ConfigurationClass second) {
return first.getName().equals(second.getName()) ? 0 : 1;
}
};
int index = Collections.binarySearch(this, configClass, comparator);
return index >= 0 ? true : false;
}
/**
* Given a stack containing (in order)
* <ol>
* <li>com.acme.Foo</li>
* <li>com.acme.Bar</li>
* <li>com.acme.Baz</li>
* </ol>
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string.
*/
@Override
public synchronized String toString() {
StringBuilder builder = new StringBuilder();
Iterator<ConfigurationClass> iterator = this.iterator();
while (iterator.hasNext()) {
builder.append(iterator.next().getSimpleName());
if (iterator.hasNext())
builder.append("->");
}
return builder.toString();
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import org.springframework.context.annotation.Import;
/**
* Holder class to expose a thread-bound {@link ImportStack}, used while detecting circular
* declarations of the {@link Import} annotation.
*
* @author Chris Beams
* @see Import
* @see ImportStack
* @see CircularImportException
*/
class ImportStackHolder {
private static ThreadLocal<ImportStack> stackHolder = new ThreadLocal<ImportStack>() {
@Override
protected ImportStack initialValue() {
return new ImportStack();
}
};
public static ImportStack getImportStack() {
return stackHolder.get();
}
@Override
public String toString() {
return "Holder for circular @Import detection stack";
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static org.springframework.util.ClassUtils.*;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
/**
* Represents a class, free from java reflection,
* populated by {@link ConfigurationParser}.
*
* @author Chris Beams
* @see ConfigurationModel
* @see ConfigurationClass
* @see ConfigurationParser
*/
class ModelClass implements BeanMetadataElement {
private String name;
private boolean isInterface;
private transient Object source;
/**
* Creates a new and empty ModelClass instance.
*/
public ModelClass() {
}
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
*/
public ModelClass(String name) {
this(name, false);
}
/**
* Creates a new ModelClass instance
*
* @param name fully-qualified name of the class being represented
* @param isInterface whether the represented type is an interface
*/
public ModelClass(String name, boolean isInterface) {
this.name = name;
this.isInterface = isInterface;
}
/**
* Returns the fully-qualified name of this class.
*/
public String getName() {
return name;
}
/**
* Sets the fully-qualified name of this class.
*/
public void setName(String className) {
this.name = className;
}
/**
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
*/
public String getSimpleName() {
return name == null ? null : ClassUtils.getShortName(name);
}
/**
* Returns whether the class represented by this ModelClass instance is an interface.
*/
public boolean isInterface() {
return isInterface;
}
/**
* Signifies that this class is (true) or is not (false) an interface.
*/
public void setInterface(boolean isInterface) {
this.isInterface = isInterface;
}
/**
* Returns a resource path-formatted representation of the .java file that declares this
* class
*/
public Object getSource() {
return source;
}
/**
* Set the source location for this class. Must be a resource-path formatted string.
*
* @param source resource path to the .java file that declares this class.
*/
public void setSource(Object source) {
this.source = source;
}
public Location getLocation() {
if(getName() == null)
throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()");
return new Location(new ClassPathResource(convertClassNameToResourcePath(getName())), getSource());
}
/**
* Given a ModelClass instance representing a class com.acme.Foo, this method will
* return
*
* <pre>
* ModelClass: name=Foo
* </pre>
*/
@Override
public String toString() {
return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + (isInterface ? 1231 : 1237);
result = (prime * result) + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelClass other = (ModelClass) obj;
if (isInterface != other.isInterface)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
/**
* Note: the visibility of this interface would be reduced to package-private save for an
* obscure restriction of JDK dynamic proxies.
* {@link MutableAnnotationUtils#createMutableAnnotation(Class)} creates a proxy based on
* two interfaces: this one, and whatever annotation is currently being parsed. The
* restriction is that both interfaces may not be package-private if they are in separate
* packages. In order to avoid unnecessarily restricting the visibility options for
* user-defined annotations, this interface becomes public. Developers should take caution
* not to use this annotation outside this package.
*
* @author Chris Beams
*/
public interface MutableAnnotation {
void setAttributeValue(String attribName, Object attribValue);
Class<?> getAttributeType(String attributeName);
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static org.springframework.context.annotation.support.MutableAnnotationUtils.*;
import static org.springframework.context.annotation.support.Util.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.ArrayList;
import org.springframework.asm.AnnotationVisitor;
/**
* ASM {@link AnnotationVisitor} that visits any annotation array values while populating
* a new {@link MutableAnnotation} instance.
*
* @author Chris Beams
* @see MutableAnnotation
* @see MutableAnnotationUtils
*/
class MutableAnnotationArrayVisitor extends AnnotationAdapter {
private final ArrayList<Object> values = new ArrayList<Object>();
private final MutableAnnotation mutableAnno;
private final String attribName;
private final ClassLoader classLoader;
public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName, ClassLoader classLoader) {
super(AsmUtils.EMPTY_VISITOR);
this.mutableAnno = mutableAnno;
this.attribName = attribName;
this.classLoader = classLoader;
}
@Override
public void visit(String na, Object value) {
values.add(value);
}
@Override
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(annoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName, classLoader);
if (annoType == null)
return super.visitAnnotation(na, annoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
values.add(anno);
return new MutableAnnotationVisitor(anno, classLoader);
}
@Override
public void visitEnd() {
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0);
mutableAnno.setAttributeValue(attribName, values.toArray(array));
}
}

View File

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

View File

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

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2002-2009 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.context.annotation.support;
import static org.springframework.context.annotation.support.MutableAnnotationUtils.*;
import static org.springframework.context.annotation.support.Util.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.Type;
import org.springframework.util.Assert;
/**
* ASM {@link AnnotationVisitor} that populates a given {@link MutableAnnotation} instance
* with its attributes.
*
* @author Chris Beams
* @see MutableAnnotation
* @see MutableAnnotationInvocationHandler
* @see MutableAnnotationUtils
*/
class MutableAnnotationVisitor implements AnnotationVisitor {
protected final MutableAnnotation mutableAnno;
private final ClassLoader classLoader;
/**
* Creates a new {@link MutableAnnotationVisitor} instance that will populate the the
* attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation} instead of
* {@link MutableAnnotation} to avoid the need for callers to typecast.
*
* @param mutableAnno {@link MutableAnnotation} instance to visit and populate
*
* @throws IllegalArgumentException if <var>mutableAnno</var> is not of type
* {@link MutableAnnotation}
*
* @see MutableAnnotationUtils#createMutableAnnotation(Class)
*/
public MutableAnnotationVisitor(Annotation mutableAnno, ClassLoader classLoader) {
Assert.isInstanceOf(MutableAnnotation.class, mutableAnno, "annotation must be mutable");
this.mutableAnno = (MutableAnnotation) mutableAnno;
this.classLoader = classLoader;
}
public AnnotationVisitor visitArray(final String attribName) {
return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader);
}
public void visit(String attribName, Object attribValue) {
Class<?> attribReturnType = mutableAnno.getAttributeType(attribName);
if (attribReturnType.equals(Class.class)) {
// the attribute type is Class -> load it and set it.
String fqClassName = ((Type) attribValue).getClassName();
Class<?> classVal = loadToolingSafeClass(fqClassName, classLoader);
if (classVal == null)
return;
mutableAnno.setAttributeValue(attribName, classVal);
return;
}
// otherwise, assume the value can be set literally
mutableAnno.setAttributeValue(attribName, attribValue);
}
@SuppressWarnings("unchecked")
public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) {
String enumClassName = AsmUtils.convertTypeDescriptorToClassName(enumTypeDescriptor);
Class<? extends Enum> enumClass = loadToolingSafeClass(enumClassName, classLoader);
if (enumClass == null)
return;
Enum enumValue = Enum.valueOf(enumClass, strEnumValue);
mutableAnno.setAttributeValue(attribName, enumValue);
}
public AnnotationVisitor visitAnnotation(String attribName, String attribAnnoTypeDesc) {
String annoTypeName = AsmUtils.convertTypeDescriptorToClassName(attribAnnoTypeDesc);
Class<? extends Annotation> annoType = loadToolingSafeClass(annoTypeName, classLoader);
if (annoType == null)
return AsmUtils.EMPTY_VISITOR.visitAnnotation(attribName, attribAnnoTypeDesc);
Annotation anno = createMutableAnnotation(annoType);
try {
Field attribute = mutableAnno.getClass().getField(attribName);
attribute.set(mutableAnno, anno);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return new MutableAnnotationVisitor(anno, classLoader);
}
public void visitEnd() {
}
}

View File

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