스프링 핵심가이드 1주차

본 게시글은 ‘스프링 부트 핵심 가이드’ 책의 내용을 정리한 것입니다.
저자 : 장정우
출판사 : 위키북스

스프링 부트란?

스프링 프레임워크(Spring Framework)

스프링 프레임워크는 자바(Java) 기반의 어플리케이션 프레임워크로, 엔터프라이즈급 애플리케이션을 개발하기 위한 다양한 기능을 제공합니다. 이 프레임워크는 현재 우리나라의 전자정부 표준 프레임워크의 기반 기술로 채택되어 공공기관 웹 서비스를 개발할 때 널리 사용되고 있습니다. 오픈소스인 스프링 프레임워크는 다음과 같은 주요 개념들을 포함하고 있습니다.

제어 역전(IoC: Inversion of Control)

일반적인 자바 개발에서는 객체의 의존성을 개발자가 직접 생성하고 제어합니다. 그러나 제어 역전(IoC) 개념을 사용하면 객체를 직접 생성하지 않고, 객체의 생명주기 관리를 외부 컨테이너에 위임하게 됩니다. 스프링 컨테이너 또는 IoC 컨테이너가 객체 관리의 제어권을 가지는 것을 제어 역전이라고 합니다.

의존성 주입(DI: Dependency Injection)

의존성 주입은 필요한 객체를 직접 생성하지 않고, 외부 컨테이너가 생성한 객체를 주입받아 사용하는 방식입니다. 스프링 프레임워크는 다양한 방식으로 의존성 주입을 지원합니다.

생성자를 통한 의존성 주입

@RestController
public class DIController {
    private final Myservice myService;

    @Autowired
    public DIController(Myservice myService) {
        this.myService = myService;
    }

    @GetMapping("/di/hello")
    public String getHello() {
        return myService.getHello();
    }
}

필드 객체 선언을 통한 의존성 주입

@RestController
public class FieldInjectionController {
    @Autowired
    private Myservice myService;
}

Setter 메서드를 통한 의존성 주입

@RestController
public class SetterInjectionController {
    private Myservice myService;

    @Autowired
    public void setMyService(Myservice myService) {
        this.myService = myService;
    }
}

관점 지향 프로그래밍(AOP: Aspect-Oriented Programming)

AOP는 관점을 기준으로 묶어서 개발하는 방식입니다. OOP(객체지향 프로그래밍)의 추상화, 캡슐화, 상속, 다형성 개념을 활용하면서, 핵심 기능과 부가 기능을 각각 구분하여 하나의 관점으로 처리합니다.

예시

핵심 기능은 비즈니스 로직을 구현하는 과정에서 비즈니스 로직이 처리하는 목적 기능입니다. 예를 들어, 클라이언트로부터 상품 정보 등록 요청을 받아 데이터베이스에 저장하고, 그 상품 정보를 조회하는 비즈니스 로직을 구현하는 것이 핵심 기능입니다. 반면에, 로깅, 보안, 트랜잭션 관리와 같은 부가 기능은 비즈니스 로직과 별도로 분리하여 관리할 수 있습니다.

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Logging before method: " + joinPoint.getSignature().getName());
    }
}

AOP의 관점에서 부가 기능은 핵심 기능이 어떤 기능인지와 무관하게 로직이 수행되기 전후에 수행되기 때문에, 여러 비즈니스 로직에 반복되는 부가 기능을 하나로 묶어서 공통 로직으로 처리할 수 있습니다. 이는 다음과 같은 방식으로 구현할 수 있습니다:

  • 컴파일 과정에서 삽입하는 방식
  • 바이트코드를 메모리에 로드하는 과정에 삽입하는 방식
  • 프락시 패턴을 이용한 방식

스프링 부트(Spring Boot)

스프링 부트는 스프링 프레임워크의 확장판으로, 설정을 간소화하고 개발 생산성을 높이기 위해 만들어졌습니다. 스프링 부트는 다양한 “starter” 의존성을 제공하여 개발자가 손쉽게 필요한 기능을 추가할 수 있도록 도와줍니다.

의존성 관리

스프링 프레임워크에서는 각 모듈의 의존성을 직접 설정하고 호환되는 버전을 명시해야 합니다. 그러나 스프링 부트에서는 “spring-boot-starter” 의존성을 통해 자주 사용되는 라이브러리와 호환되는 버전의 모듈 조합을 제공합니다.

