본문 바로가기

IT공부/스프링부트

스프링부트 2강 - 스프링부트 만들기 I

스프링부트 2강스프링부트 2강 - 스프링부트 만들기 I

이번 시간에는 스프링 이니셜라이즈 사이트에서 프로젝트를 다운로드받아서 STS에서 불러와서 코드를 작성하는 과정을 설명할 것이다. 

기회가 된다면 인텔리제이를 한 번 써보자. 

프로젝트 구성

프로젝트 구성을 해보자. 스프링 이니셜라이저 사이트에서 다운로드 받게 된다. Spring Initializer::스프링 부트 시작점이다. 사실 우리가 위 사이트에 가서 내려받을 일은 없다. 

Spring initalizer:: Make spring-boot.zip

https://start.spring.io/starter.zip/

?name=spring-boot/

&groupId=io.honeymon.tacademy/

&artifactId=spring-boot/

&version=0.0.1-SNAPSHOT/

&description=Demo+project+for+Spring+Boot/

&packageName=io.honeymon.tacademy.springboot/

&type=gradle-project/

&packaging=jar/

&javaVersion=1.8/

&language=java/

&bootVersion=2.0.5.REREASE/

&dependencies=lombok&dependencies=h2&dependencies=data-jpa&dependencies=web

 

앞서 스프링 이니셜라이저 사이트에서 GENERATE 버튼을 클릭했을 때 생성되는 URL 부분을 캡쳐한 것이다. URL 뒤에 붙는 파라미터를 보면 어떻게 구성이 되는지 대략적으로 확인을 해볼 수 있다. 첫번째 name은 이 프로젝트가 스프링 부트라고 하는 것을 알 수 있고 groupId로 "io.honeymon.tacademy"라고 하는 패키징 명을 바탕으로 시작이 될거라는 걸 알 수 있다. version은 0.0.1-SNAPSHOT이며 SNAPSHOT은 개발중이기 때문에 SNAPSHOT이라고 한다. description은 프로젝트를 설명하는 부분이고 packageName은 실제로 이 프로젝트에서 시작하는 루트패키지 위치이다. "io.honeymon.tacademy.springboot"패키지에서 스프링 부트 애플리케이션이라고 하는 자바 애플리케이션이 생성된다. type은 그레이들 프로젝트이다. packaging은 jar로 할 것이고 javaVersion은 JDK8 버전이고 language는 java를 사용하고 bootVersion은 스프링 부트 버전이다. dependencies는 프로젝트이 라이브러리 의존성을 나타낸다. 

롬복(Lombok)

  • 자바 프로젝트 필수 라이브러리
  • 클래스에서 필수적으로 작성해야 하는 접근자/설정자
    • (getter/setter), toString(필드값 출력), equalsAndHashCode(같은 객체인지 확인), 생성자
    • (Constructor)등을 작성하지 않아도 된다.
  • 코드를 간결하게 사용할 수 있다.
  • 배포버전을 확인하고 결함이 있는지 확인해야 한다.
  • 사용시 주의사항 

H2Database

  • 인-메모리(in-memory), 파일(file), TCP 지원 데이터베이스
  • JDBC url설정으로 데이터베이스 작동방식 지정가능
  • 스프링 부트 자동구성으로 /h2-console h2 webconsole 제공
  • 로컬 개발 환경에서 별도의 DB설치없이 빠른 프로토타이핑 지원
  • 필요에 따라 운영가능한 수준의 데이터베이스 활용가능

스프링 부트 프로젝트 기본구조

resources 폴더 밑에 application.properties 파일이 있는데 형식을 .yml로 변경할 수도 있다. 변경할 시 프로젝트를 생성하자마자 변경하면 된다. 

테스트 쪽에 보면 단위 테스트가 있고 통합테스트가 있는데 test폴더 밑에 TSpringBootApplication.java 파일은 통합테스트에 가깝다. 어플리케이션을 띄우면서 정상적으로 작동하는 지 확인하는 용도로 사용되서 test 폴더의 밑에 위치하는 것은 적절하지는 않다. 

 

settings.gradle

rootProject.name = 'spring-boot'

  • 멀티 프로젝트 구성시 사용
  • 프로젝트 이름
  • 하위 프로젝트 정의
  • 하위 프로젝트 설명(제 개인적으로)

