매일 땡기는 마라 코딩

[2장(1)] React.js, 스프링 부트, AWS로 배우는 웹 개발 101(개정판) 본문

TP

[2장(1)] React.js, 스프링 부트, AWS로 배우는 웹 개발 101(개정판)

cmkoi1 2023. 1. 6. 19:37

React.js, 스프링 부트, AWS로 배우는 웹 개발 101 2/e

 

React.js, 스프링 부트, AWS로 배우는 웹 개발 101 2/e - YES24

다수의 사용자를 지원하는 Todo 웹 애플리케이션을 구현하고 배포한다. 또한 현장에서 많이 사용하는 프론트엔드와 백엔드 서버가 분리된 아키텍처(Decoupled Architecture)를 구현한다. 또한 배포 시

www.yes24.com

※ 해당 도서의 내용을 바탕으로 작성된 포스팅입니다.

 

 

 

2. 백엔드 개발

2.1 백엔드 개발 환경 설정

Amazon Corretto 설치

  • Amazon Corretto: 프로덕션용 OpenJDK (Open Java Development Kit) 배포 툴으로, 아마존에서 배포하는 자바 11 버전. 무료, 상업적으로 사용 가능. 일래스틱 빈스톡 환경에서도 같은 배포본 사용.

Amazon Corretto 11 다운로드 https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html

 

Downloads for Amazon Corretto 11 - Amazon Corretto

Thanks for letting us know this page needs work. We're sorry we let you down. If you've got a moment, please tell us how we can make the documentation better.

docs.aws.amazon.com

자바 설치 후, 아래 코드로 CMD 혹은 Powershell에서 자바 설치 여부 확인.

java -version

실습 코드 2-1. 자바 설치 여부 확인

 

이클립스 설치: 자바 IDE

이클립스 인스톨러 다운로드 https://www.eclipse.org/downloads/ 

 

Eclipse Downloads | The Eclipse Foundation

The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks.

www.eclipse.org

인스톨러 실행 후 Eclipse IDE for Java Developter를 Install.

 

스프링 프레임워크와 의존성 주입

  • 스프링: 오픈 소스의 경량 프레임워크.
  • 오픈 소스: 공개되어 있는 소스 코드.
  • 프레임워크: 개발자들이 확장해서 사용할 수 있는 코드로, 여기서 확장은 프레임워크가 제공하는 클래스나 라이브러리를 사용하거나, 상속 및 구현해 우리 코드를 프레임워크의 일부로 실행하는 것을 말한다.
  • 경량 프레임워크: 보통 메모리나 CPU 자원이 많이 들지 않거나 사용이 쉽고 간편한 프레임워크를 지칭.

스프링에는 의존성 주입, 스프링AOP, 스프링ORM, 스프링Web 등 여러 방면에서 개발을 돕기 위한 프레임워크를 제공한다. 그리고, 이 책은 스프링 프레임워크에 존재하는 여러 컴포넌트 중 스프링 부트를 사용한다.

 

스프링 프레임워크의 핵심은 의존성 주입이다. 의존성 주입은 디자인 패턴으로 IoC를 구현하는 방법 중 하나이며, IoC는 제어를 역전하는 것을 보편적으로 설명하는 단어이다. 

 

 

의존성 주입

public class TodoService {
    private final FileTodoPersistence persistence;
    
    public TodoService() {
    	this.persistence = new FileTodoPersistence();
    }
    
    public void create(...) {
    ...
    persistence.create(...);
    }
}

예제 2-1. 생성자 내부에서 오브젝트 초기화

클래스 내부에서 오브젝트를 생성하는 코드가 있다. TodoService 클래스가 Todo 목록을 관리하는 기능을 제공하기 위해 파일에 Todo 목록을 저장할 수 있도록 도와주는 클래스인 FileTodoPersistence를 사용한다. TodoService는 FileTodoPersistence 없이 제 기능을 할 수 없기 때문에 FileTodoPersistence에게 의존한다. 즉, FileTodoPersistence에게 의존하는 TodoService가 FileTodoPersistence 오브젝트를 생성하고 관리한다.

public static void main(String[] args) {
    TodoService service = new TodoService();
}

예제 2-2. 메인 메서드에서 TodoService 오브젝트 생성

파일이 아닌 데이터베이스에 저장하도록 하고 DatabaseTodoPersistence를 구현하여, TodoService 클래스에서 FileTodoPersistence 대신 DatabaseTodoPersistence로 자료형을 바꾸고 생성자에서 DatabaseTodoPersistence를 생성하도록 코드를 고친 상황이라고 가정한다. DatabaseTodoPersistence를 사용하는 클래스가 약 100개가 되도록 개발이 진전되었다고 하고 데이터베이스를 AWS S3으로 대체하려고 하면, 100개의 클래스를 돌아다니며 DatabaseTodoPersistence 대신 S3TodoPersistence를 생성하도록 고쳐야 하는 상황이 발생한다.

 

또한, 예제 2-1과 같이 클래스 내에서 오브젝트 초기화하는 경우 단위 테스트 작성이 어렵다. 단위 테스트에서 실제 어플리케이션이 사용하는 퍼시스턴스를 그대로 사용하기 힘들어, 껍데기만 있는 클래스(Mock 클래스)를 만들어 사용해야 한다. 다만, 생성자 내부에서 FileTodoPersistence를 생성하기 때문에 Mock 클래스를 만들어도 TodoService가 사용할 수 없다. 

 