주요 Starter 의존성

  • spring-boot-starter-web: 스프링 MVC를 사용하는 RESTful 애플리케이션을 만들기 위한 의존성입니다. 기본으로 내장 톰캣이 포함되어 있어 jar 형식으로 실행 가능합니다.
  • spring-boot-starter-test: JUnit Jupiter, Mockito 등의 테스트용 라이브러리를 포함합니다.
  • spring-boot-jdbc: HikariCP 커넥션 풀을 활용한 JDBC 기능을 제공합니다.
  • spring-boot-starter-security: 스프링 시큐리티(인증, 권한, 인가 등) 기능을 제공합니다.
  • spring-boot-starter-data-jpa: 하이버네이트를 활용한 JPA 기능을 제공합니다.
  • spring-boot-starter-cache: 스프링 프레임워크의 캐시 기능을 지원합니다.

자동 설정

스프링 부트는 스프링 프레임워크 기능을 사용하기 위한 자동 설정(Auto Configuration)을 지원하여, 라이브러리를 실행하는 데 필요한 환경을 자동으로 설정합니다.

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

@SpringBootApplication 어노테이션은 여러 어노테이션을 합쳐놓은 인터페이스입니다.

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

스프링 부트가 실행되면, 우선 @ComponentScan 어노테이션이 시리즈 어노테이션이 붙은 클래스를 빈으로 등록합니다. @Component 어노테이션 시리즈에는 다음과 같은 어노테이션들이 포함됩니다:

  • @Controller
  • @RestController
  • @Service
  • @Repository
  • @Configuration

내장 WAS

스프링 부트의 각 웹 애플리케이션에는 내장 WAS(Web Application Server)가 존재합니다. 가장 기본적인 의존성인 spring-boot-starter-web은 톰캣을 내장하고 있습니다.

모니터링

개발이 끝나고 서비스를 운영할 때 시스템에 사용되는 스레드, 메모리, 세션 등을 모니터링하기 위해 스프링 부트 액추에이터(Spring Boot Actuator) 도구가 있습니다. 이를 통해 애플리케이션의 상태를 모니터링하고, 필요한 정보를 실시간으로 제공받을 수 있습니다. 이와 같이 스프링 프레임워크와 스프링 부트는 엔터프라이즈급 애플리케이션 개발을 위한 강력한 도구들을 제공하여, 개발자가 보다 효율적으로 애플리케이션을 개발하고 유지보수할 수 있도록 도와줍니다.

개발에 앞서 알면 좋은 기초 지식

서버 간 통신 및 마이크로서비스 아키텍처(MSA)

2.1 서버 간 통신

애플리케이션 하나에 여러 기능을 넣어 개발하지 않고, 애플리케이션을 기능별로 나눠서 개발하는데 이를 ‘마이크로서비스 아키텍처(MSA; Microservice Architecture)’라고 합니다. 이렇게 기능별로 구분해서 독립적인 애플리케이션을 개발하게 되면 각 서비스 간에 서로 통신해야 하는 경우가 발생합니다. 이러한 통신을 ‘서버 간 통신’이라고 합니다.

서버 간 통신은 한 서버가 다른 서버에 통신을 요청하는 것을 의미하고, 한 대는 서버, 다른 한 대는 클라이언트가 되는 구조입니다. 다양한 통신 방식이 존재하지만 가장 많이 사용되는 방식은 HTTP/HTTPS 방식입니다.

2.2 스프링 부트의 동작 방식

스프링 부트에서 ‘spring-boot-starter-web’ 모듈을 사용하면 톰캣을 사용하는 스프링 MVC 구조를 기반으로 동작합니다.

스프링 부트 동작 구조

서블릿(Servlet)은 클라이언트 요청을 처리하고 결과를 반환하는 자바 웹 프로그래밍 기술입니다. 서블릿은 서블릿 컨테이너에서 관리하며, 서블릿 컨테이너는 서블릿 인스턴스를 생성하고 관리하는 역할을 수행합니다. 톰캣은 WAS의 역할과 서블릿 컨테이너의 역할을 수행하는 대표적인 컨테이너입니다.

서블릿 컨테이너의 특징

  • 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기 관리
  • 서블릿 객체는 싱글톤 패턴으로 관리
  • 멀티 스레딩 지원

스프링에서는 DispatcherServlet이 서블릿의 역할을 수행합니다. 스프링은 톰캣을 임베드해 사용하기 때문에 서블릿 컨테이너와 DispatcherServlet은 web.xml의 설정값을 공유합니다.

DispatcherServlet을 통해 요청이 들어오면 핸들러 매핑을 통해 요청 URI에 매핑된 핸들러를 탐색합니다. (핸들러 매핑은 어떤 컨트롤러를 사용할지 선정하는 인터페이스) 그리고 핸들러 어댑터를 통해 컨트롤러를 호출합니다. 응답이 돌아오면 ModelAndView로 반환하고, 뷰 리졸버를 통해 뷰를 반환받아 리턴합니다.

스프링 프레임워크는 일반적으로 뷰 이름을 리턴해 사용자에게 뷰를 통해 출력되게 합니다. 하지만 REST 형식의 @ResponseBody 혹은 @RestController는 뷰를 통해 출력되지 않기에 뷰 리졸버를 호출하지 않고 MessageConverter(요청과 응답에 대해 Body값 변환하는 역할)를 거쳐 JSON 형식으로 변환해서 응답합니다.

2.3 레이어드 아키텍처(Layered Architecture)

레이어드 아키텍처란 애플리케이션의 컴포넌트를 유사 관심사를 기준으로 레이어로 묶어 수평적으로 구성한 구조를 의미합니다. 일반적으로 3계층 혹은 4계층 구성을 의미하며, 이 차이는 인프라 레이어(데이터베이스)의 추가 여부로 결정됩니다.

스프링의 레이어드 아키텍처

프레젠테이션 계층

  • 유저 인터페이스(UI; User Interface) 계층이라고도 합니다.
  • 클라이언트와의 접점이 됩니다.
  • 클라이언트로부터 데이터와 함께 요청을 받고 처리 결과를 응답으로 전달하는 역할을 합니다.

비즈니스 계층

  • 서비스(Service) 계층이라고도 합니다.
  • 핵심 비즈니스 로직을 구현하는 영역입니다.
  • 트랜잭션 처리나 유효성 검사 등의 작업도 수행합니다.

데이터 접근 계층

  • 영속(Persistence) 계층이라고도 합니다.
  • 데이터베이스에 접근해야 하는 작업을 수행합니다.
  • Spring Data JPA에서는 DAO 역할을 Repository가 수행하기 때문에 Repository로 대체할 수 있습니다.

레이어드 아키텍처의 특징

  • 각 레이어는 가장 가까운 하위 레이어의 의존성을 주입받습니다.
  • 각 레이어는 관심사에 따라 묶여 있으며, 다른 레이어의 역할을 침범하지 않습니다.
  • 각 레이어가 독립적으로 작성되면 다른 레이어와의 의존성을 낮춰 단위 테스트에 용이합니다.

2.4 디자인 패턴

디자인 패턴은 소프트웨어를 설계할 때 자주 발생하는 문제들을 해결하기 위해 고안된 해결책입니다.

GoF의 디자인 패턴 분류

생성 패턴

객체 생성에 사용되는 패턴으로 객체를 수정해도 호출부가 영향을 받지 않게 합니다.

  • 추상 팩토리: 구체적인 클래스를 지정하지 않고 상황에 맞는 객체를 생성하기 위한 인터페이스를 제공하는 패턴
  • 빌더: 객체의 생성과 표현을 분리해 객체를 생성하는 패턴
  • 팩토리 메서드: 객체 생성을 서브클래스로 분리해서 위임하는 패턴
  • 프로토타입: 원본 객체를 복사해 객체를 생성하는 패턴
  • 싱글톤: 한 클래스마다 인스턴스를 하나만 생성해 인스턴스가 하나임을 보장하고 어느 곳에서도 접근할 수 있게 제공하는 패턴

구조 패턴

