1

I have a persistance interface that implements MongoRepository interface that looks as follows:

package org.prithvidiamond1.DB.Repositories;

import org.prithvidiamond1.DB.Models.SomeModel;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ServerRepository extends MongoRepository<SomeModel, String> {
}

Despite this, I keep getting the following error:

Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'botApplication': 
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {}


PPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in org.prithvidiamond1.BotApplication required a bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' that could not be found.


Action:

Consider defining a bean of type 'org.prithvidiamond1.DB.Repositories.ServerRepository' in your configuration.

I have tried many solutions (custom @ComponentScan, using @Service instead of @Component, etc.) I found on the internet but none could help me solve the problem, can someone explain to me what is wrong and how I should fix this?

Note: The directory structure is as follows (this is not the full directory structure, but I think this should be enough to get an idea):

org.prithvidiamond1
   |
   +--BotApplication.java
   |
   +--DB
      |
      +--Repository
      |
      +--ServerRepository.java

BotApplication.java looks as follows:

package org.prithvidiamond1;

import org.jc.api.Api;
import org.jc.api.ApiBuilder;
import org.jc.api.entity.server.Server;
import org.prithvidiamond1.DB.Models.Server;
import org.prithvidiamond1.DB.Repositories.ServerRepository;
import org.slf4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class BotApplication {
    private final Api api;
    private final ServerRepository serverRepository;

    public BotApplication(ServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");

        this.api = new ApiBuilder();

        this.serverRepository = serverRepository;

        appRuntime(logger);
    }

    public void appRuntime(Logger logger){
        logger.info("Bot has started!");

        // Handling server entries in the database
        if (this.serverRepository.findAll().isEmpty()) {
            logger.trace("server data repository empty, initializing data repository...");
            Collection<Server> servers = api.getServers();
            for (Server server : servers) {
                this.serverRepository.save(new Server(String.valueOf(server.getId())));
            }
            logger.trace("server data repository initialized");
        }
    }

    @Bean
    public Api getApi() {
        return this.api;
    }
}

Edit: Here is a link to a repository with all the code: https://github.com/prithvidiamond1/DiamondBot/tree/springboot-restructure

Prithvidiamond
  • 327
  • 2
  • 15
  • Have you tried defining the base package for your repositories in `@EnableMongoRepositories` in `Main.java`? Since you are not using Java naming conventions, SpringBoot might just not be able to automagically register the `@Repository`, as it does with, for example, `Service` interface with `ServiceImpl` class – Jetto Martínez Jul 20 '22 at 22:03

4 Answers4

1

The problem is that you are using the primary source class(BotApplication.class) for executing custom code during start-up.

    public static void main(String[] args) {
        ApplicationContext AppContext = SpringApplication.run(BotApplication.class, args);
    }

    public BotApplication(DiscordServerRepository serverRepository, Logger logger){
        ...
        appRuntime(logger);
    }

It is not a good place for using repositories or any startup code.
In the SpringApplication.run method you need to pass only classes which provide beans definitions. These are @Configuration classes.
See the best practices Running code after Spring Boot starts

I describe one of the best solutions: ApplicationRunner

  1. BotApplication class must implement ApplicationRunner interface. The interface is used to indicate that a bean should run when it is contained within a SpringApplication.
    Execute all startup code at the run method.
@Component
public class BotApplication implements ApplicationRunner {
    private final DiscordApi api;
    private final DiscordServerRepository serverRepository;

    private final Logger logger;

    public BotApplication(DiscordServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");
        this.logger = logger;
        this.serverRepository = serverRepository;
        this.api = new DiscordApiBuilder()
                .setToken(botToken)
                .setAllIntents()
                .setWaitForServersOnStartup(true)
                .setWaitForUsersOnStartup(true)
                .login().exceptionally(exception -> {    // Error message for any failed actions from the above
                    logger.error("Error setting up DiscordApi instance!");
                    logger.error(exception.getMessage());
                    return null;
                })
                .join();
    }

    public void appRuntime(Logger logger){
        logger.info("Bot has started!");

        // Handling server entries in the database
        if (this.serverRepository.findAll().isEmpty()) {
            logger.trace("Bot server data repository empty, initializing data repository...");
            Collection<Server> servers = api.getServers();
            for (Server server : servers) {
                this.serverRepository.save(new DiscordServer(String.valueOf(server.getId()), Main.defaultGuildPrefix));
            }
            logger.trace("Bot server data repository initialized");
        }
    }

    @Bean
    public DiscordApi getApi() {
        return this.api;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        appRuntime(logger);
    }
}
  1. Pass Main.class to the SpringApplication.run
@SpringBootApplication
@EnableMongoRepositories
public class Main {
    public static void main(String[] args) {
        ApplicationContext AppContext = SpringApplication.run(Main.class, args);
    }
}
Eugene
  • 5,269
  • 2
  • 14
  • 22
  • 1
    Yup, I later figured out my mistake of using `BotApplication.class` instead of `Main.class` inside the `SpringApplication.run` method. However, I wasn't aware of the `ApplicationRunner` interface. Thanks for telling me about it and providing such a thorough answer! – Prithvidiamond Jul 21 '22 at 12:15
0

use annotation @Autowired with the constructor of BotApplication class as shown below -

@Autowired
public BotApplication(ServerRepository serverRepository, Logger logger){
        String botToken = System.getenv().get("BOT_TOKEN");

        this.api = new ApiBuilder();

        this.serverRepository = serverRepository;

        appRuntime(logger);
    }
  • Nope, that didn't work and moreover, @Autowired is no longer required as per this: https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/htmlsingle/#beans-autowired-annotation – Prithvidiamond Jul 18 '22 at 19:30
0

You need to use @EnableMongoRepositories on top of your main application class to enable scanning of mongo repository beans.

0

You have Created Repository bean (ServerRepository)within your application.

But in your BotApplication component (which itself is a bean), you are not telling spring to inject dependency of Repository bean (i.e. Dependancy Injection).

Such a dependancy injection can be achieved by constructor , field-based or setter based methodologies.

You can either remove ServerRepository serverRepository from public BotApplication(ServerRepository serverRepository, Logger logger) constructor & just use :

@Autowired
private final ServerRepository serverRepository;

Or as other answer suggested, use @Autowired in Constructor itself and remove field ServerRepository serverRepository :

`
@Autowired
public BotApplication(ServerRepository serverRepository, Logger logger)
`

Please note, here, this.serverRepository = serverRepository; is also not required in constructor as dependency injection will take care of this.

Ashish Patil
  • 4,428
  • 1
  • 15
  • 36
  • https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/htmlsingle/#beans-autowired-annotation Spring's documentation says @Autowired isn't required for constructor injection. However, I wasn't aware of the fact that I don't need to create fields in the class. Also based on the error message, it is clear that Spring is trying to inject the dependency but is not able to find the dependency. – Prithvidiamond Jul 18 '22 at 19:25
  • I have given 2 suggestions, have you tried another solution? – Ashish Patil Jul 18 '22 at 19:37
  • I don't wish to go the field injected route, I feel that is inferior to Constructor injection. However, I did still try it and unfortunately, the problem still persists. – Prithvidiamond Jul 18 '22 at 19:49