의존성 주입은 클래스가 의존하는 다른 클래스들을 외부에서 주입시키는 것으로, 위의 문제점을 해결할 수 있다. 의존성 주입은 생성자를 애용해 주입하는 방법과 Setter를 이용해 주입하는 방법이 있다.

public class TodoService {
    private final ITodoPersistence persistence; //인터페이스
    
    public TodoService(ITodoPersistence persistence) {
    	this.persistence = persistence;
    }
    
    public void create (...) {
    	...
    	persistence.create(...);
    }
}

예제 2-3. 생성자를 이용한 의존성 주입, 서비스 코드

예제 2-3처럼 한 오브젝트가 의존하는 오브젝트를 생성하는 것이 아니라, 외부에서 넘겨받는 것을 의존성 주입이라 한다. 

public static void main(String[] args) {
    ITodoPersistence persistence = new FileTodoPersistence();
    TodoService service = new TodoService(persistence);
}

예제 2-4. 생성자를 이용한 의존성 주입, 메인 코드

TodoSercivce 오브젝트를 생성할 때, ITodoPresistence 구현부를 넘겨줘 의존성을 주입. ITodoPresistence는 인터페이스이다.

public class TodoService {
    private final ITodoPersistence persistence;
    
    public void setITodoPersistence(ITodoPersistence persistence) {
    	this.persistence = persistence;
    }
}

예제 2-5. Setter를 이용한 의존성 주입, 서비스 코드

생성자 대신 Setter를 만듬.

public static void main(String[] args) {
        ITodoPersistence persistence = new FileTodoPersistence();
    	TodoService service = new TodoServce();
    service.setITodoPersistence(persistence);
}

예제 2-6. Setter를 이용한 의존성 주입, 메인 코드

 

오브젝트 초기화 후 다음 줄에서 Setter 이용해 의존하는 오브젝트 주입.

@Test
public void test() {
    ITodoPersistence persistence = new MockTodoPersistence();
    TodoService service = new TodoService(persistence);
}

예제 2-7. 단위 테스트에서 Mock 오브젝트 생성

의존성 주입은 단위 테스트에서 Mock 오브젝트를 주입할 때도 유용. Mock 오브젝트 초기화 후, 이 오브젝트를 테스팅할 오브젝트에 주입 가능.

 

간단하게 보면 생성자나 Setter를 사용하는 것을 의존성 주입이라 부른다 볼 수 있다. 의존성 주입을 전문적으로 해주는 것을 의존성 주입 컨테이너라고 하고, 그 중 하나가 스프링 프레임워크이다.

 

스프링 프레임워크

프로그램의 규모가 작을 경우 의존성 주입 컨테이너가 필요하지 않을 수 있다. 하지만, 프로그램의 규모가 커지고, 관리해야 하는 오브젝트가 많아질수록 오브젝트 생성에 할애하는 시간이 늘어난다. 의존성 주입 컨테이너는 효율적인 오브젝트 생성과 관리에 도움을 준다.

public class MyService {
    TodoService todoService;
    ShareService shareService;
    NotificationService notificationService;
    ScheduledExecutorService scheduledExecutorService;
}

public class ShareService {
    UserService userService;
    EventService eventService;
    SharePersistence persistence;
}

public class NotificationService {
    EventService eventService;
    UserService userService;
    NotificationPersistence persistence;
}

public class SharePersistence {
    JDBCConnection connection;
    ...
}

예제 2-8. Todo 알림 및 공유 기능을 위한 클래스 예

Todo 앱을 확장해 예약, 알림, 공유 기능을 만들기 위해 관련 클래스를 만들었다고 가정한다. 예제 2-8 서비스 오브젝트 사용을 위해서 메인 혹은 서비스를 사용하는 클래스에서 오브젝트 생성이 필요하다.

JDBCConnection connection = new JDBCConnection("url", "name", "pwd", "port");
TodoService todoService = new TodoService (...);
UserService userService = new UserService(...);
EventService eventService = new EventService(...);
NotificationPersistence notificationPersistence = new NotificationPersistence (connecti on);
SharePersistence sharePersistence = new SharePersistence(connection);
ShareService shareService = new ShareService(userservice, eventService,sharePersistence);
NotificationService notificationService = new NotificationService (userService, eventService, sharePersistence);
MyService myService = new MyService(todoservice, shareservice, notificationService, new ScheduledExecutorservice(...));

예제 2-9. Todo 알림 및 공유 기능을 위한 클래스 예

new 키워드를 이용해 오브젝트를 일일이 생성하는 코드. 이러한 클래스의 갯수가 많아질 경우 번거로움을 의존성 주입 컨테이너인 스프링 프레임워크를 사용하여 해결할 수 있다. 스프링 프레임워크를 이용하여 어노테이션, XML, 자바 코드를 이용해 오브젝트 사이의 의존성을 명시하면, 앱 시작 시 스프링 프레임워크의 IoC 컨테이너 오브젝트가 등록한 오브젝트를 생성 및 관리해 준다.

@Service
public class MyService {
    @Autowired TodoService todoService;
    @Autowired ShareService shareService;
    @Autowired NotificationService notificationService;
    @Autowired ScheduledExecutorService scheduled ExecutorService;
}