객체를 조합해서 더 큰 구조를 만드는 패턴

  • 어댑터: 클래스의 인터페이스를 의도하는 인터페이스로 변환하는 패턴
  • 브리지: 추상화와 구현을 분리해 각각 독립적으로 변형하게 하는 패턴
  • 컴포지트: 여러 객체로 구성된 복합 객체와 단일 객체를 클라이언트에서 구별 없이 다루는 패턴
  • 데코레이터: 객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 하는 패턴
  • 퍼사드: 서브시스템의 인터페이스 집합들에 하나의 통합된 인터페이스를 제공하는 패턴
  • 플라이웨이트: 특정 클래스의 인스턴스 한 개를 가지고 여러 개의 ‘가상 인스턴스’를 제공할 때 사용하는 패턴
  • 프락시: 특정 객체를 직접 참조하지 않고 해당 객체를 대행(프락시)하는 객체를 통해 접근하는 패턴

행위 패턴

객체 간의 알고리즘이나 책임 분배에 관한 패턴으로 객체 하나로는 수행할 수 없는 작업을 여러 객체를 이용해 작업을 분배합니다. 결합도 최소화를 고려할 필요가 있습니다.

  • 책임 연쇄: 요청 처리 객체를 집합으로 만들어 결합을 느슨하게 만드는 패턴
  • 커맨드: 실행될 기능을 캡슐화해 주어진 여러 기능을 실행하도록 클래스를 설계하는 패턴
  • 인터프리터: 주어진 언어의 문법을 위한 표현 수단을 정의하고 해당 언어로 구성된 문장을 해석하는 패턴
  • 이터레이터: 내부 구조를 노출하지 않으면서 해당 객체의 집합 원소에 순차적으로 접근하는 방법을 제공하는 패턴
  • 미디에이터: 한 집합에 속한 객체들의 상호작용을 캡슐화하는 객체를 정의한 패턴
  • 메멘토: 객체의 상태 정보를 저장하고 필요에 따라 상태를 복원하는 패턴
  • 옵저버: 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버 목록을 객체에 등록해 상태가 변할 때마다 메서드 등을 통해 객체가 직접 옵저버에게 통지하게 하는 디자인 패턴
  • 스테이트: 상태에 따라 객체가 행동을 변경하게 하는 패턴
  • 스트래티지: 행동을 클래스로 캡슐화해 동적으로 행동을 바꿀 수 있게 하는 패턴
  • 템플릿 메서드: 일정 작업을 처리하는 부분을 서브클래스로 캡슐화해 전체 수행 구조는 바꾸지 않으면서 특정 단계만 변경해 수행하는 패턴
  • 비지터: 실제 로직을 가지고 있는 객체(visitor)가 로직을 적용할 객체(element)를 방문해 실행하는 패턴

2.5 REST API

2.5.1 REST란?

‘Representational State Transfer’의 약자로 하이퍼미디어 시스템 아키텍처의 한 형식입니다. 주고받는 자원(Resource)에 이름을 규정하고 URI에 명시해 HTTP 메서드를 통해 해당 자원의 상태를 주고받는 것을 의미합니다.

2.5.2 REST API란?

API는 애플리케이션에서 제공하는 인터페이스를 의미합니다. 이를 통해 서버 또는 프로그램 사이를 연결할 수 있습니다. REST API는 REST 아키텍처를 따르는 시스템/애플리케이션 인터페이스입니다.

2.5.3 REST의 특징

유니폼 인터페이스

일관된 인터페이스를 의미합니다. REST 서버는 HTTP 표준 전송 규약을 따

르기 때문에 타 언어, 플랫폼, 기술 등과 호환해 사용할 수 있습니다.

무상태성

서버에 상태 정보를 따로 보관하거나 관리하지 않는다는 의미입니다. 한 클라이언트가 여러 요청을 보내든 여러 클라이언트가 각각 하나의 요청을 보내든 개별적으로 처리합니다. 서버가 불필요한 정보를 관리하지 않으므로 비즈니스 로직의 자유도가 높고 설계가 단순합니다.

캐시 가능성

HTTP의 캐싱 기능을 적용할 수 있습니다. 이 기능을 이용하기 위해서는 응답과 요청이 모두 캐싱 가능한지(Cacheable) 명시가 필요하며, 캐싱이 가능한 경우 클라이언트에서 캐시에 저장해두고 같은 요청에 대해서는 해당 데이터를 가져다 사용합니다. 이 기능을 사용하면 서버의 트랜잭션 부하가 줄어 효율적이며, 사용자 입장에서 성능이 개선됩니다.

레이어 시스템

REST 서버는 네트워크 상의 여러 계층으로 구성될 수 있으나, 클라이언트는 서버와 연결되는 포인트만 알면 됩니다.

