Spring 입문주차 -1

dev_joo·2026년 1월 27일

기술 블로그 작성

첫 날 학습 매니저께서 공유해주신 아티클을 읽어보았다.
내용은 기술 블로그의 중요성과 함께 기술 블로그 작성을 장려하는 글이었다.

사실 많은 실력있는 개발자 분들이 이미 기술 블로그를 작성하는 것을 봐 왔고, 나도 또한 기술 블로그를 작성하기로 마음먹고 최대한 블로깅을 쓰려 노력하는 상태였지만, 뭔가 글을 완성시켜서 보여줘야한다, 배운 내용을 모두 적어내야만 한다는 강박이 생겨서 한 번 글을 쓸 때 피로도가 엄청났다.

토스의 테크니컬 라이팅 가이드를 보고선 글을 쓴다면 마땅히 이처럼 써야한다고도 생각했다. 글 업로드 간격이 길어지고, 물론 작성하면서 수정의 수정을 거치면서 블로그 글 작성 없이 글을 쓸 때보다는 더 이해가 잘 되고, 머리에 남는다는 것을 체감했다. 그러나 학습매니저님이 작성해주신 아티클에서 너무 정제된 용어나 내용을 기록한다기보다, 학습 기록지에 가깝게 단순하게 정리 한 뒤, 옮겨적는다면 글을 쓴다는 부담이 덜어질 것이라고 하셨다.

아티클에서 들은 예시처럼 하루에 15분, 1주일에 1시간 정도 꾸준히 작성하는것을 목표로 했다. 이로써 나도 이력서와 포트폴리오에 강력한 무기가 하나 생기면 좋겠다!


입문 주차 강의

개인 일정으로 모든 날짜에 참여가 불가능할 것 같아서 틈틈이 해당 강의를 모두 들어놓기로 했다.


테스트

Junit 테스트를 위해 필요한 의존 설정

테스트 파일 작성
Generate - Alt+ Insert
테스트


자동으로 해당 위치에 테스트 클래스를 생성해준다.

JUnit은 테스트 실행 환경을 가지고 있기 때문에 따로 main() 메서드를 실행하거나 서버를 실행시키지 않아도 이렇게 각각의 메서드 혹은 기능별로 테스트 코드를 작성하여 실행시킬 수 있다.


Lombok

메서드/생성자 등을 자동 생성해줌으로써 코드를 절약할 수 있도록 도와주는 라이브러리

Preperences Ctrl + Alt + S > 어노테이션 프로세서 활성화

롬복 설치/확인

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor //- final 또는 @NonNull 필드를 파라미터로 갖는 생성자를 생성한다.

위와 같은 어노테이션을 클래스에 사용하면

컴파일 시점에 getter, setter, 생성자 등의 코드를 자동으로 생성해 준다.

일반적인 어노테이션 프로세서는 새로운 클래스를 생성하는 반면,
Lombok은 javac 내부에서 컴파일러 AST를 직접 조작하여
기존 클래스에 메서드를 주입하는 방식을 사용한다.

이 중 DTO와 같은 불변 데이터 객체의 경우,
Java 16부터 제공되는 record로 대체할 수 있다.


application.properties (Spring 설정 파일)

  • 또는 application.yml 파일로 설정한다.
  • SpringBoot가 자동으로 설정하는 개발에 필요한 정보 설정 값을 쉽게 수정할 수 있다.
  • DB 연결 시 DB의 정보등 쉽게 값을 전달할 수 있다.

MVC

Model
데이터베이스 연동 작업과 비즈니스 로직을 담당
View
사용자가 보는 화면과 버튼, 폼 등 사용자 인터페이스를 디자인하고 구현
Controller
Model과 View 사이의 상호작용을 조정하고 제어
사용자의 입력 -> Model -> View 업데이트

Servlet (서블릿)