@Service
public class ShareService {
    @Autowired UserService userService;
    @Autowired EventService eventService;
    @Autowired SharePersistence persistence;
}

@Service
public class NotificationService{
    @Autowired EventService eventService;
    @Autowired UserService userService;
    @Autowired Notification Persistence persistence;
}

@Service
public class SharePersistence {
    @Autowired JDBCConnection connection;
    ...
}

예제 2-10. 스프링IoC를 이용한 의존성 주입

스프링 프레임워크의 어노테이션을 이용해 간단해진 코드. 스프링 프레임워크 IoC 컨테이너 오브젝트, ApplicationContext 오브젝트가 오브젝트 생성 작업을 대신해 준다.

 

스프링 프레임워크는 어노테이션 기반, XML 기반, 자바 기반의 설정을 제공. 이 책은 그 중 가장 간편하게 사용할 수 있는 어노테이션 기반 설정을 사용한다.


어노테이션이란?

어노테이션이란 메타데이터로 다른 데이터에 대한 데이터, 어떤 데이터에 대한 아주 기본적인 정보이다. 예를 들어, 예제 2-10의 @Service 어노테이션을 보면 클래스가 서비스 클래스라는 정보를 제공한다. 이 책에서 스프링 부트가 클래스 초기화 시 어노테이션을 확인하고 클래스를 어떻게 관리해야 하는지 배운다.

 


 

스프링 부트

스프링 부트를 이용하면 스탠드얼론 프로덕션급의 스프링 기반 앱을 쉽게 구동할 수 있다.

  • 스탠드얼론(stand-alone): 앱을 실행하기 이해 다른 앱이 필요하지 않다. 해당 앱을 실행하면 끝.

아파치 톰캣(Apache Tomcat)은 스탠드얼론이 아닌 앱으로 웹 서버/서블릿 컨테이너가 필요하다. 서버에 톰캣을 먼저 설치 후, 자바 앱을 컴파일하여 톰캣이 이해할 수 있는 구조의 WAR 파일로 압축하고 톰캣에 배포한다. 스프링 부트는 임베디드 톰캣(embedded tomcat), 제티(jetty)와 같은 웹 서버를 앱 실행 시 같이 실행 및 설정해 준다(앱 실행 자체가 웹 서버 실행).

 

스프링 부트는 많은 부분을 자동으로 설정해 주는데, 이 책은 스프링 부트가 제공하는 자동 설정에 의존한다.

스프링 프레임워크와 디스패쳐 서블릿

스프링이 웹 앱 측면에서 제공하는 주요한 기능을 알아본다. spring-boot-starter-web 프로젝트에 존재.

자바 서블릿 기반의 서버 사용을 위해서 Javax.servlet.http.HttpServlet을 상속받는 서브 클래스를 작성하면, 서블릿 컨테이너(Servlet Container)가 서블릿 서브 클래스를 실행. 그림 2-2와 같이 http 요청이 서버로 전달되면 웹 서버는 요청을 해석해 해당 서블릿 클래스를 실행한다.

package com.example.Demo;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Hello extends HttpServlet {
    @Override
    public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	// parameter 해석
        String name = request.getParameter("name");
        
        // business logic 실행
        process(name);
        
        // response 구축
        response.setContentType("text/html");
        PrintWriter out = response.getwriter();
        out.print("<html>");
        // UI 부분
        out.print("</html>");
	}
    
    private void process(String name) {
    // business logic
    }
}

예제 2-11. HttpServlet 서브클래스 예

웹 서비스를 개발해기 위해 해야 하는 작업들. HttpServlet을 상속하는 서브 클래스 생성 후 doGet(_) 메서드 구현. 매개변수로 넘어오는 HttpServletRequest에서 원하는 정보 추출 후 비즈니스 로직 process(_)를 실행하고, 반환할 정보를 HttpServletResponse에 담는다. 필요한 로직이 process(_) 메서드 하나여도 매개변수 해석 응답 부분을 항상 작성해야 하며, API를 만들 때마다 반복 작업해야 한다.

스프링 부트는 어노테이션과 서브클래스를 이용해 반복 작업과 코드 최소화에 도움을 준다. 스프링 부트는 DispatcherServlet(baeldung) 서블릿 서브 클래스를 이미 구현하여 개발자가 서블릿 클래스를 작성하지 않아도 되며, 제공되는 어노테이션과 인터페이스를 이용해 스프링이 비즈니스 로직을 이해할 수 있도록 내부 기능을 구현하면 된다.

@RestController //JSON을 리턴하는 웹 서비스임을 명시
public class HelloController {

    @GetMapping("/test") // path 설정, GET 메서드 사용
    public String process(@RequestParam String name) {
    	// 비즈니스 로직
        return "test" + name;
    }
}

예제 2-12. 스프링 부트를 이용한 비즈니스 로직 클래스의 예

이와 같이 스프링을 사용했을 때의 장점을 네 가지로 정리할 수 있다.

  1. HttpServlet을 상속받지 않아도 된다.
  2. doGet을 오버라이드 하지 않아도 된다.
  3. HttpServletRequest를 직접 파싱하지 않아도 된다.
  4. HttpServletResponse를 작성하지 않아도 된다.

 