클라이언트-서버 아키텍처

REST 서버는 API를 제공하고 클라이언트는 사용자 정보를 관리하는 구조로 분리해 설계합니다. 이는 서로에 대한 의존성을 낮추는 기능을 합니다.

2.5.4 REST의 URI 설계 규칙

  1. URI의 마지막에는 ‘/’를 포함하지 않습니다.
  2. 언더바(_) 대신 하이픈(-)을 이용합니다.
  3. URI에는 행위(동사)가 아닌 결과(명사)를 포함합니다.
  4. URI는 소문자로 작성합니다.
  5. 파일의 확장자는 URI에 포함하지 않습니다.

이와 같이, 서버 간 통신과 마이크로서비스 아키텍처는 단일 서비스 구성의 문제점을 해결하고, 서비스의 독립성과 유연성을 높이며, REST API를 통해 효율적인 자원 관리를 가능하게 합니다.

개발 환경 구성

JDK 환경변수 설정과 인텔리제이 설치 시 옵션 선택 부분에 대해 집중적으로 설명합니다. 전체 설치 과정은 생략하고, 중요한 부분만 다룹니다.

개발 환경

  • 운영체제: Windows 10 64-bit
  • IDE: 인텔리제이(IntelliJ) Ultimate
  • JDK: 11 (1.8 버전 이상)
  • 스프링 부트: 2.5.6 ~ 2.5.8
  • 데이터베이스: MariaDB 10.6.5
  • 빌드 도구: 메이븐(Maven)

1. 자바 JDK 설치 시 환경변수 설정

JDK를 설치한 후 환경변수를 추가해야 합니다. 만약 .msi 파일로 자바를 설치하면 자동으로 환경변수가 추가되기도 합니다. 수동으로 환경변수를 추가하는 방법은 다음과 같습니다.

1.1 시스템 환경변수 설정

  1. 제어판시스템 및 보안시스템으로 이동합니다. (Windows 11에서는 바탕화면에 “내 컴퓨터”를 마우스 우클릭한 후 “속성”을 클릭하면 됩니다.)
  2. 고급 시스템 설정을 클릭합니다.
  3. 고급 탭에서 환경 변수를 클릭합니다.

1.2 JAVA_HOME 변수 설정

  1. 시스템 변수에서 새로 만들기를 클릭하여 JAVA_HOME 변수를 만듭니다.
  2. JDK를 설치한 경로를 JAVA_HOME 변수에 지정합니다. 예: C:\Program Files\Java\jdk-11

1.3 Path 변수 설정

  1. 시스템 변수 목록에서 Path를 선택하고 편집을 클릭합니다.
  2. 새 항목을 추가하여 %JAVA_HOME%\bin을 입력합니다.

이로써 JDK 환경변수 설정이 완료되었습니다. JDK 버전을 변경할 때는 JAVA_HOME 변수의 경로를 새로운 JDK 폴더로 지정하면 됩니다.

2. 인텔리제이(IntelliJ) 설치 시 옵션 설정

인텔리제이를 설치할 때 다음 옵션들을 선택하여 설치합니다.

2.1 설치 옵션 선택

  1. 설치 과정에서 아래와 같이 옵션을 체크한 후 Next를 클릭합니다.
  • Create Desktop Shortcut: 바탕화면에 바로가기 아이콘을 생성합니다.
  • Update PATH Variable: 인텔리제이 bin 폴더 경로를 환경변수에 추가합니다.
  • Update Context Menu: 폴더를 마우스 우클릭 시 인텔리제이 프로젝트로 열기 메뉴를 추가합니다.
  • Create Associations: 특정 확장자 파일을 인텔리제이로 열 수 있도록 설정합니다.

2.2 설치 마무리

설치가 완료된 후, 선택한 옵션에 따라 인텔리제이가 환경변수에 추가되고, 컨텍스트 메뉴 및 파일 연동 설정이 적용됩니다.

요약

  • JDK 설치 후 JAVA_HOME 변수와 Path 변수를 설정하여 환경변수를 구성합니다.
  • 인텔리제이 설치 시 필요한 옵션들을 선택하여 환경변수 및 파일 연동 설정을 적용합니다.

이로써 개발 환경 구성이 완료되었습니다. 필요한 설정을 완료한 후 개발을 시작할 수 있습니다.

Leave a comment