프로젝트에 보면 settings.gradle이라고 하는 프로젝트의 구성을 정의하는 스크립트가 있다. 이 안에 보면 루트 프로젝트 이름은 'spring-boot'라고 선언되어 있다. 프로젝트를 다른 곳에 임포트하면 이 파일을 읽어서 'spring-boot'라고 하는 프로젝트로 선언이 되고 멀티 프로젝트로 구성시 settings.gradle 파일을 읽어서 구성이 된다. 나중에 인클루드로 다른 하위모듈을 선언하는 식으로 작성이 된다. 

 

build.gradle

 

그레이들 래퍼(Wrapper)

메이븐이나 그레이들은 래퍼를 제공한다. 래퍼가 나오기 전에는 그레이들을 사용하려고 하면 각자 PC에 그레이들을 수동으로 다운로드 받아야 했다. 각 사람마다 설치하는 시점에 따라 그레이들 or 메이븐 버전이 다르게 되고 버전에 차이에 의해서 어떤 경우에는 빌드가 안되는 문제가 있다. 래퍼가 도입되고 그레이들과 메이븐 둘 다 적용을 하면서 그런 부분이 많이 줄어들었다. 지금 프로젝트를 생성해서 내려받고 ./gradlew build라고 하는 명령을 내리면 그레이들에서 지정된 위치에 그레이들 래퍼가 jar파일과 properties가 있는 지 확인을 한다. 없으면 그레이들 스크립트에 의해서 배포서버에 가서 gradle-wrapper.jar를 내려받고 압축을 풀어서 특정 위치에 설치하고 jar 파일을 기준으로 빌드를 실행하는 과정이 진행된다. 요즘에 젠킨스같은 빌드도구도 그레이들 래퍼가 있는 걸 권장해서 래퍼를 가지고 있는 프로젝트를 기본속성으로 하고 있다. 

그레이들 래퍼를 내려받게 되면 위와 같이 경로에 jar와 properties 파일이 생성된다. 그레이들 래퍼는 GRADLE_USER_HOME이라고 보면 된다. 

 

 

이클립스에서 File > New > Spring Starter Project

ServiceURL에 스프링 이니셜라이저 사이트경로가 설정되어 있다. Name은 spring-boot로 설정했는데 프로젝트의 이름이 되며 settings.gradle에 들어가게 된다. Group은 자신이 활동하고 있는 회사의 사이트 URL을 기본적으로 사용하게 된다. 위 그림과 같이 작성하고 Next 버튼을 클릭하자. 

 

의존성을 추가하는 부분이다. 위와 같이 설정하고 Next 버튼을 누르자.

 

 

위에서 살펴본 URL을 다시 볼 수 있다. 위 URL을 복사하여 웹 브라우저 주소창에 입력하고 enter를 누르면 spring-boot.zip파일을 다운로드받을 수 있다. 이클립스와 인텔리제이는 위와 같이 full URL을 생성해서 스프링 이니셜라이저에 호출을 하고 zip파일을 내려받아서 임포트를 하기까지의 과정은 동일하게 진행된다. 인터넷이 안되면 스프링 부트 프로젝트를 만들 수 없다. Finish를 누르자.

 

이제 이니셜 라이즈 사이트에 명령을 날리고 zip파일을 내려받기 시작한다. 그레이들 래퍼를 받고 웹에 선언되어 있는 의존성을 받는다. 

spring starter web을 사용하면 기본으로 톰캣을 내장 컨테이너로 사용한다. 웹 컨테이너의 기본 포트는 8080을 사용한다. 

 

프로젝트 생성이 완료되었으면 아래 경로에서 application.java 파일을 열어보자.

application.java

  1. package io.honeymon.tacademy.springboot;
  2.  
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5.  
  6. @SpringBootApplication
  7. public class Application {
  8.  
  9.            public static void main(String[] args) {
  10.                       SpringApplication.run(Application.class, args);
  11.            }
  12.  
  13. }

라인 10 SpringApplication.run(Application.class, args); 코드만 없으면 일반적인 자바 어플리케이션이다. 라인 10코드를 주석처리하고 System.out.println("Hello, world!"); 코드를 입력하고 Java application으로 실행해보자. 

위와 같이 결과가 출력되는 것을 확인할 수 있다. 자바 애플리케이션 안에서 SpringApplication.run(Application.class, args); 이라는 실행 클래스를 통해서 스프링 IOC 컨테이너를 띄우고 이 컨테이너가 떠서 스프링 어노테이션이 있는 위치를 기준으로 해서 스프링 빈들을 탐색하게 된다. 나중에 SpringApplication.run() 메서드를 열어보면 초기에 어떻게 동작하는지 볼 수 있다. xml 코드를 작성할 필요가 없다고 했는데 run() 메서드를 보면 AnnotationConfigApplicationContext라고하는 이름으로 되어 있는 컨텍스트가 있는데 애와 웹이 붙은 2가지 크게 웹과 루트 애플리케이션 컨텍스트로 나뉘게 된다. 스프링 부트는 기본적으로 웹 어플리케이션으로 시작이 된다라고 보면 된다. Application 클래스 코드를 다시 원상복구 하자. 