자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양

  1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request API 요청
  2. 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse 객체를 생성하고, 설정 정보를 통해 어떠한 Servlet에 대한 요청인지 찾음
  3. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청에 따라 doGet 혹은 doPost 등의 메서드를 호출해 결과를 그대로 반환하거나 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환
  4. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse 객체를 소멸시킴

DispatcherServlet

Front Controller

Spring은 모든 API 요청에 맞는 수많은 Servlet 클래스를 구현하는 대신DispatcherServlet을 사용하여 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리

Client(브라우저)에서 HTTP 요청이 들어오면
DispatcherServlet 객체
1. 요청을 분석하고 API path 와 Controller 메서드가 매칭되어있는 Handler mapping을 통해 Controller를 찾아 요청을 전달
해당 Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 (Model, View) 정보를 DispatcherServlet로 전달해 주면
2. ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달

MVC 코드 작성 어노테이션

@Controller

해당 클래스가 Controller의 역할을 수행할 수 있도록 등록

@GET

@GetMapping("/api/get")
@ResponseBody
public String get() {
    return "GET Method 요청";
}

@POST

@PostMapping("/api/post")
@ResponseBody
public String post() {
    return "POST Method 요청";
}

@PUT

@PutMapping("/api/put")
@ResponseBody
public String put() {
    return "PUT Method 요청";
}

@DELETE

@DeleteMapping("/api/delete")
@ResponseBody
public String delete() {
    return "DELETE Method 요청";
}

@RequestMapping