스프링 부트 프로젝트 설정

스프링에서 제공하는 툴(https://start.spring.io/)에서 원하는 라이브러리 선택 후 하단의 Gendrate 버튼을 눌러 생성.

Spring initializr에서 프로젝트 생성을 위해서 5가지 설정이 필요.

  1. project: 빌드 자동화 툴을 선택. 대표적인 Maven과 Gradle. Gradle은 Gradle - Groovy, Gradle - Kotlin 두 가지로 나누어져 있는데 Groovy는 동적, Kotlin은 정적으로 정적 언어를 사용하면 IDE가 특정 작업을 더 잘 지원. (Gradle 사용)
  2. Language: 언어 선택. (자바 사용)
  3. Spring Boot: 스프링 부트 버전 선택. (2.7.0(SNAPSHOT) 버전 사용, 나는 2.7.7 사용)
  4. Project Metadata: Group, Artifact는 프로젝트 배포 시 무슨 프로젝트인지 묘사하기 위함. (Group, Artifact, Name, Description, Package name은 기본 설정 유지, Packaging은 Jar,자바 버전은 11 선택)
  5. Dependencies: 필요한 디펜던시 추가, 추가된 디펜던시는 build.gradle 파일에 추가된다. 추가는 ADD 또는 ADD DEPENDENCIES에서 가능. 더 필요한 라이브러리는 이후에 디펜던시 설정 방법을 설명하며 추가. (Spring Web, Spring Data JPA, Lombok, H2 Database 라이브러리 추가)

프로젝트 생성을 위해 그림 2-4 또는 http://tinyurl.com/fsoftwareengineer-todotoday-r2 참고하여 프로젝트 설정. 프로젝트 설정 후 Generate 눌러 프로젝트 압축 파일 받고, 원하는 곳에 압축 해제.

 

 

이클립스에서 프로젝트 import하기

압축 해제한 프로젝트를 이클립스에서 열기 위해 이클립스 실행 후 File → Import 선택.

Gradle 프로젝트이므로 Gradle Existing Gradle Project 선택.

Project root directory에 압축 해제한 프로젝트 디렉터리 경로 입력, Browse 눌러 프로젝트 선택 후 Next 누르기.

Override workspace settings에 체크한 후 Gradle wrapper를 Gradle distribution으로 선택. 윈도우에 그래들을 설치하는 대신 Gradlew(Gradle wrapper)를 다운로드한다. 프로젝트 내의 그래들 래퍼를 사용하면 따로 그래들 설치가 필요하지 않으며, 이미 설치된 그래들 버전과 호환 문제를 방지할 수 있다.

import 완료 시 왼쪽 패널에 Package Explorer가 보이며, src/main/java → com.fsoftwareengineer.demo  DemoApplication.java가 생성.

 

메인 메서드와 @SpringBootApplication

프로젝트는 16개의 디렉터리와 10개의 파일로 구성. 가장 상위에는 그래들 관련 폴더와 파일, scr 폴더 아래로 패키지와 자바 파일이 존재한다. Src 폴더는 소스 코드가 존재하지 않는 폴더이며, Test 폴더는 단위 테스트를 담는 폴더이다. 

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

예제 2-13. DemoApplication.java

스프링 부트를 통해 설정 작업을 어노테이션으로 간단히 수행 가능하다. @SpringBootApplication 어노테이션이 달린 클래스는 스프링 부트를 설정하는 클래스임을 의미하며, 패키지에 해당 클래스가 있을 경우 베이스 패키지로 간주한다.

 

스프링은 베이스 패키지와 그 하위 패키지에서 자바 빈을 찾아 스프링 의존성 주입 컨테이너 오브젝트(ApplicationContext)에 등록한다. 또한, 앱 실행 중 어떤 오브젝트가 필요하면 예제 2-10과 같이 @Autowired 어노테이션으로 의존하는 다른 오브젝트를 자동으로 찾아 연결해 준다.

 

@Component

스프링이 애플리케이션 컨텍스트에 등록할 자바 빈을 찾는 방법, 스프링에게 이 클래스를 자바 빈으로 등록하라고 알려주는 어노테이션이다. 예제 2-10의 @Service도 어노테이션 내부에 @Component를 달고 있다(예제 2-14).

@Component
public@interface Service {
…
}

예제 2-14. @Service 어노테이션의 내부

@Component를 클래스에 달면 무조건 스프링이 검색해 등록해 주는 것이 아닌 @ComponentScan 어노테이션이 클래스에 있어야 컴포넌트 스캐닝이 가능하다. 하지만, @ComponentScan은 프로젝트 내부에 존재하지 않는다.

 

// ...다른 어노테이션들
@ComponentScan // 매개변수 생략
public @interface SpringBootApplication {
    //...
}

예제 2-15. @SpringBootAppllication의 내부

프로젝트 내부에 존재하지 않아도 DemoApplication 클래스의 @SpringBootApplication에 @ComponentScan을 포함하고 있어 추가해 주지 않아도 된다. 따라서, 스프링을 이용해 관리하고 싶은 빈의 클래스 상단에 @Component를 추가하면 자동으로 오브젝트를 스프링에서 빈으로 등록할 수 있다. @Autowire와 함께 사용하면 스프링이 필요할 때 알아서 오브젝트 생성.

 

 

@Bean

@Configuration
public class ConfigClass {

    @Bean
    public Controller getController() {
    	if(env == 'local') {
            return new LocalController(...);
        }
        return new Controller(...);
    }
}

예제 2-16. @Bean 어노테이션을 이용한 스프링 빈 등록

스프링이 자동으로 오브젝트를 찾아 생성하게 하고 싶지 않을 경우, @Component를 추가하지 않고, 스프링을 통해 빈을 관리하고 싶은 경우가 있을 것이다. @Component를 사용하고 싶지 않을 때, 사용하지 못할 때는 언제일까.

 

엔터프라이즈 애플리케이션과 같이 @Autowired를 사용하지 않고 엔지니어가 오브젝트를 어떻게 생성하고 어느 클래스에서 사용하는지 정확히 알아야 하는 경우, 예제 2-16과 같이 로컬 환경에서 애플리케이션을 실행할 때 자동으로 연결될 빈이 아닌 다른 빈을 사용하고 싶은 경우, 스프링 기반이 아닌 라이브러리를 사용하여 @Component를 추가하지 못하는 경우가 있을 것이다.

 

이런 경우, 스프링으로 빈을 관리하기 위해 직접적으로 빈을 이렇게 생성해라 말해 줄 필요가 있다. 해당 작업을 위한 어노테이션이 @Bean이다. @Bean을 이용해 스프링에게 오브젝트를 어떻게 생성해야 하는지, 매개변수를 어떻게 넣어 줘야 하는지 알려 줄 수 있다. 이 책의 프로젝트는 간단하여 @Bean을 사용하지 않고 @Component를 사용한다. @Controller, @Service, @Repository등 스테레오 타입 어노테이션 내부에는 전부 @Component 어노테이션이 달려 있다.

 

 

정리

  1. 스프링 부트 애플리케이션 시작.
  2. @ComponentScan 어노테이션이 있으면, 베이스 패키지와 그 히위 패키지에서 @Component가 달린 클래스 찾음.
  3. 필요한 경우 @Component가 달린 클래스 오브젝트 생성. 생성하려는 오브젝트가 다른 오브젝트에 의존하면(멤버 변수로 다른 클래스를 가지고 있으면) 해당 멤버 변수 오브젝트를 찾아 넣어준다. @Autowired를 사용하는 경우 스프링이 해당 오브젝트를 찾아 생성해 넣어준다.
    1. @Autowired에 연결된 변수의 클래스가 @Component가 달린 경우 스프링이 오브젝트를 생성해 넘겨 준다.
    2. @Bean 어노테이션으로 생성하는 오브젝트인 경우 @Bean이 달린 메서드를 불러 생성해 넘겨 준다.

 

애플리케이션 실행

프로젝트 실행을 위해 파웨셸 혹은 CMD를 실행해 프로젝트가 있는 디렉토리로 경로 이동.

$ ./gradlew bootRun

> Task:bootRun

  .   ___          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
  ========|_===============|___/=/_/_/_/
:: Spring Boot ::        (V2.7.0-SNAPSHOT)

2022-04-02 18:33:21.874	INFO 3025 --- [        main] com.example.demo.DemoApplication        : Starting DemoApplication using Java 11.0.14.1 on MacBookPro.local with PID 3025 (<PROJECT_DIRECTORY>/todo-application-revision-2/2.1-Backend_ Development_Environment_Configuration/demo/build/classes/java/main started by alyssakim in <PROJECT_DIRECTORY>/todo-application-revision-2/2.1-Backend_Development Environment_Configuration/demo)
2022-04-02 18:33:21.877 INFO 3025 --- [        main] com.example.demo.DemoApplication        : No active profile set, falling back to 1 default profile:"default"
2022-04-02 18:33:22.560 INFO 3025 --- [        main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-04-02 18:33:22.572 INFO 3025 --- [        main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8 ms. Found 0 JPA repositoryinterfaces.
2022-04-02 18:33:22.998 INFO 3025 --- [        main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-04-02 18:33:23.003 INFO 3025 --- [        main] o.apache.catalina.core.StandardService  : Starting service [Tomcat]
2022-04-02 18:33:23.004 INFO 3025 --- [        main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.60]
2022-04-02 18:33:23.095 INFO 3025 --- [        main] o.a.c.c.C. [Tomcat]. [localhost].[/]        : Initializing Spring embedded WebApplicationContext
2022-04-02 18:33:23.095 INFO 3025 --- [        main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1177 ms
2022-04-02 18:33:23.212 INFO 3025 --- [        main] com.zaxxer.hikari.HikariDataSource        : HikariPool-1 - Starting...
2022-04-02 18:33:23.264 INFO 3025 --- [        main] com.zaxxer.hikari.HikariDataSource        : HikariPool-1 Start completed.
2022-04-02 18:33:23.323 INFO 3025 --- [        main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-04-02 18:33:23.358 INFO 3025 --- [        main] org.hibernate.Version: HHH000412: Hibernate ORM core version 5.6.7.Final
2022-04-02 18:33:23.444 INFO 3025 --- [        main] o.hibernate.annotations.common.Version  : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-04-02 18:33:23.486 INFO 3025 --- [        main] org.hibernate.dialect.Dialect: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-04-02 18:33:23.558 INFO 3025 --- [        main] o.h.e.t.j.p.i.JtaPlatformInitiator        : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 
2022-04-02 18:33:23.562 INFO 3025 --- [        main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 
2022-04-02 18:33:23.610 WARN 3025 --- [        main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2022-04-02 18:33:23.844 INFO 3025 --- [        main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ' '
2022-04-02 18:33:23.854 INFO 3025 --- [        main] com.example.demo.DemoApplication        : Started DemoApplication in 2.586 seconds (JVM running for 2.932)
<==========---> 80% EXECUTING [14s]
> :bootRun

실습 코드 2-2. 스프링 부트 애플리케이션 실행하기

프로젝트 디렉터리에서 그래들 명령어 ./gradlew bootRun를 사용하거나 IDE를 통해 실행할 수 있다. 'Started Demo Application in 2.586 seconds (JVM running for 2.932)'가 보이면 브라우저를 켜 localhost:8080으로 접근.

Whitelable Error Page가 뜨면 서버가 정상적으로 작동.

 

 

빌드 자동화 툴: Gradle과 라이브러리

Gradle은 빌드 자동화 툴으로 컴파일, 라이브러리 다운로드, 패키징, 테스팅 등을 자동화 할 수 있다. 자동화 툴은 반복 작업을 줄여 준다.

 

빌드 자동화 툴이 없다면, 웹 애플리케이션을 만들 때 필요한 라이브러리를 사용하기 위해 라이브러리 사이트를 찾아 .jar 파일을 다운로드 받아야 한다. 또한, 코드에서 라이브러리를 사용하기 위해 이클립스 Project Build Path에 라이브러리를 추가해야 한다. 즉, 스프링 이니셜라이저 사이트를 통해 추가한 6개의 기본적인 라이브러리를 제외한, 필요 라이브러리의 개수만큼 .jar 파일을 다운받고 디렉터리에 설치하는 반복 작업을 하게 되는 것이다. 또한, 라이브러리 사이트가 이전된 경우 파일을 다운로드 받지 못하고 혼자 리서치를 해야 한다.

 

프로젝트가 커져 여러 가지 빌드를 나눠 작업해야 할 경우, 디펜던시가 있으면 빌드 순서를 고려해야 한다. 많은 프로젝트들이 프로덕션 릴리즈 빌드 과정에서 빌드 → 단위 테스트 실행 작업을 거치게 되는데, 빌드 자동화 툴이 없다면 프로덕션 배포가 있을 때마다 오퍼레이터 혹은 개발자가 모든 라이브러리를 컴파일해 빌드하고 단위 테스트를 실행시키는 작업을 해야 한다. 몇 백 개 이상의 라이브러리를 사용하는 대규모 프로젝트의 해당 작업을 반복적으로 요구하는 것은 인적 자원의 낭비이므로 자동화가 필요하다.

 

빌드 자동화 툴을 사용하면 라이브러리를 다운받는 대신 원하는 라이브러리와 버전을 코드로 작성하고, 오퍼레이터가 직접 컴파일, 빌드, 단위 테스트를 실행하는 대신 해당 과정을 일련의 코드로 적고 빌드 자동화 툴이 코드를 해석하여 프로젝트 빌드에 필요한 작업을 실행해 주는 방식을 사용한다.

 

빌드 자동화 툴 중 하나인 그래들(http://gradle.org/)는 자바, 그루비, 스칼라 등 JVM에서 실행되는 언어의 빌드 자동화를 위해 사용된다. 그래들은 그루비라는 언어로 작성된다.

plugins {
    id 'org.springframework.boot' version '2.7.0-SNAPSHOT'
    id '10.spring.dependency-management version '1.0.11.RELEASE'
    id 'java'
}

group="com.example'
version 0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileonly {
    	extendsFrom annotation Processor
    }
}

repositories {
    mavenCentral()
    maven { url'https://repo.spring.io/milestone' }
    maven { url'https://repo.spring.io/snapshot' }
}

dependencies {
    implementation 'org.springframework.boot: spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test'){
    useJUnitPlatform()
}

예제 2-17. build.gradle

그래들을 프로젝트에서 어떻게 사용하는지 확인. 아래 코드들은 섹션별로 나눠 설명한다.

 

그래들은 의도적으로 많은 기능을 제공하지 않으며, 플러그인으로 그래들을 확장해 사용할 수 있다. 대표적인 예가 Java로 자바 컴파일을 위해서는 그래들 자바 플러그인이 필요하다. 플러그인의 id 'java'는 자바이고 빌드를 위해 자바 플러그인을 사용함을 명시한다. org.springframework.boot나 io.spring.dependency-management도 마찬가지이며, 이 플러그인을 사용하기 위해선 버전 정보를 넘겨 줘야 한다.

 

 

Group, Version, SourceCompatibility

플러그인 바로 밑의 Group, Version, SourceCompatibility 확인, 이는 프로젝트의 메타 데이터.

group = 'com.example'
version = 0.0.1-SNAPSHOT'
sourceCompatibility = '11'

예제 2-18. group, version, sourceCompatibility

  • group은 artifact(애플리케이션) 배포를 위해 사용되며, Spring Initializr의 Project Matadata에서 정의한 Group과 동일.
  • version은 프로젝트이 버전. 이상적으로 프로덕션 배포마다 버전이 올라가며, 어떻게 올리는지는 개개의 프로젝트마다 다르다.
  • sourceCompatibility에 명시된 자바 버전을 이용해 Java 플러그인이 소스를 컴파일한다.

 

 

Lombok

configurations {
    compileOnly {
    	extendsFrom annotationProcessor
    }
}

dependencies {
    // 다른 디펜던시
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    // 다른 디펜던시
}

예제 2-19. 롬복과 어노테이션 프로세서

롬복(Lombok)은 어노테이션을 추가하면 컴파일 시 그에 상응하는 코드를 만들어 주는 라이브러리로, 개발 시간 단축을 위해 사용한다. 롬복이 컴파일 시 코드를 작성하려면 어노테이션 프로세서(annotationProcessor)가 필요하므로, configurations 부분에서 컴파일 당시 annotationProcessor를 사용하라고 그래들에게 알려 준다. 또, 어노테이션 프로세서로 org.projectlombok:lombok을 사용하도록 디펜던시에 명시한다.

 

 

Repository

repositories {
 mavenCentral()
 maven { url'https://repo.spring.io/milestone' }
 maven { url 'https://repo.spring.io/snapshot' }
}

예제 2-20. 리포지터리

그래들이 라이브러리를 다운로드하는 곳을 리포지터리라고 하며, 여기선 메이븐센트럴(mavenCentral, https://mvnrepository.com/repos/central)을 주로 사용한다. 나머지 두 리포지터리는 스프링 관련이다.

 

 

Dependency

dependencies {
 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 implementation 'org.springframework.boot:spring-boot-starter-web'
 compileOnly 'org.projectlombok:lombok'
 runtimeonly 'com.h2database:h2'
 annotationProcessor 'org.projectlombok:lombok'
 testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

예제 2-21. 디펜던시

dependency 섹션에서 프로젝트에서 사용할 라이브러리를 명시하면 그래들이 리포지터리에서 라이브러리를 다운 및 설치한다. implementation, runtimeOnly 등은 다운로드 후 라이브러리의 scope에 대한 내용. 각 메서드의 의미는 그래들 자바 라이브러리 컨피규레이션(https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph)를 참조.

 

The Java Library Plugin

The key difference between the standard Java plugin and the Java Library plugin is that the latter introduces the concept of an API exposed to consumers. A library is a Java component meant to be consumed by other components. It’s a very common use case

docs.gradle.org

 

 

Test

test {
    useJUnitPlatform()
}

예제 2-22. 테스트

그래들을 이용한 단위 테스트. build.gradle의 test에는 테스트 관련 설정이 가능. 이 build.gradle에서는 JUnitPlatform을 사용해 단위 테스트를 하도록 명시했다. 

 

 

디펜던시 라이브러리 추가

디펜던시 라이브러리, 구글 구아바 라이브러리를 추가하는 실습. 

 

메이븐 센트럴을 사용하므로 메이븐 지포지터리를 이용해 라이브러리를 추가한다.  원하는 라이브러리(google guava)를 메이븐 센트럴(http://mvnrepository.com/)에서 검색한다.

라이브러리 검색 결과에서 Guava: Google Core Libraries For Java 선택. 선택 시 버전을 선택하는 화면으로 전환.

라이브러리 페이지의 하단 테이블에서 원하는 버전 선택. 고민이 된다면 최근 버전 중에 Usage가 많은 버전 선택. 책에서는 31.1-jre 선택.

버전 선택 시 하단에 Maven, Gradle, SBT, Ivy 등 각 빌드 자동화 툴마다 어떤 코드를 추가해야 할지 알려 준다. 그래들을 선택하면 인풋 필드에 예제 2-23과 같은 코드가 뜬다.

// https://mvnrepository.com/artifact/com.google.guava/guava
implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre'

예제 2-23. 구글 구아바 그래들 스니펫

해당 코드를 실습 코드 2-3과 같이 build.gradle dependency 부분에 추가한다.

 

dependencies {
 implementation 'org.springframework.boot: spring-boot-starter-data-jpa'
 implementation 'org.springframework.boot: spring-boot-starter-web'
 compileOnly 'org.projectlombok:lombok'
 runtimeOnly 'com.h2database:h2'
 annotationProcessor 'org.projectlombok:lombok'
 testImplementation 'org.springframework.boot:spring-boot-starter-test'
 // <https://mvnrepository.com/artifact/com.google.guava/guava>
 implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre'
}

실습 코드 2-3.구글 구아바 그래들 스니펫에 추가하기

 

롬복

롬복 라이브러리를 이용하면 롬복이 제공하는 어노테이션 프로세서(annotation processor)가 getter, setter, builder, constructor 프로젝트 컴파일 시 관련 코드를 작성해 준다. 즉, 코드의 양과  개발 시간 단축의 이점이 있다.

 

이클립스에 롬복 설치

이클립스에서 롬복 사용을 위해 Jar 파일을 이용해 플러그인을 설치해야 한다. 메이븐 리포지터리(http://mvnrepository.com/artfact/org.projectlombok/lombok)에서 원하는 라이브러리 버전의 Jar 다운.

이 책에서는 1.18.22 사용하므로 1.18.22 리포지터리(http://mvnrepository.com/artfact/org.projectlombok/lombok/1.18.22)로 가 File란에 있는 Jar 눌러 jar 파일 다운로드 후, cmd 또는 터미널, 파워셸을 켜 Jar 파일이 다운로드된 디렉터리로 이동.

java -jar lombok-1.18.22.jar

실습 코드 2-4. 롬복 설치하기

해당 명령어로 롬복 설치. 커맨드라인을 실행하여 롬복 설치 화면이 뜨면 IDE를 추가하는 화면이 나온다.

윈도우의 경우 인스톨러가 이클립스 경로를 자동으로 찾아 준다. IDEs란에 이클립스가 보이지 않으면 Specify location을 눌러 이클립스가 설치된 경로 명시 후, Install/Update를 눌러 진행한다.

설치가 완료되면 우측 하단의 QuitInstaller 클릭 후 이클립스를 재시작한다.

annotationProcessor(“org.projectlombok:lombok”)
compileOnly(“org.projectlombok:lombok”)

예제 2-24. 롬복 디펜던시 확인

이클립스 재시작 후 builder.gradle의 dependency 부분에 위 코드처럼 롬복 관련 디펜던시가 추가되었는지 확인.

또한, 상단의 Help → About Eclipse IDE에 들어가 About 하단에서 Lombok v1.18.22 설치 여부 확인.

 

 

 

어노테이션 프로세싱 설정

그래들 디펜던시에 어노테이션 프로세서 라이브러리를 추가했지만, 이는 이클립스가 인식하지 못하는 라이브러리다. 이클립스는 롬복으로 대체한 메서드들의 존재를 모르기 때문에, 이 상태로 어노테이션을 추가하고 이용하려 하면 이클립스가 문법 에러로 간주한다. 따라서, 이클립스가 어노테이션을 이해할 수 있도록 어노테이션 프로세서 설정을 해야 한다.

좌측 프로젝트 패널에서 Demo(마우스 우클릭) → Properties 클릭해 팝업 창이 뜨면, Java Compiler → Annotation Processing을 선택하고 Annotation Processing 관련 체크박스 체크 후 Apply and Close 클릭.

package com.example.demo;

import lombok.Builder;
import lombok. NonNull;
import lombok. RequiredArgsConstructor;

@Builder
@RequiredArgsConstructor
public class DemoModel {

 @NonNull
 private String id;
}

실습 코드 2-5. 롬복 테스팅

롬복 동작 여부 확인을 위해 com.example.demo 아래에 DemoModel.java 클래스를 만들고 실습 코드 2-5 작성 후, 컴파일이 되는지 확인. 에러 없이 컴파일이 되면 정상적으로 롬복이 설치된 것이다.

 

 

 

포스트맨 API 테스트

프로젝트에서 개발할 REST API를 테스트하기 위한 툴. REST API는 URI(Unified Resource Identifie), HTTP 메서드, 요청 매개변수 또는 요청 바디로 구분되는데, 브라우저 테스팅은 한계가 있으며, 테스팅을 위해 임시 프론트엔드 UI를 만드는 것은 지속 가능 방법이 아니다. cURL 커맨드 툴을 사용하는 방법은 초보자에게는 어렵다. 따라서, 사용이 간편하고 직관적인 GUI를 제공하는 포스트맨(Postman) 프로그램을 사용한다.

 

포스트맨은 간단히 RESTful API 테스트가 가능하고, 테스트를 저장해 API 스모크 테스팅(Smoke Testing)용으로 사용할 수 있다. 포스트맨 다운로드(http://www.postman.com/downloads/)

 

Download Postman | Get Started for Free

Try Postman for free! Join 20 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

www.postman.com


로그인 창에서 'Skip and go to the app'를 클릭하면 로그인 없이 포스트맨 사용 가능.


API 테스트를 위해 + 버튼을 눌러 새 API 요청 작성.

실습을 위해 Get 부분에 www.google.com 을 입력하고  Send를 누르면, 구글 메인 페이지의 HTML이 하단 결과 부분에 출력된다.

 

스프링 부트 애플리케이션을 다시 실행 후 포스트맨을 이용해 localhost:8080 HTTP GET 요청을 날린다면 무엇이 반환되가? 브라우저에서 보이는 것과 비교했을 때 무엇이 다른가?

정리

기본적인 설정 및 설치 작업을 진행.

  • 자바와 이클립스를 설치.
  • 스프링이 해결하는 문제인 자바 오브젝트(빈)을 관리하는 의존성 주입 컨테이너와 웹 서비스 개발을 도와주는 스프링 웹의 디스패처 서블릿에 대해 알아보고, 스프링 부트 프로젝트를 설정. DemoApplication.java의 어노테이션과 메인 메서드의 의미를 알아보고 스프링 부트 애플리케이션을 실행.
  • 빌드 자동화 툴 그래들에 대해 알아보고, 메이븐 리포지터리를 통해 라이브러리를 찾고 그래들에 추가하는 과정 실습.
  • 포스트맨 프로그램 설치 및 사용법 실습.
실습 내용 이론 내용
자바/이클립스 설치 <없음>
스프링 부트 프로젝트 설정 스프링 의존성 주입, 스프링 웹 디스패쳐 서블릿
메이븐 리포지터리를 이용한 라이브러리 추가 그래들과 빌드 자동화 툴
포스트맨 API 테스팅

 

728x90