[인프런 강의] 스프링 부트 개념과 활용 - 1

2020. 5. 26. 15:57강의/스프링 부트 개념과 활용

인프런이라는 플랫폼의 백엔드 개발자 로드맵과정을 시작하게 되었다. 

첫 단계는 '신입 자바 개발자'로드맵에 포함되어있던 '예제로 배우는 스프링 입문'강의 였다.

해당 강의에 대해서는 정리를 한번 했기 때문에, 

다음 단계인 '스프링 부트와 개념활용' 강의를 듣게 되었다. 

이 분야에서 매우 유명하신 '백기선'님의 강의기 때문에 최대한 집중해서 듣고자 노력하였다.

강의를 들으며 배운 부분에 대해 업데이트 해가며 정리할 예정이다.

 

 

1. maven을 통해 스프링부트 시작하기

maven에 대해서 앞서 정리를 한적이 있지만, 나는 주로 gradle을 통해 빌드를 해왔다. 

강의에서는 maven을 통해 스프링부트 프로젝트를 시작하였는데 이번 기회에 maven을 통해 실습을 따라해 보았다.

 

1) pom.xml에 셋팅

 

위의 스프링부트 공식 document에 있는  parent 및 의존성과 빌드 플러그인들을 추가해 준다.

 

2) src/main/java에 애플리케이션 생성

3) @SpringBootApplication 어노테이션을 달아주고 애플리케이션 내용 작성

 

(위의 과정들은 인텔리제이 얼티메이트 버전을 사용하면 자동으로 위의 과정들을 포함한 프로젝트를 만들 수 있다.)

(또는 start.spring.io에서 만들어도 위의 과정 생략 가능!)

 

이 애플리케이션이 성공적으로 실행된다면 내장된 tomcat을 통해 8080포트로 접속했을때 아래와 같은 화면을 만날 수 있다.

 

이 과정까지는 아직 java프로젝트이기 때문에 mvn package를 통해 jar파일을 생성할 수 있었고 이것을 실행해도 같은 결과를 얻을 수 있었다. 실행 명령어는 java -jar (jar파일명)

 

2. 스프링 부트 프로젝트 구조

메이븐 프로젝트의 기본구조와 같은 모양새를 하고 있었다.

  • 소스 코드 (src\main\java)
  • 소스 리소스 (src\main\resource)
  • 테스트 코드 (src\test\java)
  • 테스트 리소스 (src\test\resource)
  • 메인 애플리케이션(src\main\java밑에 가장 최상위 패키지를 만든후 위치시킨다)

이때 메인 애플리케이션의 위치를  src\main\java밑에 바로 두지않는 이유는 컴포넌트 스캔 측면을 고려한 것이라고 한다. 메인 애플리케이션 같은 경우 @SpringBootApplication 어노테이션을 가지고 있는데, 여기에는 컴포넌트 스캔기능을 하는 어노테이션이 포함되어있다. 이것은 해당 클래스가 포함된 패키지의 하위에있는 모든 패키지를 탐색하여 빈으로 등록해주는 역할을한다. 이때 이것을 java밑에 바로 두게 된다면 존재하는 모든 패키지를 탐색하고 빈으로 등록하는 일이 발생한다. 따라서, 디폴트 패키지를 만들어 준다음 탐색하여 빈으로 등록해야 하는 것만 이 패키지의 하위 패키지로 만들어 위치시킨다.

즉 위와 같이 구조화를 시킨후, 빈으로 등록해야하는 것들은 me.gentlefire 패키지 밑에 위치시키는 것이다. (패키지밑에 패키지를 만들어 사용하여도 모두 탐색하여 빈으로 등록해준다고 한다.) 만약, 그러한 과정이 필요없는 것들은 src/main/java밑에 me.gentlefire가 아닌 다른 패키지를 만들어 사용하면 된다.

 

3. 의존성 관리

