Spring

[SpringBoot] Error: Unable to find a @SpringBootConfiguration 에러 원인 및 해결

Woonys 2022. 7. 3. 00:51
반응형

Current status

첫 실무로 기존 프로젝트(프로젝트명 DR)를 개선하는 업무를 받았다. 그러나 기존 프로젝트의 구조가 워낙 복잡한지라.. 일단 형태 자체는 CRUD 기반의 게시판이었기에 <스프링부트와 AWS로 혼자 구현하는 웹서비스> 책을 참고해가며 클론 코딩하기로 결정했다.

기존 프로젝트 DR 역시 엔티티 클래스와 이에 대응하는 Repository 인터페이스를 갖고 있었다. 책의 진행 흐름(3장)을 보면 프로젝트를 진행할 때 가장 먼저 Posts 클래스와 PostsRepository 인터페이스를 만드는 것을 확인할 수 있다.

(여기가 문제의 시작..)

클론 코딩에서 역시 기존 코드에 있는 엔티티 클래스와 Jpa Repository 인터페이스를 만들고 DB와 잘 연동하는지 테스트코드까지 작성을 완료했다.

정리하면

  • DR 클래스 & DRRepository JPA 인터페이스 생성
  • test
    • DRRepositoryTest 테스트 클래스 생성

그런데 문제가 발생했다.

Issue 1: Unable to find a @SpringBootConfiguration

테스트를 돌리니 test fail이 떴다.

 

fail 로그를 읽어보니 문제는 “Unable to find a @SpringBootConfiguration” 이라는데, 당장 @SpringBootConfiguration이 뭔지 몰라 열심히 로그를 통째로 복사해 검색했다.

  1. 그 뒤에 적힌 메시지인 you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
  2. 다섯 번째 줄에 있는 로그인 10:46:00.952 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [test.mydemo.DemoApplicationTests], using SpringBootContextLoader

이 두 메시지를 검색해보니 @ContextConfiguration 어노테이션을 추가하지 않아 생긴 이슈라고 판단했다. 그래서 DRRepositoryTest에 @ContextConfiguration(classes = DR.class) 어노테이션을 추가했다.

  • code
@RunWith(SpringRunner.class) 
@SpringBootTest 
@ContextConfiguration(classes = DynamicRule.class)
public class DynamicRuleRepositoryTest { 

    @Autowired 
    DynamicRuleRepository dynamicRuleRepository; 

    @After
    public void cleanup() { dynamicRuleRepository.deleteAll(); } 

    @Test 
    public void 다이나믹룰저장_불러오기() { 
    //given 
    String rulenamespace = "rulenamespace"; 
    String rulename = "rulename"; 
    Boolean enabled = true; 
    String condition = "condition"; 
    String action = "action"; 
    String admin = "woony"; 
    Integer priority = 1; 
    dynamicRuleRepository.save(DynamicRule.builder() 
            .ruleNamespace(rulenamespace) 
            .ruleName(rulename) 
            .enabled(enabled) 
            .condition(condition) 
            .action(action) 
            .admin(admin) 
            .priority(priority) 
            .build()); 

    // when 
    List<DynamicRule> dynamicRuleList = dynamicRuleRepository.findAll(); 

    // then 
    DynamicRule dynamicRule = dynamicRuleList.get(0); 

    // get the first element 
    assertThat(dynamicRule.getRuleNamespace()).isEqualTo(rulenamespace); 
    assertThat(dynamicRule.getRuleName()).isEqualTo(rulename); 
    assertThat(dynamicRule.getEnabled()).isEqualTo(enabled); 
    assertThat(dynamicRule.getCondition()).isEqualTo(condition); 
    assertThat(dynamicRule.getAction()).isEqualTo(action); 
    assertThat(dynamicRule.getAdmin()).isEqualTo(admin); 
    } 
}

Issue 2: Bean 생성이 되지 않는 이슈

