From 1bfa7013a0b5ac1f11abc70b1f940f861b2f70df Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 14:07:42 +0100 Subject: [PATCH 01/16] fixed MethodInvokingJobDetailFactoryBean for compatibility with Quartz 2.0/2.1 () --- .../MethodInvokingJobDetailFactoryBean.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java index dd71eef9a1..49563f85a8 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,6 +17,7 @@ package org.springframework.scheduling.quartz; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,6 +41,7 @@ import org.springframework.beans.support.ArgumentConvertingMethodInvoker; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.MethodInvoker; +import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.beans.factory.FactoryBean} that exposes a @@ -80,6 +82,8 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod private static Class jobDetailImplClass; + private static Method setResultMethod; + static { try { jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl"); @@ -87,6 +91,14 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod catch (ClassNotFoundException ex) { jobDetailImplClass = null; } + try { + Class jobExecutionContextClass = + QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext"); + setResultMethod = jobExecutionContextClass.getMethod("setResult"); + } + catch (Exception ex) { + throw new IllegalStateException("Incompatible Quartz API: " + ex); + } } @@ -296,7 +308,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { try { - context.setResult(this.methodInvoker.invoke()); + ReflectionUtils.invokeMethod(setResultMethod, context, this.methodInvoker.invoke()); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof JobExecutionException) { From 4831ca27d2a4e67b1811244049ea19140538f29a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 14:43:37 +0100 Subject: [PATCH 02/16] fixed MethodInvokingJobDetailFactoryBean for compatibility with Quartz 2.0/2.1 (SPR-8889) --- .../scheduling/quartz/MethodInvokingJobDetailFactoryBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java index 49563f85a8..80ce225457 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java @@ -94,7 +94,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod try { Class jobExecutionContextClass = QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext"); - setResultMethod = jobExecutionContextClass.getMethod("setResult"); + setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class); } catch (Exception ex) { throw new IllegalStateException("Incompatible Quartz API: " + ex); From 81861ca7a83ca637ac8d1f7f53380c0e4cba220f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 14:46:45 +0100 Subject: [PATCH 03/16] Resource "contentLength()" implementations work with OSGi bundle resources and JBoss VFS resources (SPR-9118) --- .../io/AbstractFileResolvingResource.java | 2 +- .../core/io/AbstractResource.java | 10 ++++++---- .../core/io/FileSystemResource.java | 10 +++++++++- .../springframework/core/io/VfsResource.java | 5 +++++ .../org/springframework/core/io/VfsUtils.java | 19 +++++++++++++------ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index f5c5f18034..a09934359b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -152,7 +152,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { URL url = getURL(); if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution... - return super.contentLength(); + return getFile().length(); } else { // Try a URL connection content-length header... diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java index 2f6c8e7d7d..e2360b4748 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java @@ -25,6 +25,7 @@ import java.net.URISyntaxException; import java.net.URL; import org.springframework.core.NestedIOException; +import org.springframework.util.FileCopyUtils; import org.springframework.util.ResourceUtils; /** @@ -108,12 +109,13 @@ public abstract class AbstractResource implements Resource { } /** - * This implementation checks the length of the underlying File, - * if available. - * @see #getFile() + * This implementation reads the entire InputStream to calculate the + * content length. Subclasses will almost always be able to provide + * a more optimal version of this, e.g. checking a File length. + * @see #getInputStream() */ public long contentLength() throws IOException { - return getFile().length(); + return FileCopyUtils.copyToByteArray(getInputStream()).length; } /** diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java index d96ba7c75b..3d0805c345 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -139,6 +139,14 @@ public class FileSystemResource extends AbstractResource implements WritableReso return this.file; } + /** + * This implementation returns the underlying File's length. + */ + @Override + public long contentLength() throws IOException { + return this.file.length(); + } + /** * This implementation creates a FileSystemResource, applying the given path * relative to the path of the underlying file of this resource descriptor. diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/VfsResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/VfsResource.java index c161858ad6..88925f6b78 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/VfsResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/VfsResource.java @@ -86,6 +86,11 @@ public class VfsResource extends AbstractResource { return VfsUtils.getFile(this.resource); } + @Override + public long contentLength() throws IOException { + return VfsUtils.getSize(this.resource); + } + @Override public long lastModified() throws IOException { return VfsUtils.getLastModified(this.resource); diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/VfsUtils.java b/org.springframework.core/src/main/java/org/springframework/core/io/VfsUtils.java index a0e268ce70..71c2ceb097 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/VfsUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/VfsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -27,6 +27,7 @@ import java.net.URL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.NestedIOException; import org.springframework.util.ReflectionUtils; @@ -58,14 +59,15 @@ public abstract class VfsUtils { private static Method VFS_METHOD_GET_ROOT_URI = null; private static Method VIRTUAL_FILE_METHOD_EXISTS = null; + private static Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; private static Method VIRTUAL_FILE_METHOD_GET_SIZE; private static Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; - private static Method VIRTUAL_FILE_METHOD_GET_CHILD; - private static Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; private static Method VIRTUAL_FILE_METHOD_TO_URL; private static Method VIRTUAL_FILE_METHOD_TO_URI; private static Method VIRTUAL_FILE_METHOD_GET_NAME; private static Method VIRTUAL_FILE_METHOD_GET_PATH_NAME; + private static Method VIRTUAL_FILE_METHOD_GET_CHILD; + protected static Class VIRTUAL_FILE_VISITOR_INTERFACE; protected static Method VIRTUAL_FILE_METHOD_VISIT; @@ -101,9 +103,10 @@ public abstract class VfsUtils { if (logger.isDebugEnabled()) logger.debug("JBoss VFS packages for JBoss AS 5 found"); - } catch (ClassNotFoundException ex1) { + } + catch (ClassNotFoundException ex2) { logger.error("JBoss VFS packages (for both JBoss AS 5 and 6) were not found - JBoss VFS support disabled"); - throw new IllegalStateException("Cannot detect JBoss VFS packages", ex1); + throw new IllegalStateException("Cannot detect JBoss VFS packages", ex2); } } @@ -117,8 +120,8 @@ public abstract class VfsUtils { Class virtualFile = loader.loadClass(pkg + "VirtualFile"); VIRTUAL_FILE_METHOD_EXISTS = ReflectionUtils.findMethod(virtualFile, "exists"); - VIRTUAL_FILE_METHOD_GET_SIZE = ReflectionUtils.findMethod(virtualFile, "getSize"); VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = ReflectionUtils.findMethod(virtualFile, "openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = ReflectionUtils.findMethod(virtualFile, "getSize"); VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = ReflectionUtils.findMethod(virtualFile, "getLastModified"); VIRTUAL_FILE_METHOD_TO_URI = ReflectionUtils.findMethod(virtualFile, "toURI"); VIRTUAL_FILE_METHOD_TO_URL = ReflectionUtils.findMethod(virtualFile, "toURL"); @@ -183,6 +186,10 @@ public abstract class VfsUtils { } } + static long getSize(Object vfsResource) throws IOException { + return (Long) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_SIZE, vfsResource); + } + static long getLastModified(Object vfsResource) throws IOException { return (Long) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED, vfsResource); } From fe57f74c1aa0063e0ccc9739b95078ccf0505a2c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 18:26:56 +0100 Subject: [PATCH 04/16] PathMatchingResourcePatternResolver preserves caching for JNLP jar connections (SPR-9117) --- .../core/io/support/PathMatchingResourcePatternResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index a9d473da2a..92716e491a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -433,7 +433,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (con instanceof JarURLConnection) { // Should usually be the case for traditional JAR files. JarURLConnection jarCon = (JarURLConnection) con; - jarCon.setUseCaches(false); + jarCon.setUseCaches(jarCon.getClass().getName().startsWith("JNLP")); jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); JarEntry jarEntry = jarCon.getJarEntry(); From 2fa87a71fae2d3074acc48d405779c61a11b646d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 21:16:10 +0100 Subject: [PATCH 05/16] use custom InputStream traversal instead of a full byte array (SPR-9118) --- .../core/io/AbstractResource.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java index e2360b4748..1d48f7fb33 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java @@ -25,7 +25,6 @@ import java.net.URISyntaxException; import java.net.URL; import org.springframework.core.NestedIOException; -import org.springframework.util.FileCopyUtils; import org.springframework.util.ResourceUtils; /** @@ -115,7 +114,22 @@ public abstract class AbstractResource implements Resource { * @see #getInputStream() */ public long contentLength() throws IOException { - return FileCopyUtils.copyToByteArray(getInputStream()).length; + InputStream is = getInputStream(); + try { + long size = 0; + byte[] buf = new byte[255]; + for (int read = is.read(buf); read != -1;) { + size += read; + } + return size; + } + finally { + try { + is.close(); + } + catch (IOException ex) { + } + } } /** From ddf0d071ad968c9136f1558881ba2de5cd9526a4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 21:29:22 +0100 Subject: [PATCH 06/16] revised CustomSQLExceptionTranslatorRegistry/Registrar method naming --- ...CustomSQLExceptionTranslatorRegistrar.java | 41 +++++-------- .../CustomSQLExceptionTranslatorRegistry.java | 57 ++++++++----------- .../jdbc/support/SQLErrorCodes.java | 52 ++++++++--------- .../jdbc/support/SQLErrorCodesFactory.java | 12 ++-- .../test-custom-translators-context.xml | 2 +- 5 files changed, 70 insertions(+), 94 deletions(-) diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java index e9806b8c0b..0d15a87025 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistrar.java @@ -16,53 +16,42 @@ package org.springframework.jdbc.support; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.InitializingBean; /** - * Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator}. + * Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} + * instances for specific databases. * * @author Thomas Risberg - * @since 3.1 + * @since 3.1.1 */ public class CustomSQLExceptionTranslatorRegistrar implements InitializingBean { - private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistrar.class); - /** * Map registry to hold custom translators specific databases. * Key is the database product name as defined in the * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}. */ - private final Map sqlExceptionTranslators = - new HashMap(); + private final Map translators = new HashMap(); + /** - * Setter for a Map of translators where the key must be the database name as defined in the - * sql-error-codes.xml file. This method is used when this registry is used in an application context. - *

Note that any existing translators will remain unless there is a match in the database name at which - * point the new translator will replace the existing one. - * - * @param sqlExceptionTranslators + * Setter for a Map of {@link SQLExceptionTranslator} references where the key must + * be the database name as defined in the sql-error-codes.xml file. + *

Note that any existing translators will remain unless there is a match in the + * database name, at which point the new translator will replace the existing one. */ - public void setSqlExceptionTranslators(Map sqlExceptionTranslators) { - this.sqlExceptionTranslators.putAll(sqlExceptionTranslators); + public void setTranslators(Map translators) { + this.translators.putAll(translators); } - public void afterPropertiesSet() throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("Registering custom SQL exception translators for database(s): " + - sqlExceptionTranslators.keySet()); - } - for (String dbName : sqlExceptionTranslators.keySet()) { - CustomSQLExceptionTranslatorRegistry.getInstance() - .registerSqlExceptionTranslator(dbName, sqlExceptionTranslators.get(dbName)); + public void afterPropertiesSet() { + for (String dbName : this.translators.keySet()) { + CustomSQLExceptionTranslatorRegistry.getInstance().registerTranslator(dbName, this.translators.get(dbName)); } } + } diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java index 89db6b8902..ccbeb4d723 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/CustomSQLExceptionTranslatorRegistry.java @@ -16,45 +16,25 @@ package org.springframework.jdbc.support; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.WeakHashMap; -import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; - /** * Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with * specific databases allowing for overriding translation based on values contained in the configuration file * named "sql-error-codes.xml". * * @author Thomas Risberg - * @since 3.1 + * @since 3.1.1 * @see SQLErrorCodesFactory */ public class CustomSQLExceptionTranslatorRegistry { private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class); - /** - * Map registry to hold custom translators specific databases. - * Key is the database product name as defined in the - * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}. - */ - private final Map sqlExceptionTranslatorRegistry = - new HashMap(); - - /** * Keep track of a single instance so we can return it to classes that request it. */ @@ -70,7 +50,15 @@ public class CustomSQLExceptionTranslatorRegistry { /** - * Create a new instance of the {@link org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistry} class. + * Map registry to hold custom translators specific databases. + * Key is the database product name as defined in the + * {@link org.springframework.jdbc.support.SQLErrorCodesFactory}. + */ + private final Map translatorMap = new HashMap(); + + + /** + * Create a new instance of the {@link CustomSQLExceptionTranslatorRegistry} class. *

Not public to enforce Singleton design pattern. */ private CustomSQLExceptionTranslatorRegistry() { @@ -78,25 +66,28 @@ public class CustomSQLExceptionTranslatorRegistry { /** * Register a new custom translator for the specified database name. - * * @param dbName the database name - * @param sqlExceptionTranslator the custom translator + * @param translator the custom translator */ - public void registerSqlExceptionTranslator(String dbName, SQLExceptionTranslator sqlExceptionTranslator) { - SQLExceptionTranslator replaced = sqlExceptionTranslatorRegistry.put(dbName, sqlExceptionTranslator); + public void registerTranslator(String dbName, SQLExceptionTranslator translator) { + SQLExceptionTranslator replaced = translatorMap.put(dbName, translator); if (replaced != null) { - logger.warn("Replacing custom translator '" + replaced + - "' for database " + dbName + - " with '" + sqlExceptionTranslator + "'"); + logger.warn("Replacing custom translator [" + replaced + "] for database '" + dbName + + "' with [" + translator + "]"); } else { - logger.info("Adding custom translator '" + sqlExceptionTranslator.getClass().getSimpleName() + - "' for database " + dbName); + logger.info("Adding custom translator of type [" + translator.getClass().getName() + + "] for database '" + dbName + "'"); } } - public SQLExceptionTranslator findSqlExceptionTranslatorForDatabase(String dbName) { - return sqlExceptionTranslatorRegistry.get(dbName); + /** + * Find a custom translator for the specified database. + * @param dbName the database name + * @return the custom translator, or null if none found + */ + public SQLExceptionTranslator findTranslatorForDatabase(String dbName) { + return this.translatorMap.get(dbName); } } diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java index 8dde372bec..c82bbad5bf 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java @@ -17,7 +17,6 @@ package org.springframework.jdbc.support; import org.springframework.util.StringUtils; -import org.springframework.dao.InvalidDataAccessResourceUsageException; /** * JavaBean for holding JDBC error codes for a particular database. @@ -38,8 +37,6 @@ public class SQLErrorCodes { private boolean useSqlStateForTranslation = false; - private SQLExceptionTranslator customSqlExceptionTranslator = null; - private String[] badSqlGrammarCodes = new String[0]; private String[] invalidResultSetAccessCodes = new String[0]; @@ -62,6 +59,8 @@ public class SQLErrorCodes { private CustomSQLErrorCodesTranslation[] customTranslations; + private SQLExceptionTranslator customSqlExceptionTranslator; + /** * Set this property if the database name contains spaces, @@ -100,31 +99,6 @@ public class SQLErrorCodes { return this.useSqlStateForTranslation; } - public SQLExceptionTranslator getCustomSqlExceptionTranslator() { - return customSqlExceptionTranslator; - } - - public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) { - this.customSqlExceptionTranslator = customSqlExceptionTranslator; - } - - public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) { - if (customSqlExceptionTranslatorClass != null) { - try { - this.customSqlExceptionTranslator = - (SQLExceptionTranslator) customSqlExceptionTranslatorClass.newInstance(); - } - catch (InstantiationException e) { - throw new InvalidDataAccessResourceUsageException( - "Unable to instantiate " + customSqlExceptionTranslatorClass.getName(), e); - } - catch (IllegalAccessException e) { - throw new InvalidDataAccessResourceUsageException( - "Unable to instantiate " + customSqlExceptionTranslatorClass.getName(), e); - } - } - } - public void setBadSqlGrammarCodes(String[] badSqlGrammarCodes) { this.badSqlGrammarCodes = StringUtils.sortStringArray(badSqlGrammarCodes); } @@ -213,4 +187,26 @@ public class SQLErrorCodes { return this.customTranslations; } + public void setCustomSqlExceptionTranslatorClass(Class customTranslatorClass) { + if (customTranslatorClass != null) { + try { + this.customSqlExceptionTranslator = customTranslatorClass.newInstance(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to instantiate custom translator", ex); + } + } + else { + this.customSqlExceptionTranslator = null; + } + } + + public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) { + this.customSqlExceptionTranslator = customSqlExceptionTranslator; + } + + public SQLExceptionTranslator getCustomSqlExceptionTranslator() { + return this.customSqlExceptionTranslator; + } + } diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java index bdaf16c0e1..a127e82937 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java @@ -17,7 +17,6 @@ package org.springframework.jdbc.support; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import javax.sql.DataSource; @@ -171,7 +170,7 @@ public class SQLErrorCodesFactory { } } if (sec != null) { - checkSqlExceptionTranslatorRegistry(dbName, sec); + checkCustomTranslatorRegistry(dbName, sec); if (logger.isDebugEnabled()) { logger.debug("SQL error codes for '" + dbName + "' found"); } @@ -248,10 +247,12 @@ public class SQLErrorCodesFactory { } } - private void checkSqlExceptionTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) { - // Check the custom sql exception translator registry for any entries + /** + * Check the {@link CustomSQLExceptionTranslatorRegistry} for any entries. + */ + private void checkCustomTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) { SQLExceptionTranslator customTranslator = - CustomSQLExceptionTranslatorRegistry.getInstance().findSqlExceptionTranslatorForDatabase(dbName); + CustomSQLExceptionTranslatorRegistry.getInstance().findTranslatorForDatabase(dbName); if (customTranslator != null) { if (dbCodes.getCustomSqlExceptionTranslator() != null) { logger.warn("Overriding already defined custom translator '" + @@ -265,7 +266,6 @@ public class SQLErrorCodesFactory { } dbCodes.setCustomSqlExceptionTranslator(customTranslator); } - } } diff --git a/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml b/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml index df3a6258da..5603f4bd0a 100644 --- a/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml +++ b/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/support/test-custom-translators-context.xml @@ -8,7 +8,7 @@ - + From acaf820941f6f541a2662b9f8e8d543ccb6ccf45 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 21:29:52 +0100 Subject: [PATCH 07/16] CustomSQLExceptionTranslatorRegistry/Registrar etc --- build-spring-framework/resources/changelog.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index 704787cc04..fa14a36ddc 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -3,7 +3,7 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org -Changes in version 3.1.1 (2012-02-13) +Changes in version 3.1.1 (2012-02-15) ------------------------------------- * official support for Hibernate 4.0 GA as well as Hibernate 4.1 @@ -13,9 +13,12 @@ Changes in version 3.1.1 (2012-02-13) * @ActiveProfiles mechanism in test context framework works with @ImportResource as well * context:property-placeholder's "file-encoding" attribute value is being applied correctly * clarified Resource's "getFilename" method to return null if resource type does not have a filename +* Resource "contentLength()" implementations work with OSGi bundle resources and JBoss VFS resources +* PathMatchingResourcePatternResolver preserves caching for JNLP (Java Web Start) jar connections * optimized converter lookup in GenericConversionService to avoid contention in JDK proxy check * DataBinder correctly handles ParseException from Formatter for String->String case * CacheNamespaceHandler actually parses cache:annotation-driven's "key-generator" attribute +* introduced CustomSQLExceptionTranslatorRegistry/Registrar for JDBC error code translation * officially deprecated TopLinkJpaDialect in favor of EclipseLink and Spring's EclipseLinkJpaDialect * fixed LocalContainerEntityManagerFactoryBean's "packagesToScan" to avoid additional provider scan * LocalContainerEntityManagerFactoryBean's "persistenceUnitName" applies to "packagesToScan" as well @@ -28,7 +31,7 @@ Changes in version 3.1.1 (2012-02-13) * added "entityInterceptor" property to Hibernate 4 LocalSessionFactoryBean * added "getConfiguration" accessor to Hibernate 4 LocalSessionFactoryBean * added "durability" and "description" properties to JobDetailFactoryBean -* corrected fix for QuartzJobBean to work with Quartz 2.0/2.1 +* fixed QuartzJobBean and MethodInvokingJobDetailFactoryBean for compatibility with Quartz 2.0/2.1 * JMS CachingConnectionFactory never caches consumers for temporary queues and topics * JMS SimpleMessageListenerContainer silently falls back to lazy registration of consumers * Servlet/PortletContextResource's "isReadable()" implementation returns false for directories From ab98f288e9e98074270e3620edf4f3318184502a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 21:30:12 +0100 Subject: [PATCH 08/16] prepared for 3.1.1 release --- build-spring-framework/resources/readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-spring-framework/resources/readme.txt b/build-spring-framework/resources/readme.txt index 589ee6635f..60d7c54c4e 100644 --- a/build-spring-framework/resources/readme.txt +++ b/build-spring-framework/resources/readme.txt @@ -1,5 +1,5 @@ -SPRING FRAMEWORK 3.1.1 (January 2012) -------------------------------------- +SPRING FRAMEWORK 3.1.1 (February 2012) +-------------------------------------- http://www.springsource.org 1. INTRODUCTION From 6fd476e2c5af60f1bf22a5c5c3be194d0f370f42 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Feb 2012 21:55:29 +0100 Subject: [PATCH 09/16] extracted ResourceUtils.useCachesIfNecessary(URLConnection) method (SPR-9117) --- .../core/io/AbstractFileResolvingResource.java | 6 +++--- .../org/springframework/core/io/UrlResource.java | 5 +++-- .../PathMatchingResourcePatternResolver.java | 2 +- .../core/io/support/PropertiesLoaderUtils.java | 5 +++-- .../org/springframework/util/ResourceUtils.java | 13 ++++++++++++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index a09934359b..e1f796847a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -95,7 +95,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { else { // Try a URL connection content-length header... URLConnection con = url.openConnection(); - con.setUseCaches(false); + ResourceUtils.useCachesIfNecessary(con); HttpURLConnection httpCon = (con instanceof HttpURLConnection ? (HttpURLConnection) con : null); if (httpCon != null) { @@ -157,7 +157,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { else { // Try a URL connection content-length header... URLConnection con = url.openConnection(); - con.setUseCaches(false); + ResourceUtils.useCachesIfNecessary(con); if (con instanceof HttpURLConnection) { ((HttpURLConnection) con).setRequestMethod("HEAD"); } @@ -175,7 +175,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { else { // Try a URL connection last-modified header... URLConnection con = url.openConnection(); - con.setUseCaches(false); + ResourceUtils.useCachesIfNecessary(con); if (con instanceof HttpURLConnection) { ((HttpURLConnection) con).setRequestMethod("HEAD"); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java index a68e919c66..2f9f162d78 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -26,6 +26,7 @@ import java.net.URL; import java.net.URLConnection; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -119,7 +120,7 @@ public class UrlResource extends AbstractFileResolvingResource { */ public InputStream getInputStream() throws IOException { URLConnection con = this.url.openConnection(); - con.setUseCaches(false); + ResourceUtils.useCachesIfNecessary(con); try { return con.getInputStream(); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 92716e491a..f9a3645a3e 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -433,7 +433,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (con instanceof JarURLConnection) { // Should usually be the case for traditional JAR files. JarURLConnection jarCon = (JarURLConnection) con; - jarCon.setUseCaches(jarCon.getClass().getName().startsWith("JNLP")); + ResourceUtils.useCachesIfNecessary(jarCon); jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); JarEntry jarEntry = jarCon.getJarEntry(); diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java index f52c0f04f7..20b74d017a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2012 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. @@ -26,6 +26,7 @@ import java.util.Properties; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; /** * Convenient utility methods for loading of java.util.Properties, @@ -106,7 +107,7 @@ public abstract class PropertiesLoaderUtils { InputStream is = null; try { URLConnection con = url.openConnection(); - con.setUseCaches(false); + ResourceUtils.useCachesIfNecessary(con); is = con.getInputStream(); properties.load(is); } diff --git a/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java index e309719029..b9dc3bd855 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -22,6 +22,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; /** * Utility methods for resolving resource locations to files in the @@ -328,4 +329,14 @@ public abstract class ResourceUtils { return new URI(StringUtils.replace(location, " ", "%20")); } + /** + * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the + * given connection, preferring false but leaving the + * flag at true for JNLP based resources. + * @param con the URLConnection to set the flag on + */ + public static void useCachesIfNecessary(URLConnection con) { + con.setUseCaches(con.getClass().getName().startsWith("JNLP")); + } + } From a3484aebb300c8021906e35a057d6fe2ec786cd6 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 15 Feb 2012 09:14:38 -0500 Subject: [PATCH 10/16] Minor fix in ServletResponseMethodArgumentResolver Issues: SPR-8983 --- .../ServletResponseMethodArgumentResolver.java | 6 ++++-- .../ServletResponseMethodArgumentResolverTests.java | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolver.java index e9344b0027..f1eaa6607d 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolver.java @@ -61,8 +61,10 @@ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgum MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws IOException { - - mavContainer.setRequestHandled(true); + + if (mavContainer != null) { + mavContainer.setRequestHandled(true); + } HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Class paramType = parameter.getParameterType(); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolverTests.java index 5ea3c893e6..14c37c7ecd 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletResponseMethodArgumentResolverTests.java @@ -70,6 +70,16 @@ public class ServletResponseMethodArgumentResolverTests { assertTrue(mavContainer.isRequestHandled()); } + // SPR-8983 + + public void servletResponseNoMavContainer() throws Exception { + MethodParameter servletResponseParameter = new MethodParameter(method, 0); + assertTrue("ServletResponse not supported", resolver.supportsParameter(servletResponseParameter)); + + Object result = resolver.resolveArgument(servletResponseParameter, null, webRequest, null); + assertSame("Invalid result", servletResponse, result); + } + @Test public void outputStream() throws Exception { MethodParameter outputStreamParameter = new MethodParameter(method, 1); From f3651c99988df740cb89b4ae7b6997ae1776fbb1 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 15 Feb 2012 15:31:27 +0100 Subject: [PATCH 11/16] Polish static imports --- .../context/annotation/AdviceModeImportSelector.java | 4 ++-- .../context/annotation/AnnotationConfigUtils.java | 4 ++-- .../annotation/AnnotationScopeMetadataResolver.java | 4 ++-- .../context/annotation/AspectJAutoProxyRegistrar.java | 4 ++-- .../ClassPathScanningCandidateComponentProvider.java | 2 +- .../context/annotation/ConfigurationClassParser.java | 4 ++-- .../context/annotation/LoadTimeWeavingConfiguration.java | 4 ++-- .../context/annotation/ImportAwareTests.java | 9 ++++----- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java index 8c4651bc37..eae51ec251 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.lang.annotation.Annotation; import org.springframework.core.GenericTypeResolver; @@ -25,6 +23,8 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Convenient base class for {@link ImportSelector} implementations that select imports * based on an {@link AdviceMode} value from an annotation (such as the {@code @Enable*} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index d184aa9b00..72592b9cf4 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.util.LinkedHashSet; import java.util.Set; @@ -32,6 +30,8 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Utility class that allows for convenient registration of common * {@link org.springframework.beans.factory.config.BeanPostProcessor} and diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java index 0d655830b3..493745bd82 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.lang.annotation.Annotation; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; @@ -25,6 +23,8 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.util.Assert; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * A {@link ScopeMetadataResolver} implementation that by default checks for * the presence of Spring's {@link Scope} annotation on the bean class. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java index b41fa7da25..aedbbbe28e 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AspectJAutoProxyRegistrar.java @@ -16,13 +16,13 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Registers an {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator * AnnotationAwareAspectJAutoProxyCreator} against the current {@link BeanDefinitionRegistry} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 04a249566d..2afeba197e 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -30,9 +30,9 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index a6533c9111..f27124e8ac 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -51,6 +49,8 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.util.StringUtils; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Parses a {@link Configuration} class definition, populating a collection of * {@link ConfigurationClass} objects (parsing a single Configuration class may result in diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java index 3f74a3d779..d81821b703 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.weaving.AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE; - import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; @@ -30,6 +28,8 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.instrument.classloading.LoadTimeWeaver; import org.springframework.util.Assert; +import static org.springframework.context.weaving.AspectJWeavingEnabler.*; + /** * {@code @Configuration} class that registers a {@link LoadTimeWeaver} bean. * diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java index 180176452b..822923d9c1 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ImportAwareTests.java @@ -16,11 +16,6 @@ package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +30,10 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Tests that an ImportAware @Configuration classes gets injected with the * annotation metadata of the @Configuration class that imported it. From 08e2669b84ec0faa2f7904441fe39ac70b65b078 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Tue, 14 Feb 2012 11:58:37 +0100 Subject: [PATCH 12/16] Fix infinite recursion bug in nested @Configuration Prior to this commit, an infinite recursion would occur if a @Configuration class were nested within its superclass, e.g. abstract class Parent { @Configuration static class Child extends Parent { ... } } This is because the processing of the nested class automatically checks the superclass hierarchy for certain reasons, and each superclass is in turn checked for nested @Configuration classes. The ConfigurationClassParser implementation now prevents this by keeping track of known superclasses, i.e. once a superclass has been processed, it is never again checked for nested classes, etc. Issue: SPR-8955 --- .../annotation/ConfigurationClassParser.java | 49 ++++++++++++------- .../configuration/spr8955/Spr8955Parent.java | 32 ++++++++++++ .../configuration/spr8955/Spr8955Tests.java | 36 ++++++++++++++ 3 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Parent.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Tests.java diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index f27124e8ac..0825b134db 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -77,6 +77,8 @@ class ConfigurationClassParser { private final ImportStack importStack = new ImportStack(); + private final Set knownSuperclasses = new LinkedHashSet(); + private final Set configurationClasses = new LinkedHashSet(); @@ -138,23 +140,12 @@ class ConfigurationClassParser { } } - while (metadata != null) { - doProcessConfigurationClass(configClass, metadata); - String superClassName = metadata.getSuperClassName(); - if (superClassName != null && !Object.class.getName().equals(superClassName)) { - if (metadata instanceof StandardAnnotationMetadata) { - Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); - metadata = new StandardAnnotationMetadata(clazz.getSuperclass(), true); - } - else { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); - metadata = reader.getAnnotationMetadata(); - } - } - else { - metadata = null; - } + // recursively process the configuration class and its superclass hierarchy + do { + metadata = doProcessConfigurationClass(configClass, metadata); } + while (metadata != null); + if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. @@ -164,7 +155,11 @@ class ConfigurationClassParser { this.configurationClasses.add(configClass); } - protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { + /** + * @return annotation metadata of superclass, null if none found or previously processed + */ + protected AnnotationMetadata doProcessConfigurationClass( + ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { // recursively process any member (nested) classes first for (String memberClassName : metadata.getMemberClassNames()) { @@ -227,8 +222,26 @@ class ConfigurationClassParser { for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } - } + // process superclass, if any + if (metadata.hasSuperClass()) { + String superclass = metadata.getSuperClassName(); + if (this.knownSuperclasses.add(superclass)) { + // superclass found, return its annotation metadata and recurse + if (metadata instanceof StandardAnnotationMetadata) { + Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); + return new StandardAnnotationMetadata(clazz.getSuperclass(), true); + } + else { + MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass); + return reader.getAnnotationMetadata(); + } + } + } + + // no superclass, processing is complete + return null; + } /** * Return a list of attribute maps for all declarations of the given annotation diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Parent.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Parent.java new file mode 100644 index 0000000000..21a1b007df --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Parent.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2012 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.configuration.spr8955; + +import org.springframework.stereotype.Component; + +/** + * @author Chris Beams + * @author Willem Dekker + */ +abstract class Spr8955Parent { + + @Component + static class Spr8955Child extends Spr8955Parent { + + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Tests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Tests.java new file mode 100644 index 0000000000..c8b6fa4dea --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/spr8955/Spr8955Tests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2012 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.configuration.spr8955; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + + +/** + * @author Chris Beams + * @author Willem Dekker + */ +public class Spr8955Tests { + + @Test + public void repro() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.scan("org.springframework.context.annotation.configuration.spr8955"); + ctx.refresh(); + } + +} From e81df2ef3e6b2359de1970f2b0b5df0075abc554 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 15 Feb 2012 10:39:16 +0100 Subject: [PATCH 13/16] Improve @Configuration bean name discovery Prior to this commit, and based on earlier changes supporting SPR-9023, ConfigurationClassBeanDefinitionReader employed a simplistic strategy for extracting the 'value' attribute (if any) from @Configuration in order to determine the bean name for imported and nested configuration classes. An example case follows: @Configuration("myConfig") public class AppConfig { ... } This approach is too simplistic however, given that it is possible in 'configuration lite' mode to specify a @Component-annotated class with @Bean methods, e.g. @Component("myConfig") public class AppConfig { @Bean public Foo foo() { ... } } In this case, it's the 'value' attribute of @Component, not @Configuration, that should be consulted for the bean name. Or indeed if it were any other stereotype annotation meta-annotated with @Component, the value attribute should respected. This kind of sophisticated discovery is exactly what AnnotationBeanNameGenerator was designed to do, and ConfigurationClassBeanDefinitionReader now uses it in favor of the custom approach described above. To enable this refactoring, nested and imported configuration classes are no longer registered as GenericBeanDefinition, but rather as AnnotatedGenericBeanDefinition given that AnnotationBeanNameGenerator falls back to a generic strategy unless the bean definition in question is assignable to AnnotatedBeanDefinition. A new constructor accepting AnnotationMetadata has been added to AnnotatedGenericBeanDefinition in order to support the ASM-based approach in use by configuration class processing. Javadoc has been updated for both AnnotatedGenericBeanDefinition and its now very similar cousin ScannedGenericBeanDefinition to make clear the semantics and intention of these two variants. Issue: SPR-9023 --- .../AnnotatedGenericBeanDefinition.java | 30 ++++++-- ...onfigurationClassBeanDefinitionReader.java | 39 +++------- .../ScannedGenericBeanDefinition.java | 13 +++- .../ConfigurationBeanNameTests.java | 76 +++++++++++++++++++ 4 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java index 5f4ee7f555..f0fd4c5cac 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -19,6 +19,7 @@ package org.springframework.beans.factory.annotation; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.util.Assert; /** * Extension of the {@link org.springframework.beans.factory.support.GenericBeanDefinition} @@ -32,27 +33,46 @@ import org.springframework.core.type.StandardAnnotationMetadata; * which also implements the AnnotatedBeanDefinition interface). * * @author Juergen Hoeller + * @author Chris Beams * @since 2.5 * @see AnnotatedBeanDefinition#getMetadata() * @see org.springframework.core.type.StandardAnnotationMetadata */ +@SuppressWarnings("serial") public class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition { - private final AnnotationMetadata annotationMetadata; + private final AnnotationMetadata metadata; /** * Create a new AnnotatedGenericBeanDefinition for the given bean class. * @param beanClass the loaded bean class */ - public AnnotatedGenericBeanDefinition(Class beanClass) { + public AnnotatedGenericBeanDefinition(Class beanClass) { setBeanClass(beanClass); - this.annotationMetadata = new StandardAnnotationMetadata(beanClass, true); + this.metadata = new StandardAnnotationMetadata(beanClass, true); + } + + /** + * Create a new AnnotatedGenericBeanDefinition for the given annotation metadata, + * allowing for ASM-based processing and avoidance of early loading of the bean class. + * Note that this constructor is functionally equivalent to + * {@link org.springframework.context.annotation.ScannedGenericBeanDefinition + * ScannedGenericBeanDefinition}, however the semantics of the latter indicate that + * a bean was discovered specifically via component-scanning as opposed to other + * means. + * @param metadata the annotation metadata for the bean class in question + * @since 3.1.1 + */ + public AnnotatedGenericBeanDefinition(AnnotationMetadata metadata) { + Assert.notNull(metadata, "AnnotationMetadata must not be null"); + setBeanClassName(metadata.getClassName()); + this.metadata = metadata; } public final AnnotationMetadata getMetadata() { - return this.annotationMetadata; + return this.metadata; } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index ffa0e08d82..23dcbe6ef0 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -16,7 +16,6 @@ package org.springframework.context.annotation; -import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -29,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; @@ -37,12 +37,10 @@ import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.SourceExtractor; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; @@ -50,7 +48,6 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; -import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; @@ -85,6 +82,8 @@ class ConfigurationClassBeanDefinitionReader { private final Environment environment; + private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); + /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used @@ -135,39 +134,21 @@ class ConfigurationClassBeanDefinitionReader { return; } - BeanDefinition configBeanDef = new GenericBeanDefinition(); - String className = configClass.getMetadata().getClassName(); + AnnotationMetadata metadata = configClass.getMetadata(); + BeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata); + String className = metadata.getClassName(); configBeanDef.setBeanClassName(className); - MetadataReader reader; - try { - reader = this.metadataReaderFactory.getMetadataReader(className); - } - catch (IOException ex) { - throw new IllegalStateException("Could not create MetadataReader for class " + className); - } if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) { - Map configAttributes = - reader.getAnnotationMetadata().getAnnotationAttributes(Configuration.class.getName()); - - // has the 'value' attribute of @Configuration been set? - String configBeanName = (String) configAttributes.get("value"); - if (StringUtils.hasText(configBeanName)) { - // yes -> register the configuration class bean with this name - this.registry.registerBeanDefinition(configBeanName, configBeanDef); - } - else { - // no -> register the configuration class bean with a generated name - configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry); - } + String configBeanName = this.beanNameGenerator.generateBeanName(configBeanDef, this.registry); + this.registry.registerBeanDefinition(configBeanName, configBeanDef); configClass.setBeanName(configBeanName); if (logger.isDebugEnabled()) { logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName)); } } else { - AnnotationMetadata metadata = reader.getAnnotationMetadata(); this.problemReporter.error( - new InvalidConfigurationImportProblem(className, reader.getResource(), metadata)); + new InvalidConfigurationImportProblem(className, configClass.getResource(), metadata)); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java index 22d918c887..b5afe9f6de 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScannedGenericBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2012 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. @@ -17,6 +17,7 @@ package org.springframework.context.annotation; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; @@ -27,16 +28,22 @@ import org.springframework.util.Assert; * class, based on an ASM ClassReader, with support for annotation metadata exposed * through the {@link AnnotatedBeanDefinition} interface. * - *

This class does not load the bean Class early. + *

This class does not load the bean {@code Class} early. * It rather retrieves all relevant metadata from the ".class" file itself, - * parsed with the ASM ClassReader. + * parsed with the ASM ClassReader. It is functionally equivalent to + * {@link AnnotatedGenericBeanDefinition#AnnotatedGenericBeanDefinition(AnnotationMetadata)} + * but distinguishes by type beans that have been scanned vs those that have + * been otherwise registered or detected by other means. * * @author Juergen Hoeller + * @author Chris Beams * @since 2.5 * @see #getMetadata() * @see #getBeanClassName() * @see org.springframework.core.type.classreading.MetadataReaderFactory + * @see AnnotatedGenericBeanDefinition */ +@SuppressWarnings("serial") public class ScannedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition { private final AnnotationMetadata metadata; diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java new file mode 100644 index 0000000000..799e55a3b6 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2012 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.configuration; + +import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Unit tests ensuring that configuration class bean names as expressed via @Configuration + * or @Component 'value' attributes are indeed respected + * + * @author Chris Beams + * @since 3.1.1 + */ +public class ConfigurationBeanNameTests { + + @Test + public void registerOuterConfig() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(A.class); + ctx.refresh(); + assertThat(ctx.containsBean("outer"), is(true)); + assertThat(ctx.containsBean("imported"), is(true)); + assertThat(ctx.containsBean("nested"), is(true)); + assertThat(ctx.containsBean("nestedBean"), is(true)); + } + + @Test + public void registerNestedConfig() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(A.B.class); + ctx.refresh(); + assertThat(ctx.containsBean("outer"), is(false)); + assertThat(ctx.containsBean("imported"), is(false)); + assertThat(ctx.containsBean("nested"), is(true)); + assertThat(ctx.containsBean("nestedBean"), is(true)); + } + + @Configuration("outer") + @Import(C.class) + static class A { + @Component("nested") + static class B { + @Bean public String nestedBean() { return ""; } + } + } + + @Configuration("imported") + static class C { + @Bean public String s() { return "s"; } + } +} From fc416bcb0bac38805c01f65ec1a8a08a13e3836f Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 15 Feb 2012 13:24:39 +0100 Subject: [PATCH 14/16] Apply @Configuration BeanNameGenerator consistently Since the introduction of the AnnotationConfig(Web)ApplicationContext types in Spring 3.0, it has been possible to specify a custom bean name generation strategy via the #setBeanNameGenerator methods available on each of these classes. If specified, that BeanNameGenerator was delegated to the underlying AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner. This meant that any @Configuration classes registered or scanned directly from the application context, e.g. via #register or #scan methods would respect the custom name generation strategy as intended. However, for @Configuration classes picked up via @Import or implicitly registered due to being nested classes would not be aware of this strategy, and would rather fall back to a hard-coded default AnnotationBeanNameGenerator. This change ensures consistent application of custom BeanNameGenerator strategies in the following ways: - Introduction of AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR singleton If a custom BeanNameGenerator is specified via #setBeanNameGenerator the AnnotationConfig* application contexts will, in addition to delegating this object to the underlying reader and scanner, register it as a singleton bean within the enclosing bean factory having the constant name mentioned above. ConfigurationClassPostProcessor now checks for the presence of this singleton, falling back to a default AnnotationBeanNameGenerator if not present. This singleton-based approach is necessary because it is otherwise impossible to parameterize CCPP, given that it is registered as a BeanDefinitionRegistryPostProcessor bean definition in AnnotationConfigUtils#registerAnnotationConfigProcessors - Introduction of ConfigurationClassPostProcessor#setBeanNameGenerator As detailed in the Javadoc for this new method, this allows for customizing the BeanNameGenerator via XML by dropping down to direct registration of CCPP as a instead of using to enable @Configuration class processing. - Smarter defaulting for @ComponentScan#beanNameGenerator Previously, @ComponentScan's #beanNameGenerator attribute had a default value of AnnotationBeanNameGenerator. The value is now the BeanNameGenerator interface itself, indicating that the scanner dedicated to processing each @ComponentScan should fall back to an inherited generator, i.e. the one originally specified against the application context, or the original default provided by ConfigurationClassPostProcessor. This means that name generation strategies will be consistent with a single point of configuration, but that individual @ComponentScan declarations may still customize the strategy for the beans that are picked up by that particular scanning. Issue: SPR-9124 --- .../AnnotationConfigApplicationContext.java | 4 +- .../annotation/AnnotationConfigUtils.java | 12 ++++ .../context/annotation/ComponentScan.java | 10 +++- .../ComponentScanAnnotationParser.java | 12 +++- ...onfigurationClassBeanDefinitionReader.java | 11 +++- .../annotation/ConfigurationClassParser.java | 9 ++- .../ConfigurationClassPostProcessor.java | 59 +++++++++++++++---- .../AsmCircularImportDetectionTests.java | 1 + .../ConfigurationBeanNameTests.java | 20 ++++++- ...AnnotationConfigWebApplicationContext.java | 3 + ...ationConfigWebApplicationContextTests.java | 25 +++++++- 11 files changed, 140 insertions(+), 26 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 914b9d9ce7..d147a50656 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -109,6 +109,8 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.reader.setBeanNameGenerator(beanNameGenerator); this.scanner.setBeanNameGenerator(beanNameGenerator); + this.getBeanFactory().registerSingleton( + AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); } /** diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 72592b9cf4..793ba314b4 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -56,6 +56,17 @@ public class AnnotationConfigUtils { public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"; + /** + * The bean name of the internally managed BeanNameGenerator for use when processing + * {@link Configuration} classes. Set by {@link AnnotationConfigApplicationContext} + * and {@code AnnotationConfigWebApplicationContext} during bootstrap in order to make + * any custom name generation strategy available to the underlying + * {@link ConfigurationClassPostProcessor}. + * @since 3.1.1 + */ + public static final String CONFIGURATION_BEAN_NAME_GENERATOR = + "org.springframework.context.annotation.internalConfigurationBeanNameGenerator"; + /** * The bean name of the internally managed Autowired annotation processor. */ @@ -249,4 +260,5 @@ public class AnnotationConfigUtils { return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); } + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java index 20729b0c5b..f0826a62eb 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -77,8 +77,14 @@ public @interface ComponentScan { /** * The {@link BeanNameGenerator} class to be used for naming detected components * within the Spring container. + *

The default value of the {@link BeanNameGenerator} interface itself indicates + * that the scanner used to process this {@code @ComponentScan} annotation should + * use its inherited bean name generator, e.g. the default + * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the + * application context at bootstrap time. + * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator) */ - Class nameGenerator() default AnnotationBeanNameGenerator.class; + Class nameGenerator() default BeanNameGenerator.class; /** * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 9fa82e40c7..a549b8d0af 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -51,11 +51,16 @@ class ComponentScanAnnotationParser { private final BeanDefinitionRegistry registry; + private final BeanNameGenerator beanNameGenerator; + public ComponentScanAnnotationParser( - ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) { + ResourceLoader resourceLoader, Environment environment, + BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry) { + this.resourceLoader = resourceLoader; this.environment = environment; + this.beanNameGenerator = beanNameGenerator; this.registry = registry; } @@ -71,7 +76,10 @@ class ComponentScanAnnotationParser { scanner.setResourceLoader(this.resourceLoader); Class generatorClass = componentScan.getClass("nameGenerator"); - scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); + boolean useInheritedGenerator = BeanNameGenerator.class.equals(generatorClass); + scanner.setBeanNameGenerator(useInheritedGenerator + ? this.beanNameGenerator + : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 23dcbe6ef0..02b4125e61 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -49,6 +49,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import static org.springframework.context.annotation.MetadataUtils.*; @@ -82,7 +83,7 @@ class ConfigurationClassBeanDefinitionReader { private final Environment environment; - private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); + private final BeanNameGenerator beanNameGenerator; /** @@ -91,16 +92,20 @@ class ConfigurationClassBeanDefinitionReader { * @param problemReporter * @param metadataReaderFactory */ - public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, + public ConfigurationClassBeanDefinitionReader( + BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, - ResourceLoader resourceLoader, Environment environment) { + ResourceLoader resourceLoader, Environment environment, + BeanNameGenerator beanNameGenerator) { + Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null"); this.registry = registry; this.sourceExtractor = sourceExtractor; this.problemReporter = problemReporter; this.metadataReaderFactory = metadataReaderFactory; this.resourceLoader = resourceLoader; this.environment = environment; + this.beanNameGenerator = beanNameGenerator; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 0825b134db..1ad4838ed8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; @@ -100,14 +101,16 @@ class ConfigurationClassParser { */ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, - ResourceLoader resourceLoader, BeanDefinitionRegistry registry) { + ResourceLoader resourceLoader, BeanNameGenerator beanNameGenerator, + BeanDefinitionRegistry registry) { + this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; - this.componentScanParser = - new ComponentScanAnnotationParser(this.resourceLoader, this.environment, this.registry); + this.componentScanParser = new ComponentScanAnnotationParser( + resourceLoader, environment, beanNameGenerator, registry); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 8e724106de..9736099c6f 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -46,6 +46,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; @@ -65,6 +66,8 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import static org.springframework.context.annotation.AnnotationConfigUtils.*; + /** * {@link BeanFactoryPostProcessor} used for bootstrapping processing of * {@link Configuration @Configuration} classes. @@ -112,6 +115,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private ConfigurationClassBeanDefinitionReader reader; + private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); + /** * Set the {@link SourceExtractor} to use for generated bean definitions @@ -142,6 +147,26 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo this.setMetadataReaderFactoryCalled = true; } + /** + * Set the {@link BeanNameGenerator} to be used when registering imported and nested + * {@link Configuration} classes. The default is {@link AnnotationBeanNameGenerator}. + *

Note that this strategy does not apply to {@link Bean} methods. + *

This setter is typically only appropriate when configuring the post-processor as + * a standalone bean definition in XML, e.g. not using the dedicated + * {@code AnnotationConfig*} application contexts or the {@code + * } element. Any bean name generator specified against + * the application context will take precedence over any value set here. + * @param beanNameGenerator the strategy to use when generating configuration class + * bean names + * @since 3.1.1 + * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator) + * @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR + */ + public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { + Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null"); + this.beanNameGenerator = beanNameGenerator; + } + public void setEnvironment(Environment environment) { Assert.notNull(environment, "Environment must not be null"); this.environment = environment; @@ -197,14 +222,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo enhanceConfigurationClasses(beanFactory); } - private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) { - if (this.reader == null) { - this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, - this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment); - } - return this.reader; - } - /** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. @@ -223,9 +240,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo return; } + // Detect any custom bean name generation strategy supplied through the enclosing application context + SingletonBeanRegistry singletonRegistry = null; + if (registry instanceof SingletonBeanRegistry) { + singletonRegistry = (SingletonBeanRegistry) registry; + if (singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) { + this.beanNameGenerator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); + } + } + // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( - this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, registry); + this.metadataReaderFactory, this.problemReporter, this.environment, + this.resourceLoader, this.beanNameGenerator, registry); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { @@ -258,12 +285,18 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } // Read the model and create bean definitions based on its content - this.getConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getConfigurationClasses()); + if (this.reader == null) { + this.reader = new ConfigurationClassBeanDefinitionReader( + registry, this.sourceExtractor, this.problemReporter, + this.metadataReaderFactory, this.resourceLoader, this.environment, + this.beanNameGenerator); + } + this.reader.loadBeanDefinitions(parser.getConfigurationClasses()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes - if (registry instanceof SingletonBeanRegistry) { - if (!((SingletonBeanRegistry) registry).containsSingleton("importRegistry")) { - ((SingletonBeanRegistry) registry).registerSingleton("importRegistry", parser.getImportRegistry()); + if (singletonRegistry != null) { + if (!singletonRegistry.containsSingleton("importRegistry")) { + singletonRegistry.registerSingleton("importRegistry", parser.getImportRegistry()); } } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index 17b232da59..36a9b1eed8 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java @@ -40,6 +40,7 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec new FailFastProblemReporter(), new StandardEnvironment(), new DefaultResourceLoader(), + new AnnotationBeanNameGenerator(), new DefaultListableBeanFactory()); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java index 799e55a3b6..445397c384 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java @@ -31,7 +31,8 @@ import static org.junit.Assert.*; /** * Unit tests ensuring that configuration class bean names as expressed via @Configuration - * or @Component 'value' attributes are indeed respected + * or @Component 'value' attributes are indeed respected, and that customization of bean + * naming through a BeanNameGenerator strategy works as expected. * * @author Chris Beams * @since 3.1.1 @@ -60,6 +61,23 @@ public class ConfigurationBeanNameTests { assertThat(ctx.containsBean("nestedBean"), is(true)); } + @Test + public void registerOuterConfig_withBeanNameGenerator() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.setBeanNameGenerator(new AnnotationBeanNameGenerator() { + public String generateBeanName( + BeanDefinition definition, BeanDefinitionRegistry registry) { + return "custom-" + super.generateBeanName(definition, registry); + } + }); + ctx.register(A.class); + ctx.refresh(); + assertThat(ctx.containsBean("custom-outer"), is(true)); + assertThat(ctx.containsBean("custom-imported"), is(true)); + assertThat(ctx.containsBean("custom-nested"), is(true)); + assertThat(ctx.containsBean("nestedBean"), is(true)); + } + @Configuration("outer") @Import(C.class) static class A { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 24b35ea763..02a46b68f8 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -19,6 +19,7 @@ package org.springframework.web.context.support; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ScopeMetadataResolver; import org.springframework.util.Assert; @@ -196,6 +197,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe if (beanNameGenerator != null) { reader.setBeanNameGenerator(beanNameGenerator); scanner.setBeanNameGenerator(beanNameGenerator); + beanFactory.registerSingleton( + AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); } if (scopeMetadataResolver != null) { reader.setScopeMetadataResolver(scopeMetadataResolver); diff --git a/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java b/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java index 7f0b27a0d3..d427f7f381 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java @@ -19,9 +19,16 @@ package org.springframework.web.context.support; import static org.junit.Assert.assertNotNull; import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import static org.hamcrest.CoreMatchers.*; + +import static org.junit.Assert.*; + /** * @author Chris Beams */ @@ -49,8 +56,24 @@ public class AnnotationConfigWebApplicationContextTests { assertNotNull(bean); } + @Test + public void withBeanNameGenerator() { + AnnotationConfigWebApplicationContext ctx = + new AnnotationConfigWebApplicationContext(); + ctx.setBeanNameGenerator(new AnnotationBeanNameGenerator() { + @Override + public String generateBeanName(BeanDefinition definition, + BeanDefinitionRegistry registry) { + return "custom-" + super.generateBeanName(definition, registry); + } + }); + ctx.setConfigLocation(Config.class.getName()); + ctx.refresh(); + assertThat(ctx.containsBean("custom-myConfig"), is(true)); + } - @Configuration + + @Configuration("myConfig") static class Config { @Bean public TestBean myTestBean() { From 58e270f7da55256e1eedeccab4c10424cfff4d51 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 15 Feb 2012 21:11:29 +0100 Subject: [PATCH 15/16] Prune dead code from JmsTransactionManager#doBegin --- .../jms/connection/JmsTransactionManager.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java b/org.springframework.jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java index 711e62ac47..aa6e235651 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/connection/JmsTransactionManager.java @@ -87,6 +87,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager * @see TransactionAwareConnectionFactoryProxy * @see org.springframework.jms.core.JmsTemplate */ +@SuppressWarnings("serial") public class JmsTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean { @@ -191,14 +192,6 @@ public class JmsTransactionManager extends AbstractPlatformTransactionManager getConnectionFactory(), txObject.getResourceHolder()); } catch (JMSException ex) { - if (session != null) { - try { - session.close(); - } - catch (Throwable ex2) { - // ignore - } - } if (con != null) { try { con.close(); From 1c2b1d2c8576391d665e0f25a17427eacd56bf5c Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 15 Feb 2012 22:01:35 +0100 Subject: [PATCH 16/16] Demonstrate use of @Configuration as meta-annotation Issue: SPR-9090 --- .../ConfigurationMetaAnnotationTests.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java new file mode 100644 index 0000000000..c556e86898 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2012 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.configuration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import test.beans.TestBean; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Ensures that @Configuration is supported properly as a meta-annotation. + * + * @author Chris Beams + */ +public class ConfigurationMetaAnnotationTests { + + @Test + public void customConfigurationStereotype() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + assertThat(ctx.containsBean("customName"), is(true)); + TestBean a = ctx.getBean("a", TestBean.class); + TestBean b = ctx.getBean("b", TestBean.class); + assertThat(b, sameInstance(a.getSpouse())); + } + + + @TestConfiguration("customName") + static class Config { + @Bean + public TestBean a() { + TestBean a = new TestBean(); + a.setSpouse(b()); + return a; + } + + @Bean + public TestBean b() { + return new TestBean(); + } + } + + + @Configuration + @Retention(RetentionPolicy.RUNTIME) + public @interface TestConfiguration { + String value() default ""; + } +}