본문 바로가기

항해 99/Spring

Spring MVC, IoC/DI

Spring MVC

MVC 패턴

  • MVC 패턴은 애플리케이션을 개발할 때 사용하는 디자인 패턴
  • 애플리케이션의 개발 영역을 MVC(Model, View, Controller)로 구분하여 각 역할에 맞게 코드를 작성하는 개발 방식
  • MVC 패턴을 도입하면서 UI 영역과 도메인(비즈니스 로직) 영역으로 구분되어 서로에게 영향을 주지 않으면서 개발과 유지보수가 가능하다
  • MVC에서 모델은 애플리케이션의 정보(데이터)를 나타내며, 는 텍스트,  체크박스 항목등과 같은 사용자 인터페이스 요소를 나타내고, 컨트롤러는 데이터와 비즈니스 로직 사이의 상호동작을 관리한다.

MVC 패턴을 사용하는 이유

사용자가 보는 페이지, 데이터 처리, 그리고 이 2가지를 중간에서 제어하는 컨트롤 3가지로 구성되는 하나의 애플리케이션을 만들면 각각 맡은 역할에만 집중할 수 있게 된다.

 

MVC 패턴의 사용 목적 : 각 컴포넌트가 서로 분리되어 각자의 역할에 집중할 수 있기 때문에 시스템 결합도를 낮출 수 있다.

유지보수가 쉬우며, 중복코드를 제거할 수 있고, 애플리케이션의 확장성 및 유연성이 증가한다.

 

Model, View, Controller

 

Model(모델)

Spring MVC 기반의 웹 애플리케이션이 클라이언트의 요청을 전달받으면 요청 사항을 처리하기 위한 작업을 한다.

처리한 작업의 결과 데이터를 클라이언트에게 응답으로 돌려주어야 하는데, 이 때 클라이언트에게 응답으로 돌려주는 작업의 처리 결과 데이터를 Model이라고 한다.

 

View(뷰)

View는 Model을 이용하여 웹 브라우저와 같은 애플리케이션의 화면에 보이는 리소스(Resource)를 제공하는 역할을 한다.

 

Spring MVC에는 다양한 View 기술이 포함되어 있다

  • HTML 페이지 출력
  • PDF, Excel 등의 문서 형태로 출력
  • XML, JSON 등 특정 형식의 포맷으로 변환

Controller(컨트롤러)

컨트롤러는 클라이언트 측의 요청을 직접적으로 전달받는 엔드포인트(Endpoint)로써 Model과 View의 중간에서 상호작용을 해주는 역할을 한다

 

클라이언트 측의 요청을 전달받아 비즈니스 로직을 거친 후, Model 데이터가 만들어지면 이 Model 데이터를 View로 전달하는 역할을 한다

 

MVC1

우리가 흔히 사용하고 있는 MVC 패턴은 MVC1, MVC2 아키텍쳐에서 발전된 패턴이다.

 

MVC1 패턴이란, 브라우저(사용자)로부터 요청이 들어오면 DB로부터 필요한 데이터를 받은 Model 객체(Java Bean)를 JSP 페이지(View)에 담아 응답으로 보내는 패턴

 

 MVC1에서는 JSP가 View와 Controller 역할을 모두 담당하기 때문에 JSP 페이지 내에 너무 많은 코드가 들어가게 되면서 코드의 가독성이 떨어질 뿐만 아니라 코드가 복잡해질 가능성이 있다.

 

이러한 점을 보완하여 Controller 역할을 하는 Servlet이 추가된 MVC2 패턴이 등장하게 되었다.

 

MVC2

 

MVC2 패턴은 요청을 하나의 컨트롤러(Servlet)가 먼저 받는다.

서블릿은 요청에 대한 비즈니스 로직을 처리한 후, 이를 JSP 파일에 반영하는 역할을 수행한다.

 

