Introduce FeatureSpecification support

Introduce FeatureSpecification interface and implementations

    FeatureSpecification objects decouple the configuration of
    spring container features from the concern of parsing XML
    namespaces, allowing for reuse in code-based configuration
    (see @Feature* annotations below).

    * ComponentScanSpec
    * TxAnnotationDriven
    * MvcAnnotationDriven
    * MvcDefaultServletHandler
    * MvcResources
    * MvcViewControllers

Refactor associated BeanDefinitionParsers to delegate to new impls above

    The following BeanDefinitionParser implementations now deal only
    with the concern of XML parsing.  Validation is handled by their
    corresponding FeatureSpecification object.  Bean definition creation
    and registration is handled by their corresponding
    FeatureSpecificationExecutor type.

    * ComponentScanBeanDefinitionParser
    * AnnotationDrivenBeanDefinitionParser (tx)
    * AnnotationDrivenBeanDefinitionParser (mvc)
    * DefaultServletHandlerBeanDefinitionParser
    * ResourcesBeanDefinitionParser
    * ViewControllerBeanDefinitionParser

Update AopNamespaceUtils to decouple from XML (DOM API)

    Methods necessary for executing TxAnnotationDriven specification
    (and eventually, the AspectJAutoProxy specification) have been
    added that accept boolean arguments for whether to proxy
    target classes and whether to expose the proxy via threadlocal.

    Methods that accepted and introspected DOM Element objects still
    exist but have been deprecated.

Introduce @FeatureConfiguration classes and @Feature methods

    Allow for creation and configuration of FeatureSpecification objects
    at the user level.  A companion for @Configuration classes allowing
    for completely code-driven configuration of the Spring container.

    See changes in ConfigurationClassPostProcessor for implementation
    details.

    See Feature*Tests for usage examples.

    FeatureTestSuite in .integration-tests is a JUnit test suite designed
    to aggregate all BDP and Feature* related tests for a convenient way
    to confirm that Feature-related changes don't break anything.
    Uncomment this test and execute from Eclipse / IDEA. Due to classpath
    issues, this cannot be compiled by Ant/Ivy at the command line.

Introduce @FeatureAnnotation meta-annotation and @ComponentScan impl

    @FeatureAnnotation provides an alternate mechanism for creating
    and executing FeatureSpecification objects.  See @ComponentScan
    and its corresponding ComponentScanAnnotationParser implementation
    for details.  See ComponentScanAnnotationIntegrationTests for usage
    examples

Introduce Default[Formatting]ConversionService implementations

    Allows for convenient instantiation of ConversionService objects
    containing defaults appropriate for most environments.  Replaces
    similar support originally in ConversionServiceFactory (which is now
    deprecated). This change was justified by the need to avoid use
    of FactoryBeans in @Configuration classes (such as
    FormattingConversionServiceFactoryBean). It is strongly preferred
    that users simply instantiate and configure the objects that underlie
    our FactoryBeans. In the case of the ConversionService types, the
    easiest way to do this is to create Default* subtypes. This also
    follows convention with the rest of the framework.

Minor updates to util classes

    All in service of changes above. See diffs for self-explanatory
    details.

    * BeanUtils
    * ObjectUtils
    * ReflectionUtils
This commit is contained in:
Chris Beams
2011-02-08 14:42:33 +00:00
parent b04987ccc3
commit b4fea47d5c
127 changed files with 7397 additions and 1132 deletions

View File

