简体   繁体   中英

@SpringBootApplication and @ComponentScan not working together (bean configuration)

I have a multi module project but I am having a problem with my configuration. I have a my main method in package nl.example.hots.boot

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages = {"nl.*"})
@EntityScan("nl.*")
public class HotsApplication {

    public static void main(String[] args) {
        SpringApplication.run(HotsApplication.class, args);
    }

In the nl.example.hots.core.* package I have the class:

@Service
@AllArgsConstructor
@Transactional(propagation = Propagation.REQUIRED)
public class MapImportService {

    private MapInputModelMapper mapInputModelMapper;
    private MapEntityRepository mapEntityRepository;

    public void add(final MapInputModel mapInputModel) {
        System.out.println(mapInputModel.getName());
        mapEntityRepository.save(mapInputModelMapper.mapToEntiy(mapInputModel));
    }

and:

@Component
@Mapper
public interface MapInputModelMapper {

    MapInputModel mapToInputModel(final MapEntity n);

    MapEntity mapToEntiy(final MapInputModel n);

}

The repository is in the package nl.example.hots.persistence.*

I get the following error when running the application:

Description:

Parameter 0 of constructor in nl.example.hots.core.dataimport.MapImportService.MapImportService required a bean of type 'nl.timonschultz.hots.core.map.mapper.MapInputModelMapper' that could not be found.


Action:

Consider defining a bean of type 'nl.example.hots.core.map.mapper.MapInputModelMapper' in your configuration.

When I remove the @EnableAutoConfiguration and @ComponentScan annotations it works. The application starts without the bean error.

In that case however my restcontroller does not work anymore:

{
    "timestamp": "2018-07-18T20:48:39.414+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/maps"
}

When I remove the MapImportService class (and the bean error doesn't pop up) it works on the same url.

package nl.example.hots.api.data_import;

@RestController
@AllArgsConstructor
public class ImportController {

    private static final String URL = // a url
    private Reader reader;

    @RequestMapping("/maps")
    public String abilityStreamImport() {
        reader.readStream(URL); // calls a class in nl.example.hots.core.*
        return "Greetings from APP!";
    }
}

I tried several different combinations and I have a different project as and example where the annotations are used together and it works right. Can somebody explain why the annotations generate the bean error when used together? And why the controller gives an error when only using @SpringBootApplication?

In my Pom.XML the boot module has a dependency on the API layer that has a dependency on the core layer that has a dependeny on the persistence layer. I'm using mapstruct and Lombok in the project.

--- edit: repository ---

@Entity(name = "MAPS")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class MapEntity extends HasId<Long> {

    private String name;

    @ElementCollection
    private List<String> translations;

}

@Repository
public interface MapEntityRepository extends JpaRepository<MapEntity, Long> {
}

@MappedSuperclass
public abstract class HasId<T> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Setter
    @Getter
    private T id;
}

project structure:

hots-application;
- hots-api
    - pom.xml
    - nl.example.hots.api.dataimport.ImportController;
- hots-boot
    - pom.xml
    - nl.example.hots.boot.HotsApplication;
- hots-core
    - pom.xml
    - nl.example.hots.core.dataimport.mapImportService.MapImportService;
    - nl.example.hots.core.map.mapper.MapInputModelMapper
- hots-persistence
    - pom.xml
    - nl.example.hots.persistence.common.HasId;
    - nl.example.hots.persistence.map.MapEntity;
    - nl.example.hots.persistence.map.MapEntityRepository;
pom.xml

Spring Boot will scan all the packages and sub packages starting with the package that the @SpringBootApplication annotated class is in. Your class is in nl.example.hots.boot scanning only that package. The other classes are in different, non-scanned packages.

Due to this package structure and not following the best practices you basically loose a lot of the auto configuration features (Spring Data JPA, ReST etc) and you have to resort to manually enabling/configuring that. Partially through adding additional @ComponentScan annotation, for JPA the @EntityScan annotations. But you would also need to add all the @EnableJpaRepository etc. annotations as those aren't added anymore (at least not with the right packages).

The fix is fairly easy. Move your @SpringBootApplication annotated class to the nl.example.hots package (as stated in the best practices ). Remove the annotations other then @SpringBootApplication and simply start your application.

您在@AllArgsConstructor上缺少@Autowired批注,请尝试:

@AllArgsConstructor(onConstructor = @__(@Autowired))

Spring is not creating a MapInputModelMapperBean. I have never used MapStruct, but it kinda reminds me of mybatis. It looks weird to me that you annotate MapInputModelMapperBean with @Mapper and @Component. How is spring supposed to create the bean? Are you using some sort of spring-boot-mapstruct package that performs this magic?

I made a quick search and found this https://www.credera.com/blog/technology-solutions/mapping-domain-data-transfer-objects-in-spring-boot-with-mapstruct/ , from which it seems like you would need something like:

@Mapper(componentModel = "spring")
public interface MapInputModelMapper

Which I guess will generate the implementation of the mapper with @Component on it or something similar.

After some trial and error I managed to get it working for the most part. I added the scanBasePackages = "nl" to the SpringBootApplication annotation.

Mapstruct did give me some trouble still however but when I removed it and made a quick mapper myself it all worked fine. Thanks for all the help and suggestions and links to read up on! I'll keep the (bestpractice) suggestions in mind for my next project. Below is my main class now

package nl.example.hots.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication(scanBasePackages = "nl")
@EntityScan("nl.*")
@EnableJpaRepositories("nl.*")
public class HotsApplication {

    public static void main(String[] args) {
        SpringApplication.run(HotsApplication.class, args);
    }
}

The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan with their default attributes.

The component scan default behaviour happens only in the same package and in its sub package. If your controllers or components are in different, dont forget to override your component scan default configurations.

Here is the sample code on how to override the default behavour.

@SpringBootApplication(scanBasePackages = "com.code")

Or you can specify multiple packages as shown below:

@SpringBootApplication(scanBasePackages = {"com.java2novice", "com.example"})

The @SpringBootApplication annotation is a convenience annotation that combines the @EnableAutoConfiguration , @Configuration and the @ComponentScan annotations in a Spring Boot application.

@EnableAutoConfiguration – This enables Spring Boot's autoconfiguration mechanism. Auto-configuration refers to creating beans automatically by scanning the classpath.

@ComponentScan – Typically, in a Spring application, annotations like @Component, @Configuration, @Service, @Repository are specified on classes to mark them as Spring beans. The @ComponentScan annotation basically tells Spring Boot to scan the current package and its sub-packages in order to identify annotated classes and configure them as Spring beans . Thus, it designates the current package as the root package for component scanning.

You have used all these three annotations @SpringBootApplication, @EnableAutoConfiguration and @ComponentScan.

Instead use single annotation like @SpringBootApplication(scanBasePackages = "abc.def.ghi")

Here the HotsApplication class with more efficient annotation

   @SpringBootApplication(scanBasePackages = "nl.example.hots")
    public class HotsApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HotsApplication.class, args);
        }

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