아래 경로에서 application.properties 파일의 확장자를 application.yml로 변경하자.

application.yml 파일에서 아래와 같이 spring.main.web이라고 입력하면 자동완성기능에 의해 아래와 같이 목록이 나온다. 첫번째를 선택하자.

아래와 같이 입력되고 web-application-type으로 none(비 웹 어플리케이션), reactive, servlet으로 선언이 가능하다. 우리는 기본적으로 SERVLET이라고 하는 웹 어플리케이션을 사용할 것이다. 다시 지우자. 스프링 부트에서 관례적으로 스프링 부트를 사용자들은 스프링 부트 프로젝트를 웹 어플리케이션으로 만들거라는 전제를 하고 구성이 되어 있다. 이 부분에 대해 궁금한 점이 있다면 WebMvcAutoConfigration이라는 클래스를 확인해보자. 

cmd 창으로 프로젝트가 생성된 워크스페이스로 가보자. 아래와 같이 spring-boot 프로젝트로 이동한 뒤 윈도우10이라면 ./gradlew.bat build을 실행하자. 리눅스에서는 ./gradlew build를 실행하자. 명령을 실행하면 그레이들 래퍼에 의해서 빌드가 실행된다. 컴파일 - 테스트 실행 - 정상적 실행 확인 후 spring-boot.jar 파일이 자바 플러그인의 jar에 의해서 만들어지고 스프링 부트 그레이들 플러그인에 의해서 만들어진 jar 파일과 임베디드 톰캣을 하나로 엮어서 다시 한번 스프링 부트 jar가 만들어진다. 이거를 fat jar라고도 한다. jar 파일이 실행에 필요한 모든 라이브러리와 실행환경에 관련된 부분을 다 포함하고 있어서 뚱뚱하다는 뜻에 fat jar라고 부르는 것이다. 

이렇게 그레이들에 의해서 빌드가 실행되면 프로젝트 안에 build/libs 안에 spring-boot-0.0.1-SNAPSHOTjar 파일이 생성된다. 

Spring Boot Repackaging

jar 파일은 보통 zip 형식으로 압축이 되어 있고 파일만 jar로 달라져 있다. unzip으로 풀어보자.

압축을 풀면 BOOT-INF/lib 경로에 우리가 사용하는 jar 파일이 있는 걸 확인할 수 있다. BOOT-INF/classes/static/은 정적인 자원들을 모아두는 곳이고 자바스크립트나 CSS, 이미지 파일이 들어있다. BOOT-INF/classes/templates/는 템플릿 엔진이라고 해서 스프링 컨트롤러가 전달해준 모델에서 데이터를 추출해서 동적으로 html을 생성할 때 쓰는 타임리프나 벨로시티나 그루비같은 템플릿 엔진들이 쓰는 파일들이 위치한다. 

폴더 구조를 보면 BOOT-INF, META-INF(자바 어플리케이션 압축을 할 때 기본 스펙으로, 이 안에서 자바 jar안에 있는 어플리케이션의 Main 클래스가 뭐고 버전이 몇인지에 대한 정보를 가지고 있다.) , org(하위에 스프링부트에서 만든 파일들이 있음)로 구성되어 있다. META-INF 안에 있는 MANIFEST.MF 파일을 보자.

위 출력 결과를 보면 Start-Class에 프로젝트를 내려받으면서 생성된 Application 클래스가 설정되어있다. 실제로는 스프링 프레임워크에 부트로더 JarLauncher가 실행이 되고 Start-Class로 정의되어 있는 Application을 호출해서 띄우는 것이다. BOOT-INF 폴더에 가면 classes 폴더와 lib 폴더가 있다. lib 폴더에는 우리가 선언했던 의존성 라이브러리들이 모여 있다. classes 폴더에는 yml 설정 파일과 Application 클래스파일이 있다.

