[Spring] 스프링을 위한 기본 개념 복습 및 학습 -2

2025. 10. 15. 16:43·Spring/백엔드 기초

💡 서블릿의 문제점

  • 낮은 응집도 (Low Cohesion) : 하나의 서블릿 클래스 내에 HTTP 요청 처리, 비즈니스 로직, 데이터 접근, 응답 생성 로직이 모두 혼재. 이는 단일 클래스가 너무 많은 책임을 갖게 하여 코드의 이해와 테스트를 어렵게 만듦
  • 강한 결합도 (High Coupling) : 비즈니스 로직이 HttpServletRequest, HttpServletResponse와 같은 서블릿 API에 직접적으로 의존. 이로 인해 웹 환경 밖에서 단위 테스트를 수행하기가 매우 까다로워짐
  • 반복적인 상용구 코드 (Boilerplate Code) : 요청 파라미터 파싱, 타입 변환, 응답 컨텐츠 타입 설정 등 모든 요청에 대해 반복적인 코드가 필수적으로 요구됨

👉 이런 문제점을 해결하기 위해 관심사의 분리(Separation of Concerns, SoC) 원칙에 입각하여 스프링 MVC를 사용하게 됨 

💡 MVC 패턴 기초와 역할

MVC 패턴이란?

MVC 패턴은 하나의 Servlet이 혼자 모든 것을 처리하던 문제를 해결하기 위해 애플리케이션의 코드를 세 가지 역할로 명확하게 나누는 설계 방식

  • Model : 데이터와 비즈니스 로직을 담당
  • View : 사용자에게 보여지는 화면(UI)를 담당
  • Controller : 사용자의 요청(Request)을 받아 Model과 View를 연결해주는 중간 다리 역할을 수행

역할 분담을 함으로서 코드가 훨씬 더 깔끔해지고 유지보수와 테스트가 용이해지는 효과를 보여주게 됨!

 

Controller 

 : 사용자의 HTTP 요청을 받아서 처리. URL 매핑을 통해 어떤 요청이 들어왔을 때 어떤 메소드를 실행할지 결정하고, 비즈니스 로직을 호출하고 데이터를 Model에 담아 View 이름을 반환

✅ @Controller 어노테이션이 붙은 클래스가 이 역할을 수행

 

Model

 : Controller에서 View로 데이터를 전달할 때 사용하는 객체. Key-Value 형태로 데이터를 저장하며, Controller에서 처리한 결과 데이터를 Model에 추가하면 View에서 해당 데이터를 참조할 수 있음. 주로 Model, ModelAndView 클래스를 사용

 

View

 : 사용자에게 보여지는 화면을 담당. Model에 담긴 데이터를 사용해 HTML을 생성하고 클라이언트에게 응답. Controller가 반환한 View 이름을 ViewResolver가 실제 View 파일과 매핑시켜 렌더링

✅ @Tymeleaf와 같은 템플릿 엔진이 이 역할을 수행

 

💡Thymeleaf

타임리프(Thymeleaf) 란?

서버에서 HTML을 동적으로 만들어주는 자바 기반의 도구, 즉 템플릿 엔진

→ HTML을 동적으로 만들어줄 수 있음에 주목

 

타임리프의 주된 역할

 : 자바 코드(스프링 컨트롤러)가 보내주는 데이터를 실제 HTML 코드와 결합하여, 내용이 채워진 완성된 웹 페이지를 만드는 것

 

예를 들어, "${userName}님 환영합니다" 라는 HTML 템플릿이 있다면, 타임리프는 ${userName} 부분을 실제 사용자 이름인 "홍길동"으로 바꿔서 최종 페이지를 완성

 

💡 SSR과 CSR

SSR(Server Side Rendering)

 : SSR은 서버가 웹 페이지에 필요한 모든 데이터를 채워 넣어 완벽하게 조립된 HTML 페이지를 브라우저에게 전달하는 방식

사용자는 거의 즉시 컨텐츠를 볼 수 있어 초기 로딩 속도가 빠르고, 모든 내용이 HTML에 포함되어 있어 검색 엔진 최적화(SEO)에 유리

타임리프가 이 방식에 해당

CSR(Client Side Rendering)

: CSR은 서버가 거의 텅 빈 HTML 뼈대와 자바스크립트 파일만 보내주는 방식

그러면 사용자 브라우저(클라이언트)가 자바스크립ㅌ를 실행해 필요한 데이터를 서버에 다시 요청하고, 브라우저가 직접 페이지를 조립하여 화면에 그림

React나 Vue 같은 최신 프론트엔드 프레임워크들이 이 방식을 사용

CSR은 초기 로딩은 느릴 수 있으나, 이후에는 앱처럼 부드럽고 빠른 화면 전환이 가능

 

💡 프론트 컨트롤러 패턴 (Front Controller Pattern)

: 모든 클라이언트 요청을 단일 진입점(Single Point of Entry)에서 처리하는 디자인 패턴

요청에 대한 공통 처리(보안, 로깅, 인코딩 등)를 중앙에서 효율적으로 관리할 수 있으며, 개별 요청을 처리할 핸들러(Controller)로 작업을 위임하는 역할을 함

 

전통적인 방식의 문제점

  • 각 서블릿마다 공통 로직(인증, 로깅, 인코딩 등)이 중복됨
  • 새로운 기능 추가 시 모든 서블릿 수정 필요
  • 관리가 어렵고 일관성 유지 어려움

프론트 컨트롤러 패턴 해결책

  • 모든 요청을 하나의 컨트롤러가 받아서 처리(공통 로직을 중앙화)
  • 요청에 따라 적절한 컨트롤러로 위임

프론트 컨트롤러 패턴의 장점

  • 공통 로직의 중앙화
  • 일관된 처리 흐름
  • 새로운 컨트롤러 추가 시에도 공통 로직 자동 적용
  • 유지보수성 향상

💡 어댑터 패턴 (Adapter Pattern)

: 어댑터 패턴이란 서로 다른 인터페이스를 가진 클래스들을 연결해주는 패턴. 다형성을 잘 활용한 패턴이라고 볼 수 있음

 

❓Spring MVC에서 왜 어댑터 패턴이 필요한가?

 : Spring에서는 다양한 형태의 컨트롤러를 지원하기 때문에, 프론트 컨트롤러 패턴만으로는 다양한 종류의 컨트롤러를 처리하기 어려움

 

어댑터 패턴의 장점

  • 다양한 컨트롤러 타입을 통일된 방식으로 처리(다형성)
  • 새로운 컨트롤러 타입 추가 시 새 어댑터만 만들면 됨
  • 기존 코드 수정 없이 확장 가능

💡 DispatcherServlet

 : DispatcherServlet은 프론트 컨트롤러 패턴(Front Controller Pattern)을 구현한 스프링 MVC의 핵심적인 프론트 컨트롤러

 

Spring MVC와 DispatcherServlet을 연관지어 하나의 흐름도로 정리하자면 아래와 같음

 

① 요청

  • 클라이언트가 웹 애플리케이션에 요청(Request)을 보내고, 이 요청은 가장 먼저 DispatcherServlet에 도달

② 핸들러 조회

  • DispatcherServlet은 HandlerMapping에게 요청을 처리할 Handler(=Controller)를 찾아달라고 요청

③ 핸들러 실행

  • DispatcherServletdms HandlerMapping으로부터 받은 정보를 이용해 해당 Controller에게 요청 처리를 위임

④ ModelAndView 반환

  • Controller는 비즈니스 로직을 수행한 후, 결과 데이터(Model)와 뷰의 논리적 이름(View Name)을 담은 ModelAndView 객체를 DispatcherServlet에 반환

⑤ 뷰 해석

  • DispatcherServlet은 ModelAndView에서 뷰 이름을 추출하여 ViewResolver에게 전달하고, 해당하는 실제 View 객체를 찾아달라고 요청

