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
이 뭔지 몰라 열심히 로그를 통째로 복사해 검색했다.
- 그 뒤에 적힌 메시지인
you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
와 - 다섯 번째 줄에 있는 로그인
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(자동 설정) 및 컴포넌트 스캔을 일으키는 역할을 한다. 이것은
- @Configuration(스프링 설정 클래스를 선언하는 어노테이션),
- @EnableAutoConfiguration(Bean을 자동으로 등록하는 자바 설정 어노테이션 - spring.factories 안에 들어있는 수많은 자동 설정들이 조건에 따라 적용되어 수많은 Bean들이 생성되고 스프링부트 어플리케이션 실행),
- @ConponentScan(@Controller, @Service, @Repository, @Component 어노테이션이 붙은 클래스를 찾아 컨테이너에 등록하는 어노테이션, @ComponentScan은 해당 패키지에서 @Component 어노테이션을 가진 Bean들을 스캔해서 등록한다)
을 선언하는 것과 동등하다는 점에서 편리한 어노테이션이다.”
즉, 메인 클래스에 붙어 있는 @SpringBootApplication은 크게 3가지가 합쳐진 것.
- @SpringBootConfiguration
- @ComponentScan
- @EnableAutoConfiguration
그런데 Application 메인 클래스를 만들지 않았으니 메인 클래스 안에 있는 @SpringBootApplication 어노테이션이 존재하지 않았을테고, 이는 그 안에 포함되어 있을 @SpringBootConfiguration 역시 존재하지 않았다는 게 된다.
그런데 이 문제를 처음에 접근할 때 Application 클래스를 작성하는 게 아니라 아래와 같이 @ContextConfiguration(classes = DR.class)
어노테이션을 Test에 추가해 해결하려고 했다. 그러면 이는 또 어떤 문제를 야기했을까.
2. @ContextConfiguration(classes = DR.class) 어노테이션으로 인한 Bean 이슈
@ContextConfiguration(classes = DynamicRule.class)
추가 → 얘 때문에 빈 생성 안됐던 것!- 얘 역할은 뭐지?2
DynamicRuleRepository
에@Repository
어노테이션 달지 않았던 건?- 놉. 얘는 생략 가능.
느낀점
- 혼자 골머리 싸매는 시간도 필요하지만 하루를 넘기지 않는다.
- 사수한테 찾아갈 때는 정리를 해서 간다.
- javadoc의 중요성: 설명서를 잘 읽어보자.
- 스프링에서는 어노테이션이 매우매우 중요하다.
Further Study
- 컴포넌트스캔 → 빈 생성
- 빈이 뭔지
- 스프링이 빈을 왜 만드는지
- 자바독만 잘 읽어봐도 좋다.
- ContextConfiguration의 정체는?
- 왜 @Repository 없어도 빈 생성되는지?
Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data repositories by default.
Reference
'Spring' 카테고리의 다른 글
[SpringBoot]GET API route 중복 issue 에러 해결 & SpringBoot와 React는 어떻게 통신하나?(Feat. Tomcat) (0) | 2022.07.29 |
---|---|
[Spring]Web - Service Layer 간 상호 의존 문제(Circular Dependency) (0) | 2022.07.16 |
Spring의 설계 철학(Design philosophy)은? (0) | 2022.06.08 |
[디자인 패턴]빌더(Builder) 패턴이란? (0) | 2022.06.04 |
[패턴]Early return pattern이란? (0) | 2022.05.26 |