It's going to be one day 🍀

안녕하세요! 매일 매일 공부하려고 노력하는 백엔드 개발자 지망생의 공부 흔적입니다.

Back-End/Spring

[Spring] 동적 페이지 구현/스프링 핵심 개념

2jin2 2024. 3. 5. 18:04

Thymeleaf : 템플릿 엔진

- 서버에서 데이터를 받아 우리가 보는 HTML 웹페이지에 데이터를 넣어 보여주는 도구를 뜻함.

- HTML 파일을 그대로 유지하면서 템플릿을 렌더링할 수 있는 기능이 가능함.

 

<h1 text=${이름}>
<p text=${나이}>

만약 서버에서 아래와 같은 json이 넘어왔을 때, 템플릿 엔진은 아래 값을 받아서 HTML에 값을 적용한다.

{
	"이름" : "김자바",
	"나이" : 20
}

 

타임리프 표현식과 문법

타임리프를 사용하기 위해 HTML 파일에 다음과 같은 선언문을 작성해야 한다.

<html xmlns:th="http://www.thymeleaf.org">

 

타임리프 표현식

 

타임리프 문법

- 타임리프의 핵심은, th:xxx가 붙은 부분은 서버에서 렌더링되고 th 라는 값이 없으면 HTML의 기존 태그를 따른다.


동적 페이지 구현해보기

 

1) html 파일 셋팅

bookManager.html
bookDetail.html

- 먼저 html 파일을 Thymleaf를 사용하여 템플릿 렌더링을 해준다.

 

코드 작성

 

- BookController.java

package com.estsoft.hello.controller;

import com.estsoft.hello.repository.BookRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
@Slf4j
@Controller
public class BookController {
    private final BookRepository bookRepository;

    public BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @GetMapping("/books")
    public String home(Model model) {
        List<BookDTO> bookList = bookRepository.getAllBooks();
        model.addAttribute("bookList", bookList);
        return "bookManager";
    }

    @PostMapping("/books")
    public String add(@RequestParam String id,
                      @RequestParam String name,
                      @RequestParam String author){

        bookRepository.saveBook(new BookDTO(id, name, author));
        return "redirect:/books";
    }

    // GET /books/{id}
    @GetMapping("/books/{id}")
    public String detail(@PathVariable("id") String isbn, Model model) {

        log.info("isbn = "+isbn); // 특정 포맷으로 로그가 출력이 됨

        model.addAttribute("book", bookRepository.findBookById(isbn));

        return "bookDetail";
    }
}

코드 뜯어보기

- @Slf4j : Lombok에서 제공하는 애노테이션. 로깅을 위한 코드를 간소화해주는 역할을 함. log 출력을 위해 사용했음.

- @Controller : 스프링에게 해당 클래스가 컨트롤러임을 알려주는 어노테이션임.

- @BookController : 생성자를 통해 BookRepository 객체를 주입받음.

 

<HTTP GET 메서드 처리 - 도서 목록 정보를 화면에 표시>

- @GetMapping("/books") : HTTP GET 메서드를 처리하는 메서드임. /books 경로로 들어오는 요청을 처리함. home 메서드는 bookRepository 객체를 통해 모든 도서 목록을 bookList에 불러옴. 이 목록은 도서 정보를 담고 있는 BookDTO 객체들의 리스트임. 

- model.addAttribute("bookList", bookList) : model 객체에 bookList 라는 이름으로 도서 목록을 추가함. 이렇게 함으로써 뷰에서 해당 데이터를 사용할 수 있게 됨.

- return bookManager : 마지막으로, bookManager라는 문자열을 반환하는데 이는 뷰의 이름을 나타냄. 스프링은 이 뷰 이름을 기반으로 실제 뷰 템플릿을 찾아서 렌더링함!

 

<HTTP POST 메서드 처리 - 도서 정보를 데이터베이스에 저장하고, 도서 목록 화면으로 리다이렉트>

- @PostMapping("/books") : HTTP POST 메서드를 처리하는 메서드임. /books 경로로 들어오는 요청을 처리함. 

- @RequestParam : add메서드에서는 이 어노테이션을 사용해서 요청 파라미터를 받음. 즉, HTTP POST 요청의 body에서 "id", "name", "author" 파라미터를 추출하여 각각의 매개변수에 매핑함.

- bookRepository.saveBook(id, name, author) : 객체의 saveBook 메서드를 호출하여 전달받은 도서 정보를 데이터베이스에 저장함. 새로운 도서를 추가하는 역할을 함.

- return "redirect:/books" : 도서를 추가한 후에 "/books"로 리다이렉트함. 이는 사용자가 도서를 추가한 후에 도서 목록 화면으로 다시 이동하게 됨을 의미함. 사용자는 도서를 추가한 결과를 즉시 확인 가능

 

<HTTP GET 메서드 처리 - 특정 도서의 상세 정보를 조회하고 해당 정보를 bookDetail 뷰로 전달함>

- @GetMapping("/books/{id}") : 경로에 {id}가 있으므로, 실제로는 해당 도서의 isbn을 나타냄.

- public String detail(@PathVariable("id") String isbn, Model model) : @PathVariable 어노테이션을 사용하여 /books/{id}경로로 들어오는 GET 요청을 매개변수로 받음. '{id}' 변수의 값을 'isbn' 매개변수에 매핑함. 또한 'Model' 객체를 매개변수로 받아서 뷰로 데이터를 전달할 수 있게 함.

- model.addAttribute("book", bookRepository.findBookById(isbn)) : bookRepository 객체의 findBookById 메서드를 호출하여 해당 isbn에 해당하는 도서 정보를 가져오고 book이라는 이름으로 모델에 추가함. 이렇게 함으로써 해당 도서의 정보를 뷰로 전달할 수 있음.

- return "bookDetail" : 뷰의 이름을 나타냄. 스프링은 이 뷰 이름을 기반으로 찾고 랜더링함.

 

- BookRepository.java

package com.estsoft.hello.repository;

import com.estsoft.hello.controller.BookDTO;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class BookRepository {
    private Map<String, BookDTO> bookMap;

    public BookRepository() {
        bookMap = new HashMap<>();
        BookDTO book1 = new BookDTO(
                "123", "오늘도 개발자가 안된다고 말했다", "기획자");
        BookDTO book2 = new BookDTO(
                "978-89-98139-76-6", "객체지향의 사실과 오해", "조영호");
        bookMap.put(book1.getId(), book1);
        bookMap.put(book2.getId(), book2);
    }

    public List<BookDTO> getAllBooks() {
        List<BookDTO> bookList = new ArrayList<>();
        for (Map.Entry<String, BookDTO> book : bookMap.entrySet()) {
            bookList.add(book.getValue());
        }
        return bookList;
    }

    public BookDTO findBookById(String isbn) {
        return bookMap.get(isbn);
    }

    public void saveBook(BookDTO book) {
        bookMap.put(book.getId(), book);
    }
}

코드 뜯어보기

- getAllBooks() : 도서 목록을 조회하는 메서드임. 반환값으로는 List<BookDTO>를 가지고 있으며, 모든 도서 목록을 담고 있는 리스트를 반환함. bookMap이라는 Map에서 각각의 엔트리를 가져오고 해당 도서 객체 'BookDTO'를 얻음. 그리고  각 도서 객체를 bookList에 추가하고 return으로 반환함.

- findBookById() : 주어진 도서 ID에 해당하는 도서 정보를 조회하는 메서드임. bookMap에서 해당 ID에 매핑된 도서 정보를 반환함.

- saveBook() : 새로운 도서 정보를 저장함. BookDTO 객체를 받아서 해당 도서의 ID를 키로 하여 bookMap에 저장함.

 

- BookDTO.java

package com.estsoft.hello.controller;


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class BookDTO {
    private String id;
    private String name;
    private String author;
}

이렇게 Controller와 Repository에 데이터를 전송하고 처리할 때 DTO를 사용하면 데이터의 구조를 표준화하고 목적에 따라 필요한 정보를 캡슐화하여 코드를 유지보수하기 쉽게 만든다. 


사용한 Spring Annotation 개념 정리

  • @RequestParam : 주로 웹 요청에서 전달되는 쿼리 파라미터를 받아와서 메소드에서 사용할 수 있게 함.

  • @PathVariable 

@RequestParam과 @PathVariable 차이점은 뭐지??

- @RequestParam은 쿼리스트링을 통해 전달된 값들을 각각의 파라미터에 대입해준다.

- @PathVariable은 url에 전달되는 값을 받아오는 역할을 한다. 

 

  • @ResponseBody : 자바 객체 -> HttpResponse의 본문 responseBody의 내용으로 매핑
  • @RequestBody : HttpResponse의 본문 responseBody의 내용 -> 자바 객체로 매핑

SLF4J로 Log 남기기

SLF4J : 다양한 로깅 프레임워크에 대한 인터페이스 모음. 여러가지 log를 남길 수 있음!


스프링 핵심 개념

 

제어의 역전(IoC)과 의존성 주입(DI)

제어의 역전(Inversion Of Control) : 필요한 객체를 직접 생성하거나 제어하는 것이 아니라 외부에서 관리하는 객체를 가져와서 사용하는 것을 '제어의 역전'이라고 한다.

/* 스프링 컨테이너가 객체를 관리하는 방식 예 */
public class A {
		private B b;    // 코드에서 객체를 생성하지 않고, 어디선가 받아온 객체를 b에 할당
}

 

의존성 주입(Dependency Injection) : 어떤 클래스가 다른 클래스에 의존하기 위해 주입한다는 말이다.

-> 생성자 주입, 필드 주입, Setter 주입 방법이 존재함. (@Autowired 사용)

 

결국 스프링은 객체를 관리하기 위해서 IoC를 하고있고 그것을 실제로 구현한 방식이 DI이다.

 

빈과 스프링 컨테이너

스프링 컨테이너 : 빈이 생성되고 소멸되는 모든 생명주기를 관리한다. 

빈 : 스프링 컨테이너가 관리하고 생성하는 객체이다.

스프링은 빈을 등록하기 위해 어노테이션을 사용하여 스프링 컨테이너에 등록한다.

@Component  // 클래스 MyBean을 스프링 컨테이너에 등록 
public class MyBean {
}

-> MyBean이라는 클래스에 Component 어노테이션을 붙이면 MyBean 클래스가 빈으로 등록된다. 이렇게 등록된 이후에는 스프링 컨테이너에서 이 클래스를 관리한다.

+) 이때 빈의 이름은 클래스 이름의 첫 글자를 소문자로 바꿔서 관리함.

 

관점 지향 프로그래밍 (AOP)

: Aspect Oriented Programming. 프로그래밍에 대한 관심을 핵심, 관심사항, 공통 관심사항으로 나누어 관심 기준으로 모듈화하는 것을 의미한다. -> 쉽게 말하자면 관심사를 분리해서 프로그래밍하는 것!

ex) 

핵심 관심사항 : 회원관리(회원가입, 전체회원조회)

공통 관심사항 : 호출 시간 로깅 작업

AOP를 적용한다면, 공통 관심사항에 해당하는 호출 시간 로깅 로직을 모듈화하여 핵심 관심사항이랑 분리할 수 있음!

호출시간을 로깅하는 공통로직을 별도로 만들어서 모듈화하고, 핵심 로직에 끼워넣는 방식임.

이렇게 AOP를 적용하면 핵심 관심사항(회원관리) 코드에만 집중할 수 있게될 뿐만 아니라, 여러 장점이 있음.

- 핵심 관심사항과 공통 관심사항을 분리하여 유지보수가 쉬움.

- 공통 관심사항인 호출시간 로깅 로직이 모듈화되어 원하는 적용 대상을 선택 가능.

 

AOP 동작 방식

??? 코드 돌려보고 다시 정리하기.......