아래와 같이 프로젝트를 생성하고나면 매우 많은 라이브러리들이 자동으로 주입되어있는 것을 확인할 수 있다. 

 

이것이 가능한 이유는 스프링부트가 알아서 의존성 관리를 해주기 때문인데, pom.xml에 등록한 parent가 그러한 역할을 한다. parent의 spring-boot-starter-parent을 타고 올라가 보면 이것이 spring-boot-dependencies로 부터 버전이 미리 등록된 라이브러리들을 받아 온다는 것을 알 수 있다. 따라서 spring-boot-dependencies에 명시되어있는 라이브러리를 사용할 경우에는 버전을 따로 정해주지 않아도 미리 적절한 버전으로 사용할 수 있게 되는 것이다. 

이렇게 알아서 의존성관리를 해주는 것의 장점은 두 말할 필요도 없을 것이다. 위에 나타난 의존성만해도 매우 많은데 이것을 직접 pom.xml에 명시하고 관리해야 한다고 생각하면 정말 끔찍하다. 실제로 스프링 부트가 나오기 전에는 그러했다고 한다. (또한 버전업데이트에 따른 호환성 문제도 해결할 수 있다고 한다.)

 

4. 의존성 추가

 위 와 같은 그림으로 의존성을 추가할 수 있다. 이때, 왼쪽에 파란 동그라미 모양이 뜬다면 parent에서 버전관리를 해주고 있다는 것을 의미로 따로 버전을 적어주지 않아도 되지만, 이것이 없는 경우 버전도 함께 명시해 주어야 의존성이 추가된다. (새로운 의존성을 추가하고 적용하려면 메이븐 프로젝트 업데이트를 해주어야 적용이 된다.) 만약, 이미 버전관리를 해주고 싶지만 버전을 바꾸어 적용하고 싶다면 properties태그를 만들어 따로 버전을 명시해주면 된다.

위의 사이트에 가면 필요한 의존성을 검색하여 태그내용을 얻어낼 수 있다. 

 

5. 자동 설정 이해

@SpringBootApplication 어노테이션을 통해 애플리케이션을 실행하는것이 가능했던 것은

아래의 세가지 핵심 어노테이션이 그 안에 숨어있기 때문이라고 한다.

@SpringBootConfiguration

@ComponentScan
@EnableAutoConfiguration

그 중에서도 자동설정은 @ComponentScan 와 @EnableAutoConfiguration 두단계를 거치게 된다.

 

@ComponentScan

위의 어노테이션은 @component가 붙은 클래스를 빈으로 등록한다. (정확히 말하자면 @Configuration @Repository @Service @Controller @RestController 도 빈으로 등록한다.) 

2번에서 잠깐 언급했듯이 이렇게 스캔하는 범위는 해당 패키지와 그 하위 패키지 이다. 즉, 아무런 관계가 없는 패키지에 위치한 곳에 위와 같은 어노테이션들이 붙어있어도 빈으로 등록되지 않는다. 

 

@EnableAutoConfiguration

그렇다면 이 어노테이션에서는 어떠한 대상을 빈으로 등록할까? EnableAutoConfiguration은 spring.factories에 명시되어있는 클래스들을 빈으로 자동 등록해준다. (단, 조건에 따라 등록이 될 수도 있고 안될 수도 있다. 조건은 ConditionalOn, ConditionalClass등으로 지정가능.) 3번에서 봤던 수많은 의존성들은 바로 spring.factories에 지정되어있고 이것을 @EnableAutoConfiguration 어노테이션을 통해 읽어서 등록해주는 것이다.

 

이것이 스프링 부트가 자동으로 수많은 설정들을 해주는 방법이다.

 

6. 자동 설정 커스텀

@EnableAutoConfiguration을 통해 의존성을 주입받는 과정을 더 잘 이해하기 위해 직접 만든 의존성을 프로젝트에서 불러와 보는 과정을 해보았다.

1) 새 프로젝트를 만들고 pom.xml을 설정한다.

 

