Spring Framework
Spring Framework Basic
프로그래밍 세계에 처음 입문하는 분들이 하나의 핵심 프로그래밍 언어에 익숙해지기까지는 일정 기간의 시간이 필요한 것이 사실입니다. 여러분들이 앞에서 학습하면서 익숙해진 Java의 기본 문법만으로 어떠한 소프트웨어를 만들 수 있을까요?
아마도 콘솔 출력 프로그램 또는 Java의 GUI API인 AWT나 Swing을 사용한 데스크톱 애플리케이션 정도가 될 것입니다. 하지만 여러분들이 앞으로 만들어야 하는 소프트웨어는 서버에서 동작하는 Java 기반의 웹 애플리케이션입니다.
Java 기반의 웹 애플리케이션을 만드는 다양한 기술들이 존재하는데, 그중에서 여러분들이 앞으로 배워야 할 기술은 바로 Spring Framework이라는 오픈소스 기반의 기술입니다.
- Spring Framework(줄여서 Spring)은 Java 기반의 웹 애플리케이션을 개발하는 개발자가 되기 위해서 반드시 익혀야 되는 핵심 중에 핵심 기술입니다.
Spring Framework의 핵심 개념들을 이해하는 것이 쉽지 않은 것은 사실입니다. 여러분들이 Spring Framework 학습을 진행하다 보면 ‘이 개념은 도저히 이해가 되지 않아’라고 생각하는 내용들이 꽤나 많을 수 있다고 생각합니다.
하지만 이번 유닛부터 단계적으로 천천히 그리고 포기하지 말고 꾸준히 Spring에 대한 학습을 진행한다면 어느새 Spring에 익숙해져 있는 자신을 보게 될 것이라고 생각합니다.
이번 유닛에서는 Spring Framework이 무엇인지, Spring Framework을 왜 배워야 하는지 등 Spring Framework의 기본 개념을 알아보면서 Spring Framework과 친해지는 시간을 가져보도록 하겠습니다.
Spring Framework을 여러분들 것으로 만들기 위해 마지막까지 잘 따라와 주시길 바랍니다!
학습 목표
- Spring Framework이 무엇인지 이해할 수 있다.
- Spring Framework을 왜 배워야 하는지 이해할 수 있다.
- Spring Framework의 아키텍처를 버드 아이 뷰 관점에서 이해할 수 있다.
- Spring Framework 모듈이 무엇이고 Spring Framework에서 지원하는 모듈에는 어떤 것이 있는지 이해할 수 있다.
- Spring Boot이 무엇이고 Spring Boot을 왜 사용해야 하는지 이해할 수 있다.
실습 환경 구성
본격적인 실습은 Section 3부터 진행되겠지만 Section 2의 일부 유닛에서도 여러분들이 의미 있는 실습을 진행할 예정이므로 학습이 시작되기 전에 실습 환경부터 먼저 구성해 보도록 하겠습니다.
실습 환경은 다음의 요구 조건을 필요로 합니다.
- 여러분의 PC에 JDK-11이 설치되어 있어야 합니다.
- 여러분의 환경 변수에 JAVA_HOME 설정이 되어 있어야 합니다.
- 여러분의 PC에 IntelliJ Community Edition이 설치되어 있어야 합니다.
실습 환경 구성은 아래의 순서로 진행하니, 천천히 잘 따라 하길 바랍니다.
Spring Boot 기반의 샘플 프로젝트 생성
Spring Boot 샘플 프로젝트를 생성하는 방법은 다양하지만 IntelliJ Community Edition에서 Spring Boot 샘플 프로젝트를 생성하는 가장 쉬운 방법은 Spring 공식 사이트에서 Spring Initializr 기능을 이용하는 것입니다.
- 아래 링크를 통해 Spring Initializr 공식 사이트로 이동합니다. Spring Initializr 공식 사이트에서는 여러분이 원하는 Spring Boot 실행 환경과 필요한 모듈을 손쉽게 포함하는 템플릿 프로젝트를 생성해 줍니다.
현재 스프링부트 2.x버전의 지원이 중단되었습니다. 아래 레포에서 템플릿 프로젝트를 다운받을 수 있습니다.
- 템플릿 프로젝트 Repository
- [그림 2-0-3]의 (1)과 같이 다운로드한 샘플 프로젝트 압축 파일의 압축을 해제합니다.
[그림 2-0-3] 다운로드한 템플릿 프로젝트 압축 파일의 압축 해제 모습
여러분들이 앞으로 Section의 주 차별로 템플릿 프로젝트를 자주 생성해야 될 수도 있으므로 템플릿 프로젝트는 ‘project’ 같은 폴더를 하나 만들어서 가급적 한 곳에서 관리를 하길 권장합니다.
템플릿 프로젝트를 IntelliJ에서 열기
- IntelliJ를 실행합니다. (아래 두 가지 방법 중 해당되는 한 가지 방법대로 진행해 주세요)
- 만약 IntelliJ를 최초로 실행한 경우라면 [그림 2-0-4]와 같은 창이 열릴 것입니다.
[그림 2-0-4] IntelliJ 설치 후, 최초 실행 시
이 경우, [그림 2-0-4]와 같이 [Open] 버튼을 눌러서 압축 해제한 템플릿 프로젝트 폴더를 선택합니다.
- 만약 IntelliJ로 프로젝트를 생성한 적이 있다면 [그림 2-0-5]와 같이 마지막에 오픈했던 프로젝트가 열릴 것입니다.
이 경우, [그림 2-0-5]와 같이 좌측 상단의 [File] > [Open] 메뉴를 눌러서 압축 해제한 템플릿 프로젝트 폴더를 선택합니다.
[그림 2-0-6] 압축 해제한 템플릿 프로젝트 폴더를 선택하는 모습
[그림 2-0-6]과 같이 압축 해제한 템플릿 프로젝트 폴더를 선택한 후, [OK] 버튼을 클릭합니다.
- IntelliJ IDE에서 지금 오픈하고 있는 프로젝트를 신뢰하는지를 묻습니다.
[그림 2-0-7] 오픈하려는 프로젝트의 신뢰 여부를 묻는 창
[Trust Project] 버튼을 클릭합니다.
- 오픈하려는 프로젝트를 현재 창에서 열 것인지, 새 창에서 열 것인지를 묻습니다.
[그림 2-0-8] 오픈하려는 프로젝트의 현재 창 및 새 창으로 오픈 여부
새 창으로 오픈할 필요가 없다면 [This winodw] 버튼을 클릭합니다.
Gradle Reload
- 템플릿 프로젝트를 오픈하면 Gradle 빌드 툴이 자동으로 빌드 작업을 하기 때문에 [그림 2-0-9]와 같이 [External Libraries] 하위에 의존 라이브러리들이 보여야 합니다.
[그림 2-0-9] 외부 의존 라이브러리들이 다운로드되어 포함된 모습
만약 [External Libraries] 하위에 의존 라이브러리들이 보이지 않을 경우, [그림 2-0-10]과 같이 Gradle 명령으로 프로젝트를 reload 합니다.
[그림 2-0-10] Gradle 프로젝트 Reload
- (1)처럼 [Gradle] 탭을 눌러서 오픈합니다.
- (2) 프로젝트 이름에서 마우스 오른쪽 버튼을 눌러 컨텍스트 메뉴를 오픈한 후, [Reload Gradle Project]를 클릭합니다.
- [External Libraries] 하위에 의존 라이브러리들이 보이는지 확인합니다.
- 이렇게 진행을 했는데도 의존 라이브러리들이 보이지 않는다면 IntelliJ 자체를 닫고 다시 실행시켜보세요.
IntelliJ로 Gradle 기반의 프로젝트를 오픈하면 이따금씩 의존 라이브러리들을 찾지 못하는 경우가 발생하는데, 그땐 [그림 2-0-10]과 같은 순서로 프로젝트를 reload 해주면 대부분 문제가 해결이 됩니다.
Lombok 설정
- IntelliJ 상단의 메인 메뉴에서 [File] > [Settings] 창을 오픈합니다.
[그림 2-0-11] Settings 선택
- [Build, Execution, Deployment] > [Annotation Processors] 메뉴를 선택한 후, [Enable annotation processing] 체크 박스에 체크한 후, [Apply] 버튼과 [OK] 버튼을 차례차례 클릭합니다.
[그림 2-0-12] Lombok 사용을 위한 Annotation processing Enable
[Enable annotation processing] 체크 박스에 체크를 해야 Lombok을 정상적으로 사용할 수 있습니다.
Spring Boot Application 동작 확인
- [그림 2-0-13]과 같이 Section2Week3Application.java 파일을 더블 클릭해서 오픈합니다.
[그림 2-0-13] Section2Week3Application 클래스 파일 오픈
[그림 2-0-14]와 같이 파일 좌측의 녹색 삼각형을 클릭해서 애플리케이션을 시작합니다.
[그림 2-0-14] Section2Week3Application 실행
- [그림 2-0-15]와 같이 애플리케이션이 정상적으로 실행되었는지 확인합니다.
[그림 2-0-15] Section2Week3Application의 정상 동작
(1)과 같은 로그가 출력이 되면 애플리케이션이 정상적으로 실행된 것입니다.
[그림 2-0-15]의 로그 출력 좌측을 보면 (2)와 같이 애플리케이션 실행 아이콘이 계속해서 돌고 있는 것을 볼 수 있습니다.
IntelliJ Community 버전만 사용을 해본 분들이라면 그리 이상한 점을 느낄 수 없지만 IntelliJ Ultimate 버전을 사용하면 저렇게 뱅글뱅글 도는 아이콘을 볼 수 없기 때문에 의아해할 수도 있을 것 같습니다.
결론적으로 저렇게 실행 아이콘이 돌고 있는 것은 애플리케이션 자체가 내장 WAS에서 실행이 되고 있음을 의미하는 지극히 정상적인 동작이라는 사실 참고하기 바랍니다.
심화 학습
- 이번 챕터에서 설명한 section2-week3 프로젝트를 직접 생성해서 \[그림 2-0-15\]와 같이 애플리케이션이 정상 동작하는 것을 확인해 보세요.
- 애플리케이션이 정상 동작하지 않는다면 실행 로그에 어떤 에러가 발생하는지 확인한 후, 구글에서 검색해서 확인해 보세요.
- 해결을 하지 못해도 괜찮습니다. 해결을 못하더라도 스스로 문제를 해결하려고 시도하는 것과 누군가가 처음부터 해결해 주는 것은 엄청난 차이가 있습니다.
- 실습을 진행하면서 문제가 발생한 부분을 기록하기 위한 에러 노트 같은 게 있으면 좋습니다. 아래와 같은 내용을 구글 워드나 에버노트, Notion 등에 기록해 두면 나중에 같은 문제가 발생할 경우 빠르게 해결할 수 있고, 개발자로서 성장하는 밑거름이 됩니다.
- 어떤 문제가 발생했는지..
- 해결하기 위해 어떤 시도를 했는지..
- 해결이 되었는지 해결이 되지 않았는지..
- 어떤 방법으로 해결이 되었는지..
- 애플리케이션이 정상 동작하지 않는다면 실행 로그에 어떤 에러가 발생하는지 확인한 후, 구글에서 검색해서 확인해 보세요.
Spring Framework 소개
이번 챕터에서는 Framework가 어떤 것인지, 그리고 Framework와 Library의 차이를 같이 학습합니다.
웹 애플리케이션을 만들기 위해 사용되는 Framework는 Spring뿐 아니라 다양한 종류의 Framework가 존재합니다. 왜 우리는 Spring을 학습하고 사용하는지 Spring을 사용함으로써 어떤 장점이 있는지를 학습하게 됩니다.
학습 목표
- Framework이 무엇인지 이해할 수 있다.
- Spring Framework을 왜 배워야 하는지 이해할 수 있다.
- Spring Framework과 다른 Framework와의 차이를 이해할 수 있다.
Framework이란?
Framework이란 무엇일까요?
"소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것" - Ralph Johnson - 라고 정의하고 있지만, 이 문장만으로 소프트웨어 관점에서 Framework의 의미를 이해하기는 쉽지 않습니다.
Frame이란 단어는 여러 가지 의미가 있지만 가장 대표적인 Frame의 의미로는 ‘뼈대, ‘틀’, ‘구조’ 등이 있습니다. 일상생활의 예를 들어보면 Frame의 의미를 더 쉽게 이해할 수 있을 것입니다.
우리가 벽에 거는 그림이나 사진 등의 액자를 프레임이라고 부르기도 합니다. 그리고 자동차의 뼈대가 되는 강판으로 된 껍데기 역시 자동차의 프레임이라고 부릅니다.
이처럼 Frame은 어떤 대상의 큰 틀이나 외형적인 구조를 의미하는데 프로그래밍 세계에서의 Frame 역시 이와 비슷한 의미를 가지고 있습니다.
하나의 애플리케이션을 건물이라고 가정한다면, Frame은 위 그림과 같이 건물의 구조라고 이해하면 됩니다.
이런 단어의 뜻을 이해한다면, 소프트웨어 관점에서의 Framework는 우리가 어떠한 애플리케이션을 만들기 위한 틀 혹은 구조를 제공한다고 생각하시면 됩니다.
그럼 실제로 frame이라는 단어가 사용된 예시를 한번 볼까요?
HTML 5 버전에서는 지원하고 있지 않지만 웹의 초창기 시절에는 HTML 문서를 구성하는 태그 중에서 frame이라는 태그가 존재했습니다.
<frameset cols="33%,*,33%">
<frame name="left" src="/left_menu"/>
<frame name="center" src="/context"/>
<frame name="right" src="/right_menu"/>
</frameset>
[코드 2-1]은 웹의 초창기 시절 사용하던 frame 태그의 사용 예시입니다. 보시는 것처럼 frameset과 frame 태그를 이용해서 HTML 문서의 틀만 구성하고 있는 것을 볼 수 있습니다. 그리고 예제 코드에는 나와 있지 않지만 HTML 문서의 세부 내용들은 별도의 html 파일에 구성이 되어 있습니다.
그렇다면 이번에는 frame과 유사한 의미를 가지는 Framework의 예를 들어보겠습니다.
여러분들이 이미 배웠던 Java에서 Framework의 의미를 찾아볼 수 있는데, 그것은 바로 Collections Framework입니다.
여러분들이 이미 알고 계시다시피 Java에서 자주 사용하는 Map이나 Set, List 등의 Collection들은 데이터를 저장하기 위해 널리 알려져 있는 자료구조를 바탕으로 비슷한 유형의 데이터들을 가공 및 처리하기 쉽도록 표준화된 방법을 제공하는 클래스의 집합입니다.
그런데 왜 하필 Java의 Collection에 Framework이라는 용어를 붙였을까요?
여러분들은 ‘틀’이나 ‘뼈대’ 같은 단어를 듣게 되었을 때, Java 언어의 구성요소 중에서 어떤 것이 제일 먼저 떠오르나요?
Java 클래스의 유형 중에서 기본적인 뼈대로만 구성되어 있는 것은 바로 추상 메서드만 정의되어 있는 **인터페이스(Interface)**입니다. 그리고 Java에서의 Collection은 바로 Map, Set, List 같은 인터페이스와 그 인터페이스들을 구현한 구현체들의 집합인 것입니다.
결론적으로 프로그래밍 상에서의 Framework은 기본적으로 프로그래밍을 하기 위한 어떠한 틀이나 구조를 제공한다라는 것을 알 수 있습니다.
마지막으로 Framework를 사용하는 장점과 단점을 살펴볼까요?
- 효율적으로 코드를 작성할 수 있습니다.
- 아무것도 없는 상황에서 코드를 작성하는 것과, 기본 구조가 만들어져 있는 상황에서 코드를 작성하는 것은 많은 차이가 있겠죠? 개발하고자 하는 애플리케이션을 그 밑바닥부터 일일이 전부 개발하는 것이 아니라 서로 다른 애플리케이션 간의 통신이나, 데이터를 데이터 저장소에 저장하는 등의 다양한 기능들 역시 Framework이 라이브러리 형태로 제공함으로써 개발자가 애플리케이션의 핵심 로직을 개발하는 것에 집중할 수 있도록 해줍니다.
- 정해진 규약이 있어 애플리케이션을 효율적으로 관리할 수 있습니다.
- 우리가 사용하는 Framework의 규약에 맞게 코드를 작성하기 때문에, 유지보수가 필요한 경우 더 빠르고 쉽게 문제점을 파악해 수정할 수 있습니다. 동시에 내가 작업했던 코드를 다른 사람이 수정할 경우에도 이미 Framework에 규약에 맞게 작성된 코드이기 때문에, 빠르게 코드를 파악하고 수정하기 용이합니다. 이는 곧 유지보수 이외에도 비슷한 기능을 개발할 때 코드의 재사용이 용이하고 기능의 확장 또한 쉽게 확장이 가능합니다.
그렇다면 단점은 어떤 점이 있을까요?
- 내가 사용하고자 하는 Framework에 대한 학습이 필요합니다.
- 즉 Framework에서 정하는 규약들을 학습할 시간이 필요하다는 의미입니다. Spring의 경우 Java언어에 대한 이해도 필요하지만 추가로 Spring이라는 Framework에 대한 학습이 필요한 이유입니다.
- 자유롭고 유연한 개발이 어렵습니다.
- 우리가 사용하는 Framework에 규약을 벗어나기가 어렵습니다. 가장 처음 보여드렸던 집의 경우 집의 구조자체를 변경하게 될 경우 이미 만들어진 것들을 모두 허물고 새롭게 구조부터 만들어야겠죠? 소프트웨어 관점에서의 Framework도 동일합니다. 이미 만들어진 애플리케이션에서 Framework를 변경하거나, 유연한 개발을 위해 Framework를 사용하지 않게 변경할 경우 정말 많은 시간과 노력이 필요합니다.
이렇게 Framework에 대한 설명과, 사용함으로써 얻을 수 있는 장점과 단점에 대해 살펴보았습니다.
핵심 포인트
- 프로그래밍 상에서의 Framework은 기본적으로 프로그래밍을 하기 위한 어떠한 틀이나 구조를 제공한다.
Framework와 Library의 차이
Framework과 Library의 차이는?
많은 분들이 Framework을 Library와 같은 의미라고 오해하기도 하는데, 그 의미가 헷갈리는 것은 프로그래밍 입문자들에게는 어찌 보면 자연스러운 일이라고 생각합니다.
앞선 유닛에서 Framework에 대해 알아보았으니, Library에 대해서 알아볼까요?
먼저 소프트웨어 관점에서의 Library는 다음과 같이 표현합니다.
Library는 애플리케이션을 개발하는 데 사용되는 일련의 데이터 및 프로그래밍 코드입니다
해당 문구만으로는 정확한 뜻을 이해하기가 어렵습니다. Framework와 어떠한 점이 다른지 구분하기 어렵습니다.
Library 단어의 사전적 의미는 다양하지만, 도서관이라는 의미를 바로 찾을 수 있습니다.
소프트웨어 관점에서도 Library는 애플리케이션을 개발할 때 필요한 기능을 미리 구현해 놓은 집합체라고 생각할 수 있습니다.
그럼, Framework와 Library의 차이점을 실생활에 가까운 예시로 알아볼까요?
우리가 만드는 애플리케이션을 하나의 자동차로 비유해 볼까요? 자동차를 만들기 위해 필요한 것들은 어떤 게 있을까요?
차체를 구성하는 Frame, 그리고 바퀴 혹은 핸들 엔진과 같은 다양한 부품들이 모여서 하나의 자동차를 이루고 있습니다. 이 자동차를 구성하고 있는 요소에서 Framework는 자동차의 뼈대, 즉 Frame을 의미합니다. 그리고, Library는 자동차에서 다양한 기능을 제공하는 부품을 의미합니다. 자동차가 나아가기 위한 바퀴, 엔진 또는 밤에 운전하기 위한 라이트, 비가 올 때 필요한 와이퍼등의 부품이 되겠네요!
실제로 자동차를 구매 후, 부품을 교체할 때 Frame은 쉽게 교체할 수 없습니다. 자동차를 새로 사지 않는 이상 Frame을 교체하는 건 너무 크고 어려운 일이 되겠죠. 하지만 바퀴나 와이퍼, 라이트는 언제든지 쉽게 교체가 가능합니다. 이 말은 곧 소프트웨어 관점에서도 한번 정해진 Framework를 교체하는 일은 어렵지만, Library는 쉽게 교체가 가능하며 필요한 Library들을 선택적으로 사용할 수 있다는 의미입니다.
이를 조금 더 명료하게 표현한다면 애플리케이션에 대한 제어권의 차이가 있다고 표현할 수 있습니다.
아래 코드를 통한 예시를 통해 보도록 합시다.
@SpringBootApplication
@RestController
@RequestMapping(path = "/v1/message")
public class SampleApplication {
@GetMapping
public String getMessage() { // (2)
String message = "hello world";
return StringUtils.upperCase(message); // (1)
}
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
[코드 2-2] Framework과 Library의 차이점을 이해하기 위한 예시 코드
[코드 2-2]는 Java 웹 애플리케이션을 개발하기 위해 우리가 앞으로 배우게 될 Spring Framework 코드 중 일부입니다.
무언가 복잡해 보이지만 Spring Framework에 대한 문법 자체는 몰라도 됩니다.
아니, 모르는 것이 좋습니다.
[코드 2-2]에서 우리가 알아야 하는 것은 Spring Framework에서 사용하는 코드를 통해 Framework과 Library의 차이점이 무엇인지 이해하는 것, 그것 한 가지라는 사실만 기억하면서 가볍게 읽어보면 되겠습니다.
먼저 [코드 2-2]에서 명확하게 Library를 사용하는 부분은 (1)의 ‘StringUtils.upperCase(message)’입니다.
StringUtils 클래스는 Apache Commons Lang3 라이브러리의 유틸리티 클래스 중 하나인데 애플리케이션이 동작하는 중에 이 StringUtils 클래스의 upperCase() 메서드의 파라미터로 전달하는 문자열(message 변수)을 대문자로 변환하고 있습니다.
그렇다면 StringUtils 클래스가 라이브러리라는 것을 어떻게 판단할 수 있을까요?
[코드 2-2]와 같이 애플리케이션 코드는 애플리케이션을 개발하는 사람 즉, 개발자가 작성을 할 것입니다. 이렇게 개발자가 짜 놓은 코드 내에서 필요한 기능이 있으면 해당 라이브러리를 호출해서 사용하는 것이 바로 Library입니다.
즉, 애플리케이션 흐름의 주도권이 개발자에게 있는 것입니다.
그렇다면 Framework은 어떨까요?
[코드 2-2]에서 사용한 애너테이션이나 main() 메서드내의 SpringApplication.run() 메서드는 Spring Framework에서 지원하는 기능들인데 이러한 기능들은 라이브러리와는 다르게 코드 상에는 보이지 않는 상당히 많은 일들을 합니다.
(2)의 getMessage() 메서드 내부의 코드처럼 개발자가 메서드 내에 코드를 작성해 두면, Spring Framework에서 개발자가 작성한 코드를 사용해서 애플리케이션의 흐름을 만들어냅니다.
즉, 애플리케이션 흐름의 주도권이 개발자가 아닌 Framework에 있는 것입니다.
짧은 내용이긴 하지만 Framework과 Library의 차이점을 학습하면서 여러분은 Spring Framework의 핵심 개념인 IoC(Inversion Of Control, 제어의 역전)라는 중요한 개념을 알게 된 것입니다.
지금 당장 Framework과 Library의 차이점이 명확하게 이해되지 않아도 실망할 필요는 없습니다.
여러분들이 Spring Framework의 개념에 익숙해진 후에, 다시 한번 이 챕터의 내용을 확인하신다면 ‘아, 이게 바로 그 의미구나! 별거 아니네?’라는 생각을 할 거라 확신합니다.
핵심 포인트
- Framework은 개발자가 애플리케이션의 핵심 로직을 개발하는 것에 집중할 수 있도록 해준다.
- Library는 애플리케이션 흐름의 주도권이 개발자에게 있는 반면, Framework은 애플리케이션 흐름의 주도권이 개발자가 아닌 Framework에 있다.
Spring Framework이란?
이전 챕터에서 Framework이 무엇인지를 학습했습니다.
또한 간단한 코드 예제를 통해서 Spring Framework의 샘플 코드를 통해서 살펴보았기 때문에 Spring Framework이 웹 애플리케이션을 개발하기 위한 여러 Framework들 중에 하나일 거라고 유추해 볼 수 있습니다.
그렇다면, 다양한 Framework 중 우리는 왜 Spring Framework를 학습하게 될까요?
우리가 웹 애플리케이션 개발을 위한 Framework에는 Spring뿐만 아니라, Django, Express, Flask, Lalavel 등 다양한 Framework를 통해 개발이 가능합니다.
각각 Framework마다 사용하는 언어도 다르고 개발 방법도 조금씩 달라지게 됩니다. Spring Framework만의 장점은 어떤 점이 있을까요?
- POJO(Plan Old Java Object) 기반의 구성
- DI(Dependency Injection) 지원
- AOP(Aspect Oriented Programming, 관점지향 프로그래밍) 지원
- Java 언어를 사용함으로써 얻는 장점
1, 2, 3번의 경우, 이후 다뤄지는 유닛에서 학습하게 됩니다.
4번의 경우 앞서서 학습했던 내용을 다시 떠올려 볼까요?
Java 언어의 특징 중 어떤 점이 장점으로 다가올 수 있을까요?
정적 타입 언어로서 변수의 타입, 메서드의 입력과 출력이 어떤 타입을 가져야 하는지를 강제합니다.
이는 곳 여러 사람이 함께 작업할 때, 다른 사람의 코드 혹은 이전에 내가 작성했던 코드를 수정, 보완이 용이하고 웹 서버를 구축하는 데 있어서 런타임에 발생하는 오류를 사전에 방지할 수 있습니다.
그렇다면 Java 언어를 사용하는 Framework는 Spring 이외에는 없는 걸까요?
Java 기반의 웹 애플리케이션 개발을 위한 Framework에 Spring Framework만 있는 것은 아닙니다.
Apache Struts2나 Apache Wicket, JSF(Java Server Faces), Grails 같은 Java 또는 JVM 기반의 Web Framework들이 존재하며, 현재도 꾸준히 그 기능들이 업데이트되고 있습니다.
그런데 사람들은 왜 유독 Spring Framework에 더 열광을 하는 걸까요?
대부분의 기업들이 기업용 엔터프라이즈 시스템용 애플리케이션 개발에 있어 Framework을 선택할 때, 개발 생산성을 높이고 어떻게 하면 애플리케이션의 유지 보수를 조금 더 용이하게 할 것인가에 많은 초점을 맞추는 것이 사실입니다.
기업용 엔터프라이즈 시스템 기업용 엔터프라이즈 시스템이란 기업의 업무(기업 자체 조직의 업무, 고객을 위한 서비스 등)를 처리해 주는 시스템을 의미합니다. 기업용 엔터프라이즈 시스템은 대량의 사용자 요청을 처리해야 하기 때문에 서버의 자원 효율성, 보안성, 시스템의 안전성이나 확장성 등을 충분히 고려해서 시스템을 구축하는 것이 일반적입니다.
그런데 Spring Framework은 개발 생산성을 향상하고 애플리케이션의 유지 보수를 용이하게 하는 Framework의 기본 목적 그 이상을 달성할 수 있게 해 줍니다.
Spring Framework을 통해 어떠한 것들을 달성할 수 있을지에 대해서 지금 너무 거창하게 그리고 상세하게 글로 설명을 한다고 해서 여러분이 쉽게 체감하기는 어렵다고 생각합니다.
다만 지금은 Spring Framework을 학습함으로 인해서 객체 지향 설계 원칙에 잘 맞는 재사용과 확장이 가능한 애플리케이션 개발 스킬을 향상할 수 있다는 것, 그리고 보다 나은 성능과 서비스의 안전성이 필요한 복잡한 기업용 엔터프라이즈 시스템을 제대로 구축하기 위한 능력을 기를 수 있다는 사실만 기억해 두시길 바랍니다.
Spring Framework을 배워야 하는 이유
그렇다면 이번에는 여러분들이 Spring Framework(이하 줄여서 Spring)을 배워야 하는 이유를 조금은 현실적으로 체감할 수 있도록 과거의 기술과 Spring의 기술을 프로그래밍 코드 기반으로 살펴보겠습니다.
여러분들이 과거의 기술들을 사용해 본 적이 없다면 Spring의 편리함이 덜 와닿을 수 있겠지만, 그렇다 하더라도 과거에 사용한 기술의 불편함을 조금이라도 인식할 수 있다면 여러분들이 Spring을 학습하는 데 있어 약간의 동기부여가 되리라 생각합니다.
지금부터 Spring을 본격적으로 도입하기 전의 기술들을 단계적으로 살펴볼까요? 아직 학습하지 않은 내용이고, Spring 이전에 사용하던 코드는 여러분이 완벽하게 이해하기 위해 새롭게 학습할 필요는 없습니다. 어떠한 방식으로 코드가 간결하고 직관적으로 변했는지를 중점적으로 학습해 주세요.
JSP를 이용한 애플리케이션
JSP는 Java Server Page의 약자로 초창기 Java 기반의 웹 애플리케이션 개발은 JSP를 통해 이루어졌습니다.
JSP 개발 방식은 사용자에게 보이는 View 페이지 쪽 코드와 사용자의 요청을 처리하는 서버 쪽 코드가 섞여있는 형태의 개발 방식입니다. 쉽게 말하자면 웹 브라우저를 통해서 사용자에게 보이는 클라이언트 측 html/Javascript 코드와 사용자의 요청을 처리하는 서버 측 Java 코드가 뒤섞여 있는 방식입니다.
JSP 방식이 어떤 형태인지는 예제 코드를 직접 보면 이해가 쉬울 것 같습니다.
- JSP 방식 예제 코드
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="<http://java.sun.com/jsp/jstl/core>" prefix="c"%>
<%@ taglib uri="<http://java.sun.com/jsp/jstl/functions>" prefix="fn" %>
<!-- (1) 시작 -->
<%
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("Hello Servlet doPost!");
String todoName = request.getParameter("todoName");
String todoDate = request.getParameter("todoDate");
ToDo.todoList.add(new ToDo(todoName, todoDate));
RequestDispatcher dispatcher = request.getRequestDispatcher("/todo_model1.jsp");
request.setAttribute("todoList", ToDo.todoList);
dispatcher.forward(request, response);
%>
<!-- (1) 끝 -->
<html>
<head>
<meta http-equiv="Content-Language" content="ko"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>TODO 등록</title>
<style>
#todoList {
border: 1px solid #8F8F8F;
width: 500px;
border-collapse: collapse;
}
th, td {
padding: 5px;
border: 1px solid #8F8F8F;
}
</style>
<script>
function registerTodo(){
var todoName = document.getElementById("todoName").value;
var todoDate = document.getElementById("todoDate").value;
if(!todoName){
alert("할 일을 입력해주세요..");
return false;
}
if(!todoDate){
alert("날짜를 입력해주세요.");
return false;
}
var form = document.getElementById("todoForm");
form.submit();
}
</script>
</head>
<body>
<h3>TO DO 등록</h3>
<div>
<form id="todoForm" method="POST" action="/todo_model1.jsp">
<input type="text" name="todoName" id="todoName" value=""/>
<input type="date" name="todoDate" id="todoDate" value=""/>
<input type="button" id="btnReg" value="등록" onclick="registerTodo()"/>
</form>
</div>
<div>
<h4>TO DO List</h4>
<table id="todoList">
<thead>
<tr>
<td align="center">todo name</td><td align="center">todo date</td>
</tr>
</thead>
<!-- (2) 시작 --->
<tbody>
<c:choose>
<c:when test="${fn:length(todoList) == 0}">
<tr>
<td align="center" colspan="2">할 일이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${todoList}" var="todo">
<tr>
<td>${todo.todoName}</td><td align="center">${todo.todoDate}</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
<!-- (2) 끝 -->
</table>
</div>
</body>
</html>
[코드2-3] JSP 방식의 예제 코드
[코드 2-3]은 JSP 방식의 예제 코드입니다.
위 코드는 사용자가 자신의 할 일을 등록하는 화면과 등록한 할 일을 목록으로 보여주는 화면을 포함한 아주 간단한 로직입니다.
여러분들이 JSP 방식의 구체적인 구현 방법은 알 필요가 없기 때문에 코드의 대략적인 구조만 설명하고 이 방식의 문제점을 살펴보겠습니다.
먼저 코드 상단의 (1) 영역은 클라이언트의 요청을 처리하는 서버 쪽 코드입니다. 익숙한 Java의 코드가 눈에 들어오시나요?
다음으로 코드 아래쪽의 (2) 영역은 서버로부터 전달받은 응답을 화면에 표시하기 위한 JSP에서 지원하는 jstl 태그 영역입니다.
마지막으로 (1), (2)의 영역 이외의 나머지 구현 코드들은 사용자에게 보이는 화면을 구성하는 html 태그 및 css 스타일 코드와 할 일 등록 시 유효성 검사를 실시하는 Javascript 코드 즉, 클라이언트 측에서 사용하는 기술들에 해당하는 코드입니다.
기술의 구현 방법을 잘 모르긴 해도 코드 자체가 너무 길어서 가독성도 떨어지고, 너무 복잡해 보인다는 생각이 들지 않나요?
실제로 이 방식은 애플리케이션의 유지 보수 측면에서 최악의 방식이라고 볼 수 있습니다. 웹 디자이너와 html 퍼블리셔 그리고 자바스크립트 개발자 및 자바 개발자 간에 효율적으로 협업하는 것이 거의 불가능한 수준이기도 합니다.
실제로 JSP 방식으로 개발할 때에는 프론트엔드/백엔드 영역을 구분하지 않고 양쪽을 모두 개발하는 개발자들이 많았습니다. [코드 2-3]처럼 프론트엔드 영역과 백엔드 영역의 코드가 뒤섞여있다 보니 어찌 보면 당연한 결과라고 볼 수 있겠습니다.
여러분들이 JSP 방식을 사용하는 시대에 개발자로 입문하지 않는다는 것이 너무너무 다행이라는 생각까지 드는군요.
그러면 이번에는 JSP 방식보다는 조금 나은 개발 방식인 서블릿(Servlet)을 이용한 개발 방식을 살펴보도록 하겠습니다.
서블릿(Servlet)을 이용한 애플리케이션
앞에서 설명한 JSP 방식 역시 내부적으로는 Servlet 방식을 사용합니다. Servlet은 클라이언트 웹 요청 처리에 특화된 Java 클래스의 일종이라고 보시면 되는데, Spring을 사용한 웹 요청을 처리할 때에도 내부적으로는 Servlet을 사용한다라는 사실은 기억해 두시면 좋을 것 같습니다.
여기서 의미하는 Servlet을 이용한다라는 의미는 Servlet을 위한 Java 코드가 클라이언트 측 코드에서 분리되어 별도의 Java 클래스로 관리된다는 것을 의미합니다.
코드를 보면 쉽게 이해가 되실 테니 역시 예제 코드를 통해 이 방식을 이해해 보도록 하겠습니다.
- 서블릿 방식 예제코드
@WebServlet(name = "TodoServlet")
public class TodoServlet extends HttpServlet {
// (1) Database를 대신한다.
private List<ToDo> todoList;
@Override
public void init() throws ServletException {
super.init();
this.todoList = new ArrayList<>();
}
// (2)
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String todoName = request.getParameter("todoName");
String todoDate = request.getParameter("todoDate");
todoList.add(new ToDo(todoName, todoDate));
RequestDispatcher dispatcher =
request.getRequestDispatcher("/todo.jsp");
request.setAttribute("todoList", todoList);
dispatcher.forward(request, response);
}
// (3)
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Hello Servlet doGet!");
RequestDispatcher dispatcher =
request.getRequestDispatcher("/todo.jsp");
dispatcher.forward(request, response);
}
}
[코드2-4] 서블릿 방식의 예제 코드
[코드 2-4]는 [코드 2-3]의 jsp 코드에서 Java 코드만 별도의 서블릿 클래스로 분리된 모습입니다.
클라이언트 측의 JSP 코드에서 서버 측 Java 코드는 서블릿 클래스로 분리했기 때문에 클라이언트와 서버 간에 어느 정도 역할이 분리되었다고 볼 수 있습니다.
여러분들은 서블릿 클래스의 문법을 여전히 알 필요가 없지만 한눈에 봐도 코드 자체가 너무나 길어 보입니다.
(2)의 경우, 클라이언트 측에서 등록할 할 일 데이터를 전송하면 이 요청을 받아서 데이터 저장소에 등록해 주는 역할을 하는데 [코드 2-4]에서는 데이터베이스 같은 별도의 저장소를 사용하지 않고, (1)과 같이 Java의 List에 추가를 했습니다.
뭔가 아주 많은 일을 할 것 같지만 데이터를 가공하는 비즈니스 로직이 있는 것도 아니고, 가공된 데이터를 데이터베이스에 저장하는 등의 데이터 액세스 로직 역시 존재하지 않는데도 불구하고 코드 자체가 너무 길어 보입니다.
그렇다면 Spring에서는 이러한 서블릿 클래스의 코드들이 어떤 식으로 개선되는지 보도록 하겠습니다.
Spring MVC를 이용한 애플리케이션
- Spring MVC 방식 예제코드
@Controller
public class ToDoController {
@RequestMapping(value = "/todo", method = RequestMethod.POST)
@ResponseBody
public List<ToDo> todo(@RequestParam("todoName")String todoName,
@RequestParam("todoDate")String todoDate) {
ToDo.todoList.add(new ToDo(todoName, todoDate));
return ToDo.todoList;
}
@RequestMapping(value = "/todo", method = RequestMethod.GET)
@ResponseBody
public List<ToDo> todoList() {
return ToDo.todoList;
}
}
[코드2-5] Spring MVC 방식의 예제 코드
[코드 2-5]는 [코드 2-4]의 서블릿 방식의 코드를 Spring MVC 방식의 예제 코드로 변경한 모습입니다.
코드 자체가 거짓말처럼 간결해졌습니다.
서블릿 방식의 코드에서는 클라이언트의 요청에 담긴 데이터를 꺼내오는 작업을 개발자가 직접 코드로 작성해야 되고, 캐릭터 셋도 지정해주어야 하는데 반면에 Spring MVC 방식의 코드에서는 눈에 보이지 않지만 그런 작업들을 Spring에서 알아서 처리해 줍니다.
그런데 Spring의 이런 편리함과 간결함에도 불구하고 Spring 기반의 애플리케이션의 기본 구조를 잡는 설정 작업이 여전히 불편하다는 단점이 존재했었습니다.
- Spring MVC 설정 파일 예제 코드
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="<http://xmlns.jcp.org/xml/ns/javaee>"
xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://xmlns.jcp.org/xml/ns/javaee> <http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd>"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>com.codestates.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
[코드2-6] Spring MVC 설정 파일(web.xml)
[코드 2-6]은 [코드 2-5]처럼 구현한 Spring 애플리케이션을 정상적으로 구동하기 위한 설정 파일들의 일부입니다. [코드 2-6]의 web.xml이라는 설정 파일 이외에 다른 설정 파일들도 필요합니다.
실제로 Spring MVC의 이런 복잡한 설정 때문에 Spring 학습을 포기하는 입문자들도 많이 있었고, Spring의 핵심 원리를 학습하고, 이해하기도 모자란 시간에 Spring의 설정 문제로 애플리케이션을 띄워보지도 못한 채 며칠을 끙끙 앓는 그런 상황들이 많았던 게 사실입니다.
이러한 Spring 설정 상의 문제가 개선이 되지 않았다면 여러분들이 Spring에 익숙해지는 시간은 그만큼 더 길어졌을 거라고 생각합니다.
다행히 이러한 문제가 대부분 개선된 Spring Boot이 탄생하게 됩니다.
Spring Boot을 이용한 애플리케이션
- Spring Boot 기반의 예제 코드
@RestController
public class TodoController {
private TodoRepository todoRepository;
@Autowired
TodoController(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@PostMapping(value = "/todo/register")
@ResponseBody
public Todo register(Todo todo){ // (1)
todoRepository.save(todo); // (2)
return todo;
}
@GetMapping(value = "/todo/list")
@ResponseBody
public List<Todo> getTodoList(){
return todoRepository.findAll(); // (3)
}
}
[예제2-7] Spring MVC 기반의 예제 코드
[코드 2-7]은 Spring MVC 기반의 코드를 Spring Boot 기반에서 조금 더 개선한 모습입니다.
이번에는 클라이언트 측에서 전달한 요청 데이터를 (1)과 같이 Todo라는 클래스에 담아서 한 번에 전달받을 수 있도록 했습니다. 요청 데이터가 Todo 객체로 변경되는 것은 Spring이 알아서 해줍니다.
그리고 이 전 방식까지는 클라이언트가 전달한 요청 데이터를 테스트 목적으로 단순히 List에 담았는데 이번에는 (2), (3)과 같이 데이터베이스에 저장해서 데이터 액세스 처리까지 하도록 했습니다.
이렇게 데이터를 실제로 저장하는 기능을 추가했는데도 불구하고 코드의 길이는 크게 바뀐 것이 없고, 오히려 더 깔끔해지기까지 했습니다.
[코드 2-6]과 같은 Spring MVC 방식에서의 복잡한 설정 파일은 어떻게 되었을까요?
spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
[코드2-8] Spring Boot의 구성 파일
[코드 2-8]은 Spring Boot에서의 구성(설정) 파일입니다.
조금 허탈할지도 모르지만 Spring Boot을 사용하지 않는 Spring MVC 방식에서 겪어야 했던 설정의 복잡함을 Spring Boot에서는 찾아볼 수 없습니다. 심지어 데이터베이스를 연동하지 않았다면 \[코드 2-8\]에 입력한 4줄의 코드도 필요하지 않습니다.
그 많던 설정 파일의 내용들은 어디에 갔을까요?
이제는 Spring의 복잡한 설정 작업마저도 Spring이 대신 처리를 해주기 때문에 개발자는 애플리케이션의 핵심 비즈니스 로직에만 집중할 수 있게 되었습니다.
이제는 Spring Framework을 왜 배워야 하는지 어느 정도 이해 했을 거라고 생각합니다.
물론 입문자들이 처음부터 Spring의 핵심 개념들을 쉽게 이해 생각할 거라고 생각하지는 않습니다.
하지만 세상에 태어나서 순백의 도화지에서 언어에 대한 지식을 스펀지처럼 흡수하는 아기들처럼 개발 지식과 경험이 오히려 거의 없는 입문자들이 개발 경험이 많은 경력자들보다 Spring을 학습하는 속도가 더 빠를 수도 있다고 생각합니다.
어려움이 있을 수 있겠지만 Spring은 정말 재미있고, 객체 지향의 설계 원칙을 잘 따르는 놀라운 기술이기도 합니다.
Spring을 여러분들의 것으로 만들기 위해 마지막까지 포기하지 않고 저희와 함께 하기를 바랍니다.
핵심 포인트
- Spring Framework이 도입되기 전에는 JSP나 Servlet 기술을 사용한 Model1, Model2 아키텍처를 기반으로 한 Java 웹 애플리케이션을 제작하였다.
- Spring MVC 방식이 도입됨으로써 Java 웹 애플리케이션의 제작 방식이 획기적으로 변하게 되었다.
- Spring MVC 설정의 복잡함과 어려움을 극복하기 위해 Spring Boot이 탄생하게 되었다
심화 학습
- Java 서블릿(Servlet)이란?
- Java Servlet 자체를 사용하는 기술은 현재 거의 사용하고 있지 않지만 Servlet은 Spring MVC 같은 Java 기반의 웹 애플리케이션 내부에서 여전히 사용이 되고 있습니다.
- Servlet에 대해서 더 알고 싶다면 아래 링크를 클릭하세요!
- 서블릿 컨테이너(Servlet Container)란?
- 서블릿 컨테이너(Servlet Container)는 서블릿(Servlet) 기반의 웹 애플리케이션을 실행해 주는 것부터 시작해서 Servlet의 생명 주기를 관리하며, 스레드 풀(Thread Pool)을 생성해서 Servlet과 Thread를 매핑시켜주기도 합니다.
- 서블릿 컨테이너에 대한 자세한 내용이 궁금하다면 아래 링크를 클릭하세요!
- 서블릿 컨테이너: https://ko.wikipedia.org/wiki/웹_컨테이너
- 아파치 톰캣(Apache Tomcat)은 서블릿 컨테이너의 한 종류로써 Spring MVC 기반의 웹 애플리케이션 역시 기본적으로 아파치 톰캣에서 실행이 됩니다.
- 아파치 톰캣에 대한 내용은 아래 링크에서 확인하세요!
Spring Framework의 특징
이번 챕터에서는 Spring의 특징을 간략하게 살펴보면서 Spring에 대해서 조금 더 알아보는 시간을 가져보도록 하겠습니다.
이번 챕터 역시 Spring의 구체적인 사용 방법에 대한 내용은 아니기 때문에 가벼운 마음으로 읽어도 상관없습니다.
다만, 이번 챕터에서 언급하는 Spring의 특징들을 항상 염두에 두시면서 앞으로의 학습을 진행한다면 조금 더 빨리 Spring에 익숙해질 수 있을 거라고 생각합니다.
그럼 지금부터 시작해 볼까요?
학습 목표
- POJO(Plain Old Java Object)
- POJO의 의미를 이해할 수 있다.
- POJO가 필요한 이유를 알 수 있다.
- IoC(Inversioin of Control)/DI(Dependency Injection)
- IoC/DI의 의미를 이해할 수 있다.
- IoC/DI가 필요한 이유를 알 수 있다.
- AOP(Aspect Oriented Programming)
- AOP의 의미를 이해할 수 있다.
- AOP가 필요한 이유를 알 수 있다.
- PSA(Portable Service Abstraction)
- PSA의 의미를 이해할 수 있다.
- PSA가 필요한 이유를 알 수 있다.
POJO(Plain Old Java Object)
이번 시간에는 POJO가 무엇인지 그리고 POJO가 Spring과 어떤 관계가 있는지를 살펴보도록 하겠습니다.
[그림 2-1] Spring 삼각형 - POJO 설명
[그림 2-1]은 Spring 삼각형이라는 유명한 그림입니다. 이 Spring 삼각형 하나로 Spring의 핵심 개념들을 모두 표현을 하고 있다고 해도 과언이 아닙니다.
[그림 2-1]에서 POJO는 Spring에서 사용하는 핵심 개념들에 둘러 싸여 있는 모습입니다. 이는 POJO라는 것을 IoC/DI, AOP, PSA를 통해서 달성할 수 있다는 것을 의미합니다.
Spring 삼각형은 이 안에 표현된 개념들을 학습하는 동안 수시로 사용을 하도록 하겠습니다.
여기에 나오는 용어들이 무슨 뜻인지 지금은 몰라도 중요한 용어들이니 이 삼각형을 꼭 기억해 주길 바랍니다!
Spring 삼각형에서 나오는 나머지 개념들은 뒤에서 차근차근 살펴볼 예정인데 이번 시간에는 삼각형 가운데에 있는 POJO라는 개념만 알아보도록 하겠습니다.
POJO(Plain Old Java Object)란?
POJO는 알고 보면 어려운 용어가 아니다.
POJO는 Plain Old Java Object라는 단어의 첫 글자를 따서 만든 약자입니다.
여러분들이 Java 프로그래밍을 이미 학습하셨기 때문에 Java로 짜인 코드는 어떤 식으로든 객체와 객체가 관계를 맺을 수밖에 없는 그야말로 객체지향 프로그래밍인 것입니다. 그렇다면 POJO라는 용어에서 ‘JO’는 이해가 될 거라 생각합니다.
그렇다면 ‘PO’는 무엇을 의미하는 걸까요? ‘Plain’이라는 의미는 일상생활에서 요구르트를 주문할 때 자주 사용하는 용어 중에 하나입니다.
“플레인 요구르트로 주세요!”
이 말의 의미는 손님이 점원에게 요구르트 안에 시럽이든 과일이든 시리얼이든 아무것도 넣지 말고 그냥 요구르트 자체만 주문하는 것입니다.
이처럼 POJO에서 ‘PO’는 여러분이 이미 배웠던 Java로 생성하는 순수한 객체를 의미합니다.
“원래 Java를 배웠으니까 Java 문법에 맞춰서 객체를 생성하는 건 당연한 거 아냐?”라고 생각하실지도 모르겠습니다. 네, 맞습니다. 그런데 POJO가 평범한 Java 객체를 의미하는 게 맞지만 프로그래밍 관점에서 조금 더 깊은 의미가 있습니다.
POJO 프로그래밍이란?
POJO 프로그래밍이란 POJO를 이용해서 프로그래밍 코드를 작성하는 것을 의미합니다. 그런데 단순히 순수 자바 객체만을 사용해서 프로그래밍 코드를 작성한다라고 해서 POJO 프로그래밍이라고 볼 수는 없습니다.
POJO 프로그래밍으로 작성한 코드라고 불리기 위해서는 크게 두 가지 정도의 기본적인 규칙은 지켜주어야 합니다.
- Java나 Java의 스펙(사양)에 정의된 것 이외에는 다른 기술이나 규약에 얽매이지 않아야 한다.
- 두 가지 규칙 중에서 첫 번째 규칙의 의미를 예제 코드로 확인해보겠습니다.
public class User {
private String userName;
private String id;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
[코드 2-8 getter, setter만 가지고 있는 예]
위 코드는 자바에서 제공하는 기능만 사용하여 getter, setter만 가지고 있는 코드입니다. 해당 클래스의 코드에서는 Java 언어 이외에 특정한 기술에 종속되어 있지 않은 순수한 객체이기 때문에 POJO라고 부를 수 있습니다.
public class MessageForm extends ActionForm{ // (1)
String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public class MessageAction extends Action{ // (2)
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
MessageForm messageForm = (MessageForm) form;
messageForm .setMessage("Hello World");
return mapping.findForward("success");
}
}
[코드 2-9 특정 기술에 종속적인 예]
코드 2-9는 Java 코드가 특정 기술에 종속적인 예를 보여주기 위한 코드입니다. ActionForm 클래스는 과거에 Struts라는 웹 프레임워크에서 지원하는 클래스입니다. (1)에서는 Struts라는 기술을 사용하기 위해서 ActionForm을 상속하고 있습니다. (2)에서는 역시 Struts 기술의 Action 클래스를 상속받고 있습니다.
이렇게 특정 기술을 상속해서 코드를 작성하게 되면 나중에 애플리케이션의 요구사항이 변경돼서 다른 기술로 변경하려면 Struts의 클래스를 명시적으로 사용했던 부분을 전부 다 일일이 제거하거나 수정해야 합니다.
그리고, Java는 다중 상속을 지원하지 않기 때문에 ‘extends’ 키워드를 사용해서 한 번 상속을 하게 되면 상위 클래스를 상속받아서 하위 클래스를 확장하는 객체지향 설계 기법을 적용하기 어려워지게 됩니다.
- 특정 환경에 종속적이지 않아야 한다.순수 Java로 작성한 애플리케이션 코드 내에서 Tomcat이 지원하는 API를 직접 가져다가 사용한다고 가정을 해봅시다.
- 서블릿(Servlet) 기반의 웹 애플리케이션을 실행시키는 서블릿 컨테이너(Servlet Container)인 아파치 톰캣(Apache Tomcat)을 예로 들어보겠습니다.
“Tomcat 대신에 Zetty를 쓰세요!!”
그런데 만약에 시스템의 요구 사항이 변경되어서 Tomcat 말고 제티(Zetty)라는 다른 Servlet Container를 사용하게 된다면 어떻게 될까요?
애플리케이션 코드에서 사용하고 있는 Tomcat API 코드들을 모두 걷어내고 Zetty로 수정하든가 최악의 경우에는 애프리케이션을 전부 뜯어고쳐야 될지도 모르는 상황에 직면하게 될 수 있습니다.
POJO 프로그래밍이 필요한 이유
POJO 프로그래밍이 필요한 이유는 앞에서 설명한 예시에 잘 나와 있습니다.
- 특정 환경이나 기술에 종속적이지 않으면 재사용 가능하고, 확장 가능한 유연한 코드를 작성할 수 있다.
- 저수준 레벨의 기술과 환경에 종속적인 코드를 애플리케이션 코드에서 제거함으로써 코드가 깔끔해진다.
- 코드가 깔끔해지기 때문에 디버깅하기도 상대적으로 쉽다.
- 특정 기술이나 환경에 종속적이지 않기 때문에 테스트 역시 단순해진다.
- 객체지향적인 설계를 제한 없이 적용할 수 있다.(가장 중요한 이유)
이처럼 POJO 프로그래밍이 필요한 이유를 생각하다 보면 자연스럽게 객체지향적인 사고에 대한 고민을 하게 되고, 이러한 사고를 통해서 Spring을 조금 더 객체지향적으로 사용할 수 있을 거라고 생각합니다.
POJO와 Spring의 관계
Spring은 POJO 프로그래밍을 지향하는 Framework입니다.
그리고 최대한 다른 환경이나 기술에 종속적이지 않도록 하기 위한 POJO 프로그래밍 코드를 작성하기 위해서 Spring에서는 세 가지 기술을 지원하고 있습니다.
그 세 가지 기술은 바로 [그림 2-1]에서 POJO를 감싸고 있는 IoC/DI, AOP, PSA입니다.
여러분들이 POJO에 대한 개념을 학습하면서 한 가지 알아야 두어할 것은 애플리케이션 프로그래밍 코드를 작성할 때 항상 내가 작성한 코드가 객체지향스러운가에 대한 고민을 하는 습관을 가지는 것입니다.
지금은 학습한 내용이 많지 않다 보니 객체지향스러운 사고를 하려고 해도 제대로 되지 않는 것이 자연스러운 일이라고 생각합니다.
하지만 프로그래밍 시작 단계에서 이런 습관을 잘 들여놓으면 자신이 작성한 코드가 한층 깔끔해지고, 객체지향스러워지고 있는 모습에 스스로 놀라는 것을 볼 수 있을 거라 확신합니다!
핵심 포인트
- POJO란 순수한 Java 객체를 의미한다.
- POJO 프로그래밍이란 순수 Java 객체가 다른 기술이나 환경에 종속되지 않도록 하기 위한 프로그래밍 기법이다.
- POJO 프로그래밍을 효과적으로 적용하기 위해서는 특정 기술에 대한 지식보다는 JDK의 API에 대한 지식과 객체지향적인 사고방식과 설계를 위한 훈련이 우선시되어야 한다.
- Spring Framework은 POJO 프로그래밍을 지향하기 위해 IoC/DI, AOP, PSA라는 기술을 제공한다.
심화 학습
- 객체 지향 설계 원칙
- 객체지향 설계 원칙에 대해 학습을 하고 싶다면 아래 링크를 클릭하세요.
- SOLID(객체 지향 설계 원칙): https://ko.wikipedia.org/wiki/SOLID_(객체_지향_설계)
- SOLID 요약: https://itvillage.tistory.com/entry/객체지향-설계-원칙-SOLID-원칙
- 객체지향 설계 원칙에 대해 학습을 하고 싶다면 아래 링크를 클릭하세요.
POJO와 Spring Framework의 관계
POJO와 Spring의 관계
[그림 2-1] Spring 삼각형 - POJO 설명
Spring Framework를 사용하기 전에는 원하는 특정한 기술이 있다면 해당 기술을 직접적으로 사용하는 객체를 만들어 사용했습니다. 다만 프로젝트가 커지고 필요한 기술들이 늘어나며 특정 기술과 환경에 종속되는 경우가 자주 발생하여 작성된 코드의 유지/보수가 어렵고, Java에서의 상속(extends)의 특성상 이미 특정 클래스를 상속하게 되어 정작 다른 상위 클래스를 상속해서 기능을 확장하기 어려운 경우도 많이 생겼습니다. 결국 좋은 객체지향 설계를 할 수 있는 Java 언어를 사용하면서도 객체지향 설계 본질을 잃어버리는 문제점들을 해결하고자 POJO라는 개념이 등장하게 되었습니다.
Spring은 POJO 프로그래밍을 지향하는 Framework입니다.
그리고 최대한 다른 환경이나 기술에 종속적이지 않도록 하기 위한 POJO 프로그래밍 코드를 작성하기 위해서 Spring에서는 세 가지 기술을 지원하고 있습니다.
그 세 가지 기술은 바로 [그림 2-1]에서 POJO를 감싸고 있는 IoC/DI, AOP, PSA입니다. 해당 기술에 대해서는 다음으로 하나씩 자세히 살펴보도록 하겠습니다.
마지막으로 여러분들이 POJO에 대한 개념을 학습하면서 한 가지 알아야 두어할 것은 애플리케이션 프로그래밍 코드를 작성할 때 항상 내가 작성한 코드가 객체지향스러운가에 대한 고민을 하는 습관을 가지는 것입니다.
지금은 학습한 내용이 많지 않다 보니 객체지향스러운 사고를 하려고 해도 제대로 되지 않는 것이 자연스러운 일이라고 생각합니다.
하지만 프로그래밍 시작 단계에서 이런 습관을 잘 들여놓으면 자신이 작성한 코드가 한층 깔끔해지고, 객체지향스러워지고 있는 모습에 스스로 놀라는 것을 볼 수 있을 거라 확신합니다!
핵심 포인트
- POJO 프로그래밍을 효과적으로 적용하기 위해서는 특정 기술에 대한 지식보다는 JDK의 API에 대한 지식과 객체지향적인 사고방식과 설계를 위한 훈련이 우선시되어야 한다.
- Spring Framework은 POJO 프로그래밍을 지향하기 위해 IoC/DI, AOP, PSA라는 기술을 제공한다.
심화 학습
- 객체 지향 설계 원칙
- 객체지향 설계 원칙에 대해 학습을 하고 싶다면 아래 링크를 클릭하세요.
- SOLID(객체 지향 설계 원칙): https://ko.wikipedia.org/wiki/SOLID_(객체_지향_설계)
- SOLID 요약: https://itvillage.tistory.com/entry/객체지향-설계-원칙-SOLID-원칙
- 객체지향 설계 원칙에 대해 학습을 하고 싶다면 아래 링크를 클릭하세요.
IoC(Inversion of Control)
이번 시간에는 Spring Framework의 3대 핵심 개념 중 하나인 IoC/DI에 대해서 살펴보도록 하겠습니다.
[그림 2-2] Spring 삼각형 - IoC/DI 설명
[그림 2-2]는 이전 챕터에서 살펴보았던 Spring 삼각형입니다. Spring 삼각형에 나와 있는 개념들은 중요하기 때문에 여러분들이 익숙해질 수 있도록 지속적으로 노출을 하도록 하겠습니다.
IoC(Inversion of Control)란?
Framework과 Library의 차이점을 설명할 때 IoC에 대해서 잠깐 언급을 했었는데, 기억나시나요?
Library는 애플리케이션 흐름의 주도권이 개발자에게 있고, Framework은 애플리케이션 흐름의 주도권이 Framework에 있다고 했습니다.
여기서 말하는 애플리케이션 흐름의 주도권이 뒤바뀐 것을 바로 IoC(Inversion of Control)라고 합니다.
IoC를 이해하기 위해서 기존에 우리가 알고 있던 애플리케이션의 흐름과 그 반대 흐름의 의미를 샘플 코드 및 그림을 통해 알아보도록 합시다.
Java 콘솔 애플리케이션의 일반적인 제어권
여러분이 순수 Java 코드만으로 간단한 메시지를 콘솔에 출력하는 프로그램을 코드 2-10과 같이 만들었다고 가정해 봅시다.
public class Example2_10 {
public static void main(String[] args) {
System.out.println("Hello IoC!");
}
}
[코드 2-10] Java 코드만으로 메시지를 콘솔에 출력하는 예
일반적으로 [코드 2-10]과 같은 Java 콘솔 애플리케이션을 실행하려면 main() 메서드가 있어야 합니다.
그래야 이 main() 메서드 안에서 다른 객체의 메서드를 호출한다던가 하는 프로세스가 진행이 될 테니까요.
[코드 2-10]에서는 main() 메서드가 호출되고 난 다음에 System 클래스를 통해서 static 멤버 변수인 out의 println()을 호출합니다.
이렇게 개발자가 작성한 코드를 순차적으로 실행하는 게 애플리케이션의 일반적인 제어 흐름입니다.
Java 웹 애플리케이션에서 IoC가 적용되는 예
그렇다면 이번에는 Java 콘솔 애플리케이션이 아니라 웹 상에서 돌아가는 Java 웹 애플리케이션의 경우를 한번 생각해 봅시다.
[그림 2-3] 서블릿 컨테이너의 서블릿 호출 예
[그림 2-3]은 서블릿 기반의 애플리케이션을 웹에서 실행하기 위한 서블릿 컨테이너의 모습입니다.
Java 콘솔 애플리케이션의 경우 main() 메서드가 종료되면 애플리케이션의 실행이 종료됩니다.
하지만 웹에서 동작하는 애플리케이션의 경우 클라이언트가 외부에서 접속해서 사용하는 서비스이기 때문에 main() 메서드가 종료되지 않아야 할 것입니다.
그런데 서블릿 컨테이너에는 서블릿 사양(Specification)에 맞게 작성된 서블릿 클래스만 존재하지 별도의 main() 메서드가 존재하지 않습니다.
main() 메서드처럼 애플리케이션이 시작되는 지점을 엔트리 포인트(Entry point)라고도 부릅니다.
main() 메서드가 없는데 어떻게 애플리케이션이 실행되는 걸까요?
서블릿 컨테이너의 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직(service() 메서드)이 서블릿을 직접 실행시켜 주기 때문에 main() 메서드가 필요 없습니다.
이 경우에는 서블릿 컨테이너가 서블릿을 제어하고 있기 때문에 애플리케이션의 주도권은 서블릿 컨테이너에 있습니다.
바로 서블릿과 웹 애플리케이션 간에 IoC(제어의 역전)의 개념이 적용되어 있는 것입니다.
그렇다면 Spring에는 이 IoC의 개념이 어떻게 적용되어 있을까요?
답은 바로 DI(Dependency Injection)입니다.
DI(Dependency Injection)
DI(Dependency Injection)란?
IoC(제어의 역전)는 서버 컨테이너 기술, 디자인 패턴, 객체 지향 설계 등에 적용하게 되는 일반적인 개념인데 반해 DI(Dependency Injection)는 IoC 개념을 조금 구체화시킨 것이라고 볼 수 있습니다.
영문으로 된 많은 용어들은 단어 하나씩의 의미들을 분석해 보면 용어 자체를 이해하는데 많은 도움이 됩니다.
특히나 DI라는 용어는 이런 식으로 접근하는 것이 DI를 이해하는 좋은 출발점이라고 생각합니다.
Dependency는 ‘의존하는 또는 종속되는’이라는 의미를 가지고 있습니다. 그리고 Injection은 ‘주입’이라는 의미를 가지고 있고요.
우리가 아플 때 병원에 가서 맞는 주사를 영어로 Injection이라고 합니다. ‘약물을 몸속에 주입하다’할 때 그 주입을 생각하시면 되겠습니다.
두 단어를 합쳐서 의미를 파악해 보면 DI는 ‘의존성 주입’이라는 의미가 될 텐데, 이 ‘의존성 주입’이라는 용어에 대해서 우리가 궁금해할 만한 것들을 육하원칙에 따라서 단계적으로(step by step) 알아보도록 하겠습니다.
새로운 기술이나 프로그래밍 기법을 익힐 때, 육하원칙에 맞춰서 그 기술을 분석해 보면 해당 기술을 큰 틀 안에서 이해하는데 많은 도움이 됩니다.
What(의존성 주입은 무엇일까요?)
객체지향 프로그래밍에서 의존성이라고 하면 대부분 객체 간의 의존성을 의미합니다.
객체 간의 의존성이라고 하니까 뭔가 좀 어려워 보이는 데 절대 그렇지 않습니다.
여러분들이 A, B라는 두 개의 클래스 파일을 만들어서 A 클래스에서 B클래스의 기능을 사용하기 위해 B 클래스에 구현되어 있는 어떤 메서드를 호출하는 상황을 생각해 보세요. (머리속에서 그림을 그리듯이 상상을 해보시길 바랍니다. ^^)
[그림 2-4] 클래스 간의 의존 관계를 나타내는 클래스 다이어그램 예
[그림 2-4]는 A 클래스가 B 클래스의 기능을 사용하는 것을 클래스 다이어그램으로 표현한 것입니다.
[그림 2-4]처럼 A 클래스가 B 클래스의 기능을 사용할 때, ‘A클래스는 B클래스에 의존한다’라고 합니다.
어렵게 생각하지 말고 이렇게 생각해 주세요!
‘A 클래스의 프로그래밍 로직 완성을 위해 B 클래스에게 도움을 요청한다. ‘ 즉, ‘B 클래스에게 의지(의존)한다’라고 생각하면 좋을 것 같습니다.
실 생활의 예를 들어보면,
딸이 “아빠 이것 좀 도와줘!”라고 이야기한다면, 딸이 아빠의 도움이 필요하기 때문에 아빠에게 의지(의존)하는 것입니다.
클래스 다이어그램은 클래스들의 관계를 다이어그램으로 표현한 것인데, 애플리케이션 설계를 위해서 굉장히 많이 사용하는 다이어그램인데, 일반적으로 애플리케이션 구현에 필요한 클래스들의 큰 그림을 그려보기 위해서 사용합니다.
클래스 다이어그램은 애플리케이션 설계에 있어 굉장히 중요한 역할을 하기 때문에 시간 날 때마다 틈틈이 클래스 다이어그램 작성을 연습해 보길 권장드립니다!
[그림 2-4]에서 사용한 클래스 다이어그램 도구: Visual Paradigm
그럼 이번에는 코드를 통해 의존성 주입이 무엇인지 조금 더 구체적으로 알아보도록 하겠습니다.
- 클래스 간의 의존 관계 성립
[그림 2-5] 클래스 간의 의존 관계 예시 코드
여러분들이 커피를 주문하는 Java 콘솔 애플리케이션을 개발한다고 생각을 해봅시다.
지금은 DI를 이해하기 위한 용도이기 때문에 서버 애플리케이션을 개발하기 위한 Spring 같은 기술의 사용은 최대한 하지 않도록 하겠습니다.
[그림 2-5]에서 MenuController클래스는 클라이언트의 요청을 받는 엔드포인트(Endpoint) 역할을 하고, MenuService클래스는 MenuController클래스가 전달받은 클라이언트의 요청을 처리하는 역할을 합니다.
클라이언트 측면에서 서버의 엔드포인트(Endpoint)란 클라이언트가 서버의 자원(리소스, Resource)을 이용하기 위한 끝 지점을 의미합니다.
MenuController 클래스는 메뉴판에 표시되는 메뉴 목록을 조회하기 위해서 MenuService의 기능을 사용하고 있습니다.
바로 여러분들이 잘 아시는 Java의 객체 생성 방법인 new 키워드를 사용해서 MenuService 클래스의 객체를 생성한 후, 이 객체로 MenuService의 getMenuList() 메서드를 호출하고 있습니다.
이처럼 클래스끼리는 사용하고자 하는 클래스의 객체를 생성해서 참조하게 되면 의존 관계가 성립하게 됩니다.
- 의존성을 주입해 보자
두 클래스 간에 의존 관계는 성립되었지만 아직까지 의존성 주입은 이루어지지 않았습니다.
그럼 이제 의존성 주입을 해보도록 하겠습니다.
[그림 2-6] 의존성 주입 예시 코드
[그림 2-6]은 의존성 주입이 일어나는 예입니다.
[그림 2-5]에서는 MenuService의 기능을 사용하기 위해 MenuController에서 MenuService의 객체를 new 키워드로 직접 생성한 반면에 [그림 2-6]에서는 MenuController 생성자로 MenuService의 객체를 전달받고 있습니다.
이처럼 생성자를 통해서 어떤 클래스의 객체를 전달받는 것을 ‘의존성 주입’이라고 합니다.
생성자의 파라미터로 객체를 전달하는 것을 외부에서 객체를 주입한다라고 표현을 하는 것입니다.
그렇다면 여기서 의미하는 객체를 주입해 주는 ‘외부’는 무엇일까요?
바로 CafeClient 클래스가 MenuController의 생성자 파라미터로 menuService를 전달하고 있기 때문에 객체를 주입해 주는 외부가 됩니다.
의존성 주입이란 이게 전부입니다. 어렵지 않죠?
아무튼 여러분은 지금 의존성 주입에 관해서 굉장히 중요한 부분을 학습한 셈인데, 의존성 주입이 정확하게 이해가 되지 않는다면 아래 문장 하나만이라도 기억을 하길 바랍니다.
클래스의 생성자로 객체를 전달받는 코드가 있다면 ‘아, 객체를 외부에서 주입받고 있구나. 의존성 주입이 이루어지고 있구나’
Why(의존성 주입은 왜 필요할까요?)
그렇다면 이번에는 의존성 주입이 왜 필요한지에 대한 이야기를 해보겠습니다.
객체지향 언어인 Java에서 생성자를 통해 객체를 전달하는 일은 아주 흔한 일입니다.
즉, 의존성 주입이 필요한 건 어찌 보면 당연한 일이라고 볼 수 있습니다.
그런데 우리가 의존성 주입을 사용할 때, 항상 염두에 두어야 하는 부분이 한 가지 있습니다.
그것은 바로 현재의 클래스 내부에서 외부 클래스의 객체를 생성하기 위한 new 키워드를 쓸지 말지 여부를 결정하는 것입니다.
‘아니, 객체를 생성해야 하는데 new 키워드를 당연히 써야지 뭔 말인가요?’
당연한 이야기입니다.
일반적으로 Java에서 new 키워드를 사용해서 객체를 생성하는데, Reflection이라는 기법을 이용해서 Runtime시에 객체를 동적으로 생성할 수 있는 방법이 있다는 사실도 기억하기 바랍니다.
그런데 애플리케이션 코드 내부에서 직접적으로 new 키워드를 사용할 경우 객체지향 설계의 관점에서 중요한 문제가 발생할 수 있습니다.
어떤 문제가 발생할 수 있는지 애플리케이션 개발 과정에서 실제로 발생할 수 있는 대화 사례를 통해 알아보도록 합시다.
커피 주문 애플리케이션을 개발하기 위해 백엔드(Backend) 개발자인 여러분들은 프론트엔드(Frontend) 개발자와 협업을 하고 있습니다.
다음은 백엔드 개발자와 프론트엔드 개발자의 대화 내용입니다.
프론트엔드 개발자: “저희 팀에서 메뉴 목록 화면에 표시할 데이터를 API로 통신하고 싶은데 혹시 메뉴 목록 데이터 조회 API 사용 가능할까요?”
백엔드 개발자: 아직 API 틀만 구성한 상태인데 필요하시면 스텁(Stub)으로 제공해도 될까요?
프론트엔드 개발자: 네, API 통신 잘 되는지랑 화면 구성이 괜찮은지 정도만 확인할 거라서 Stub으로 해주셔도 될 것 같아요.
백엔드 개발자: 알겠습니다. 금방 만들어 드릴게요 조그만 기다려주세요.
프론트엔드 개발자: 감사합니다!
스텁(Stub)은 메서드가 호출되면 미리 준비된 데이터를 응답하는 것입니다. 즉, 고정된 데이터이기 때문에 몇 번을 호출해도 동일한 데이터를 리턴합니다.
몇 번을 호출해도 동일한 데이터를 리턴하는 것을 전문 용어로 멱등성(idempotent)을 가진다라고 합니다.
이런 대화는 실제로 협업을 하다 보면 종종 발생할 수 있는 대화 중 하나입니다.
이제 백엔드 팀에서는 메뉴 데이터 조회 API를 위한 Stub을 준비하기 위해서 MenuServiceStub 클래스를 추가로 작성해서 코드 구성을 [그림 2-7]과 같이 수정했습니다.
[그림 2-7] MenuServiceStub으로 변경된 클래스
메뉴 목록 조회 API로 Stub을 제공하기 위해서 MenuServiceStub 클래스를 사용하는 것으로 변경되었습니다. MenuServiceStub 클래스를 보시면 getMenuList()에 Stub 데이터로 채워져 있는 걸 볼 수 있습니다.
그런데 MenuServiceStub 클래스를 사용하려고 보니, 이 MenuService 클래스를 의존하고 있는 CafeClient와 MenuController에서 MenuService를 MenuServiceStub 클래스로 불가피하게 변경해야 되는 상황이 발생하게 되었습니다.
백엔드 개발자가 생각하기를,
‘에이, 그냥 변경하고 나중에 프론트엔드 쪽에서 테스트 끝나면 MenuService로 다시 바꾸지 뭐’...
이미 작성한 코드를 요구 사항이 변경되었다고 직접적으로 변경하는 것이 바람직한 일일까요?
만약 MenuServiceStub 클래스를 사용할 대상이 수십, 수백 군데라면 어떻게 해야 될까요?
물론 야근을 해서라도 바꿀 수는 있습니다. 하지만 불필요한 야근도 해야 되고, 게다가 수동으로 일일이 많은 곳을 수정하다 보니 버그가 발생할 가능성도 있습니다. 테스트 코드를 다시 짜야 될 가능성도 높고요.
결국 new 키워드를 사용해서 객체를 생성하게 되면 참조할 클래스가 바뀌게 될 경우, 이 클래스를 사용하는 모든 클래스들을 수정할 수밖에 없습니다.
이처럼 new 키워드를 사용해서 의존 객체를 생성할 때, 클래스들 간에 강하게 결합(Tight Coupling)되어 있다고 합니다.
결론적으로 의존성 주입을 하더라도 의존성 주입의 혜택을 보기 위해서는 클래스들 간의 강한 결합은 피하는 것이 좋아 보입니다.
느슨한 결합(Loose Coupling)이 필요할 때이군요.
애플리케이션의 요구사항은 언제든 변할 수 있기 때문에 코드를 작성할 때, ‘이렇게 작성해 놓으면 나중에 또 수정이 필요한 부분이 뭐가 있을까?’라는 생각은 한번쯤 해보는 게 좋다고 생각합니다.
How(느슨한 의존성 주입은 어떻게 할까요?)
MenuService를 MenuServiceStub으로 변경하다 보니, MenuService 클래스를 의존하고 있는 나머지 클래스들을 수정해야 했습니다.
이제 수정이 필요한 부분을 조금 줄여보도록 하겠습니다.
어떻게 해야 할까요?
Java에서 클래스들 간의 관계를 느슨하게 만드는 대표적인 방법은 바로 인터페이스(Interface)를 사용하는 것입니다.
[그림 2-8] 인터페이스를 사용한 클래스 간의 느슨한 결합 예
[그림 2-8]은 인터페이스를 사용해서 클래스 간의 결합을 느슨하게 해주는 예입니다.
[그림 2-8]의 클래스 다이어그램에 따르면 MenuController가 MenuService라는 클래스를 직접적으로 의존하는 게 아니라 클래스 이름은 같지만 인터페이스를 의존하고 있습니다.
MenuController가 MenuService를 의존하고 있지만 MenuService의 구현체는 MenuServiceImpl인지 MenuServiceStub인지 알지 못합니다. 아니, 알 필요가 없습니다.
MenuController 입장에서는 그저 메뉴 목록 데이터를 조회할 수 만 있으면 그만이니까요.
이처럼 어떤 클래스가 인터페이스 같이 일반화된 구성 요소에 의존하고 있을 때, 클래스들 간에 느슨하게 결합(Loose Coupling)되어 있다고 합니다.
그럼 [그림 2-8]의 클래스 다이어그램이 코드로 어떻게 표현되는지 보도록 하겠습니다.
[그림 2-9] 인터페이스를 이용한 느슨한 결합 예
[그림 2-9]를 [그림 2-7]과 비교해 보면 MenuController는 Stub 데이터를 받을 수 있는데도 불구하고 아무런 변경이 발생하지 않았습니다.
[그림 2-7]에서는 MenuController가 생성자로 MenuServiceStub 클래스를 주입받았지만 여기서는 주입받은 대상이 MenuService 인터페이스이기 때문에 MenuService 인터페이스의 구현 클래스이면 어떤 클래스도 전부 주입을 받을 수 있습니다.
(1)을 자세히 보시면 new로 MenuServiceStub 클래스의 객체를 생성해서 MenuService 인터페이스에 할당합니다.
이처럼 인터페이스 타입의 변수에 그 인터페이스의 구현 객체를 할당할 수 있는데 이를 업캐스팅(Upcasting)이라고 합니다.
이제 업캐스팅을 통한 의존성 주입으로 인해 MenuController와 MenuService는 느슨한 결합 관계를 유지하게 되었습니다.
[그림 2-7] 보다는 변경해야 될 곳이 조금 적어졌는데 한 가지 마음에 걸리는 게 있습니다.
클래스들 간의 관계를 느슨하게 만들기 위해서는 new 키워드를 사용하지 않아야 되는데, CafeClient 클래스의 (1)을 보시면 MenuServiceStub의 객체와 MenuController 객체를 생성하기 위해 여전히 new를 사용하고 있습니다.
이 new 키워드는 어떻게 하면 제거하고 의존 관계를 느슨하게 만들 수 있을까요?
바로 Spring이 대신해줍니다.
Who(Spring 기반 애플리케이션에서는 의존성 주입을 누가 해줄까요?)
[그림 2-10] Spring을 이용한 의존성 주입 예
[그림 2-9]에서는 CafeClient 클래스에 MenuServiceStub과 MenuController 객체를 생성하기 위해 new 키워드를 사용했었는데 [그림 2-10]에서는 어찌 된 영문인지 보이질 않습니다.
대신에 (1)과 같이 알 수 없는 코드들이 보이는데 (1)에 해당하는 코드는 모두 Spring에서 지원하는 API 코드입니다.
이 코드에 나오는 Spring 관련 사용법은 지금은 알 필요가 없습니다. 그리고 여기서는 Java 콘솔 애플리케이션의 예를 보여주기 위해서 사용한 것이지 웹 애플리케이션에서는 사실 몰라도 되는 코드이기도 합니다.
단지 new 키워드로 객체를 생성하던 것을 Spring이 대신한다는 것을 보여주기 위한 최소한의 코드라고 생각하시면 되겠습니다.
POJO 프로그래밍의 내용 기억나시나요?
POJO 프로그래밍을 위한 규칙 중에 하나가 ‘다른 기술이나 규약에 얽매이지 않아야 한다’인데 [그림 2-10]에서는 (1)과 같이 Spring Framework에 해당하는 코드가 애플리케이션 코드에 직접적으로 나와 버렸습니다. 맞습니다. 좋은 개발 방식이 아닙니다.
여러분들에게 Spring을 이용한 DI(의존성 주입)의 예를 보여주기 위해서 사용한 코드이며, 실제 Spring 기반의 웹 애플리케이션에 (1)과 같은 코드는 나오지 않습니다.
아무튼 (1)의 Spring 코드 덕분에 [그림 2-9]에서 고민했던 new 키워드를 없애는 데 성공했습니다.
new 키워드를 어떻게 없앨 수 있었을까요?
답은 바로 [그림 2-10] 제일 하단에 있는 Config라는 클래스입니다.
Config 클래스에도 무언가 뜻을 알 수 없는 @Configuration, @ComponentScan, @Bean과 같은 애노테이션(Annotation)들이 많이 보입니다.
이런 내용들은 ‘Spring Framework 핵심개념’ 챕터에서 자세하게 배울 예정이니 지금은 설명하지 않겠습니다.
다만 이 Config 클래스의 역할 하나만큼은 기억을 해두면 좋겠습니다.
Config 클래스에서 (3)과 같이 MenuController 객체 생성을 정의해 두면 (1)을 이용해서 이 객체를 애플리케이션 코드에서 사용하게 됩니다.
한마디로 Config 클래스에 정의해 둔 MenuController 객체를 Spring의 도움을 받아서 CafeClient클래스에게 제공을 하고 있는 것입니다.
그런데 분명히 누군가는 이렇게 얘기할 수도 있습니다.
Config 클래스 안에서 new 키워드로 객체를 생성했으니까 이것도 문제인 거 아니냐..
그렇게 볼 수도 있겠지만 Config 클래스는 단순한 클래스가 아니라 Spring Framework의 영역에 해당하는 것이고 이 Config 클래스가 실제 애플리케이션의 핵심 로직에 관여하지 않고 있습니다.
온전히 Spring Framework의 영역인 것입니다.
이처럼 Spring 기반의 애플리케이션에서는 Spring이 의존 객체들을 주입해 주기 때문에 애플리케이션 코드를 유연하게 구성할 수 있습니다.
앞에서 했던 프론트엔드 팀과 백엔드 팀의 대화 기억 나시죠?
만약에 백엔드 팀에서 메뉴 목록 조회 기능을 완성했다면 이제 Stub 데이터 대신에 실제 데이터를 데이터베이스에서 제공해 주면 될 텐데 이럴 경우 애플리케이션 코드에서 변경할 내용들이 있을까요?
(4)와 같이 Spring Framework 영역에 있는 MenuServiceStub 클래스를 MenuServiceImpl 클래스로 단 한 번만 변경해 주면 됩니다.
핵심 포인트
- 애플리케이션 흐름의 주도권이 사용자에게 있지 않고, Framework이나 서블릿 컨테이너 등 외부에 있는 것 즉, 흐름의 주도권이 뒤바뀐 것을 IoC(Inversion of Control)라고 한다.
- DI(Dependency Injection)는 IoC 개념을 조금 구체화시킨 것으로 객체 간의 관계를 느슨하게 해 준다.
- 클래스 내부에서 다른 클래스의 객체를 생성하게 되면 두 클래스 간에 의존 관계가 성립하게 된다.
- 클래스 내부에서 new를 사용해 참조할 클래스의 객체를 직접 생성하지 않고, 생성자 등을 통해 외부에서 다른 클래스의 객체를 전달받고 있다면 의존성 주입이 이루어지고 있는 것이다.
- new 키워드를 사용하여 객체를 생성할 때, 클래스 간에 강하게 결합(Tight Coupling)되어 있다고 한다.
- 어떤 클래스가 인터페이스 같이 일반화된 구성 요소에 의존하고 있을 때, 클래스들 간에 느슨하게 결합(Loose Coupling)되어 있다고 한다.
- 객체들 간의 느슨한 결합은 요구 사항의 변경에 유연하게 대처할 수 있도록 해 준다.
- 의존성 주입(DI)은 클래스들 간의 강한 결합을 느슨한 결합으로 만들어준다.
- Spring에서는 애플리케이션 코드에서 이루어지는 의존성 주입(DI)을 Spring에서 대신해 준다.
심화 학습
- [그림 2-9]의 클래스와 코드들을 직접 타이핑해보며, 의존성 주입에 대한 개념을 코드로 직접 익혀보세요.
준비 사항
- [실습 환경 구성] 챕터에서 만든 section2-week3 프로젝트에서 [그림 P-2-1]과 같이 패키지를 추가합니다.
[그림 P-2-1] 패키지 추가 메뉴 선택
: ‘com.codestates’ 패키지에서 마우스 오른쪽 버튼을 눌러 [Package] 메뉴를 선택합니다.
- [그림 P-2-2]와 같이 ‘chapter2.di’ 패키지를 입력한 후 엔터 키를 누릅니다.
[그림 P-2-2] 패키지 입력
[그림 P-2-3]과 같이 [Java Class] 메뉴를 선택한 후, 실습할 클래스 및 인터페이스 파일들을 생성한 후, 코드를 작성합니다.
[그림 P-2-3] 클래스 및 인터페이스 생성을 위한 [Java Class] 메뉴 선택
- 추가 코드 작성
- [그림 2-9]에는 Menu 클래스의 코드가 빠져있습니다. 아래의 메뉴 클래스를 추가로 작성해 주세요.
package com.codestates.chapter2.di;
public class Menu {
private long id;
private String name;
private int price;
public Menu(long id, String name, int price) {
this.id = id;
this.name = name;
this.price = price;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
}
- Visual Paradigm 같은 설계 도구를 이용해서 예제 코드에 사용된 클래스 다이어그램을 직접 그려 보세요.
- 객체 지향 설계 원칙에서 DI와 관련된 설계 원칙을 찾아보세요.
- SOLID(객체 지향 설계 원칙): https://ko.wikipedia.org/wiki/SOLID_(객체_지향_설계)
- SOLID 요약: https://itvillage.tistory.com/entry/객체지향-설계-원칙-SOLID-원칙