Browse Source
+ Finished support for @Import, including detection of circular imports git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@775 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
50 changed files with 1108 additions and 299 deletions
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Inherited; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
|
||||
/** |
||||
* Annotation that allows one {@link Configuration} class to import another Configuration, |
||||
* and thereby all its {@link Bean} definitions. |
||||
* |
||||
* <p>Provides functionality equivalent to the {@literal <import/>} element in Spring XML. |
||||
* |
||||
* @author Chris Beams |
||||
* @since 3.0 |
||||
* @see Configuration |
||||
*/ |
||||
@Target({ ElementType.TYPE }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Inherited |
||||
@Documented |
||||
public @interface Import { |
||||
|
||||
/** |
||||
* The {@link Configuration} class or classes to import. |
||||
*/ |
||||
Class<?>[] value(); |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import static java.lang.String.*; |
||||
|
||||
import java.util.Stack; |
||||
|
||||
|
||||
/** |
||||
* Thrown by {@link ConfigurationParser} upon detecting circular use of the {@link Import} annotation. |
||||
* |
||||
* @author Chris Beams |
||||
* @see Import |
||||
* @see ImportStack |
||||
* @see ImportStackHolder |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
class CircularImportException extends IllegalStateException { |
||||
public CircularImportException(ConfigurationClass attemptedImport, Stack<ConfigurationClass> currentImportStack) { |
||||
super(format("A circular @Import has been detected: " + |
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + |
||||
"already present in the current import stack [%s]", |
||||
currentImportStack.peek().getSimpleName(), attemptedImport.getSimpleName(), |
||||
attemptedImport.getSimpleName(), currentImportStack)); |
||||
} |
||||
} |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import static java.lang.String.*; |
||||
import static org.springframework.config.java.support.AsmUtils.*; |
||||
import static org.springframework.util.ClassUtils.*; |
||||
|
||||
import java.util.ArrayList; |
||||
|
||||
import org.springframework.asm.AnnotationVisitor; |
||||
import org.springframework.asm.ClassReader; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.config.java.Import; |
||||
import org.springframework.util.Assert; |
||||
|
||||
|
||||
/** |
||||
* ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation |
||||
* for all its specified classes and then one by one processes each class with a new |
||||
* {@link ConfigurationClassVisitor}. |
||||
* |
||||
* @author Chris Beams |
||||
* @see Import |
||||
* @see ImportStack |
||||
* @see ImportStackHolder |
||||
* @see ConfigurationClassVisitor |
||||
*/ |
||||
class ImportAnnotationVisitor extends AnnotationAdapter { |
||||
private final ArrayList<String> classesToImport = new ArrayList<String>(); |
||||
private final ConfigurationModel model; |
||||
private final ClassLoader classLoader; |
||||
|
||||
public ImportAnnotationVisitor(ConfigurationModel model, ClassLoader classLoader) { |
||||
super(AsmUtils.EMPTY_VISITOR); |
||||
this.model = model; |
||||
this.classLoader = classLoader; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationVisitor visitArray(String attribName) { |
||||
Assert.isTrue("value".equals(attribName), |
||||
format("expected 'value' attribute, got unknown '%s' attribute", attribName)); |
||||
|
||||
return new AnnotationAdapter(AsmUtils.EMPTY_VISITOR) { |
||||
@Override |
||||
public void visit(String na, Object type) { |
||||
Assert.isInstanceOf(Type.class, type); |
||||
classesToImport.add(((Type) type).getClassName()); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public void visitEnd() { |
||||
for (String classToImport : classesToImport) |
||||
processClassToImport(classToImport); |
||||
|
||||
ImportStackHolder.getImportStack().pop(); |
||||
} |
||||
|
||||
private void processClassToImport(String classToImport) { |
||||
ConfigurationClass configClass = new ConfigurationClass(); |
||||
|
||||
ClassReader reader = newClassReader(convertClassNameToResourcePath(classToImport), classLoader); |
||||
|
||||
reader.accept(new ConfigurationClassVisitor(configClass, model, classLoader), false); |
||||
|
||||
model.add(configClass); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.Iterator; |
||||
import java.util.Stack; |
||||
|
||||
import org.springframework.config.java.Import; |
||||
import org.springframework.util.Assert; |
||||
|
||||
|
||||
/** |
||||
* Stack used for detecting circular use of the {@link Import} annotation. |
||||
* |
||||
* @author Chris Beams |
||||
* @see Import |
||||
* @see ImportStackHolder |
||||
* @see CircularImportException |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
class ImportStack extends Stack<ConfigurationClass> { |
||||
|
||||
/** |
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass} |
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of |
||||
* type ConfigurationClass. |
||||
*/ |
||||
@Override |
||||
public boolean contains(Object elem) { |
||||
Assert.isInstanceOf(ConfigurationClass.class, elem); |
||||
|
||||
ConfigurationClass configClass = (ConfigurationClass) elem; |
||||
|
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() { |
||||
public int compare(ConfigurationClass first, ConfigurationClass second) { |
||||
return first.getName().equals(second.getName()) ? 0 : 1; |
||||
} |
||||
}; |
||||
|
||||
int index = Collections.binarySearch(this, configClass, comparator); |
||||
|
||||
return index >= 0 ? true : false; |
||||
} |
||||
|
||||
/** |
||||
* Given a stack containing (in order) |
||||
* <ol> |
||||
* <li>com.acme.Foo</li> |
||||
* <li>com.acme.Bar</li> |
||||
* <li>com.acme.Baz</li> |
||||
* </ol> |
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. |
||||
*/ |
||||
@Override |
||||
public synchronized String toString() { |
||||
StringBuilder builder = new StringBuilder(); |
||||
|
||||
Iterator<ConfigurationClass> iterator = this.iterator(); |
||||
|
||||
while (iterator.hasNext()) { |
||||
builder.append(iterator.next().getSimpleName()); |
||||
if (iterator.hasNext()) |
||||
builder.append("->"); |
||||
} |
||||
|
||||
return builder.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import org.springframework.config.java.Import; |
||||
|
||||
|
||||
/** |
||||
* Holder class to expose a thread-bound {@link ImportStack}, used while detecting circular |
||||
* declarations of the {@link Import} annotation. |
||||
* |
||||
* @author Chris Beams |
||||
* @see Import |
||||
* @see ImportStack |
||||
* @see CircularImportException |
||||
*/ |
||||
class ImportStackHolder { |
||||
|
||||
private static ThreadLocal<ImportStack> stackHolder = new ThreadLocal<ImportStack>() { |
||||
@Override |
||||
protected ImportStack initialValue() { |
||||
return new ImportStack(); |
||||
} |
||||
}; |
||||
|
||||
public static ImportStack getImportStack() { |
||||
return stackHolder.get(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Holder for circular @Import detection stack"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.config.java.Bean; |
||||
import org.springframework.config.java.Import; |
||||
|
||||
import test.beans.TestBean; |
||||
|
||||
|
||||
/** |
||||
* TCK-style unit tests for handling circular use of the {@link Import} annotation. Explore |
||||
* subclass hierarchy for specific concrete implementations. |
||||
* |
||||
* @author Chris Beams |
||||
*/ |
||||
public abstract class AbstractCircularImportDetectionTests { |
||||
protected abstract ConfigurationParser newParser(); |
||||
|
||||
protected abstract String loadAsConfigurationSource(Class<?> clazz) throws Exception; |
||||
|
||||
@Test |
||||
public void simpleCircularImportIsDetected() throws Exception { |
||||
boolean threw = false; |
||||
try { |
||||
newParser().parse(loadAsConfigurationSource(A.class), null); |
||||
} catch (CircularImportException ex) { |
||||
assertTrue("Wrong message. Got: " + ex.getMessage(), |
||||
ex.getMessage().contains( |
||||
"Illegal attempt by @Configuration class 'AbstractCircularImportDetectionTests.B' " + |
||||
"to import class 'AbstractCircularImportDetectionTests.A'")); |
||||
threw = true; |
||||
} |
||||
|
||||
assertTrue(threw); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void complexCircularImportIsDetected() throws Exception { |
||||
boolean threw = false; |
||||
try { |
||||
newParser().parse(loadAsConfigurationSource(X.class), null); |
||||
} catch (CircularImportException ex) { |
||||
assertTrue("Wrong message. Got: " + ex.getMessage(), |
||||
ex.getMessage().contains( |
||||
"Illegal attempt by @Configuration class 'AbstractCircularImportDetectionTests.Z2' " + |
||||
"to import class 'AbstractCircularImportDetectionTests.Z'")); |
||||
threw = true; |
||||
} |
||||
|
||||
assertTrue(threw); |
||||
} |
||||
|
||||
@Import(B.class) |
||||
static class A { |
||||
@Bean |
||||
TestBean b1() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Import(A.class) |
||||
static class B { |
||||
@Bean |
||||
TestBean b2() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Import( { Y.class, Z.class }) |
||||
class X { |
||||
@Bean |
||||
TestBean x() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
class Y { |
||||
@Bean |
||||
TestBean y() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Import( { Z1.class, Z2.class }) |
||||
class Z { |
||||
@Bean |
||||
TestBean z() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
class Z1 { |
||||
@Bean |
||||
TestBean z1() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Import(Z.class) |
||||
class Z2 { |
||||
@Bean |
||||
TestBean z2() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import org.springframework.config.java.Import; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
|
||||
/** |
||||
* Unit test proving that ASM-based {@link ConfigurationParser} correctly detects circular use of |
||||
* the {@link Import @Import} annotation. |
||||
* |
||||
* <p>While this test is the only subclass of {@link AbstractCircularImportDetectionTests}, the |
||||
* hierarchy remains in place in case a JDT-based ConfigurationParser implementation needs to be |
||||
* developed. |
||||
* |
||||
* @author Chris Beams |
||||
*/ |
||||
public class AsmCircularImportDetectionTests extends AbstractCircularImportDetectionTests { |
||||
@Override |
||||
protected ConfigurationParser newParser() { |
||||
return new ConfigurationParser(ClassUtils.getDefaultClassLoader()); |
||||
} |
||||
|
||||
@Override |
||||
protected String loadAsConfigurationSource(Class<?> clazz) throws Exception { |
||||
return clazz.getName(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.config.java.support; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.config.java.Configuration; |
||||
|
||||
|
||||
/** |
||||
* Unit tests covering cases where a user defines an invalid Configuration |
||||
* class, e.g.: forgets to annotate with {@link Configuration} or declares |
||||
* a Configuration class as final. |
||||
* |
||||
* @author Chris Beams |
||||
*/ |
||||
public class InvalidConfigurationClassDefinitionTests { |
||||
|
||||
@Test |
||||
public void configurationClassesMayNotBeFinal() { |
||||
@Configuration |
||||
final class Config { } |
||||
|
||||
BeanDefinition configBeanDef = rootBeanDefinition(Config.class).getBeanDefinition(); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerBeanDefinition("config", configBeanDef); |
||||
|
||||
try { |
||||
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); |
||||
fail("expected exception"); |
||||
} catch (BeanDefinitionParsingException ex) { |
||||
assertTrue(ex.getMessage(), ex.getMessage().contains("Remove the final modifier")); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,275 @@
@@ -0,0 +1,275 @@
|
||||
/* |
||||
* Copyright 2002-2008 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 test.feature.atimport; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.config.java.Bean; |
||||
import org.springframework.config.java.Configuration; |
||||
import org.springframework.config.java.Import; |
||||
import org.springframework.config.java.support.ConfigurationClassPostProcessor; |
||||
|
||||
import test.beans.ITestBean; |
||||
import test.beans.TestBean; |
||||
|
||||
|
||||
/** |
||||
* System tests for {@link Import} annotation support. |
||||
* |
||||
* @author Chris Beams |
||||
*/ |
||||
public class ImportTests { |
||||
|
||||
private DefaultListableBeanFactory processConfigurationClasses(Class<?>... classes) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
for (Class<?> clazz : classes) |
||||
beanFactory.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz)); |
||||
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); |
||||
return beanFactory; |
||||
} |
||||
|
||||
private void assertBeanDefinitionCount(int expectedCount, Class<?>... classes) { |
||||
DefaultListableBeanFactory beanFactory = processConfigurationClasses(classes); |
||||
assertThat(beanFactory.getBeanDefinitionCount(), equalTo(expectedCount)); |
||||
} |
||||
|
||||
@Test |
||||
public void testProcessImports() { |
||||
int configClasses = 2; |
||||
int beansInClasses = 2; |
||||
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), ConfigurationWithImportAnnotation.class); |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(OtherConfiguration.class) |
||||
static class ConfigurationWithImportAnnotation { |
||||
@Bean |
||||
public ITestBean one() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class OtherConfiguration { |
||||
@Bean |
||||
public ITestBean two() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test |
||||
public void testImportAnnotationWithTwoLevelRecursion() { |
||||
int configClasses = 2; |
||||
int beansInClasses = 3; |
||||
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), AppConfig.class); |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(DataSourceConfig.class) |
||||
static class AppConfig { |
||||
|
||||
@Bean |
||||
public ITestBean transferService() { |
||||
return new TestBean(accountRepository()); |
||||
} |
||||
|
||||
@Bean |
||||
public ITestBean accountRepository() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class DataSourceConfig { |
||||
@Bean |
||||
public ITestBean dataSourceA() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test |
||||
public void testImportAnnotationWithThreeLevelRecursion() { |
||||
int configClasses = 3; |
||||
int beansInClasses = 5; |
||||
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), FirstLevel.class); |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test |
||||
public void testImportAnnotationWithMultipleArguments() { |
||||
int configClasses = 3; |
||||
int beansInClasses = 3; |
||||
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), |
||||
WithMultipleArgumentsToImportAnnotation.class); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testImportAnnotationWithMultipleArgumentsResultingInOverriddenBeanDefinition() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition( |
||||
WithMultipleArgumentsThatWillCauseDuplication.class)); |
||||
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); |
||||
assertThat(beanFactory.getBeanDefinitionCount(), equalTo(4)); |
||||
assertThat(beanFactory.getBean("foo", ITestBean.class).getName(), equalTo("foo2")); |
||||
} |
||||
|
||||
@Configuration |
||||
@Import( { Foo1.class, Foo2.class }) |
||||
static class WithMultipleArgumentsThatWillCauseDuplication { |
||||
} |
||||
|
||||
@Configuration |
||||
static class Foo1 { |
||||
@Bean |
||||
public ITestBean foo() { |
||||
return new TestBean("foo1"); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class Foo2 { |
||||
@Bean |
||||
public ITestBean foo() { |
||||
return new TestBean("foo2"); |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test |
||||
public void testImportAnnotationOnInnerClasses() { |
||||
int configClasses = 2; |
||||
int beansInClasses = 2; |
||||
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), OuterConfig.InnerConfig.class); |
||||
} |
||||
|
||||
@Configuration |
||||
static class OuterConfig { |
||||
@Bean |
||||
String whatev() { |
||||
return "whatev"; |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(ExternalConfig.class) |
||||
static class InnerConfig { |
||||
@Bean |
||||
public ITestBean innerBean() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class ExternalConfig { |
||||
@Bean |
||||
public ITestBean extBean() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Configuration |
||||
@Import(SecondLevel.class) |
||||
static class FirstLevel { |
||||
@Bean |
||||
public TestBean m() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(ThirdLevel.class) |
||||
static class SecondLevel { |
||||
@Bean |
||||
public TestBean n() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class ThirdLevel { |
||||
@Bean |
||||
public ITestBean thirdLevelA() { |
||||
return new TestBean(); |
||||
} |
||||
|
||||
@Bean |
||||
public ITestBean thirdLevelB() { |
||||
return new TestBean(); |
||||
} |
||||
|
||||
@Bean |
||||
public ITestBean thirdLevelC() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
@Import( { LeftConfig.class, RightConfig.class }) |
||||
static class WithMultipleArgumentsToImportAnnotation { |
||||
@Bean |
||||
public TestBean m() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class LeftConfig { |
||||
@Bean |
||||
public ITestBean left() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class RightConfig { |
||||
@Bean |
||||
public ITestBean right() { |
||||
return new TestBean(); |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test(expected=BeanDefinitionParsingException.class) |
||||
public void testImportNonConfigurationAnnotationClassCausesError() { |
||||
processConfigurationClasses(ConfigAnnotated.class); |
||||
} |
||||
|
||||
@Configuration |
||||
@Import(NonConfigAnnotated.class) |
||||
static class ConfigAnnotated { } |
||||
|
||||
static class NonConfigAnnotated { } |
||||
} |
||||
@ -1,139 +1,154 @@
@@ -1,139 +1,154 @@
|
||||
package org.springframework.core.type.classreading; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collection; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.objectweb.asm.AnnotationVisitor; |
||||
import org.objectweb.asm.MethodAdapter; |
||||
import org.objectweb.asm.Opcodes; |
||||
import org.objectweb.asm.Type; |
||||
import org.objectweb.asm.commons.EmptyVisitor; |
||||
import org.springframework.core.type.MethodMetadata; |
||||
|
||||
public class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { |
||||
|
||||
private final Map<String, Map<String, Object>> attributesMap = new LinkedHashMap<String, Map<String, Object>>(); |
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(); |
||||
|
||||
private ClassLoader classLoader; |
||||
private String name; |
||||
private int access; |
||||
private boolean isStatic; |
||||
|
||||
public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) { |
||||
super(new EmptyVisitor()); |
||||
this.classLoader = classLoader; |
||||
this.name = name; |
||||
this.access = access; |
||||
this.isStatic = ((access & Opcodes.ACC_STATIC) != 0); |
||||
} |
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) { |
||||
return this.attributesMap.get(annotationType); |
||||
} |
||||
|
||||
public Set<String> getAnnotationTypes() { |
||||
return this.attributesMap.keySet(); |
||||
} |
||||
|
||||
public String getMethodName() { |
||||
return name; |
||||
} |
||||
|
||||
public int getModifiers() { |
||||
return access; |
||||
} |
||||
|
||||
public boolean hasAnnotation(String annotationType) { |
||||
return this.attributesMap.containsKey(annotationType); |
||||
} |
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) { |
||||
return this.metaAnnotationMap.get(annotationType); |
||||
} |
||||
|
||||
public boolean hasMetaAnnotation(String metaAnnotationType) { |
||||
Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values(); |
||||
for (Set<String> metaTypes : allMetaTypes) { |
||||
if (metaTypes.contains(metaAnnotationType)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public boolean isStatic() { |
||||
return isStatic; |
||||
} |
||||
|
||||
|
||||
public Set<String> getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) { |
||||
|
||||
///metaAnnotationMap.put(className, metaAnnotationTypeNames);
|
||||
Set<String> annotationTypes = new LinkedHashSet<String>(); |
||||
Set< Map.Entry<String, Set<String>> > metaValues = metaAnnotationMap.entrySet(); |
||||
Iterator<Map.Entry<String, Set<String>> > metaIterator = metaValues.iterator(); |
||||
while (metaIterator.hasNext()) |
||||
{ |
||||
Map.Entry<String, Set<String>> entry = metaIterator.next(); |
||||
String attributeType = entry.getKey(); |
||||
Set<String> metaAttributes = entry.getValue(); |
||||
if (metaAttributes.contains(metaAnnotationType)) |
||||
{ |
||||
annotationTypes.add(attributeType); |
||||
} |
||||
} |
||||
return annotationTypes; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { |
||||
final String className = Type.getType(desc).getClassName(); |
||||
final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); |
||||
return new EmptyVisitor() { |
||||
@Override |
||||
public void visit(String name, Object value) { |
||||
// Explicitly defined annotation attribute value.
|
||||
attributes.put(name, value); |
||||
} |
||||
@Override |
||||
public void visitEnd() { |
||||
try { |
||||
Class annotationClass = classLoader.loadClass(className); |
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods(); |
||||
for (int i = 0; i < annotationAttributes.length; i++) { |
||||
Method annotationAttribute = annotationAttributes[i]; |
||||
String attributeName = annotationAttribute.getName(); |
||||
Object defaultValue = annotationAttribute.getDefaultValue(); |
||||
if (defaultValue != null && !attributes.containsKey(attributeName)) { |
||||
attributes.put(attributeName, defaultValue); |
||||
} |
||||
} |
||||
// Register annotations that the annotation type is annotated with.
|
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations(); |
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>(); |
||||
for (Annotation metaAnnotation : metaAnnotations) { |
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); |
||||
} |
||||
metaAnnotationMap.put(className, metaAnnotationTypeNames); |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
// Class not found
|
||||
} |
||||
attributesMap.put(className, attributes); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.core.type.classreading; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Collection; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.asm.AnnotationVisitor; |
||||
import org.springframework.asm.MethodAdapter; |
||||
import org.springframework.asm.Opcodes; |
||||
import org.springframework.asm.Type; |
||||
import org.springframework.asm.commons.EmptyVisitor; |
||||
import org.springframework.core.type.MethodMetadata; |
||||
|
||||
public class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { |
||||
|
||||
private final Map<String, Map<String, Object>> attributesMap = new LinkedHashMap<String, Map<String, Object>>(); |
||||
|
||||
private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(); |
||||
|
||||
private ClassLoader classLoader; |
||||
private String name; |
||||
private int access; |
||||
private boolean isStatic; |
||||
|
||||
public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) { |
||||
super(new EmptyVisitor()); |
||||
this.classLoader = classLoader; |
||||
this.name = name; |
||||
this.access = access; |
||||
this.isStatic = ((access & Opcodes.ACC_STATIC) != 0); |
||||
} |
||||
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) { |
||||
return this.attributesMap.get(annotationType); |
||||
} |
||||
|
||||
public Set<String> getAnnotationTypes() { |
||||
return this.attributesMap.keySet(); |
||||
} |
||||
|
||||
public String getMethodName() { |
||||
return name; |
||||
} |
||||
|
||||
public int getModifiers() { |
||||
return access; |
||||
} |
||||
|
||||
public boolean hasAnnotation(String annotationType) { |
||||
return this.attributesMap.containsKey(annotationType); |
||||
} |
||||
|
||||
public Set<String> getMetaAnnotationTypes(String annotationType) { |
||||
return this.metaAnnotationMap.get(annotationType); |
||||
} |
||||
|
||||
public boolean hasMetaAnnotation(String metaAnnotationType) { |
||||
Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values(); |
||||
for (Set<String> metaTypes : allMetaTypes) { |
||||
if (metaTypes.contains(metaAnnotationType)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public boolean isStatic() { |
||||
return isStatic; |
||||
} |
||||
|
||||
|
||||
public Set<String> getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) { |
||||
|
||||
///metaAnnotationMap.put(className, metaAnnotationTypeNames);
|
||||
Set<String> annotationTypes = new LinkedHashSet<String>(); |
||||
Set< Map.Entry<String, Set<String>> > metaValues = metaAnnotationMap.entrySet(); |
||||
Iterator<Map.Entry<String, Set<String>> > metaIterator = metaValues.iterator(); |
||||
while (metaIterator.hasNext()) |
||||
{ |
||||
Map.Entry<String, Set<String>> entry = metaIterator.next(); |
||||
String attributeType = entry.getKey(); |
||||
Set<String> metaAttributes = entry.getValue(); |
||||
if (metaAttributes.contains(metaAnnotationType)) |
||||
{ |
||||
annotationTypes.add(attributeType); |
||||
} |
||||
} |
||||
return annotationTypes; |
||||
} |
||||
|
||||
@Override |
||||
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { |
||||
final String className = Type.getType(desc).getClassName(); |
||||
final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); |
||||
return new EmptyVisitor() { |
||||
@Override |
||||
public void visit(String name, Object value) { |
||||
// Explicitly defined annotation attribute value.
|
||||
attributes.put(name, value); |
||||
} |
||||
@Override |
||||
public void visitEnd() { |
||||
try { |
||||
Class<?> annotationClass = classLoader.loadClass(className); |
||||
// Check declared default values of attributes in the annotation type.
|
||||
Method[] annotationAttributes = annotationClass.getMethods(); |
||||
for (int i = 0; i < annotationAttributes.length; i++) { |
||||
Method annotationAttribute = annotationAttributes[i]; |
||||
String attributeName = annotationAttribute.getName(); |
||||
Object defaultValue = annotationAttribute.getDefaultValue(); |
||||
if (defaultValue != null && !attributes.containsKey(attributeName)) { |
||||
attributes.put(attributeName, defaultValue); |
||||
} |
||||
} |
||||
// Register annotations that the annotation type is annotated with.
|
||||
Annotation[] metaAnnotations = annotationClass.getAnnotations(); |
||||
Set<String> metaAnnotationTypeNames = new HashSet<String>(); |
||||
for (Annotation metaAnnotation : metaAnnotations) { |
||||
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); |
||||
} |
||||
metaAnnotationMap.put(className, metaAnnotationTypeNames); |
||||
} |
||||
catch (ClassNotFoundException ex) { |
||||
// Class not found
|
||||
} |
||||
attributesMap.put(className, attributes); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2002-2009 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/** |
||||
* Core support package for type introspection through ASM-based class reading. |
||||
*/ |
||||
package org.springframework.core.type.classreading; |
||||
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
<html> |
||||
<body> |
||||
|
||||
Core support package for type introspection through ASM-based class reading. |
||||
|
||||
</body> |
||||
</html> |
||||
Loading…
Reference in new issue