자동 설정을 위한 의존성을 등록하고 버전관리를 위해 dependencyManagement를 추가로 등록해주었다.

 

2) 도메인 클래스를 만들고 관련된 설정클래스를 만들어준다.

3) src/main/resource 밑에 META-INF 디렉토리를 만들고 안에 spring.factories 파일을 만들어준다.

4) spring.factories안에 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 를 넣고, 원하는 설정파일역할의 자바클래스를 넣어준다. (해당 클래스는 @Configuration을 달고 있어야하고, 풀패키지 이름으로 넣어주어야한다.)

5) mvn install을 통해 외부에서 사용할 수 있도록 만들어 준다. 

 

mvn install을 하고나면 외부의 프로젝트에서 이 프로젝트를 dependency를 통해 주입받을 수 있다.

이렇게 pom.xml에 dependency를 추가하면,

위와 같이 자동주입되어 사용할 수 있는 것을 볼 수 있다. 

이것을 실행시켜보면 dog에 대해 설정해준 적이 없는데도, 외부로부터 주입받은 정보가 출력되는 것을 확인할 수 있었다. 

 

정리하자면, minsub-spring-boot-starter프로젝트에서 정의된 Dog 객체를 주입받아서 사용할 수 있게 된 것인데 이것은 해당 프로젝트에서 4)번에서 보이듯 자동 설정 리스트에 DogConfiguration이 들어가 있기 때문에 가능한 것이다.

 

6-1 자동 설정 커스텀 문제

하지만 위와같이 하면 문제가 발생한다.

초반부에 정리했듯이 빈이 등록되는 과정은 두번에 걸쳐서 이루어진다.

만약, 본 프로덕션 코드에 @Bean을 통해 Dog객체를 빈으로 등록한다고 가정해보자. 그러면 먼저 ComponentScan을 통해 해당 객체를 빈으로 등록한다. 하지만 이후에 @EnableAutoConfiguration으로 빈을 등록하던중 같은 이름이 있다면 그것이 기존의 객체를 덮어 써버리는 것이다. 한마디로 이런식으로 하면 자동설정된 객체를 수정할 수가 없게 되는 것이다. 

 

이것을 해결하기 위해서 @EnableAutoConfiguration은 조건에 따라 빈을 등록하거나 하지않을 수 있는 것이다. (5번에서 정리한 내용을 보면 알 수 있다.) 위와 같은 상황에서 사용할 수 있는 조건은 @ConditionalOnMissingBean이다. 자동설정시에 이와 같은 조건을 붙여주면, 해당 타입의 빈이 없을때만 빈으로 등록해주도록 만들 수 있다. 

(변경된 내용을 적용하려면 해당 프로젝트를 다시 mvn install해주고 이 프로젝트를 사용하는 곳에서는 다시 maven을 refresh해주어야한다.)

 

6-2 자동 설정 커스텀 문제

위와 같이 해결했다고 만족스럽지 않을것이다. 왜냐하면 자신이 원하는 객체를 만들기 위해 계속 @Bean을 통해 생성해주어야 하기 때문이다. 이러한 bean의 재정의 과정을 막기 위해 properties를 사용할 수 있다.

1)Dog를 주입해주는 프로젝트에 DogProperties 클래스를 만들고 해당 클래스에 @ConfigurationProperties(“dog”)를 달아준다. 

2)DogConfiguration에서 이 Properties를 사용하기 위해 @EnableConfigurationProperties(DogProperties.class)를 달아준다.

3)이 프로젝트를 사용하는 본 프로덕션 프로젝트의 application.properties에 dog.crawling , dog.age를 설정한다. (이때 dog는 1번에서 "dog"로 만들었기 때문에 이것을 사용한다.)

4)이렇게 하면 빈을 재정의 할 필요없이 properties에 지정한 값으로 항상 객체를 만들 수 있다. 

 

 

 

애플리케이션 실행시 빈을 따로 재정의 해주지 않아도 원하는 값을 출력