@@ -65,6 +65,7 @@ public class FooServiceImpl implements FooService {
private boolean initCalled = false;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
if (this.initCalled) {

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2002-2010 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 example.scannable;
/**
* Marker class for example.scannable package.
*
* @see org.springframework.context.annotation.ComponentScan#basePackageClasses()
*/
public class _package { }

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2002-2011 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 example.scannable_scoped;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.stereotype.Component;
@Component
@MyScope(BeanDefinition.SCOPE_PROTOTYPE)
public class CustomScopeAnnotationBean {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2002-2011 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 example.scannable_scoped;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ScopedProxyMode;
public @interface MyScope {
String value() default BeanDefinition.SCOPE_SINGLETON;
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests that @FeatureConfiguration classes may implement Aware interfaces,
* such as BeanFactoryAware. This is not generally recommended but occasionally
* useful, particularly in testing.
*
* @author Chris Beams
* @since 3.1
*/
public class BeanFactoryAwareFeatureConfigurationTests {
@Test
public void test() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
FeatureConfig fc = ctx.getBean(FeatureConfig.class);
assertThat(fc.featureMethodWasCalled, is(true));
assertThat(fc.gotBeanFactoryInTime, is(true));
assertThat(fc.beanFactory, is(ctx.getBeanFactory()));
}
@FeatureConfiguration
static class FeatureConfig implements BeanFactoryAware {
ConfigurableListableBeanFactory beanFactory;
boolean featureMethodWasCalled = false;
boolean gotBeanFactoryInTime = false;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
this.gotBeanFactoryInTime = (this.beanFactory != null);
return new StubSpecification();
}
}
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
import java.io.IOException;
import java.util.HashSet;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.SimpleMapScope;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScanParserTests.CustomAnnotationAutowiredBean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.SerializationTestUtils;
import example.scannable.FooService;
import example.scannable.ScopedProxyTestBean;
import example.scannable_scoped.CustomScopeAnnotationBean;
import example.scannable_scoped.MyScope;
/**
* Integration tests for processing ComponentScan-annotated Configuration
* classes.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanAnnotationIntegrationTests {
@Test
public void controlScan() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan(example.scannable._package.class.getPackage().getName());
ctx.refresh();
assertThat("control scan for example.scannable package failed to register FooServiceImpl bean",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaContextRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanAnnotatedConfig.class);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaContextRegistration_WithValueAttribute() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanAnnotatedConfig_WithValueAttribute.class);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig_WithValueAttribute.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig_WithValueAttribute"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaBeanRegistration() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("componentScanAnnotatedConfig",
genericBeanDefinition(ComponentScanAnnotatedConfig.class).getBeanDefinition());
bf.registerBeanDefinition("configurationClassPostProcessor",
genericBeanDefinition(ConfigurationClassPostProcessor.class).getBeanDefinition());
GenericApplicationContext ctx = new GenericApplicationContext(bf);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered " +
"as bean definition did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void invalidComponentScanDeclaration_noPackagesSpecified() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithNoPackagesConfig.class);
try {
ctx.refresh();
fail("Expected exception when parsing @ComponentScan definition that declares no packages");
} catch (BeanDefinitionParsingException ex) {
assertThat(ex.getMessage(), containsString("At least one base package must be specified"));
}
}
@Test
public void withCustomBeanNameGenerator() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithBeanNameGenenerator.class);
ctx.refresh();
assertThat(ctx.containsBean("custom_fooServiceImpl"), is(true));
assertThat(ctx.containsBean("fooServiceImpl"), is(false));
}
@Test
public void withScopeResolver() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithScopeResolver.class);
// custom scope annotation makes the bean prototype scoped. subsequent calls
// to getBean should return distinct instances.
assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
}
@Test
public void withCustomTypeFilter() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithCustomTypeFilter.class);
CustomAnnotationAutowiredBean testBean = ctx.getBean(CustomAnnotationAutowiredBean.class);
assertThat(testBean.getDependency(), notNullValue());
}
@Test
public void withScopedProxy() throws IOException, ClassNotFoundException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithScopedProxy.class);
ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
ctx.refresh();
// should cast to the interface
FooService bean = (FooService) ctx.getBean("scopedProxyTestBean");
// should be dynamic proxy
assertThat(AopUtils.isJdkDynamicProxy(bean), is(true));
// test serializability
assertThat(bean.foo(1), equalTo("bar"));
FooService deserialized = (FooService) SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(deserialized, notNullValue());
assertThat(deserialized.foo(1), equalTo("bar"));
}
@Test
public void withBasePackagesAndValueAlias() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithBasePackagesAndValueAlias.class);
ctx.refresh();
assertThat(ctx.containsBean("fooServiceImpl"), is(true));
}
}
@Configuration
@ComponentScan(basePackageClasses=example.scannable._package.class)
class ComponentScanAnnotatedConfig {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@Configuration
@ComponentScan("example.scannable")
class ComponentScanAnnotatedConfig_WithValueAttribute {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@Configuration
@ComponentScan
class ComponentScanWithNoPackagesConfig { }
@Configuration
@ComponentScan(basePackages="example.scannable", nameGenerator=MyBeanNameGenerator.class)
class ComponentScanWithBeanNameGenenerator { }
class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return "custom_" + super.generateBeanName(definition, registry);
}
}
@Configuration
@ComponentScan(basePackages="example.scannable_scoped", scopeResolver=MyScopeMetadataResolver.class)
class ComponentScanWithScopeResolver { }
class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
MyScopeMetadataResolver() {
this.scopeAnnotationType = MyScope.class;
}
}
@Configuration
@ComponentScan(basePackages="org.springframework.context.annotation",
useDefaultFilters=false,
includeFilters=@Filter(type=FilterType.CUSTOM, value=ComponentScanParserTests.CustomTypeFilter.class),
// exclude this class from scanning since it's in the scanned package
excludeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ComponentScanWithCustomTypeFilter.class))
class ComponentScanWithCustomTypeFilter {
@Bean
@SuppressWarnings({ "rawtypes", "serial", "unchecked" })
public CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer cac = new CustomAutowireConfigurer();
cac.setCustomQualifierTypes(new HashSet() {{ add(ComponentScanParserTests.CustomAnnotation.class); }});
return cac;
}
public ComponentScanParserTests.CustomAnnotationAutowiredBean testBean() {
return new ComponentScanParserTests.CustomAnnotationAutowiredBean();
}
}
@Configuration
@ComponentScan(basePackages="example.scannable",
scopedProxy=ScopedProxyMode.INTERFACES,
useDefaultFilters=false,
includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ScopedProxyTestBean.class))
class ComponentScanWithScopedProxy { }
@Configuration
@ComponentScan(
value="example.scannable",
basePackages="example.scannable",
basePackageClasses=example.scannable._package.class)
class ComponentScanWithBasePackagesAndValueAlias { }

View File

@@ -18,8 +18,7 @@ package org.springframework.context.annotation;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan.ExcludeFilter;
import org.springframework.context.annotation.ComponentScan.IncludeFilter;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.type.filter.TypeFilter;
/**
@@ -27,11 +26,13 @@ import org.springframework.core.type.filter.TypeFilter;
*
* @author Chris Beams
* @since 3.1
* @see ComponentScanAnnotationIntegrationTests
*/
public class ComponentScanAnnotationTests {
@Test
public void test() {
public void noop() {
// no-op; the @ComponentScan-annotated MyConfig class below simply excercises
// available attributes of the annotation.
}
}
@@ -39,22 +40,22 @@ public class ComponentScanAnnotationTests {
@Configuration
@ComponentScan(
packageOf={TestBean.class},
basePackageClasses={TestBean.class},
nameGenerator = DefaultBeanNameGenerator.class,
scopedProxy = ScopedProxyMode.NO,
scopeResolver = AnnotationScopeMetadataResolver.class,
useDefaultFilters = false,
resourcePattern = "**/*custom.class",
includeFilters = {
@IncludeFilter(type = FilterType.ANNOTATION, value = MyAnnotation.class)
@Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class)
},
excludeFilters = {
@ExcludeFilter(type = FilterType.CUSTOM, value = TypeFilter.class)
@Filter(type = FilterType.CUSTOM, value = TypeFilter.class)
}
)
class MyConfig {
}
@ComponentScan(packageOf=example.scannable.NamedComponent.class)
@ComponentScan(basePackageClasses=example.scannable.NamedComponent.class)
class SimpleConfig { }

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.config.ExecutorContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.mock.env.MockEnvironment;
/**
* Unit tests for {@link ComponentScanExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanExecutorTests {
private ComponentScanExecutor executor;
private ExecutorContext executorContext;
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
this.bf = new DefaultListableBeanFactory();
this.executor = new ComponentScanExecutor();
this.executorContext = new ExecutorContext();
this.executorContext.setRegistry(bf);
this.executorContext.setResourceLoader(new DefaultResourceLoader());
this.executorContext.setEnvironment(new MockEnvironment());
this.executorContext.setRegistrar(new SimpleComponentRegistrar(bf));
this.executorContext.setProblemReporter(new FailFastProblemReporter());
}
@Test
public void validSpec() {
this.executor.execute(new ComponentScanSpec("example.scannable"), this.executorContext);
assertThat(bf.containsBean("fooServiceImpl"), is(true));
}
@Test(expected=BeanDefinitionParsingException.class)
public void invalidSpec() {
// ff problem reporter should throw due to no packages specified
this.executor.execute(new ComponentScanSpec(), this.executorContext);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class ComponentScanFeatureTests {
@Test
public void viaContextRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanFeatureConfig.class);
ctx.register(ComponentScanFeatureConfig.Features.class);
ctx.refresh();
ctx.getBean(ComponentScanFeatureConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanFeatureConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
}
@Configuration
//@Import(ComponentScanFeatureConfig.Features.class)
class ComponentScanFeatureConfig {
@FeatureConfiguration
static class Features {
@Feature
public ComponentScanSpec componentScan() {
return new ComponentScanSpec(example.scannable._package.class);
}
}
@Bean
public TestBean testBean() {
return new TestBean();
}
}

View File

@@ -0,0 +1,411 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.util.StringUtils.arrayToCommaDelimitedString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Unit tests for {@link ComponentScanSpec}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanSpecTests {
private CollatingProblemReporter problemReporter;
private ClassLoader classLoader;
@Before
public void setUp() {
problemReporter = new CollatingProblemReporter();
classLoader = ClassUtils.getDefaultClassLoader();
}
@Test
public void includeAnnotationConfig() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.includeAnnotationConfig(), nullValue());
spec.includeAnnotationConfig(true);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig(false);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("trUE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig("falSE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((String)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((Boolean)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), nullValue());
}
@Test
public void resourcePattern() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.resourcePattern(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("**/Foo*.class");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern(null);
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void useDefaultFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((Boolean)null);
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(true);
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(false);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("trUE");
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("falSE");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((String)null);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void includeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.includeFilters(
new AnnotationTypeFilter(MyAnnotation.class),
new AssignableTypeFilter(Object.class));
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(2));
}
@Test
public void stringIncludeExcludeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("annotation", MyAnnotation.class.getName(), classLoader);
spec.addExcludeFilter("assignable", Object.class.getName(), classLoader);
spec.addExcludeFilter("annotation", Override.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(1));
assertThat(spec.excludeFilters().length, is(2));
}
@Test
public void bogusStringIncludeFilter() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("bogus-type", "bogus-expr", classLoader);
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.includeFilters().length, is(1));
try {
spec.includeFilters()[0].match(null, null);
fail("expected placholder TypeFilter to throw exception");
} catch (UnsupportedOperationException ex) {
// expected
}
}
@Test
public void exerciseFilterTypes() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("aspectj", "*..Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("regex", ".*Foo", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", StubTypeFilter.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", "org.NonExistentTypeFilter", classLoader);
assertThat(spec.validate(problemReporter), is(false));
spec.addIncludeFilter("custom", NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void missingBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec();
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withBasePackageViaAdderMethod() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("org.p1");
spec.addBasePackage("org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaStringConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaClassConstructor() {
ComponentScanSpec spec = new ComponentScanSpec(java.lang.Object.class, java.io.Closeable.class);
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "java.lang", "java.io");
}
@Test
public void forDelimitedPackages() {
ComponentScanSpec spec = ComponentScanSpec.forDelimitedPackages("pkg.one,pkg.two");
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.one"));
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.two"));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void withSomeEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "", "org.p3");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p3");
}
@Test
public void withAllEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("", "", "");
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withInstanceBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.beanNameGenerator(), nullValue());
BeanNameGenerator bng = new DefaultBeanNameGenerator();
spec.beanNameGenerator(bng);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), is(bng));
}
@Test
public void withStringBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.beanNameGenerator(DefaultBeanNameGenerator.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), instanceOf(DefaultBeanNameGenerator.class));
}
@Test
public void withInstanceScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.scopeMetadataResolver(), nullValue());
ScopeMetadataResolver smr = new AnnotationScopeMetadataResolver();
spec.scopeMetadataResolver(smr);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), is(smr));
}
@Test
public void withStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(AnnotationScopeMetadataResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), instanceOf(AnnotationScopeMetadataResolver.class));
}
@Test
public void withNonAssignableStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(Object.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonExistentStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver("org.Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonNoArgStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withStringScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopedProxyMode("targetCLASS");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.TARGET_CLASS));
spec.scopedProxyMode("interFACES");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.INTERFACES));
spec.scopedProxyMode("nO");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.NO));
spec.scopedProxyMode("bogus");
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.scopedProxyMode(), nullValue());
}
@Test
public void withScopeMetadataResolverAndScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(new AnnotationScopeMetadataResolver());
assertThat(spec.validate(problemReporter), is(true));
spec.scopedProxyMode(ScopedProxyMode.INTERFACES);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void addBasePackage() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(1));
}
@Test
public void addBasePackageWithConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void addExcludeFilterString() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addExcludeFilter("annotation", MyAnnotation.class.getName(), ClassUtils.getDefaultClassLoader());
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.excludeFilters().length, is(1));
assertThat(spec.excludeFilters()[0], instanceOf(AnnotationTypeFilter.class));
}
@Test(expected=BeanDefinitionParsingException.class)
public void withFailFastProblemReporter() {
new ComponentScanSpec().validate(new FailFastProblemReporter());
}
private <T> void assertExactContents(T[] actual, T... expected) {
if (actual.length >= expected.length) {
for (int i = 0; i < expected.length; i++) {
assertThat(
String.format("element number %d in actual is incorrect. actual: [%s], expected: [%s]",
i, arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual[i], equalTo(expected[i]));
}
}
assertThat(String.format("actual contains incorrect number of arguments. actual: [%s], expected: [%s]",
arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual.length, equalTo(expected.length));
}
private static class CollatingProblemReporter implements ProblemReporter {
private List<Problem> errors = new ArrayList<Problem>();
private List<Problem> warnings = new ArrayList<Problem>();
public void fatal(Problem problem) {
throw new BeanDefinitionParsingException(problem);
}
public void error(Problem problem) {
this.errors.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getErrors() {
return this.errors.toArray(new Problem[this.errors.size()]);
}
public void warning(Problem problem) {
System.out.println(problem);
this.warnings.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getWarnings() {
return this.warnings.toArray(new Problem[this.warnings.size()]);
}
}
private static class NonNoArgResolver implements ScopeMetadataResolver {
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
throw new UnsupportedOperationException();
}
}
private static class StubTypeFilter implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,316 @@
/*
* Copyright 2002-2011 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;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.FINAL_CLASS_ERROR_MESSAGE;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Unit tests for {@link EarlyBeanReferenceProxyCreator}, ensuring that
* {@link EarlyBeanReferenceProxy} objects behave properly.
*
* @author Chris Beams
* @since 3.1
*/
public class EarlyBeanReferenceProxyCreatorTests {
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
bf = new DefaultListableBeanFactory();
}
@Test
public void proxyToStringAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.toString(), equalTo("EarlyBeanReferenceProxy for bean of type TestBean"));
}
@Test(expected=NoSuchBeanDefinitionException.class)
public void proxyThrowsNoSuchBeanDefinitionExceptionWhenDelegatingMethodCallToNonExistentBean() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
proxy.getName();
}
@Test
public void proxyHashCodeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.hashCode(), equalTo(System.identityHashCode(proxy)));
}
@Test
public void proxyEqualsAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.equals(new Object()), is(false));
assertThat(proxy.equals(proxy), is(true));
TestBean proxy2 = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy, not(sameInstance(proxy2)));
assertThat(proxy.equals(proxy2), is(false));
assertThat(proxy2.equals(proxy), is(false));
assertThat(proxy2.equals(proxy2), is(true));
}
@Test
public void proxyFinalizeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
BeanWithFinalizer proxy = (BeanWithFinalizer) pc.createProxy(descriptorFor(BeanWithFinalizer.class));
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
BeanWithFinalizer.class.getDeclaredMethod("finalize").invoke(proxy);
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
}
@Test
public void proxyMethodsDelegateToTargetBeanCausingSingletonRegistrationIfNecessary() throws Exception {
bf.registerBeanDefinition("testBean",
BeanDefinitionBuilder.rootBeanDefinition(TestBean.class)
.addPropertyValue("name", "testBeanName").getBeanDefinition());
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(bf.containsBeanDefinition("testBean"), is(true));
assertThat(bf.containsSingleton("testBean"), is(false));
assertThat(proxy.getName(), equalTo("testBeanName"));
assertThat(bf.containsSingleton("testBean"), is(true));
}
@Test
public void beanAnnotatedMethodsReturnEarlyProxyAsWell() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxy(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(bf.containsBeanDefinition("componentWithInterfaceBeanMethod"), is(true));
assertThat("calling a @Bean method on an EarlyBeanReferenceProxy object " +
"should not cause its instantation/registration",
bf.containsSingleton("componentWithInterfaceBeanMethod"), is(false));
Object obj = proxy.normalInstanceMethod();
assertThat(bf.containsSingleton("componentWithInterfaceBeanMethod"), is(true));
assertThat(obj, not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void proxiesReturnedFromBeanAnnotatedMethodsDereferenceAndDelegateToTheirTargetBean() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
RootBeanDefinition beanMethodBeanDef = new RootBeanDefinition();
beanMethodBeanDef.setFactoryBeanName("componentWithConcreteBeanMethod");
beanMethodBeanDef.setFactoryMethodName("aBeanMethod");
bf.registerBeanDefinition("aBeanMethod", beanMethodBeanDef);
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxy(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean.getName(), equalTo("concrete"));
}
@Test
public void interfaceBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ITestBean proxy = (ITestBean) pc.createProxy(descriptorFor(ITestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
proxy.getClass().getSuperclass(), Object.class);
}
@Test
public void concreteBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
proxy.getClass().getSuperclass(), TestBean.class);
}
@Test
public void beanAnnotatedMethodsWithInterfaceReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxy(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
bean.getClass().getSuperclass(), Object.class);
}
@Test
public void beanAnnotatedMethodsWithConcreteReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxy(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
bean.getClass().getSuperclass(), TestBean.class);
}
@Test
public void attemptToProxyClassMissingNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(BeanMissingNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanMissingNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyClassWithPrivateNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(BeanWithPrivateNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanWithPrivateNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyFinalClassFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(FinalBean.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(FINAL_CLASS_ERROR_MESSAGE, FinalBean.class.getName())));
}
}
private DependencyDescriptor descriptorFor(Class<?> paramType) throws Exception {
@SuppressWarnings("unused")
class C {
void m(ITestBean p) { }
void m(TestBean p) { }
void m(BeanMissingNoArgConstructor p) { }
void m(BeanWithPrivateNoArgConstructor p) { }
void m(FinalBean p) { }
void m(BeanWithFinalizer p) { }
void m(ComponentWithConcreteBeanMethod p) { }
void m(ComponentWithInterfaceBeanMethod p) { }
}
Method targetMethod = C.class.getDeclaredMethod("m", new Class<?>[] { paramType });
MethodParameter mp = new MethodParameter(targetMethod, 0);
DependencyDescriptor dd = new DependencyDescriptor(mp, true, false);
return dd;
}
static class BeanMissingNoArgConstructor {
BeanMissingNoArgConstructor(Object o) { }
}
static class BeanWithPrivateNoArgConstructor {
private BeanWithPrivateNoArgConstructor() { }
}
static final class FinalBean {
}
static class BeanWithFinalizer {
static Boolean finalizerWasCalled = false;
@Override
protected void finalize() throws Throwable {
finalizerWasCalled = true;
}
}
static class ComponentWithConcreteBeanMethod {
@Bean
public TestBean aBeanMethod() {
return new TestBean("concrete");
}
public Object normalInstanceMethod() {
return new Object();
}
}
static class ComponentWithInterfaceBeanMethod {
@Bean
public ITestBean aBeanMethod() {
return new TestBean("interface");
}
public Object normalInstanceMethod() {
return new Object();
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2011 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;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
public class FeatureConfigurationClassTests {
@Test(expected=FeatureMethodExecutionException.class)
public void featureConfigurationClassesMustNotContainBeanAnnotatedMethods() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfigWithBeanAnnotatedMethod.class);
ctx.refresh();
}
}
@FeatureConfiguration
class FeatureConfigWithBeanAnnotatedMethod {
/**
* This is illegal use. @FeatureConfiguration classes cannot have @Bean methods.
*/
@Bean
public TestBean testBean() {
return new TestBean();
}
/**
* This will never get called. An exception will first be raised regarding the illegal @Bean method above.
*/
@Feature
public FeatureSpecification feature() {
return new StubSpecification();
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean class="test.beans.TestBean" c:name="beanFromXml"/>
</beans>

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.TestBean;
/**
* Tests proving that @FeatureConfiguration classes may be use @ImportResource
* and then parameter autowire beans declared in the imported resource(s).
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportResourceTests {
@Test
public void importResourceFromFeatureConfiguration() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("beanFromXml"));
// and just quickly prove that the target of the bean proxied for the Feature method
// is indeed the same singleton instance as the one we just pulled from the container
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
TestBean proxyBean = ifc.testBean;
assertThat(proxyBean, instanceOf(EarlyBeanReferenceProxy.class));
assertNotSame(proxyBean, testBean);
assertSame(((EarlyBeanReferenceProxy)proxyBean).dereferenceTargetBean(), testBean);
}
@FeatureConfiguration
@ImportResource("org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml")
static class ImportingFeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests proving that @Configuration classes may @Import @FeatureConfiguration
* classes, and vice versa.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportTests {
@Test
public void importFeatureConfigurationFromConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingConfig.class);
ImportedFeatureConfig ifc = ctx.getBean(ImportedFeatureConfig.class);
assertThat(
"@FeatureConfiguration class was imported and registered " +
"as a bean but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
}
@Test
public void importConfigurationFromFeatureConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
ImportedConfig ic = ctx.getBean(ImportedConfig.class);
assertThat(
"@FeatureConfiguration class was registered directly against " +
"the container but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
assertThat(
"@Configuration class was @Imported but its @Bean method" +
"was never registered / called",
ic.beanMethodWasCalled, is(true));
}
@Configuration
@Import(ImportedFeatureConfig.class)
static class ImportingConfig {
}
@FeatureConfiguration
static class ImportedFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
@Configuration
static class ImportedConfig {
boolean beanMethodWasCalled = true;
@Bean
public TestBean testBean() {
this.beanMethodWasCalled = true;
return new TestBean();
}
}
@FeatureConfiguration
@Import(ImportedConfig.class)
static class ImportingFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference the product of @Bean methods.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class, Config.class);
ctx.refresh();
TestBean registeredBean = ctx.getBean("testBean", TestBean.class);
TestBean proxiedBean = ctx.getBean(FeatureConfig.class).testBean;
assertThat(registeredBean, not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(proxiedBean, notNullValue());
assertThat(proxiedBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(proxiedBean.getSpouse(), is(registeredBean.getSpouse()));
}
@FeatureConfiguration
static class FeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
@Configuration
static class Config {
@Bean
public ITestBean testBean() {
return new TestBean(new TestBean("mySpouse"));
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests that @Bean methods referenced from within @Feature methods
* get proxied early to avoid premature instantiation of actual
* bean instances.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodEarlyBeanProxyTests {
@Test
public void earlyProxyCreationAndBeanRegistrationLifecycle() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
//
// see additional assertions in FeatureConfig#feature()
//
// sanity check that all the bean definitions we expecting are present
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyPassthroughBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerPassthroughBean"), is(true));
// the lazy helper bean had methods invoked during feature method execution. it should be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyHelperBean"), is(true));
// the eager helper bean had methods invoked but should be registered in any case is it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerHelperBean"), is(true));
// the lazy passthrough bean was referenced in the feature method, but never invoked. it should not be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyPassthroughBean"), is(false));
// the eager passthrough bean should be registered in any case as it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerPassthroughBean"), is(true));
// now actually fetch all the beans. none should be proxies
assertThat(ctx.getBean("lazyHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("lazyPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void earlyProxyBeansMayBeInterfaceBasedOrConcrete() {
new AnnotationConfigApplicationContext(FeatureConfigReferencingNonInterfaceBeans.class);
}
}
@FeatureConfiguration
@Import(TestBeanConfig.class)
class FeatureConfig implements BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory)beanFactory;
}
@Feature
public StubSpecification feature(TestBeanConfig beans) {
assertThat(
"The @Configuration class instance itself should be an early-ref proxy",
beans, instanceOf(EarlyBeanReferenceProxy.class));
// invocation of @Bean methods within @Feature methods should return proxies
ITestBean lazyHelperBean = beans.lazyHelperBean();
ITestBean eagerHelperBean = beans.eagerHelperBean();
ITestBean lazyPassthroughBean = beans.lazyPassthroughBean();
ITestBean eagerPassthroughBean = beans.eagerPassthroughBean();
assertThat(lazyHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(lazyPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
// but at this point, the proxy instances should not have
// been registered as singletons with the container.
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
// invoking a method on the proxy should cause it to pass through
// to the container, instantiate the actual bean in question and
// register that actual underlying instance as a singleton.
assertThat(lazyHelperBean.getName(), equalTo("lazyHelper"));
assertThat(eagerHelperBean.getName(), equalTo("eagerHelper"));
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(true));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(true));
// since no methods were called on the passthrough beans, they should remain
// uncreated / unregistered.
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
return new StubSpecification();
}
}
@Configuration
class TestBeanConfig {
@Lazy @Bean
public ITestBean lazyHelperBean() {
return new TestBean("lazyHelper");
}
@Bean
public ITestBean eagerHelperBean() {
return new TestBean("eagerHelper");
}
@Lazy @Bean
public ITestBean lazyPassthroughBean() {
return new TestBean("lazyPassthrough");
}
@Bean
public ITestBean eagerPassthroughBean() {
return new TestBean("eagerPassthrough");
}
}
@FeatureConfiguration
@Import(NonInterfaceBeans.class)
class FeatureConfigReferencingNonInterfaceBeans {
@Feature
public FeatureSpecification feature1(NonInterfaceBeans beans) throws Throwable {
beans.testBean();
return new StubSpecification();
}
@Feature
public FeatureSpecification feature2(TestBean testBean) throws Throwable {
return new StubSpecification();
}
}
@Configuration
class NonInterfaceBeans {
@Bean
public TestBean testBean() {
return new TestBean("invalid");
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference beans using @Qualifier
* as a parameter annotation.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodQualifiedBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Features.class, TestBeans.class);
ctx.refresh();
}
@FeatureConfiguration
static class Features {
@Feature
public FeatureSpecification f(@Qualifier("testBean1") ITestBean testBean) {
assertThat(testBean.getName(), equalTo("one"));
return new StubSpecification();
}
}
@Configuration
static class TestBeans {
@Bean
public ITestBean testBean1() {
return new TestBean("one");
}
@Bean
public ITestBean testBean2() {
return new TestBean("two");
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2002-2011 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;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.util.Assert;
/**
* Simple tests to ensure that @Feature methods are invoked and that the
* resulting returned {@link FeatureSpecification} object is delegated to
* the correct {@link FeatureSpecificationExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class SimpleFeatureMethodProcessingTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class);
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(false));
ctx.refresh();
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(true));
}
@FeatureConfiguration
static class FeatureConfig {
@Feature
public FeatureSpecification f() {
return new StubSpecification(MySpecificationExecutor.class);
}
}
static class MySpecificationExecutor implements FeatureSpecificationExecutor {
static boolean executeMethodWasCalled = false;
public void execute(FeatureSpecification spec, ExecutorContext executorContext) {
Assert.state(executeMethodWasCalled == false);
executeMethodWasCalled = true;
}
}
}

View File

@@ -16,11 +16,13 @@
package org.springframework.context.annotation.configuration;
import static org.junit.Assert.*;
import org.junit.Test;
import test.beans.ITestBean;
import test.beans.TestBean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Required;
@@ -43,7 +45,9 @@ import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.PriorityOrdered;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and error

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2011 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* A configuration class that registers a placeholder configurer @Bean method
* cannot also have @Value fields. Logically, the config class must be instantiated
* in order to invoke the placeholder configurer bean method, and it is a
* chicken-and-egg problem to process the @Value field.
*
* Therefore, placeholder configurers should be put in separate configuration classes
* as has been done in the test below. Simply said, placeholder configurer @Bean methods
* and @Value fields in the same configuration class are mutually exclusive.
*
* @author Chris Beams
*/
public class ConfigurationClassWithPlaceholderConfigurerBeanTests {
/**
* Intentionally ignored test proving that a property placeholder bean
* cannot be declared in the same configuration class that has a @Value
* field in need of placeholder replacement. It's an obvious chicken-and-egg issue.
* The solution is to do as {@link #valueFieldsAreProcessedWhenPlaceholderConfigurerIsSegregated()}
* does and segragate the two bean definitions across configuration classes.
*/
@Ignore @Test
public void valueFieldsAreNotProcessedWhenPlaceholderConfigurerIsIntegrated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithValueFieldAndPlaceholderConfigurer.class);
System.setProperty("test.name", "foo");
ctx.refresh();
System.clearProperty("test.name");
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), nullValue());
}
@Test
public void valueFieldsAreProcessedWhenPlaceholderConfigurerIsSegregated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithValueField.class);
ctx.register(ConfigWithPlaceholderConfigurer.class);
System.setProperty("test.name", "foo");
ctx.refresh();
System.clearProperty("test.name");
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("foo"));
}
}
@Configuration
class ConfigWithValueField {
@Value("${test.name}")
private String name;
@Bean
public ITestBean testBean() {
return new TestBean(this.name);
}
}
@Configuration
class ConfigWithPlaceholderConfigurer {
@Bean
public PropertySourcesPlaceholderConfigurer ppc() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class ConfigWithValueFieldAndPlaceholderConfigurer {
@Value("${test.name}")
private String name;
@Bean
public ITestBean testBean() {
return new TestBean(this.name);
}
@Bean
public PropertySourcesPlaceholderConfigurer ppc() {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.context.annotation.configuration.ColourHolder"/>
</beans>

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2011 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.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.context.annotation.FeatureMethodEarlyBeanProxyTests;
/**
* Test suite that groups all tests related to @Feature method lifecycle issues.
*
* @author Chris Beams
*/
@RunWith(Suite.class)
@SuiteClasses({
FeatureMethodEarlyBeanProxyTests.class,
ConfigurationClassWithPlaceholderConfigurerBeanTests.class,
})
public class FeatureMethodLifecycleIssueTestSuite {
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2011 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.springframework.beans.factory.parsing.SimpleProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
public class StubSpecification extends AbstractFeatureSpecification {
public StubSpecification() {
this(StubSpecificationExecutor.class);
}
public StubSpecification(Class<? extends FeatureSpecificationExecutor> excecutorType) {
super(excecutorType);
}
@Override
protected void doValidate(SimpleProblemCollector reporter) {
}
}
class StubSpecificationExecutor implements FeatureSpecificationExecutor {
public void execute(FeatureSpecification spec, ExecutorContext executorContext) {
}
}

View File

@@ -36,7 +36,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionService;
@@ -54,7 +54,7 @@ public class JodaTimeFormattingTests {
@Before
public void setUp() {
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.registerFormatters(conversionService);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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,7 +27,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;
import org.springframework.format.support.FormattingConversionService;
@@ -46,7 +46,7 @@ public class NumberFormattingTests {
@Before
public void setUp() {
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.setEmbeddedValueResolver(new StringValueResolver() {
public String resolveStringValue(String strVal) {
if ("${pattern}".equals(strVal)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -16,6 +16,9 @@
package org.springframework.format.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@@ -27,10 +30,8 @@ import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
@@ -40,7 +41,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.datetime.joda.DateTimeParser;
import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory;
import org.springframework.format.datetime.joda.ReadablePartialPrinter;
@@ -57,7 +58,7 @@ public class FormattingConversionServiceTests {
@Before
public void setUp() {
formattingService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(formattingService);
DefaultConversionService.addDefaultConverters(formattingService);
LocaleContextHolder.setLocale(Locale.US);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -30,8 +30,6 @@ import java.util.TreeSet;
import junit.framework.TestCase;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.BeanWithObjectProperty;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean;
@@ -46,7 +44,7 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.number.NumberFormatter;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.StringUtils;
@@ -319,7 +317,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -350,7 +348,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -372,7 +370,7 @@ public class DataBinderTests extends TestCase {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -393,7 +391,7 @@ public class DataBinderTests extends TestCase {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -415,7 +413,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
binder.initDirectFieldAccess();
@@ -448,7 +446,7 @@ public class DataBinderTests extends TestCase {
DataBinder binder = new DataBinder(tb);
binder.initDirectFieldAccess();
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -553,7 +551,7 @@ public class DataBinderTests extends TestCase {
assertEquals(1, disallowedFields.length);
assertEquals("age", disallowedFields[0]);
Map m = binder.getBindingResult().getModel();
Map<?,?> m = binder.getBindingResult().getModel();
assertTrue("There is one element in map", m.size() == 2);
TestBean tb = (TestBean) m.get("person");
assertTrue("Same object", tb.equals(rod));
@@ -1415,6 +1413,7 @@ public class DataBinderTests extends TestCase {
assertEquals("badName", nameError.getCode());
}
@SuppressWarnings("unchecked")
public void testBindingWithResortedList() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");