All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mats3.spring.ComponentScanExcludingConfigurationForTest Maven / Gradle / Ivy

Go to download

Mats^3 Spring integration ("SpringConfig"), supplying a set of annotations including @EnableMats to enable bean scanning for @MatsMapping and @MatsClassMapping annotations, simplifying Mats^3 use in a Spring context.

There is a newer version: 0.19.22-2024-11-09
Show newest version
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 nameGenerator() default BeanNameGenerator.class; /** * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components. */ @AliasFor(annotation = ComponentScan.class, attribute = "scopeResolver") Class 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; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy