Seamless support for Groovy bean definitions mixed with XML bean definitions

GroovyBeanDefinitionReader and Groovy ApplicationContexts redirect ".xml" files to XmlBeanDefinitionReader now, similar to what they've been doing for importBeans directives already. Analogously, @ImportResource for configuration classes redirects ".groovy" to GroovyBeanDefinitionReader now.

Issue: SPR-11924
This commit is contained in:
Juergen Hoeller 2014-06-30 22:53:25 +02:00
parent 06d3e1b94e
commit 37ba1b966e
5 changed files with 91 additions and 52 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -53,7 +53,7 @@ import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.core.io.DescriptiveResource; import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.util.StringUtils;
/** /**
* A Groovy-based reader for Spring bean definitions: like a Groovy builder, * A Groovy-based reader for Spring bean definitions: like a Groovy builder,
@ -108,6 +108,9 @@ import org.springframework.core.io.support.ResourcePatternUtils;
* } * }
* }</pre> * }</pre>
* *
* <p><b>This bean definition reader also understands XML bean definition files,
* allowing for seamless mixing and matching with Groovy bean definition files.</b>
*
* <p>Typically applied to a * <p>Typically applied to a
* {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
* or a {@link org.springframework.context.support.GenericApplicationContext}, * or a {@link org.springframework.context.support.GenericApplicationContext},
@ -187,7 +190,9 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
// TRADITIONAL BEAN DEFINITION READER METHODS // TRADITIONAL BEAN DEFINITION READER METHODS
/** /**
* Load bean definitions from the specified Groovy script. * Load bean definitions from the specified Groovy script or XML file.
* <p>Note that ".xml" files will be parsed as XML content; all other kinds
* of resources will be parsed as Groovy scripts.
* @param resource the resource descriptor for the Groovy script * @param resource the resource descriptor for the Groovy script
* @return the number of bean definitions found * @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors * @throws BeanDefinitionStoreException in case of loading or parsing errors
@ -197,14 +202,22 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
} }
/** /**
* Load bean definitions from the specified Groovy script. * Load bean definitions from the specified Groovy script or XML file.
* <p>Note that ".xml" files will be parsed as XML content; all other kinds
* of resources will be parsed as Groovy scripts.
* @param encodedResource the resource descriptor for the Groovy script, * @param encodedResource the resource descriptor for the Groovy script,
* allowing to specify an encoding to use for parsing the file * allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found * @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors * @throws BeanDefinitionStoreException in case of loading or parsing errors
*/ */
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Closure beans = new Closure(this){ // Check for XML files and redirect them to the XmlBeanDefinitionReader
String filename = encodedResource.getResource().getFilename();
if (StringUtils.endsWithIgnoreCase(filename, ".xml")) {
return this.xmlBeanDefinitionReader.loadBeanDefinitions(encodedResource);
}
Closure beans = new Closure(this) {
public Object call(Object[] args) { public Object call(Object[] args) {
invokeBeanDefiningClosure((Closure) args[0]); invokeBeanDefiningClosure((Closure) args[0]);
return null; return null;
@ -213,7 +226,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
Binding binding = new Binding() { Binding binding = new Binding() {
@Override @Override
public void setVariable(String name, Object value) { public void setVariable(String name, Object value) {
if (currentBeanDefinition !=null) { if (currentBeanDefinition != null) {
applyPropertyToBeanDefinition(name, value); applyPropertyToBeanDefinition(name, value);
} }
else { else {
@ -321,17 +334,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
* @param resourcePattern the resource pattern * @param resourcePattern the resource pattern
*/ */
public void importBeans(String resourcePattern) throws IOException { public void importBeans(String resourcePattern) throws IOException {
Resource[] resources = loadBeanDefinitions(resourcePattern);
ResourcePatternUtils.getResourcePatternResolver(getResourceLoader()).getResources(resourcePattern);
for (Resource resource : resources) {
String filename = resource.getFilename();
if (filename.endsWith(".groovy")) {
loadBeanDefinitions(resource);
}
else if (filename.endsWith(".xml")) {
this.xmlBeanDefinitionReader.loadBeanDefinitions(resource);
}
}
} }
@ -410,7 +413,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
} }
dp.apply(); dp.apply();
} }
deferredProperties.clear(); this.deferredProperties.clear();
} }
/** /**

View File

@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
@ -42,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -287,28 +289,42 @@ class ConfigurationClassBeanDefinitionReader {
Map<String, Class<? extends BeanDefinitionReader>> importedResources) { Map<String, Class<? extends BeanDefinitionReader>> importedResources) {
Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>(); Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>();
for (Map.Entry<String, Class<? extends BeanDefinitionReader>> entry : importedResources.entrySet()) { for (Map.Entry<String, Class<? extends BeanDefinitionReader>> entry : importedResources.entrySet()) {
String resource = entry.getKey(); String resource = entry.getKey();
Class<? extends BeanDefinitionReader> readerClass = entry.getValue(); Class<? extends BeanDefinitionReader> readerClass = entry.getValue();
if (!readerInstanceCache.containsKey(readerClass)) {
// Default reader selection necessary?
if (readerClass.equals(BeanDefinitionReader.class)) {
if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
// When clearly asking for Groovy, that's what they'll get...
readerClass = GroovyBeanDefinitionReader.class;
}
else {
// Primarily ".xml" files but for any other extension as well
readerClass = XmlBeanDefinitionReader.class;
}
}
BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
if (reader == null) {
try { try {
// Instantiate the specified BeanDefinitionReader // Instantiate the specified BeanDefinitionReader
BeanDefinitionReader readerInstance = reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
// Delegate the current ResourceLoader to it if possible // Delegate the current ResourceLoader to it if possible
if (readerInstance instanceof AbstractBeanDefinitionReader) { if (reader instanceof AbstractBeanDefinitionReader) {
AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) readerInstance); AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
abdr.setResourceLoader(this.resourceLoader); abdr.setResourceLoader(this.resourceLoader);
abdr.setEnvironment(this.environment); abdr.setEnvironment(this.environment);
} }
readerInstanceCache.put(readerClass, readerInstance); readerInstanceCache.put(readerClass, reader);
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException( throw new IllegalStateException(
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
} }
} }
BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
reader.loadBeanDefinitions(resource); reader.loadBeanDefinitions(resource);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,25 +23,25 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
/** /**
* Indicates one or more resources containing bean definitions to import. * Indicates one or more resources containing bean definitions to import.
* *
* <p>Like {@link Import @Import}, this annotation provides functionality similar to the * <p>Like {@link Import @Import}, this annotation provides functionality similar to
* {@code <import/>} element in Spring XML. It is typically used when * the {@code <import/>} element in Spring XML. It is typically used when designing
* designing {@link Configuration @Configuration} classes to be bootstrapped by * {@link Configuration @Configuration} classes to be bootstrapped by
* {@link AnnotationConfigApplicationContext}, but where some XML functionality such as * {@link AnnotationConfigApplicationContext}, but where some XML functionality such
* namespaces is still necessary. * as namespaces is still necessary.
* *
* <p>By default, arguments to the {@link #value()} attribute will be processed using * <p>By default, arguments to the {@link #value()} attribute will be processed using
* an {@link XmlBeanDefinitionReader}, i.e. it is assumed that resources are Spring * {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader} if ending in
* {@code <beans/>} XML files. Optionally, the {@link #reader()} attribute may be * ".groovy"; otherwise, {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}
* supplied, allowing the user to specify a different {@link BeanDefinitionReader} * will be used to parse Spring {@code <beans/>} XML files. Optionally, the {@link #reader()}
* implementation, such as * attribute may be supplied, allowing the user to choose a custom {@link BeanDefinitionReader}
* {@link org.springframework.beans.factory.support.PropertiesBeanDefinitionReader}. * implementation.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @see Configuration * @see Configuration
* @see Import * @see Import
@ -52,15 +52,22 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
public @interface ImportResource { public @interface ImportResource {
/** /**
* Resource paths to import. Resource-loading prefixes such as {@code classpath:} and * Resource paths to import. Resource-loading prefixes such as {@code classpath:}
* {@code file:}, etc may be used. * and {@code file:}, etc may be used.
* <p>Out of the box, ".groovy" files are going to be specifically parsed with
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader};
* others with {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
*/ */
String[] value(); String[] value();
/** /**
* {@link BeanDefinitionReader} implementation to use when processing resources specified * {@link BeanDefinitionReader} implementation to use when processing resources
* by the {@link #value()} attribute. * specified by the {@link #value()} attribute.
* <p>By default, the reader will be adapted to the resource path specified:
* ".groovy" files are going to be specifically parsed with
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader};
* others with {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
*/ */
Class<? extends BeanDefinitionReader> reader() default XmlBeanDefinitionReader.class; Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
} }

View File

@ -35,8 +35,9 @@ import org.springframework.core.io.Resource;
* can be retrieved with the dot de-reference syntax instead of using {@link #getBean}. * can be retrieved with the dot de-reference syntax instead of using {@link #getBean}.
* *
* <p>Consider this as the equivalent of {@link GenericXmlApplicationContext} for * <p>Consider this as the equivalent of {@link GenericXmlApplicationContext} for
* Groovy bean definitions. The main difference is that, within a Groovy script, * Groovy bean definitions, or even an upgrade thereof since it seamlessly understands
* the context can be used with an inline bean definition closure as follows: * XML bean definition files as well. The main difference is that, within a Groovy
* script, the context can be used with an inline bean definition closure as follows:
* *
* <pre class="code"> * <pre class="code">
* import org.hibernate.SessionFactory * import org.hibernate.SessionFactory
@ -104,6 +105,11 @@ import org.springframework.core.io.Resource;
* ApplicationContext context = new GenericGroovyApplicationContext("org/myapp/applicationContext.groovy"); * ApplicationContext context = new GenericGroovyApplicationContext("org/myapp/applicationContext.groovy");
* </pre> * </pre>
* *
* <p><b>This application context also understands XML bean definition files,
* allowing for seamless mixing and matching with Groovy bean definition files.</b>
* ".xml" files will be parsed as XML content; all other kinds of resources will
* be parsed as Groovy scripts.
*
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Jeff Brown * @author Jeff Brown
* @since 4.0 * @since 4.0
@ -180,7 +186,9 @@ public class GenericGroovyApplicationContext extends GenericApplicationContext i
} }
/** /**
* Load bean definitions from the given Groovy scripts. * Load bean definitions from the given Groovy scripts or XML files.
* <p>Note that ".xml" files will be parsed as XML content; all other kinds
* of resources will be parsed as Groovy scripts.
* @param resources one or more resources to load from * @param resources one or more resources to load from
*/ */
public void load(Resource... resources) { public void load(Resource... resources) {
@ -188,7 +196,9 @@ public class GenericGroovyApplicationContext extends GenericApplicationContext i
} }
/** /**
* Load bean definitions from the given Groovy scripts. * Load bean definitions from the given Groovy scripts or XML files.
* <p>Note that ".xml" files will be parsed as XML content; all other kinds
* of resources will be parsed as Groovy scripts.
* @param resourceLocations one or more resource locations to load from * @param resourceLocations one or more resource locations to load from
*/ */
public void load(String... resourceLocations) { public void load(String... resourceLocations) {
@ -196,7 +206,9 @@ public class GenericGroovyApplicationContext extends GenericApplicationContext i
} }
/** /**
* Load bean definitions from the given Groovy scripts. * Load bean definitions from the given Groovy scripts or XML files.
* <p>Note that ".xml" files will be parsed as XML content; all other kinds
* of resources will be parsed as Groovy scripts.
* @param relativeClass class whose package will be used as a prefix when * @param relativeClass class whose package will be used as a prefix when
* loading each specified resource name * loading each specified resource name
* @param resourceNames relatively-qualified names of resources to load * @param resourceNames relatively-qualified names of resources to load
@ -206,7 +218,7 @@ public class GenericGroovyApplicationContext extends GenericApplicationContext i
for (int i = 0; i < resourceNames.length; i++) { for (int i = 0; i < resourceNames.length; i++) {
resources[i] = new ClassPathResource(resourceNames[i], relativeClass); resources[i] = new ClassPathResource(resourceNames[i], relativeClass);
} }
this.load(resources); load(resources);
} }

View File

@ -30,8 +30,8 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/** /**
* {@link org.springframework.web.context.WebApplicationContext} implementation * {@link org.springframework.web.context.WebApplicationContext} implementation which takes
* which takes its configuration from Groovy bean definition scripts, understood by * its configuration from Groovy bean definition scripts and/or XML files, as understood by
* an {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader}. * an {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader}.
* This is essentially the equivalent of * This is essentially the equivalent of
* {@link org.springframework.context.support.GenericGroovyApplicationContext} * {@link org.springframework.context.support.GenericGroovyApplicationContext}
@ -46,7 +46,8 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
* init-param of {@link org.springframework.web.servlet.FrameworkServlet}. Config locations * init-param of {@link org.springframework.web.servlet.FrameworkServlet}. Config locations
* can either denote concrete files like "/WEB-INF/context.groovy" or Ant-style patterns * can either denote concrete files like "/WEB-INF/context.groovy" or Ant-style patterns
* like "/WEB-INF/*-context.groovy" (see {@link org.springframework.util.PathMatcher} * like "/WEB-INF/*-context.groovy" (see {@link org.springframework.util.PathMatcher}
* javadoc for pattern details). * javadoc for pattern details). Note that ".xml" files will be parsed as XML content;
* all other kinds of resources will be parsed as Groovy scripts.
* *
* <p>Note: In case of multiple config locations, later bean definitions will * <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files. This can be leveraged to * override ones defined in earlier loaded files. This can be leveraged to
@ -119,7 +120,7 @@ public class GroovyWebApplicationContext extends AbstractRefreshableWebApplicati
* therefore this method is just supposed to load and/or register bean definitions. * therefore this method is just supposed to load and/or register bean definitions.
* <p>Delegates to a ResourcePatternResolver for resolving location patterns * <p>Delegates to a ResourcePatternResolver for resolving location patterns
* into Resource instances. * into Resource instances.
* @throws IOException if the required Groovy script isn't found * @throws IOException if the required Groovy script or XML file isn't found
* @see #refreshBeanFactory * @see #refreshBeanFactory
* @see #getConfigLocations * @see #getConfigLocations
* @see #getResources * @see #getResources