중복되는 URL를 단축 ( /api/* -> /* )

@Controller
@RequestMapping("/api") // 해당 경로는 이 컨트롤러 클래스에서 처리된다.
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello World!";
    }

    @GetMapping("/get")
    @ResponseBody
    public String get() {
        return "GET Method 요청";
    }

    @PostMapping("/post")
    @ResponseBody
    public String post() {
        return "POST Method 요청";
    }

    @PutMapping("/put")
    @ResponseBody
    public String put() {
        return "PUT Method 요청";
    }

    @DeleteMapping("/delete")
    @ResponseBody
    public String delete() {
        return "DELETE Method 요청";
    }
}

RequestMapping은 “최종적으로는 메서드 단위”로 등록된다.
클래스 단위 RequestMapping은 prefix(공통 조건) 역할만 한다.
클래스 + 메서드의 RequestMapping이 합쳐져서 하나의 “핸들러 메서드”가 된다.

즉, 그럴 일은 없겠지만 컨트롤러 클래스를 HTTP 메서드별로 나누는 것도 가능하다.

@Controller
@RequestMapping("/api/hello")
public class HelloGetController {
    @GetMapping
    public String getHello() { ... }
}
@Controller
@RequestMapping("/api/hello")
public class HelloPostController {
    @PostMapping
    public String postHello() { ... }
}
👉 요청 예
GET  /api/hello
POST /api/hello

Template engine

종류: 타임리프(Thymeleaf), Groovy, FreeMarker, Jade, JSP 등

정적 페이지

1. 정적 페이지 직접 요청 (Spring이 반환)

http://localhost:8080/hello.html 요청
-> /resources/static/hello.html 반환

SpringBoot 서버에 요청된 html 파일은 컨트롤러를 거치지 않고 Spring이 자동으로 static 폴더에서 파일을 찾아서 반환한다.

2. 컨트롤러에서 정적 파일 반환 (템플릿 엔진 x)

<@GetMapping("static-hello")
public String hello() {
	return "hello.html";
}

3. 컨트롤러에서 정적 파일 반환 (템플릿 엔진 o)

동적 페이지 처리를 위한 템플릿 엔진 thymeleaf를 프로젝트에 추가하면
Controller가 반환한 View 이름을 /resources/templates 디렉토리에서 해석하도록 ViewResolver가 등록된다

@GetMapping("/static-hello")
public String hello() {
    return "hello.html";
} // -> templates 폴더에 해당 파일이 없으므로 500 error

a. 정적 파일 요청으로 redirect

템플릿 엔진을 적용한 상태에서 static 폴더의 html 파일을 Controller를 통해서 처리하고 싶다면 이렇게 redirect 요청으로 처리할 수 있다.

@GetMapping("/html/redirect")
    public String htmlStatic() {
        return "redirect:/hello.html";
} -> 정적 파일을 직접 요청하는 

b. Template engine 에 View 전달

/resources/templates/hello.html
html 파일을 찾는 경로에 정적파일을 추가하면 다음과 같이 처리할 수 있다.

@GetMapping("/html/templates")
public String htmlTemplates() {
    return "hello";
}

동적 페이지

<div>
  (방문자 수: <span th:text="${visits}"></span>)
</div>
private static long visitCount = 0;

@GetMapping("/html/dynamic")
public String htmlDynamic(Model model) {
    visitCount++;
    model.addAttribute("visits", visitCount);
    return "hello-visit";
}

동적 페이지 처리 과정

  • Client 의 요청을 Controller에서 (필요하다면 DB 작업 후) 처리한 데이터를 Model에 저장

  • Template engine(Thymeleaf) 에게 View, Model 전달
    View: 동적 HTML 파일 hello-visit.html
    Model: View 에 적용할 정보들 visits

  • Template engine이
    View에 Model을 적용 → 동적 웹페이지 생성

  • Client(브라우저)에게 View(동적 웹 페이지, HTML)를 전달

데이터를 Client에 반환하는 방법

웹 생태계가 고도화 되는 과정중에 상대적으로 프론트엔드와 백엔드가 각각 따로 발전하게 되면서, 느슨하게 결합하는 방식을 더 많이 채택하게 되었다.

최근에는 서버가 직접 뷰(html/css/js)를 반환하기 보다
요청에 맞는 특정한 정보만 반환(주로 JSON 형태)하는 것이 표준적인 방식이 되었다.

반환 데이터 정보

문자열(String)

  • 기본 Content-Type: text/plain;charset=UTF-8
  • HTML을 명시적으로 반환할 경우: text/html
// HTML을 명시적으로 반환
// (@ResponseBody 또는 @RestController 환경에서)
@GetMapping(value = "/html", produces = "text/html;charset=UTF-8")

객체(Object)

  • Content-Type: application/json
    @ResponseBody 또는 @RestController 환경에서 객체는 key-value 형태의 JSON으로 변환되며,
    이 과정은 Spring Boot가 HttpMessageConverter + Jackson 라이브러리를 통해 처리한다.

Jackson

Java에서 JSON 데이터 구조를 처리해주는 라이브러리.
SpringBoot의 starter-web에서 Jackson 관련 라이브러리가 기본 포함되어 있다.

Object Mapper

직접 JSON 데이터를 처리해야할 때는 Jackson 라이브러리의 ObjectMapper를 사용할 수 있다.

1. Seriallize (Object -> JSON)

// public String writeValueAsString(Object value) throws JsonProcessingException
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(obj); // 내부적으로 객체의 getter 사용 

2. Deserialize (JSON -> Object)

// public <T> T readValue(String content, Class<T> valueType) throws JsonProcessingException
ObjectMapper objectMapper = new ObjectMapper();
MyClass obj = objectMapper.readValue(json, MyClass.class);
// json 문자열을 파싱하여 MyClass 타입의 객체로 역직렬화
// 내부적으로 객체의 기본 생성자 + setter(또는 필드 접근)를 사용

@ResponseBody

@ResponseBody가 붙은 컨트롤러 메서드는 반환값을 View로 해석하지 않고 ViewResolver를 거치지 않게 해 반환값을 HTTP 응답 본문(body)에 직접 작성하도록 Spring에 알린다.

@RestController

@Controller + @ResponseBody
해당 클래스 내 모든 메서드에 @ResponseBody가 자동 적용되도록 하는 애노테이션이다.


Client가 보낸 데이터 처리하기

1. Path Variable로 전달된 데이터

GET http://localhost:8080/hello/request/star/Robbie/age/95

@PathVariable

컨트롤러 요청 경로에서{}로 해당 부분이 변수임을 표시해주고, @PathVariable 애노테이션으로 해당 값을 메서드 파라미터에 바인딩한다.

@GetMapping("/star/{name}/age/{age}")
    @ResponseBody
    public String helloRequestPath(@PathVariable String name, @PathVariable int age)
    {
        return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
    }

2. Request Param (쿼리스트링)으로 전달된 데이터

GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
URL?Key1=Value1&Key2=Value2

a. @RequestParam

@RequestParam 애노테이션을 사용해 매개변수명을 Key로 설정해 쿼리스트링 값을 파라미터에 바인딩 한다.

// [Request sample]
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
@GetMapping("/form/param")
@ResponseBody
public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
    return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}

b. @ModelAttribute

Java의 객체로 데이터를 받아온다.

// [Request sample]
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

3. Form에서 전달된 데이터 (application/x-www-form-urlencoded)

POST http://localhost:8080/hello/request/form/param
(요청 Body의 JSON이 아닌 application/x-www-form-urlencoded형식)

a. @ReqiestParam

// [Request sample]
    // POST http://localhost:8080/hello/request/form/param
    // Header
    //  Content type: application/x-www-form-urlencoded
    // Body
    //  name=Robbie&age=95
    @PostMapping("/form/param")
    @ResponseBody
    public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }

b. @ModelAttribute

Java의 객체 형태로 받는 방법

// [Request sample]
// POST http://localhost:8080/hello/request/form/model
// Header
//  Content type: application/x-www-form-urlencoded
// Body
//  name=Robbie&age=95
@PostMapping("/form/model")
@ResponseBody
public String helloRequestBodyForm(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

4. JSON 데이터로 전달된 요청

@RequestBody

데이터를 객체 형태로 받을 수 있다.
객체 클래스에는 set or get 메서드 또는 오버로딩된 생성자가 있어야 한다.

// [Request sample]
// POST http://localhost:8080/hello/request/form/json
// Header
//  Content type: application/json
// Body
//  {"name":"Robbie","age":"95"}
@PostMapping("/form/json")
@ResponseBody
public String helloPostRequestJson(@RequestBody Star star) {
    return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
}

required 옵션

@PathVariable(required = false)
PathVariable은 URL 경로 자체에 포함되므로 값이 없으면 요청 자체가 매핑되지 않아 404 발생.즉, required=false라도 컨트롤러까지 오지 않음

@RequestParam(required = false)
해당 매개변수 데이터가 들어오지 않아도 데이터를 null 로 처리함.
따라서 required 옵션을 사용할 땐 반드시 파라미터 타입으로 primitive 타입이 아닌 Wrapper 타입 사용해야 함.

defaultValue 옵션

@RequestParam(required = false, defaultValue = "0") int age

defaultValue를 쓰면 자동으로 기본값 사용되어 값이 없어도 에러가 나지 않는다.
(단, 이 경우 required는 의미 없어짐)

매개변수 추론 불가

스프링부트 버전 ≥3.2.1
@RequestParam, @PathVariable 활용하여 URI의 Query string, Path variable 값을 가져올 때 매개변수 명을 추론하여 binding 해주지 못한다.

매개변수 추론 

@RequestParam와 @ModelAttribute 의 생략

@GetMapping("/test")
public String test(String name, int age) { ... }

해당 파라미터(매개변수)가 SimpleValueType(primitive, String)이라면 @RequestParam으로 간주하고 아니라면(Wrapper) @ModelAttribute가 생략되어있다 판단한다.
가독성과 명확성 때문에 명시하는것을 권장한다.

profile
풀스택 연습생. 끈기있는 삽질로 무대에서 화려하게 데뷔할 예정 ❤️🔥

0개의 댓글