그랬더니 이번에는 또 다른 문제가 발생했다. 빈이 생성되지 않는다는 것. 해당 에러 로그는 아래와 같다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.woony.springboot.domain.dynamicrule.DynamicRuleRepositoryTest': Unsatisfied dependency expressed through field 'dynamicRuleRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.woony.springboot.domain.dynamicrule.DynamicRuleRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

 

여기까지 왔을 때는 책에 있는 내용 전부를 옮기지 않았다는 판단이 들어 3장 이전 내용으로 가서 처음부터 찬찬히 훑었다. 확인해보니 Application.java 메인 클래스를 생성하지 않았더라. 바로 코드를 짰다.

package com.woony.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

그럼에도 여전히 Bean이 생성되지 않는다는 에러가 발생했다.

무엇이 문제였나?

1. Application.java 클래스를 만들지 않았다: SpringBootConfiguration이 없으니 에러 발생

<스프링부트 웹서비스> 책을 3장부터 펼쳐서 Posts(엔티티 클래스)와 PostsRepository(JPA 리포지토리 인터페이스)에 대응하는 코드(DynamicRule, DynamicRuleRepository)만 작성하는 바람에 그 앞 2장에서 Application 클래스를 만들었다는 것을 알지 못했음(그냥 get 메소드 테스트하는 코드만 짜는 줄..)

그럼 왜 Application 클래스가 필요하냐? 기본적으로 자바는 main 메소드가 필요하다. 엔트리 포인트 역할을 하는 게 main()인데 얘가 없으면 어디에서 출발해야 하는지 알 수 없기 때문이다.

그런데 스프링에서 Application 클래스는 그 외에도 스프링에서 핵심 개념 중 하나인 빈을 생성하는 역할 역시 담당하고 있다. Application 클래스의 코드를 살펴 보자.  

 

package com.woony.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

여기서 @SpringBootApplication 에 대한 javadoc을 살펴보면  

 

 

Indicates a configuration class that declares one or more @Bean methods and also triggers auto-configuration and component scanning. This is a convenience annotation that is equivalent to declaring @Configuration, @EnableAutoConfiguration and @ComponentScan.

이를 해석하면

“@SpringBootApplication은 1개 이상의 @Bean 메소드를 선언하는 설정 클래스이자 auto-configuration(자동 설정) 및 컴포넌트 스캔을 일으키는 역할을 한다. 이것은  

 

 

을 선언하는 것과 동등하다는 점에서 편리한 어노테이션이다.”

 

 

즉, 메인 클래스에 붙어 있는 @SpringBootApplication은 크게 3가지가 합쳐진 것.

  1. @SpringBootConfiguration
  2. @ComponentScan
  3. @EnableAutoConfiguration

그런데 Application 메인 클래스를 만들지 않았으니 메인 클래스 안에 있는 @SpringBootApplication 어노테이션이 존재하지 않았을테고, 이는 그 안에 포함되어 있을 @SpringBootConfiguration 역시 존재하지 않았다는 게 된다.

그런데 이 문제를 처음에 접근할 때 Application 클래스를 작성하는 게 아니라 아래와 같이 @ContextConfiguration(classes = DR.class) 어노테이션을 Test에 추가해 해결하려고 했다. 그러면 이는 또 어떤 문제를 야기했을까.

2. @ContextConfiguration(classes = DR.class) 어노테이션으로 인한 Bean 이슈

  • @ContextConfiguration(classes = DynamicRule.class) 추가 → 얘 때문에 빈 생성 안됐던 것!
    • 얘 역할은 뭐지?2
  • DynamicRuleRepository@Repository 어노테이션 달지 않았던 건?
    • 놉. 얘는 생략 가능.

느낀점

  1. 혼자 골머리 싸매는 시간도 필요하지만 하루를 넘기지 않는다.
  2. 사수한테 찾아갈 때는 정리를 해서 간다.
  3. javadoc의 중요성: 설명서를 잘 읽어보자.
  4. 스프링에서는 어노테이션이 매우매우 중요하다.

Further Study

Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data repositories by default.

Reference

반응형