MVC2는 MVC1 패턴보다 구조가 복잡해질 수 있으나, 이러한 문제점들을 해결하기 위해 각종 프레임워크들이 지금까지 잘 발전되어 왔으며, 그 중에서 대표적인 것이 스프링 프레임워크이다.

 

Spring MVC

스프링 프레임워크에서 MVC2 모델을 좀 더 발전시켜 Spring MVC가 나왔으며 이는 MVC2 모델이 기반인 웹 모듈이다.

 

프론트 컨트롤러(Front Controller)가 우선적으로 클라이언트로부터 모든 요청을 받게 되며, 실제 요청의 처리는 개별 컨트롤러 클래스로 위임을 한다.

 

개별 컨트롤러 클래스는 핸들러(Handler)라고도 하며, DI를 통해 생성해둔 Bean을 통해 비즈니스 로직 처리 결과를 Model에 담아 프론트 컨트롤러로 보낸다.

 

프론트 컨트롤러는 받은 Model을 알맞은 View 템플릿으로 전달하여 반영시키고, 최종적으로 클라이언트로 보낼 화면을 응답 결과로 전송하는 것이다.

 

Spring MVC 구조

DispatcherServlet

  • Front Controller의 역할을 수행하며 Request를 각각의 Controller에게 위임한다.
  • 가장 앞 단에서 클라이언트의 요청을 처리하는 Controller로써 요청부터 응답까지 전반적인 처리 과정을 통제한다.
DispatcherServlet을 Front Controller로 설정하는 방법:
1. web.xml에 명시한다.
2. org.springframework.web.WebApplicationInitializer 인터페이스를 구현한다.

 

HandlerMapping

  • 요청을 직접 처리할 컨트롤러를 탐색한다
  • 구체적인 Mapping은 xml파일이나 java config 관련 어노테이션 등을 통해 처리할 수 있다

HandlerAdapter

  • 매핑된 컨트롤러의 실행을 요청한다

Controller

  • DispatcherServlet이 전달해준 HTTP 요청을 처리하고 결과를 Model에 저장한다
    • 직접 요청을 처리하며, 처리 결과를 반환한다.
    • 결과가 반환되면 HandlerAdapter가 ModelAndView 객체로 변환되며, 여기에는 ViewName과 같이 응답을 통해 보여줄 View에 대한 정보와 관련된 데이터가 포함되어 있다

ModelAndView

  • ModelAndView는 Controller에 의해 반환된 Model과 View가 Wrapping된 객체이다

View Resolver

  • View Name을 확인한 후, 실제 컨트롤러로부터 받은 로직 처리 결과를 반영할 View 파일(JSP)을 탐색한다.

View

  • 로직 처리 결과를 반영한 최정 화면을 생성한다

 

Spring MVC 동작 구조

 

  1. 클라이언트가 서버에 요청을 하면, Front Controller인 DispatcherServlet 클래스가 요청을 받는다
  2. DispatcherServlet은 프로젝트 파일 내의 servlet-context.xml 파일의 @Controller 인자를 통해 등록한 요청 위임 컨트롤러를 찾아 매핑(mapping)된 컨트롤러가 존재하면 @RequestMapping을 통해 처리할 메서드로 이동한다
  3. 컨트롤러는 해당 요청을 처리한 Service(서비스)를 받아 비즈니스 로직을 서비스에게 위임한다
  4. Service는 요청에 필요한 작업을 수행하고, 요청에 대해 DB에 접근해야 한다면 DAO에 요청하여 처리를 위임한다
  5. DAO는 DB 정보를 DTO를 통해 받아 서비스에게 전달한다
  6. 서비스는 전달받은 데이터를 컨트롤러에게 전달한다
  7. 컨트롤러는 Model(모델) 객체에게 요청에 맞는 View 정보를 담아 DispatcherServlet에게 전송한다
  8. DispatcherServlet은 ViewResolver에게 전달받은 View 정보를 전달한다
  9. ViewResolver는 응답할 View에 대한 JSP를 찾아 DispatcherServlet에게 전달한다
  10. DispatcherServlet은 응답할 뷰의 Render를 지시하고 뷰는 로직을 처리한다
  11. DispatcherServlet은 클라이언트에게 Rending된 뷰를 응답하며 요청을 마친다

기술면접 질문/답안

MVC 모델이란 무엇인지 설명해주실 수 있을까요?

MVC(Model-View-Controller) 모델은 애플리케이션을 세 가지 주요 구성 요소로 분리하여 관리하는 소프트웨어 디자인 패턴입니다. 이 패턴은 애플리케이션의 구조를 명확하게 하고, 유지보수를 용이하게 하며, 개발 과정에서 각 부분의 재사용성을 높이는 데 목적이 있습니다.

 

모델(Model): 모델은 애플리케이션의 데이터와 비즈니스 로직을 캡슐화합니다. 데이터의 접근, 저장, 수정과 같은 기능을 제공하며, 애플리케이션의 상태를 나타냅니다. 모델은 데이터 변경 시 뷰와 컨트롤러에 이를 알리는 역할도 합니다.

 

뷰(View): 뷰는 사용자에게 데이터를 시각적으로 표현하는 인터페이스입니다. 뷰는 모델로부터 데이터를 받아 사용자에게 보여주며, 사용자 인터페이스(UI) 요소를 관리합니다. 뷰는 모델이 가지고 있는 정보를 바탕으로 사용자에게 정보를 표시하지만, 모델의 로직을 직접적으로 처리하지는 않습니다.


컨트롤러(Controller): 컨트롤러는 사용자의 입력을 받아 처리하는 역할을 담당합니다. 사용자의 액션에 대한 로직을 구현하고, 모델을 업데이트하거나 뷰에 변경을 요청합니다. 컨트롤러는 모델과 뷰 사이의 중재자 역할을 하며, 애플리케이션의 흐름을 제어합니다.

 

MVC 패턴을 사용하면, 애플리케이션의 비즈니스 로직과 사용자 인터페이스가 분리되어, 각 부분을 독립적으로 개발하고 테스트할 수 있습니다. 이는 개발 과정의 복잡성을 줄이고, 코드의 재사용성을 높이며, 유지보수를 용이하게 합니다. 대규모 애플리케이션 개발에서 특히 이러한 구조적 접근 방식은 개발을 체계화하고, 팀 작업을 효율적으로 만들어 줍니다.

IoC(Inversion of Control)

IoC(Inversion of Control)이란 "제어의 역전"이라는 의미로 말 그대로 메서드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미

  • 제어의 역전이란 간단히 말하면 제어의 흐름을 바꾼다라고 할 수 있다

객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.

 

기존에는 다음과 순서로 객체가 만들어지고 실행됨

  1. 객체 생성
  2. 의존성 객체 생성(클래스 내부에서 생성)
  3. 의존성 객체 메서드 호출

스프링에서는 다음과 같은 순서로 객체가 만들어지고 실행됨

  1. 객체 생성
  2. 의존성 객체 주입(제어권을 스프링에게 위임하여 스프링이 만들어놓은 객체를 주입함)
  3. 의존성 객체 메서드 호출

스프링이 모든 의존성 객체를 스프링이 실행될때 만들어주고 필요한 곳에 주입시켜 줌으로써 Bean들은 싱글톤 패턴의 특징을 가지며, 제어의 흐름을 사용자가 컨트롤하는 것이 아니라 스프링에게 맡겨 작업을 처리하게 됨

IoC 장점

  • 객체 간 낮은 결합도
  • 유연한 코드 작성 가능
  • 가독성 증가
  • 코드 중복 방지
  • 유지 보수 용이

Spring IoC 공식 문서

 

DI(Dependency Injection)

스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로 객체를 직접 생성하는 것이 아닌 외부에서 생성한 후 주입시켜 주는 방식이다.

 

