简体   繁体   中英

Spring Boot @ComponentScan not working via custom annotation in multi module project

I have a multi module project (gradle) with following structure:

Root project 'mp-search'
+--- Project ':analyzer'
+--- Project ':common'
|    \--- Project ':common:es-model'
...

Description:

  • :analyzer : contains a spring boot application
    • Contains @SpringBootApplication and all other needed dependencies (Web, Feign, etc.)
  • :common:es-model : contains models + repositories for Spring Data Elasticsearch
    • Contains only the "spring-boot-starter-data-elasticsearch" dependency (no @SpringBootApplication)

Say I have the following classes in :common:es-model with package com.example.esmodel.document.model :

package com.example.esmodel.document.model;

//imports

@org.springframework.data.elasticsearch.annotations.Document(indexName = "document")
public class Document {
    @Id
    private String documentId;

    @Field
    private String content;
// Getter + Setter + Constructor
}
package com.example.esmodel.document.repository;

// imports

@Repository
public interface DocumentRepository extends ElasticsearchRepository<Document, String> {
}

Furthermore I created a configuration class with @ComponentScan to find them

package com.example.esmodel.document.configuration;

// Imports

@Configuration
@ComponentScan(basePackages = "com.example.esmodel.document")
public class DocumentConfiguration {
}

And an custom annotation for simple inclusion in the application which imports the configuration:

package com.example.esmodel.document;

//Imports

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(DocumentConfiguration.class)
public @interface EnableDocumentModel {
}

I want to use the DocumentRepository in a controller in my application project ( :analyzer ). So I want to include it via the EnableDocumentModel annotation like this:

package com.example.analyzer;

import com.example.esmodel.document.EnableDocumentModel;
// Other imports

@SpringBootApplication
@EnableFeignClients
@EnableDocumentModel // [1]
//@Import(DocumentConfiguration.class) // [2]
//@ComponentScan(basePackages = "com.example.esmodel.document") // [3]
public class AnalyzerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AnalyzerApplication.class, args);
    }
}

Of these three tests [1] and [2] are not working. The classes ( EnableDocumentModel , DocumentConfiguration ) are found and application tries to start, but fails with a UnsatisfiedDependencyException :

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.example.analyzer.controller.DocumentController required a bean of type 'com.example.esmodel.document.repository.DocumentRepository' that could not be found.

But test [3], using the same @ComponentScan(basePackages = "com.example.esmodel.document") from DocumentConfiguration in the AnalyzerApplication , works fine. But this is not what I desire.

Do I miss something? Any ideas?

Thanks in advance!

Edit :

Just to make sure the DocumentConfiguration is considered by spring at all, I added @Import(DocumentRepository.class) in DocumentConfiguration , but it throws an BeanInstantiationException because it's an interface (which is of course reasonable).

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.esmodel.document.repository.DocumentRepository]: Specified class is an interface
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:70) ~[spring-beans-5.3.19.jar:5.3.19]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1326) ~[spring-beans-5.3.19.jar:5.3.19]
    ... 31 common frames omitted

So Spring definitely considers DocumentConfiguration otherwise the exception wouldn't be thrown. So somehow the configuration is considered, but @ComponentScan(basePackages = "de.sva.medpower.esmodel.document") doesn't do its job...

I also found a tutorial on medium ( https://medium.com/trendyol-tech/how-to-write-a-spring-boot-library-project-7064e831b63b ) doing it that way, but somehow it doesn't work for me..

@ComponentScan does not trigger Spring Data repository initialization. You'd need an @EnableElasticsearchRepositories somewhere in your configuration (ideally on a class in the package that you'd want to scan for ES repositories).

This is even activated automatically if Boot finds Spring Data Elasticsearch on the execution classpath, but in your case doesn't show any effect as the application's root package is com.example.analyzer (the package that your @SprignBootApplication class resides in).

Annotating DocumentConfiguration with @EnableElasticsearchRepositories and point that to the packages that the ES repositories reside in should fix the issue.

Spring is just a framework, meaning it has some rules based on which it will just parse the code you have written, and convert it into some other code to have more functionalities avoiding boileplate code.

The point however is that spring has some rules based on which it reads your code and makes the conversion.

One of those rules for Spring Boot is that it begins it's search from the package that has the class with the annotation @SpringBootApplication and then automatically scans all other nested packages under the package where this class has been found.

So by moving the @ComponentScan outside this default path that the framework will start searching for usuful metadata, you are actually hiding this meta information from spring.

Also by creating your own annotation @EnableDocumentModel , you are not changing how spring is parsing the code of your project. So even if with your custom annotation you point to some other class, java annotations will be considered by java compiler and not by spring framework when doing the parsing.

You have to place @ComponentScan somewhere, where by default the framework will initially search and when it catches this metadata, it will be able to continue the search in some other packages as well. But this information contained in @ComponentScan must be in the default path that spring framework searches, meaning the package where the @SpringBootApplication is to be found or some other nested to this package.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM