io.mats3.spring.ComponentScanExcludingConfigurationForTest Maven / Gradle / Ivy
Show all versions of mats-spring Show documentation
package io.mats3.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.annotation.AliasFor;
/**
* A simple convenience replacement for @ComponentScan which excludes any configuration classes employing the special
* {@link ConfigurationForTest @ConfigurationForTest} annotation instead of the standard @Configuration
* annotation. This is meant as a solution for a rather common problem that arises when your application
* uses @ComponentScan to find @Services, @Components etc, and have integration tests that reside in "src/test/java",
* but has the same package as the application code (to allow for package access). In the tests, you might want to
* include the entire application's Spring configuration, thus you include the same @ComponentScan. The problem now is
* that since all the test classes are included on the classpath when running a test, and at the same time reside in the
* same package structure, the component scan will pick up all tests' @Configuration classes too. This is absolutely not
* what you wanted - in particular if e.g. two different tests tries to set up two different variants of a mock
* collaborating Mats endpoint (that is, an application-external service that this application communicates with):
* You'll end up trying to take up both variants at the same time, and since they have the same endpointId, Mats will
* refuse this.
*
* If you employ this annotation (@ComponentScanExcludingConfigurationForTest) - or set up the same "excludeFilters" as
* this annotation does - you will not include any configuration classes that are annotated with
* {@link ConfigurationForTest @ConfigurationForTest} instead of the ordinary @Configuration. However, each test will
* still pick up its own inner static @ConfigurationForTest class(es).
*
* A test class employing @ConfigurationForTest would look something like this:
*
*
* @MatsTestProfile // <- If you employ the JmsSpringConnectionFactoryProducer's logic to handle JMS ConnectionFactory
* @RunWith(SpringRunner.class)
* public class IntegrationTestClass {
*
* @ConfigurationForTest
* @Import(ApplicationSpringConfigurationClass_Using_ComponentScanExcludingConfigurationForTest.class)
* public static class InnerStaticConfigurationClassForTest {
* // @Bean definitions
* // @MatsMapping definitions
* }
*
* // @Inject'ed fields..
* // @Test-methods..
* }
*
*
* If you want to do the exclusion on your own, you can use the standard @ComponentScan, making sure you include this
* excludeFilters property:
*
*
* @ComponentScan(excludeFilters = {
* @Filter(type = FilterType.ANNOTATION, value = ConfigurationForTest.class)
* })
*
*
* This class has @AliasFor all properties that Spring 4.x has on @ComponentScan. If new properties arrive later which
* you want to use, you will have to do the exclusion on your own.
*
* NOTICE: An alternative way to achieve the same effect is to annotate the integration test class
* with @ContextConfiguration, pointing to both the application's Spring setup, and also the configuration class
* residing within the test class - but where the test's configuration class is not annotated
* with @Configuration. Such an integration test class would look something like this:
*
*
* @MatsTestProfile // <- If you employ the JmsSpringConnectionFactoryProducer's logic to handle JMS ConnectionFactory
* @RunWith(SpringRunner.class)
* @ContextConfiguration(classes = { ApplicationSpringConfigurationClass_Using_ComponentScan.class,
* InnerStaticConfigurationClassForTest.class })
* public class IntegrationTestClass {
*
* // Notice how this configuration class is NOT annotated with @Configuration, to avoid being picked up by
* // application's @ComponentScan
* public static class InnerStaticConfigurationClassForTest {
* // @Bean definitions
* // @MatsMapping definitions
* }
*
* // @Inject'ed fields..
* // @Test-methods..
* }
*
*
* @see ConfigurationForTest
* @see Stackoverflow answer to question about this problem, where
* the idea for this pair of annotations is ripped from.
*
* @author Endre Stølsvik 2019-08-12 22:33 - http://stolsvik.com/, [email protected]
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
// meta-annotation:
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.ANNOTATION, value = ConfigurationForTest.class)
})
public @interface ComponentScanExcludingConfigurationForTest {
/**
* Alias for {@link #basePackages}.
*
* Allows for more concise annotation declarations if no other attributes are needed — for example,
* {@code @ComponentScan("org.my.pkg")} instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor(annotation = ComponentScan.class, attribute = "value")
String[] value() default {};
/**
* Base packages to scan for annotated components.
*
* {@link #value} is an alias for (and mutually exclusive with) this attribute.
*
* Use {@link #basePackageClasses} for a type-safe alternative to String-based package names.
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned.
*
* Consider creating a special no-op marker class or interface in each package that serves no purpose other than
* being referenced by this attribute.
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class>[] basePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components within the Spring container.
*
* The default value of the {@link BeanNameGenerator} interface itself indicates that the scanner used to process
* this {@code @ComponentScan} annotation should use its inherited bean name generator, e.g. the default
* {@link AnnotationBeanNameGenerator} or any custom instance supplied to the application context at bootstrap time.
*
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
*/
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
*/
@AliasFor(annotation = ComponentScan.class, attribute = "scopeResolver")
Class extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/**
* Indicates whether proxies should be generated for detected components, which may be necessary when using scopes
* in a proxy-style fashion.
*
* The default is defer to the default behavior of the component scanner used to execute the actual scan.
*
* Note that setting this attribute overrides any value set for {@link #scopeResolver}.
*
* @see ClassPathBeanDefinitionScanner#setScopedProxyMode(ScopedProxyMode)
*/
@AliasFor(annotation = ComponentScan.class, attribute = "scopedProxy")
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* Controls the class files eligible for component detection.
*
* Consider use of {@link #includeFilters} and {@link #excludeFilters} for a more flexible approach.
*/
// Notice: "ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN" is package-private
@AliasFor(annotation = ComponentScan.class, attribute = "resourcePattern")
String resourcePattern() default "**/*.class";
/**
* Indicates whether automatic detection of classes annotated with {@code @Component} {@code @Repository},
* {@code @Service}, or {@code @Controller} should be enabled.
*/
@AliasFor(annotation = ComponentScan.class, attribute = "useDefaultFilters")
boolean useDefaultFilters() default true;
/**
* Specifies which types are eligible for component scanning.
*
* Further narrows the set of candidate components from everything in {@link #basePackages} to everything in the
* base packages that matches the given filter or filters.
*
* Note that these filters will be applied in addition to the default filters, if specified. Any type under the
* specified base packages which matches a given filter will be included, even if it does not match the default
* filters (i.e. is not annotated with {@code @Component}).
*
* @see #resourcePattern()
* @see #useDefaultFilters()
*/
@AliasFor(annotation = ComponentScan.class, attribute = "includeFilters")
Filter[] includeFilters() default {};
/**
* Specifies which types are not eligible for component scanning. NOTICE: For this special variant, the default
* is set to exclude @ConfigurationForTest annotated classes
*
* @see #resourcePattern
*/
@AliasFor(annotation = ComponentScan.class, attribute = "excludeFilters")
// NOTICE: Must set the exclusion here as default, as that is what really what is applied - the stuff in the meta-
// annotation at top of @interface definition is just to point out what happens (If this @AliasFor wasn't present,
// it would pick up the one up there, though).
Filter[] excludeFilters() default {
@Filter(type = FilterType.ANNOTATION, value = ConfigurationForTest.class)
};
/**
* Specify whether scanned beans should be registered for lazy initialization.
*
* Default is {@code false}; switch this to {@code true} when desired.
*
* @since 4.1
*/
@AliasFor(annotation = ComponentScan.class, attribute = "lazyInit")
boolean lazyInit() default false;
}