객체가 생성자 인수, 팩토리 메서드에 대한 인수 또는 생성된 후 객체 인스턴스에 설정된 속성을 통해서만 객체의 족송석(즉, 작업하는 다른 객체)을 정의하는 프로세스입니다.

 

DI(의존성 주입)을 통해서 모듈간의 결합도가 낮아지고 유연성이 높아진다.

 

DI(의존성 주입)은 생성자 기반 의존성 주입과 Setter 기반 의존성 주입이라는 두 가지가 있음

 

생성자 기반 의존성 주입

생성자 기반 DI는 컨테이너가 가각각 종속성을 나타내는 여러 인수를 사용하여 생성자를 호출함으로써 수행됩니다.

특정 인수를 사용하여 정적 팩토리 메서드를 호출하여 빈을 생성하는 것은 거의 동일하며, 이 설명에서는 생성자와 정적 팩토리 메서드에 대한 인수를 비슷하게 취급합니다.

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on a MovieFinder
	private final MovieFinder movieFinder;

	// a constructor so that the Spring container can inject a MovieFinder
	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}

 

Setter 기반 의존성 주입

Setter 기반 DI는 인수가 없는 생성자 또는 인수가 없는 정적 팩토리 메서드를 호출하여 빈을 인스턴스화한 후 컨테이너가 빈의 Setter 메서드를 호출하여 수행됩니다.

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on the MovieFinder
	private MovieFinder movieFinder;

	// a setter method so that the Spring container can inject a MovieFinder
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}


DI 사용 시 장점

  • 의존성이 줄어든다(변경에 덜 취약해짐)
  • 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워진다
  • 가독성이 높아진다
  • 재사용성이 높아진다
  • 불변성을 활용할 수 있다(final 선언 가능)
  • NullPointerException의 발생을 막는다

Spring DI 공식 문서

기술면접 질문/답안

DI와 IoC에 대해 아는 만큼 설명해주실 수 있을까요?

IoC (Inversion of Control, 제어의 역전): IoC는 애플리케이션의 흐름 제어를 사용자가 아닌 프레임워크나 컨테이너가 관리하는 디자인 패턴을 말합니다. 전통적인 프로그래밍에서는 사용자가 프로그램의 흐름을 직접 제어하지만, IoC에서는 프레임워크가 이를 담당하여 개발자는 애플리케이션의 특정 부분만을 구현하게 됩니다. 이로 인해 코드의 모듈성과 유연성이 향상되며, 테스트 및 유지보수가 용이해집니다.

DI (Dependency Injection, 의존성 주입): DI는 IoC의 한 형태로, 객체의 생성과 이들 간의 의존 관계 설정을 외부 컨테이너나 프레임워크가 담당하는 방법입니다. 개발자는 사용할 객체를 직접 생성하지 않고, 이를 프레임워크에 위임합니다. 프레임워크는 구성 파일이나 어노테이션을 기반으로 필요한 객체를 생성하고, 이를 사용하는 클래스에 주입합니다. 이는 객체 간의 결합도를 낮추고, 코드의 재사용성을 높이며, 테스트를 용이하게 합니다.

 

DI의 방식은 생성자 주입, 세터 주입, 필드 주입 세 가지가 있으며 생성자 주입은 의존성이 불변하고 필수적일 때 사용되며, 세터 주입은 의존성이 선택적이거나 변경 가능할 때 사용됩니다, 필드 주입은 간편하지만 결합도와 테스트의 복잡성을 증가시킬 수 있습니다.

세 가지 DI 방식 중 스프링은 생성자 주입 방식을 권장하고 있습니다.

'항해 99 > Spring' 카테고리의 다른 글

즉시로딩, 지연로딩, N+1 문제  (0) 2024.04.13
Spring PSA  (0) 2024.04.12
Spring Context  (0) 2024.04.10
Spring AOP  (0) 2024.04.09
Override, Overload / JPA 더티체킹 / JVM  (0) 2024.04.04