From 37ba1b966ee164ada46866e800bde317a238b4d7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 30 Jun 2014 22:53:25 +0200 Subject: [PATCH] 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 --- .../groovy/GroovyBeanDefinitionReader.java | 39 ++++++++++-------- ...onfigurationClassBeanDefinitionReader.java | 30 ++++++++++---- .../context/annotation/ImportResource.java | 41 +++++++++++-------- .../GenericGroovyApplicationContext.java | 24 ++++++++--- .../support/GroovyWebApplicationContext.java | 9 ++-- 5 files changed, 91 insertions(+), 52 deletions(-) diff --git a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java index 534f80cd949..0146c39b798 100644 --- a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java +++ b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java @@ -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"); * 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.Resource; 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, @@ -108,6 +108,9 @@ import org.springframework.core.io.support.ResourcePatternUtils; * } * } * + *

This bean definition reader also understands XML bean definition files, + * allowing for seamless mixing and matching with Groovy bean definition files. + * *

Typically applied to a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * or a {@link org.springframework.context.support.GenericApplicationContext}, @@ -187,7 +190,9 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp // TRADITIONAL BEAN DEFINITION READER METHODS /** - * Load bean definitions from the specified Groovy script. + * Load bean definitions from the specified Groovy script or XML file. + *

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 * @return the number of bean definitions found * @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. + *

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, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ 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) { invokeBeanDefiningClosure((Closure) args[0]); return null; @@ -213,7 +226,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp Binding binding = new Binding() { @Override public void setVariable(String name, Object value) { - if (currentBeanDefinition !=null) { + if (currentBeanDefinition != null) { applyPropertyToBeanDefinition(name, value); } else { @@ -321,17 +334,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp * @param resourcePattern the resource pattern */ public void importBeans(String resourcePattern) throws IOException { - Resource[] resources = - 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); - } - } + loadBeanDefinitions(resourcePattern); } @@ -410,7 +413,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp } dp.apply(); } - deferredProperties.clear(); + this.deferredProperties.clear(); } /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 7c6566fc8d1..903431ad809 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; 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.Problem; 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.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; @@ -287,28 +289,42 @@ class ConfigurationClassBeanDefinitionReader { Map> importedResources) { Map, BeanDefinitionReader> readerInstanceCache = new HashMap, BeanDefinitionReader>(); + for (Map.Entry> entry : importedResources.entrySet()) { String resource = entry.getKey(); Class 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 { // Instantiate the specified BeanDefinitionReader - BeanDefinitionReader readerInstance = - readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); + reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); // Delegate the current ResourceLoader to it if possible - if (readerInstance instanceof AbstractBeanDefinitionReader) { - AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) readerInstance); + if (reader instanceof AbstractBeanDefinitionReader) { + AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader); abdr.setResourceLoader(this.resourceLoader); abdr.setEnvironment(this.environment); } - readerInstanceCache.put(readerClass, readerInstance); + readerInstanceCache.put(readerClass, reader); } catch (Exception ex) { throw new IllegalStateException( "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); } } - BeanDefinitionReader reader = readerInstanceCache.get(readerClass); + // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations reader.loadBeanDefinitions(resource); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportResource.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportResource.java index 16506965d9b..15345132f25 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportResource.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportResource.java @@ -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"); * 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 org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; /** * Indicates one or more resources containing bean definitions to import. * - *

Like {@link Import @Import}, this annotation provides functionality similar to the - * {@code } element in Spring XML. It is typically used when - * designing {@link Configuration @Configuration} classes to be bootstrapped by - * {@link AnnotationConfigApplicationContext}, but where some XML functionality such as - * namespaces is still necessary. + *

Like {@link Import @Import}, this annotation provides functionality similar to + * the {@code } element in Spring XML. It is typically used when designing + * {@link Configuration @Configuration} classes to be bootstrapped by + * {@link AnnotationConfigApplicationContext}, but where some XML functionality such + * as namespaces is still necessary. * *

By default, arguments to the {@link #value()} attribute will be processed using - * an {@link XmlBeanDefinitionReader}, i.e. it is assumed that resources are Spring - * {@code } XML files. Optionally, the {@link #reader()} attribute may be - * supplied, allowing the user to specify a different {@link BeanDefinitionReader} - * implementation, such as - * {@link org.springframework.beans.factory.support.PropertiesBeanDefinitionReader}. + * {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader} if ending in + * ".groovy"; otherwise, {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader} + * will be used to parse Spring {@code } XML files. Optionally, the {@link #reader()} + * attribute may be supplied, allowing the user to choose a custom {@link BeanDefinitionReader} + * implementation. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.0 * @see Configuration * @see Import @@ -52,15 +52,22 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; public @interface ImportResource { /** - * Resource paths to import. Resource-loading prefixes such as {@code classpath:} and - * {@code file:}, etc may be used. + * Resource paths to import. Resource-loading prefixes such as {@code classpath:} + * and {@code file:}, etc may be used. + *

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(); /** - * {@link BeanDefinitionReader} implementation to use when processing resources specified - * by the {@link #value()} attribute. + * {@link BeanDefinitionReader} implementation to use when processing resources + * specified by the {@link #value()} attribute. + *

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 reader() default XmlBeanDefinitionReader.class; + Class reader() default BeanDefinitionReader.class; } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java index f2b511031f4..b68a694120f 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java @@ -35,8 +35,9 @@ import org.springframework.core.io.Resource; * can be retrieved with the dot de-reference syntax instead of using {@link #getBean}. * *

Consider this as the equivalent of {@link GenericXmlApplicationContext} for - * Groovy bean definitions. The main difference is that, within a Groovy script, - * the context can be used with an inline bean definition closure as follows: + * Groovy bean definitions, or even an upgrade thereof since it seamlessly understands + * 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: * *

  * import org.hibernate.SessionFactory
@@ -104,6 +105,11 @@ import org.springframework.core.io.Resource;
  * ApplicationContext context = new GenericGroovyApplicationContext("org/myapp/applicationContext.groovy");
  * 
* + *

This application context also understands XML bean definition files, + * allowing for seamless mixing and matching with Groovy bean definition files. + * ".xml" files will be parsed as XML content; all other kinds of resources will + * be parsed as Groovy scripts. + * * @author Juergen Hoeller * @author Jeff Brown * @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. + *

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 */ 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. + *

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 */ 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. + *

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 * loading each specified resource name * @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++) { resources[i] = new ClassPathResource(resourceNames[i], relativeClass); } - this.load(resources); + load(resources); } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/GroovyWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/GroovyWebApplicationContext.java index b578ce7647e..684bf1cdc3a 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/GroovyWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/GroovyWebApplicationContext.java @@ -30,8 +30,8 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.DefaultListableBeanFactory; /** - * {@link org.springframework.web.context.WebApplicationContext} implementation - * which takes its configuration from Groovy bean definition scripts, understood by + * {@link org.springframework.web.context.WebApplicationContext} implementation which takes + * its configuration from Groovy bean definition scripts and/or XML files, as understood by * an {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader}. * This is essentially the equivalent of * {@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 * 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} - * 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. * *

Note: In case of multiple config locations, later bean definitions will * 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. *

Delegates to a ResourcePatternResolver for resolving location patterns * 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 #getConfigLocations * @see #getResources