⑥ 뷰 렌더링

  • DispatcherServlet은 ViewResolver로부터 받은 View 객체에게 모델 데이터를 전달하여 뷰를 렌더링하도록 요청

⑦ 응답

  • 렌더링된 View의 결과물이 DispatcherServlet을 통해 클라이언트에게 최종적으로 응답(Response)으로 전달


Spring MVC에서 일부 역할이 프론트엔드로 넘어갔지만 아직까지도 Controller와 Model의 일부 역할은 백엔드가 맡아 진행하고 있음 그에 따라 아래와 같이 역할이 바뀌게 됨

프론트엔드가 가져간 M과 V의 역할

  • V (View)
  • M (Model)

변화된 Spring 백엔드의 역할

  • C (Controller)
    컨트롤러의 역할은 더욱 중요해지고 명확해졌음. View를 반환하는 대신 데이터(JSON)을 반환하는 API 엔드포인트의 역할을 수행
  • (변형된) M (Model)
    백엔드에서 Model의 개념은 'View에 전달할 데이터'가 아니라 'API 응답으로 보낼 데이터(DTO:Data Transfer Object)'로 바뀜
  • (사라진) V (View)
    View의 역할은 사라지고 백엔드는 더이상 HTML을 만들지 않음 

 

1. @Controller

: 전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용

2. @RestController

: @RestController는 @Controller에 @ResponseBody가 추가된 것


📌 @ResponseBody란?

@ResponseBody는 Spring MVC 컨트롤러의 메서드에 사용되는 어노테이션

이 어노테이션이 붙은 메서드가 반환하는 값은 View를 찾는데 사용되지 않고, HTTP 응답 본문(Response Body)에 직접 작성됨

즉, JSON 형식으로 리턴하기 위해 필수적으로 사용해야 하는 어노테이션

 

  • @Controller와 @ResponseBody를 둘 다 쓰는 경우
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    @ResponseBody
    public User getUser() {
        // ...
    }
}

 

  • @RestController만 쓰는 경우
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // @Controller와 @ResponseBody가 합쳐졌다!
public class UserRestController {

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    public User getUser() {
        // ...
    }
}

 

✅ 위의 두개의 코드는 완벽히 같으며, 굳이 분리하여 따로 사용할 필요가 없으므로 @RestController를 주로 사용하도록 할 예정

 

💡 요청 매핑 어노테이션

1. 기본 매핑 어노테이션 (@RequestMapping)

 

- 사용법 1 : 해당 컨트롤러의 전체 경로 prefix를 설정

@RestController
@RequestMapping("/hello")
public class HelloController {

		// ...

}

 

- 사용법 2 : 특정 API의 전체 경로를 허용

@RestController
public class HelloController {

		@RequestMapping(value = "/hello", method = RequestMethod.GET)
    public void getHello() {
		    // ...
    }
    
		@RequestMapping(value = "/hello", method = RequestMethod.POST)
    public void postHello() {
		    // ...
    }
    
		@RequestMapping(value = "/hello", method = RequestMethod.PUT)
    public void putHello() {
		    // ...
    }
    
		@RequestMapping(value = "/hello", method = RequestMethod.PATCH)
    public void patchHello() {
		    // ...
    }
    
		@RequestMapping(value = "/hello", method = RequestMethod.DELETE)
    public void deleteHello() {
		    // ...
    }
}

 

사용법 1의 용도로 주로 사용하게 되며 @RequestMapping 어노테이션을 활용, 사용법2는 HTTP 메서드별 전용 어노테이션으로 대체

 

2. HTTP 메서드별 전용 어노테이션 (사용법 2를 대체)

HTTP 메서드로는 GET, POST, PUT, PATCH, DELETE가 있음

 

  • GET : @GetMapping을 사용
@RestController
public class HelloController {

    @GetMapping("/hello")
    public void getHello() {
		    // ...
    }
}
  • POST : @PostMapping을 사용
@RestController
public class HelloController {

    @PostMapping("/hello")
    public void postHello() {
		    // ...
    }
}
  • PUT : @PutMapping을 사용
@RestController
public class HelloController {

    @PutMapping("/hello")
    public void putHello() {
		    // ...
    }
}
  • PATCH : @PatchMapping을 사용
@RestController
public class HelloController {

    @PatchMapping("/hello")
    public void patchHello() {
		    // ...
    }
}
  • DELETE : @DeleteMapping을 사용
@RestController
public class HelloController {

    @DeleteMapping("/hello")
    public void deleteHello() {
		    // ...
    }
}

💡 데이터 전달 어노테이션

클라이언트가 서버로 데이터를 전달하는 방식은 크게 4가지가 있음

1. 쿼리 파라미터(Query Parameter)

2. 폼 데이터(Form Data)

3. 경로 변수(Path Variable)

4. 요청 본문(Request Body)

스프링에서는 어떻게 사용되고 있는지 알아보자

 

1. 쿼리 파라미터(@RequestParam)

쿼리 파라미터는 URL의 ? 뒤에 오는 파라미터들을 처리할 때 사용

예를 들어, GET /users?name=john&age=30 요청이 있다면, 이 중 ? 뒤에 오는

name = john,
age= 30
이 파라미터이며 쿼리스트링이라고 부르기도 함
  • 기본 사용법
@RestController
public class UserController {
    
    // /users?name=john&age=30 요청이 들어온 상황이라면
    @GetMapping("/users")
    public String getUser(
				    @RequestParam String name, // name 값은 john
				    @RequestParam int age // age 값은 30
    ) {
				// ...
    }
}

이 상황에서 String name 은 john이 되고, int age는 30이 됨

  • 선택적 파라미터와 기본값 설정
@RestController
public class UserController {
    
    // /users?page=1&size=10 요청이 들어온 상황이라면
    @GetMapping("/users")
    public List<String> getUsers(
            @RequestParam(defaultValue = "0") int page, // 1
            @RequestParam(defaultValue = "10") int size, // 10
            @RequestParam(required = false) String sort // null
    ) {
        // ...
    }
}

👉 required = flase로 설정하지 않으면 파라미터가 필수가 됨

  • @RequestParam의 default required 값은 true

defualtValue를 설정하면 자동으로 required = flase 가 됨

 

2. 폼 데이터 or 쿼리 파라미터 여러 개(@ModelAttribute)

HTML 폼 or 쿼리 파라미터 여러 개를 객체로 바인딩할 때 사용
@RequestParam을 여러 개 쓰는 대신 객체를 하나로 받을 수 있음
  • @RequestParam 여러 개를 @ModelAttribute 하나로 처리
// @RequestParam이 너무 많은 경우
@GetMapping("/api/search")
public List<String> searchUsers(
        @RequestParam(required = false) String name,
        @RequestParam(required = false) String email,
        @RequestParam(required = false) Integer minAge,
        @RequestParam(required = false) Integer maxAge,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size
) {
    // 파라미터가 많아서 복잡함!
}

// @ModelAttribute 사용 (추천)
@GetMapping("/api/search")
public List<String> searchUsers(@ModelAttribute UserSearchDto dto) {
    // 하나의 @ModelAttribute로 데이터를 받을 수 있다.
}
// 바인딩용 DTO 클래스
public class UserSearchDto {
    private String name;
    private String email;
    private Integer minAge;
    private Integer maxAge;
    private Integer page = 0;      // 기본값 설정 가능
    private Integer size = 10;     // 기본값 설정 가능
    
    // ...
}

 

3. 경로 변수(@Path Variable)

URL 경로의 일부를 변수로 받을 때 사용
GET /users/123 형태의 요청에서 123 값을 받을 수 있음
쿼리 파라미터와 혼동되지 않도록 주의 필요!
  • 기본 사용법
@RestController
public class UserController {
    
    // GET /users/123
    @GetMapping("/users/{userId}")
    public String getUser(@PathVariable Long userId) { // userId는 123
        // ...
    }
}

