web-dev-qa-db-ja.com

@ComponentScanでメタアノテーションされたSpringカスタム@Enableアノテーション

Springフレームワーク用に独自の_@Enable_アノテーションを記述しようとしています。これは次のように使用する必要があります。

_package com.example.package.app;

@SpringBootApplication
@com.example.annotations.EnableCustom("com.example.package.custom")
public class MyApplication {}
_

私は カスタムアノテーションを使用したコンポーネントスキャン に従いましたが、これにはいくつかの制限があります。

  1. 基本パッケージプロパティを動的にすることはできません。つまり、_"com.example.package.base"_を渡すことはできませんが、構成でパッケージを事前定義する必要があります。

    _@AliasFor_を確認しましたが、機能しません。

  2. 基本パッケージを省略した場合、スキャンは、注釈付きクラスのパッケージからではなく、注釈の定義パッケージから開始されます。上記の例では、_com.example.annotations_のクラスのBeanのみをスキャンして作成し、_com.example.package.*_のBeanは作成しません。

    _EntityScanPackages.Registrar.class_アノテーションにインポートされた_@EntityScan_を確認しましたが、これは内部クラスであり、アノテーションをインポートできません。

MyApplicationクラスに@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class))を置くとすべてが機能しますが、これを_@EnableCustom_のメタ注釈に移動すると機能しなくなります。いくつかのデフォルト値で_@EnableCustom_を指定する別の方法として_@ComponentScan_を考慮するようにSpring Frameworkに指示する方法_@Configuration_、_@Component_などを使用して自分の注釈にメタ注釈を付けてみましたが、役に立ちませんでした:

_@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ComponentScan(
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                value = ApplicationService.class))
public @interface EnableApplicationServices {
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] value() default {};
}
_

これに関するドキュメントはどこにありますか、またはどの出発点をお勧めしますか?私の長期的な目標は、多数のプロジェクトで使用できるSpring Bootスターターを持つことです。


M(N)WEは次のリポジトリにあります: https://github.com/knittl/stackoverflow/tree/spring-enable-annotation

パッケージ構造の概要は次のとおりです。

_// com.example.annotations.EnableCustom.Java
@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// this annotation is never honored:
@ComponentScan(
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                value = MyAnnotation.class))
//@Import(EnableCustom.EnableCustomConfiguration.class)
public @interface EnableCustom {
    // this annotation works in combination with @Import, but scans the wrong packages.
    @ComponentScan(
            includeFilters = @ComponentScan.Filter(
                    type = FilterType.ANNOTATION,
                    value = MyAnnotation.class))
    class EnableCustomConfiguration {}
}

// file:com.example.app.Application.Java
@SpringBootApplication
@EnableCustom("com.example.app.custom.services")
// @ComponentScan(
//         includeFilters = @ComponentScan.Filter(
//                 type = FilterType.ANNOTATION,
//                 value = MyAnnotation.class)) // <- this would work, but I want to move it to a custom annotation
public class Application {
}

// file:com.example.app.custom.services.MyService
@MyAnnotation
public class MyService {
    public MyService() {
        System.out.println("Look, I'm a bean now!");
    }
}

// file:com.example.annotations.services.WrongService.Java
@MyAnnotation
public class WrongService {
    public WrongService() {
        System.out.println("I'm in the wrong package, I must not be instantiated");
    }
}
_
5
knittl

ファビオフォルモサの答え の助けを借りて、不足しているビットを この答え で埋め、_@EntityScan_アノテーションからインスピレーションを得て、これを最終的に機能させることができました。コンパイル可能で動作する例は https://github.com/knittl/stackoverflow/tree/spring-enable-annotation-working にあります。

簡単に言うと、Fabioの回答に基づいて構築する場合、インクルードフィルターを使用してClassPathScanningCandidateComponentProviderインスタンスを適切に構成し、提供されたすべての基本クラスに対してそれを実行することが重要です。 @AliasFor(annotation = Import.class, …)は必須ではないようで、別の属性(例:同じアノテーションのbasePackages

最小の実装は次のとおりです。

_@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableCustom.EnableCustomConfiguration.class)
public @interface EnableCustom {
    @AliasFor(attribute = "basePackages")
    String[] value() default {};

    @AliasFor(attribute = "value")
    String[] basePackages() default {};

    class EnableCustomConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {
        private static final BeanNameGenerator BEAN_NAME_GENERATOR = AnnotationBeanNameGenerator.INSTANCE;
        private Environment environment;

        @Override
        public void setEnvironment(final Environment environment) {
            this.environment = environment;
        }

        @Override
        public void registerBeanDefinitions(
                final AnnotationMetadata metadata,
                final BeanDefinitionRegistry registry) {
            final AnnotationAttributes annotationAttributes = new AnnotationAttributes(
                    metadata.getAnnotationAttributes(EnableCustom.class.getCanonicalName()));

            final ClassPathScanningCandidateComponentProvider provider
                    = new ClassPathScanningCandidateComponentProvider(false, environment);
            provider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotation.class, true));

            final Set<String> basePackages
                    = getBasePackages((StandardAnnotationMetadata) metadata, annotationAttributes);

            for (final String basePackage : basePackages) {
                for (final BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
                    final String beanClassName = BEAN_NAME_GENERATOR.generateBeanName(beanDefinition, registry);
                    if (!registry.containsBeanDefinition(beanClassName)) {
                        registry.registerBeanDefinition(beanClassName, beanDefinition);
                    }
                }
            }
        }

        private static Set<String> getBasePackages(
                final StandardAnnotationMetadata metadata,
                final AnnotationAttributes attributes) {
            final String[] basePackages = attributes.getStringArray("basePackages");
            final Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));

            if (packagesToScan.isEmpty()) {
                // If value attribute is not set, fallback to the package of the annotated class
                return Collections.singleton(metadata.getIntrospectedClass().getPackage().getName());
            }

            return packagesToScan;
        }
    }
}
_
1
knittl