Application.yml이 있는 위치는 프로젝트 상에서 src/main/resources에 위치한다. Application.class는 src/main/java/생성한패키지명 밑에 존재한다. 그레이들과 메이븐은 메이븐 기본 코드구조를 따르고 루트 패키지를 기준으로 해서 Application 클래스가 있고 그 하위에 컴포넌트 클래스를 작성해서 탐색하는 식으로 되어 있다. 다른 클래스들을 상위에 지정하거나 Application.java 파일을 다른 패키지에 위치시키면 동작을 안 할 수 있다. 생활코딩이나 스택오버플로우같은 사이트에 가면 스프링 부트 프로젝트를 만들어 실행을 하려고 했는데 실행이 안된다는 글들이 있다. 그런 경우 프로젝트 구조를 보면 초기에 생성된 스프링 부트 애플리케이션의 위치가 엉뚱한 곳에 있어서 다른 애들이 스캔이 안되는 경우이다. 

jar 어플리케이션을 실행하는 커맨드는 java -jar jar파일명 이다. 

위와 같이 실행을 하면 로그들이 찍히는데 어플리케이션 개발시 콘솔에 찍히는 로그레벨도 중요하다. 운영레벨에서는 주로 info로 찍고 에러가 생길 때에는 error not fatal로 선언한다. 스프링 부트에서 나름 열심히 밀고 있는게 배너이다. 웹 상에서 banner.txt 파일을 src/resources 폴더에 위치시키면 그 파일로 변경해서 출력해준다. 이미지 파일을 올리면 아스키 코드를 이용해서 그림처럼 만들어 주는 코드도 있다. gif 이미지 파일을 아스키 코드로 전환해서 이 콘솔을 띄울 때 보여주는 부분까지 만들어져있다. 배너를 한 번 변경해보자. src/main/resources 디렉터리 밑에 파일을 하나 만들자.

banner.txt 파일이 생성되면 아래와 같이 작성하고 저장하자.

cmd에서 아래와 같이 입력한다. banner.txt 파일이 포함되서 빌드가 다시 일어난다. 

그리고 프로젝트파일/build/libs 경로에서 java -jar jar파일명.jar 명령어를 실행하면 배너가 변경되어 출력되는 것을 확인할 수 있다. 

프로젝트 시작 시 나름대로 공을 들여서 만든 배너이다. 배너 파일이 만들어질 때 어플리케이션 속성을 넣어서 버전, 저장소 주소를 넣는 등의 작업도 가능해진다.

SpringApplication

SpringApplication:: 스프링 부트 앱 시작점

/**

 * 스프링 부트 애플리케이션이 시작되는 곳!

 * {@link SpringBootApplication}을 살펴보세요. 스프링 붙의

 * 마법이 시작되는 곳입니다.

 *

 * @author honeymon

 */

//@SpringBootApplication어노테이션을 빼면 그냥 자바 어플리케이션이다. 

@SpringBootApplication

public class BootSpringBootApplication {

   //main메서드라는 엔트리 포인트 (진입점) 기준으로 해서 어플리케이션이 실행이 된다. 

   public static void main(String[] args) {

           //SpringApplication이라는 클래스가 있다. run 메서드로 실행을 하면 스프링 IOC 컨테이너를 실행시키고 @Sp

           //ringBootApplication 어노테이션이 붙은 위치를 기준으로 하향식으로 밑에 있는 패키지를 쭉 탐색을 하는게 

           //스프링 부트의 기본적인 동작방식이다. 

           SpringApplication.run(BootSpringBootApplication.class, args);

   }

}

spring-boot-start

 

build.gradle을 보면 web에서 사용하는 모듈들만 정의되어 있다. starter-json, starter-tomcat, spring-web, spring-webmvc 이 모듈들을 쓰겠다라고 하는 정의만 있다. 실제로 이 모듈을 사용하기 위한 코드는 없다. 그 부분은 spring-boot-autoconfigure 부분에 모듈별로 autoconfiguration(자동구성)으로 작성이 되어 있다. github에서 아래 경로로 가보자.

아래와 같이 앞서 본 spring-boot-starters에 있던 모듈들 별로 정의가 되어 있다. 

WEB에 경우에는 스프링 5가 되면서 서블릿과 리액티브로 나뉜다. 우리가 쓰는 WEB은 serlvet에 있다.

위와 같이 servlet에 보면 DispatcherServletAutoConfiguration 클래스도 있다. DispatcherServlet은 스프링 웹과 관련해서 가장 중요한 동작을 한다. 나중에 기회가 된다면 WebMvcAutoConfiguration(자동구성관련 클래스) 클래스를 살펴보자. 

@Component

스프링 프레임워크는 스테이로 타입 어노테이션을 기준으로 해서 @Controller, @Service, @Repositoy 3개의 하위 구분을 지을 수 있다.

