ConfigurationClassParser processes late-arriving DeferredImportSelectors as regular import selectors

Also contains refined exception handling, treating regular class loading and ASM-based loading consistently in terms of exception wrapping, and always mentioning the current configuration class in all exception messages.

Issue: SPR-11997
This commit is contained in:
Juergen Hoeller 2014-07-15 22:04:52 +02:00
parent 10a4c2cd81
commit bbf5800831
2 changed files with 65 additions and 67 deletions

View File

@ -158,8 +158,11 @@ class ConfigurationClassParser {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Exception ex) {
throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
processDeferredImportSelectors();
@ -228,7 +231,7 @@ class ConfigurationClassParser {
* multiple times as relevant sources are discovered.
* @param configClass the configuration class being build
* @param sourceClass a source class
* @return the superclass, {@code null} if none found or previously processed
* @return the superclass, or {@code null} if none found or previously processed
*/
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
// recursively process any member (nested) classes first
@ -258,7 +261,7 @@ class ConfigurationClassParser {
}
// process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
processImports(configClass, sourceClass, getImports(sourceClass), true, false);
// process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
@ -283,12 +286,7 @@ class ConfigurationClassParser {
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// superclass found, return its annotation metadata and recurse
try {
return sourceClass.getSuperClass();
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
return sourceClass.getSuperClass();
}
}
@ -367,39 +365,38 @@ class ConfigurationClassParser {
* @throws IOException if there is any problem reading metadata from the named class
*/
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException {
try {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Unable to collect imports", ex);
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
private void processDeferredImportSelectors() {
Collections.sort(this.deferredImportSelectors, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : this.deferredImportSelectors) {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false, true);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Exception ex) {
throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
}
this.deferredImportSelectors.clear();
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
Collection<SourceClass> importCandidates, boolean checkForCircularImports, boolean deferred) throws IOException {
if (importCandidates.isEmpty()) {
return;
@ -416,13 +413,13 @@ class ConfigurationClassParser {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
invokeAwareMethods(selector);
if (selector instanceof DeferredImportSelector) {
if (!deferred && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
processImports(configClass, currentSourceClass, importSourceClasses, false, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
@ -439,8 +436,12 @@ class ConfigurationClassParser {
}
}
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Failed to load import candidate class", ex);
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Exception ex) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
@ -515,22 +516,17 @@ class ConfigurationClassParser {
* Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
*/
public SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
try {
AnnotationMetadata metadata = configurationClass.getMetadata();
if (metadata instanceof StandardAnnotationMetadata) {
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
}
return asSourceClass(configurationClass.getMetadata().getClassName());
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
AnnotationMetadata metadata = configurationClass.getMetadata();
if (metadata instanceof StandardAnnotationMetadata) {
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
}
return asSourceClass(configurationClass.getMetadata().getClassName());
}
/**
* Factory method to obtain a {@link SourceClass} from a {@link Class}.
*/
public SourceClass asSourceClass(Class<?> classType) throws IOException, ClassNotFoundException {
public SourceClass asSourceClass(Class<?> classType) throws IOException {
try {
// Sanity test that we can read annotations, if not fall back to ASM
classType.getAnnotations();
@ -545,7 +541,7 @@ class ConfigurationClassParser {
/**
* Factory method to obtain {@link SourceClass}s from class names.
*/
public Collection<SourceClass> asSourceClasses(String[] classNames) throws IOException, ClassNotFoundException {
public Collection<SourceClass> asSourceClasses(String[] classNames) throws IOException {
List<SourceClass> annotatedClasses = new ArrayList<SourceClass>();
for (String className : classNames) {
annotatedClasses.add(asSourceClass(className));
@ -556,10 +552,15 @@ class ConfigurationClassParser {
/**
* Factory method to obtain a {@link SourceClass} from a class name.
*/
public SourceClass asSourceClass(String className) throws IOException, ClassNotFoundException {
public SourceClass asSourceClass(String className) throws IOException {
if (className.startsWith("java")) {
// Never use ASM for core java types
return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className));
try {
return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className));
}
catch (ClassNotFoundException ex) {
throw new NestedIOException("Failed to load class [" + className + "]", ex);
}
}
return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
}
@ -653,7 +654,7 @@ class ConfigurationClassParser {
*/
private class SourceClass {
private final Object source; // Class or MetaDataReader
private final Object source; // Class or MetaDataReader
private final AnnotationMetadata metadata;
@ -675,7 +676,7 @@ class ConfigurationClassParser {
if (this.source instanceof Class<?>) {
return (Class<?>) this.source;
}
String className = ((MetadataReader) source).getClassMetadata().getClassName();
String className = ((MetadataReader) this.source).getClassMetadata().getClassName();
return resourceLoader.getClassLoader().loadClass(className);
}
@ -695,19 +696,13 @@ class ConfigurationClassParser {
public Collection<SourceClass> getMemberClasses() throws IOException {
Object sourceToProcess = this.source;
if (sourceToProcess instanceof Class<?>) {
Class<?> sourceClass = (Class<?>) sourceToProcess;
try {
Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
List<SourceClass> members = new ArrayList<SourceClass>(declaredClasses.length);
for (Class<?> declaredClass : declaredClasses) {
try {
members.add(asSourceClass(declaredClass));
}
catch (ClassNotFoundException ex) {
// ignore
}
members.add(asSourceClass(declaredClass));
}
return members;
}
@ -723,17 +718,12 @@ class ConfigurationClassParser {
String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames();
List<SourceClass> members = new ArrayList<SourceClass>(memberClassNames.length);
for (String memberClassName : memberClassNames) {
try {
members.add(asSourceClass(memberClassName));
}
catch (ClassNotFoundException ex) {
// ignore
}
members.add(asSourceClass(memberClassName));
}
return members;
}
public SourceClass getSuperClass() throws IOException, ClassNotFoundException {
public SourceClass getSuperClass() throws IOException {
if (this.source instanceof Class<?>) {
return asSourceClass(((Class<?>) this.source).getSuperclass());
}
@ -754,9 +744,7 @@ class ConfigurationClassParser {
return result;
}
public Collection<SourceClass> getAnnotationAttributes(String annotationType, String attribute)
throws IOException, ClassNotFoundException {
public Collection<SourceClass> getAnnotationAttributes(String annotationType, String attribute) throws IOException {
Map<String, Object> annotationAttributes = this.metadata.getAnnotationAttributes(annotationType, true);
if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) {
return Collections.emptySet();
@ -769,7 +757,7 @@ class ConfigurationClassParser {
return result;
}
private SourceClass getRelated(String className) throws IOException, ClassNotFoundException {
private SourceClass getRelated(String className) throws IOException {
if (this.source instanceof Class<?>) {
try {
Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
@ -778,7 +766,7 @@ class ConfigurationClassParser {
catch (ClassNotFoundException ex) {
// Ignore -> fall back to ASM next, except for core java types.
if (className.startsWith("java")) {
throw ex;
throw new NestedIOException("Failed to load class [" + className + "]", ex);
}
return new SourceClass(metadataReaderFactory.getMetadataReader(className));
}

View File

@ -99,11 +99,16 @@ public class PropertySourceAnnotationTests {
}
}
@Test(expected=IllegalArgumentException.class)
@Test
public void withUnresolvablePlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithUnresolvablePlaceholder.class);
ctx.refresh();
try {
ctx.refresh();
}
catch (BeanDefinitionStoreException ex) {
assertTrue(ex.getCause() instanceof IllegalArgumentException);
}
}
@Test
@ -124,11 +129,16 @@ public class PropertySourceAnnotationTests {
System.clearProperty("path.to.properties");
}
@Test(expected = IllegalArgumentException.class)
@Test
public void withEmptyResourceLocations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithEmptyResourceLocations.class);
ctx.refresh();
try {
ctx.refresh();
}
catch (BeanDefinitionStoreException ex) {
assertTrue(ex.getCause() instanceof IllegalArgumentException);
}
}
// SPR-10820