01-2. Spring의 4가지 특징과 동작 과정
Spring의 4가지 특징
POJO 프로그래밍
POJO : Plain Old Java Object의 약자로, 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트
# POJO의 조건
- 특정 규약에 종속되지 않는다.
자바 언어와 꼭 필요한 API 외에는 종속되지 말아야 한다. 특정 규약을 따라 만들게 하는 경우, 대부분 규약에서 제시하는 특정 클래스를 상속하도록 요구한다. 그 경우 자바의 단일 상속 제한으로 인해 더이상 해당 클래스에 객체지향적인 설계 기법을 적용하기 어려워지는 문제가 발생하기 때문이다. - 특정 환경에 종속되지 않는다.POJO는 환경에 독립적이여야 한다. 특정 기업의 프레임워크나 서버에서만 동작 가능한 코드이면 안되며, 특히 비지니스 로직을 담고 있는 POJO 클래스는 웹이라는 환경 정보나 웹 기술을 담고 있는 클래스나 인터페이스를 사용해서는 안된다. 그렇게 되면 웹 이외의 클라이언트가 사용하지 못하기 때문이다.
- 객체 지향적 원리에 충실해야 한다.
POJO는 객체 지향적인 자바 언어의 기본에 충실하게 만들어져야 한다. 책임과 역할이 각기 다른 코드를 한 클래스에 몰아 넣어 덩치 큰 만능 클래스를 만들고, 상속과 다형성의 적용이 아닌 if/switch 문으로 가득 설계된 오브젝트라면 POJO라고 부를 수 없다.
# POJO의 장점
- 깔끔한 코드
- 간편한 테스트
- 객체지향적인 설계를 자유롭게 적용
# POJO 프레임워크
POJO 프레임워크란 POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크이다. 스프링 프레임워크와 Hibernate가 대표적인 POJO 프레임워크라고 할 수 있다.
# 스프링의 핵심, POJO
Spring Application은 POJO를 이용해서 만든 애플리케이션 코드와, POJO가 어떻게 관계를 맺고 동작하는 지를 정의해놓은 설계 정보로 구분된다. 스프링의 주요 기술인 IoC/DI, AOP, PSA는 애플리케이션을 POJO로 개발할 수 있게 해주는 기술이며, DI는 유연하게 확장 가능한 오브젝트를 만들어두고 그 관계는 외부에서 다이나믹하게 설정해준다. 스프링은 이런 DI의 개념을 애플리케이션 전반에 걸쳐 적용한다.
IoC / DI
IoC : Inversion of Control의 약자로, 메소드나 객체의 호출작업을 개발자가 직접 제어하는 것이 아니라, 외부에서 관리하는 것을 의미한다. IoC는 제어의 역전이라고 말하며, 간단히 말해 제어의 흐름을 바꾼다고 한다.
DI : Dependency Injection의 약자로, 객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입시켜 주는 방식이다. DI는 의존관계 주입이라고 하며, 스프링이 다른 프레임워크와 차별화되어 제공하는 기능이다.
참고로 스프링에서는 객체를 Bean이라고 부르며, 프로젝트가 실행될때 사용자가 Bean으로 관리하는 객체들의 생성과 소멸에 관련된 작업을 자동적으로 수행해주는데 객체가 생성되는 곳을 스프링에서는 Bean 컨테이너라고 부른다.
# IoC와 DI의 관계성
IoC와 DI는 어떤 관계에 놓인걸까?
아래 예시를 통해 IoC와 DI를 이해해보자.
현재 Calculator 클래스가 FirstAdd 클래스에 의존하고 있는 의존 관계가 형성되어 있다.
만약 이 때 FirstAdd 클래스의 add()가 더 이상 필요하지 않고 새로운 SecondAdd라는 클래스가 필요하다면?
위의 왼쪽 코드와 같이 변경이 필요하다. 하지만 만약 FirstAdd라는 클래스를 의존하는 클래스가 매애애애애우 많다면? 코드의 변경이 쉽지 않다.
그렇다면 위의 오른쪽 코드는 어떨까? 왼쪽 코드와 비교하여 바뀐 부분은 아래 코드와 같이 Calculator가 이제 더이상 클래스 내부에서 스스로 객체를 생성하지 않고, 생성자를 통해 외부에서 객체를 받아온다는 점이다. 위 코드를 추가함으로써 DI가 구현되었다고 할 수 있으며, 이는 생성자를 통한 의존성 주입의 경우이다.
private AddCalculator addCalculator;
public Calculator(AddCalculator a) {
addCalculator = a;
}
이때 Calculator에게 AddCalculator 객체를 전달하는 건 누구일까?
바로 Spring이다.
기존에는 다음과 같은 순서로 객체가 만들어지고 실행되었다.
1. 객체 생성
2. 의존성 객체 생성
→ 클래스 내부에서 스스로 객체 생성
3. 의존성 객체 메소드 호출
하지만, Spring에서는 다음과 같은 순서로 객체가 만들어지고 실행된다.
1. 객체 생성
2. 의존성 객체 주입
→ 제어권을 스프링에게 위임하여 스프링이 만들어놓은 객체를 주입한다.
3. 의존성 객체 메소드 호출
이렇게 개발자가 직접 객체를 생성하는 것이 아니라 Spring이 객체를 대신 생성해서 의존 관계를 맺어주는 것을 IoC(Inversion of Control, 제어의 역전)라고 하고, 생성자를 통해서 객체를 주입하는 것을 DI(Dependency Injection, 의존성 주입)라고 한다.
즉, 둘의 관계성에 대해 말하자면 DI는 IoC 프로그래밍 모델을 구현하는 방식 중 하나인 것이다.
Spring에서는 IoC를 DI라는 구체적인 방식을 통해 의존성 역전 제어를 하고 있는 것!
IoC는 객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연성을 높여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.
추가로, 의존성 주입의 종류 3가지로는 필드 주입, 세터 주입, 생성자 주입이 있다. 의존성 주입의 종류에 대해서는 아래 포스팅을 참고하자!
[Spring Intro] Section 04. 스프링 빈과 의존관계_컴포넌트 스캔과 자동 의존관계 설정, 자바 코드로 직
컴포넌트 스캔과 자동 의존관계 설정 # 스프링 빈을 등록하고 의존관계 설정하기 지난 Section3에서 구축했던 백엔드 기능에 화면을 붙여보자. 화면 구축을 위해서는 Controller와 View Template이 필요
monmonde.tistory.com
# 왜 제어를 역전시켰을까?
IoC에 대해 공부하다보면 왜 제어를 역전시켰는지에 대한 순수한 궁금증이 생긴다. 관련 레퍼런스들을 찾아보며 가장 이해하기 쉬운 설명을 찾아 정리했다. 나처럼 제어의 역전이 왜 필요한 지 궁금한 사람들은 한 번씩 읽어보면 좋을 것 같다.
IoC는 Spring에서만 사용되는 개념이 아닙니다. 프로그래밍 패턴이기 때문에 범용적으로 쓰이며, 작게는 객체 간에 디자인 패턴으로 크게는 컨테이너, 프레임워크 역할에 적합한 구조이기 때문에 상황에 맞게 사용되는 프로그래밍 모델인 것이죠. Spring은 Framework입니다. 그렇기 때문에 흐름을 직접 핸들링 해줘야 하는 것이고, 자연스럽게 적합한 모델, 패턴을 적용해야 했기에 Framework에 적합한 IoC, DI 개념 모델이 들어가게 된 것이죠.
그럼 이점은 무엇이 있을까요?
IoC 프로그램 모델은 곧 역할과 책임의 분리라는 내용과 관련이 있다고 생각합니다. 왜 내가 직접 객체를 제어하지 않고, 제3자에게 제어를 위임한 뒤에 나는 수동적으로 따라가는 길을 택했을까요?
이는 객체 지향 프로그래밍과 아주 관련이 깊다고 생각합니다. 객체 지향 프로그래밍은 각 객체마다 자기의 역할과 책임을 온전히 다하며 서로 협력하며 변경에 유연한 프로그래밍을 할 수 있는 프로그래밍 기법입니다. 즉, 각 객체마다 올바른 캡슐화를 통해 높은 응집도와 낮은 결합도를 이루어나가는 것이 핵심이죠.
이러한 관점에서 제어의 역전으로 인해 제3자(다른 객체/컨테이너)에게 제어에 대한 역할과 책임을 위임하고, 다시 말해 신경 쓰지 않고 지금 내가 하고자 하는 역할과 책임에만 관심이 있는 것이죠. 왜 이렇게 할까요? 답은 변경에 유연한 코드 구조를 가져가기 위해서입니다. 내가 작성하고자 하는 코드에서 객체 생성, 소멸 등의 관리 코드와 함께 비즈니스 코드까지 들어가면 어떨까요?
기능은 얼마든지 변경될 수 있습니다. 자, A라는 객체를 생성하고 있었는데 A 객체는 삭제하고 B 객체를 추가해야 한다고 하면 어떻게 될까요? 뭐 한 곳에서만 객체 생성을 추가했다면 한 곳에서만 변경하면 되겠죠. 그럼 10 곳에서 A라는 객체를 사용했다고 해보죠. 그럼 10번을 수정해줘야겠네요?
또 다른 예를 들어보죠. 객체 생명 주기를 직접 관리하기 때문에 객체 생성, 삭제를 직접 할 수 있습니다. 그런데 도메인 모델 상 객체 삭제는 절대 하면 안된다고 해보죠. 하지만 개발자가 직접 객체를 핸들링 할 수 있기 때문에 객체를 삭제하거나 새로운 객체를 생성할 수도 있습니다. 다시 말해서 권한이 너무 많다는 것입니다. 이것은 곧 캡슐화 위반을 일으킨 것입니다.
결국 엔터프라이즈 차원에서 수많은 객체들을 편리하게 관리하기 위함입니다.
현재 설명한 객체는 등장한 것이 2~3개 밖에 되지 않습니다. 하지만 각 객체마다 협력을 하다 보면 IoC 역할을 하는 객체들이 계속 생길 수 있습니다. 그럼 개발자는 그런 IoC 역할을 하는 객체를 계속 알고 개발을 해야 합니다. 1~2개는 괜찮겠지만 개발하다 보면, 또 유지보수 하다 보면 객체는 계속 생길 수 있습니다. 요구사항이 변하니까요.
그래서 스프링은 Application Context로 모든 객체를 일괄적으로 관리하는 IoC를 Wrapping하는 개념으로, 컨테이너가 되어 객체에 의존관계 주입, 객체 생명 주기 관리, 그 밖에 우리가 알지 못하는 영역에서 많은 유용한 일을 담당하고 있습니다.
자 그럼 질문을 다시 해보겠습니다. 왜 제어를 역전시켰을까요?
객체를 관리해주는 독립적인 존재(거시적으로는 컨테이너, 미시적으로는 디자인 패턴 적용)와 그 외에 내가 구현하고자 하는 부분으로 각각의 관심을 분리하고, 서로의 역할을 충실히 하면서 변경에 유연한 코드를 작성할 수 있는 구조이기 때문이라고 저는 생각했습니다.
스프링 컨테이너 차원에서는 엔터프라이즈 개발에 적합하게 수많은 객체 생명 주기를 관리하고, 의존 관계를 설정해주고, 그 외 많은 기능들을 제공하여 개발자가 비즈니스 로직에 집중할 수 있게 해주는 것이죠.
[출처] IoC, DI란 무엇일까 - 백문이불여일타
AOP
AOP : Aspect Oriented Programming의 약자로, 관점 지향 프로그래밍을 의미한다.
> 기능을 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)으로 분리, 소스 코드 상에서 다른 부분에 계속 반복해서 쓰는 코드들을 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다.
AOP의 적용 예시와, AOP가 필요한 이유에 대해 자세히 알고 싶다면 아래 포스팅을 참고하자.
[Spring Intro] Section 07. AOP_AOP가 필요한 상황, AOP 적용
AOP가 필요한 상황 갑자기 악덕 상사가 나타나 요새 우리 프로그램에 문제가 있는 것 같다며 모든 메소드의 호출 시간을 측정해 보라고 한다. 난 메소드 1000개의 시작과 끝에 시간 측정 로직을 모
monmonde.tistory.com
PSA
PSA : Portable Service Abstraction의 약자로, 환경의 변화와 관계없이 일관된 방식의 기술로의 접근 환경을 제공하는 추상화 구조를 의미한다. 즉, 어떤 서비스를 이용하기 위한 접근 방식을 일관된 방식으로 유지하여 애플리케이션에서 사용하는 기술이 변경되더라도 최소한의 변경만으로 변경된 요구 사항을 반영하기 위해 사용한다. PSA는 아주 잘 만든 인터페이스라고 부르기도 한다.
이는 앞서 알아본 POJO 원칙과도 연결되는데, 이 POJO 원칙을 철저히 따른 Spring의 기능으로 Spring에서 동작할 수 있는 Library들은 POJO원칙을 지키게끔 PSA 형태의 추상화가 되어있음을 말한다.
PSA를 통해 Spring은 사용자에게 일관성 있는 서비스 추상화를 제공하며, 간편한 코드 작성이 가능하게 해주는 편의성 까지 제공하게 된다. Spring은 Spring Web MVC, Spring Transaction, Spring Cache 등의 다양한 PSA를 제공한다.
Spring의 동작 과정
- DispatcherServlet이 클라이언트로부터 요청을 받고, 요청된 URL을 HandlerMapping 객체에 넘긴다.
- Handler Mapping에서는 어떤 Controller가 이 요청을 처리할 것인지 찾아 정하고, DispatcherServlet은 HandlerAdapter 객체를 가져와 메서드를 실행한다.
- HandlerMapping은 Dispatcher Servlet으로부터 전달된 URL을 바탕으로 HandlerAdapter 객체를 포함하는 HandlerExecutionChain 객체를 생성한다.
- 이후 DispatcherServlet이 HandlerExecutionChain 객체로부터 HandlerAdapter 객체를 가져와서 해당 메소드를 실행하게 된다.
- Controller 객체는 비즈니스 로직을 처리하고, 그 결과를 바탕으로 View에 전달할 객체를 Model 객체에 저장한다. 또한, DispatcherServlet에게 해당 View의 name을 리턴한다.
- DispatcherServlet은 view name을 View Resoler에게 전달하여 View 객체를 얻는다.
- DispatcherServlet은 View 객체에 화면 표시를 요청한다.
- View 객체는 해당하는 뷰(JSP, Thymeleaf, ...etc)를 호출하며, 뷰는 Model 객체에서 화면 표시에 필요한 객체를 가져와 화면에 표시한다.
스프링 MVC에 대한 동작 과정과 자세한 예시는 아래 포스팅을 참고하자.
[Spring Intro] Section 02. 스프링 웹 개발 기초_정적 컨텐츠, MVC와 템플릿 엔진, API
스프링 웹 개발 방법 정적 컨텐츠 ; 파일을 그대로 웹브라우저에 내려주는 것 MVC와 템플릿 엔진 ; 서버에서 HTML을 프로그래밍해서 동적으로 내리는 것 API ; JSON이라는 데이터 구조 포맷으로 클라
monmonde.tistory.com