스프링을 공부하다 보면 @Bean, @Component 어노테이션을 볼 수 있다. @Bean은 외부에서 클래스를 주입받을 때 선언하고 @Component는 개발자가 작성한 클래스에 붙인다. 주로 @Component와 @Service 어노테이션을 많이 쓰게 된다. 트랜잭션 안에서 관리를 할 경우 @Service 어노테이션을 쓰고 트랜잭션 처리가 필요없는 경우에는 @Component 어노테이션을 쓴다.

스프링 IOC 컨테이너에서 생성하고 호출되고 소멸이 되기까지 생명주기를 관리받는 객체를 스프링 빈 객체라고 한다. 

그런 객체들중 하나가 @Bean 어노테이션이 붙은 클래스이다. 

의존성 주입(Dependency Injection, DI)

스프링에서 가장 큰 개념 중에 하나는 의존성 주입(Dependency Injection, DI)이다. 면접을 볼 때 라이브러리와 프레임워크의 차이가 무엇인지 물어보는 경우가 있다. 그 차이를 살펴보자. 

  1. public class ObjectMapperTest {
  2.  
  3.   @Test
  4.   public void test() throws JsonProcessingException (
  5.       ObjectMapper objectMapper = new ObjectMapper();
  6.  
  7.       Book book =
  8.              new Book("test-book", "test-isbn13", "test-isbn10");
  9.  
  10.       String strBook = objectMapper.writeValueAsString(book);
  11.       //검증 생략
  12.   }
  13. }

라인 5 ObjectMapper 객체를 생성하고 있다. 객체를 JSON으로 변환시켜주는 라이브러리에서 생성한 객체이다. 라인  7에서는 Book 객체를 생성하고 있고 라인 10에서 objectMapper의 writeValueAsString 메서드의 매개변수로 book 객체를 넘겨주어 JSON으로 변환하고 있다. 위 코드와 같이 라이브러리를 사용하기 위해서는 해당 라이브러리를 인스턴스로 만들고 호출해야한다. 

 

스프링 IOC 컨테이너는 objectMapper를 인스턴스로 생성하는 코드가 필요없다. application-context.xml 설정파일에서 objectMapper 인스턴스를 주입시켜주기 때문이다. 

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(webEnvironment=WebEnviroment.RAMDOM_PORT)
  3. public class ObjectMapperTest2 {
  4.    @Autowired
  5.    ObjectMapper objectMapper;
  6.  
  7.    @Test
  8.    public void test() throws JsonProcessingException {
  9.       Book book = 
  10.            new Book("test-book", "test-isbn13", "test-isbn10");
  11.        
  12.       String srtBook = objectMapper.writeValueAsString(book);
  13.       //검증 생략
  14.    }
  15. }

new를 선언할 필요없이 @Autowired 어노테이션을 사용해서 DI를 통해 객체를 사용할 수 있다. 라이브러리와 프레임워크는  개발자가 인스턴스를 직접 생성하는 것이 아니라  IOC 컨테이너에 등록을 해놓고 가져다 쓰는 차이점이 있다. 

의존성 주입 방법 - Note

  • 생성자 주입(권장)
  • 설정자 주입
  • 필드 @Autowired 선언

의존성 주입 방법 - 생성자 주입(권장)

@Service

public class BookServiceImpl implements BookService {

   private final BookRepository repository;

 

   public BookServiceImpl(BookRepository repository) {

      this.repository = repository;

   }

   // 코드 생략

}

생성자 주입 방식을 사용할 경우에는 생성자가 하나만 있어야 사용이 가능하다. 이 때는 @Autowired 어노테이션을 붙이지 않아도 @Service 어노테이션이 붙은 클래스는 스프링에서 관리해야할 컴포넌트로 등록하기 때문에 생성자 매개변수로 선언된 BookRepository 객체를 주입시켜준다. 물론 BookRepository 클래스가 빈으로 생성되어 있어야 한다. 

의존성 주입 방법 - 설정자(Setter) 주입

@Service

public class BookServiceImpl implements BookService {

   private final BookRepository repository;

 

   @Autowired

   public void setRepository(BookRepository repository) {

      this.repository = repository;

   }

   // 코드 생략

}

설정자 주입 방식은 setter 메서드에 @Autowired 어노테이션을 붙인다.

의존성 주입 방법 - 필드 @Autowired 선언

@Service

public class BookServiceImpl implements BookService {

   @Autowired

   private final BookRepository repository;

 

   // 코드 생략

}

필드에 @Autowired 어노테이션을 붙이면 스프링이 프록시 패턴을 통해 @Autowired 선언되있는 필드에 객체를 주입해준다.