4. 요청 본문(@Request Body)

HTTP 요청의 본문(Body)에 있는 데이터를 객체로 변환할 때 사용
주로 JSON 형태의 데이터를 받을 때 사용하며, REST API에서 가장 많이 사용됨

 

@RestController
public class UserController {
    
    // POST /users
    // Body: {"name": "John", "email": "john@example.com", "age": 30}
    @PostMapping("/api/users")
    public String createUser(@RequestBody CreateUserRequest request) {
        // ...
    }
}
@Getter
public class CreateUserRequest {
		// 위의 요청의 경우 아래와 같이 값이 매핑됩니다.
		private String name; // John
		private String email; // john@example.com
		private int age; // 30
}

👉 CreateUserRequest 같은 특정 로직의 메서드 없이 데이터 필드만 가지고 있는 타입의 객체를 DTO라고 함

 

💡PathVariable과 RequestParam은 언제 어떤 것을 써야할까?

PathVariable을 사용하는 경우

@PathVariable은 URL 경로 자체에 값이 포함되기 때문에 반드시 값이 있어야 함

→ 생략되면 안되는 필수 값

 

만약 /posts/123에서 /123을 삭제한다면 /posts가 되어버리므로 다른 API

GET /posts/123
GET /posts

 

RequestParam을 사용하는 경우

@RequestParam은 쿼리 파라미터(?key=value)를 받아오게 되는데 각 Key/Value 값은 조건부

즉, 있어도 되고 없어도 되는 값일 때 사용. 주로 검색용에 많이 쓰임

→ 쿼리 파라미터가 있든 없든 동일한 API

 

아래의 예시는 모두 같은 API, 삭제의 유무가 크게 상관이 없음

GET /users?nickname=스파르타
GET /users
GET /users?address=서울시

'Spring > 백엔드 기초' 카테고리의 다른 글

[백엔드 기초] Filter, Spring Security  (0) 2025.10.24
[백엔드 기초] 쿠키를 활용한 인증 인가 개념 실습  (0) 2025.10.23
[백엔드 기초] 백엔드 기초 개념정리  (0) 2025.10.22
[백엔드 기초] 기초 개념 정리하기  (0) 2025.10.21
[Spring] 스프링을 위한 기본 개념 복습 및 학습  (0) 2025.10.14
'Spring/백엔드 기초' 카테고리의 다른 글
  • [백엔드 기초] 쿠키를 활용한 인증 인가 개념 실습
  • [백엔드 기초] 백엔드 기초 개념정리
  • [백엔드 기초] 기초 개념 정리하기
  • [Spring] 스프링을 위한 기본 개념 복습 및 학습
stark77
stark77
하마의 IT 자기개발 이모저모, 백엔드 개발자로 거듭나기
  • stark77
    하마의 개발자 성장일기
    stark77
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 컴퓨터구조와 운영체제
        • 컴퓨터구조
        • 운영체제
      • SQL 기초
      • Spring
        • 백엔드 기초
        • Spring 실습
      • JAVA
        • Java 실습
      • HTML&CSS
        • HTML&CSS 실습
      • Git&GitHub
        • Git&GitHub 실습
      • 내배캠 끄적끄적
        • Today I Learned
      • 유용한 툴 및 사이트 정리
      • 취미
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    jsp
    for문
    네트워크 기초
    Spring
    MVC
    String.format
    Java 문법기초
    웹소켓
    Github
    객체지향프로그래밍
    algorithm
    다형성
    WebSocket
    HTML&CSS
    객체지향
    경합조건과 교착상태
    Stomp
    thymleaf
    java
    백엔드 기초다지기
    Til
    BEAN
    프로세스와 쓰레드
    SpringSecurity
    실시간 데이터 처리
    RestTemplate
    git
    백엔드 기초
    Spriingboot
    JPA
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
stark77
[Spring] 스프링을 위한 기본 개념 복습 및 학습 -2
상단으로

티스토리툴바