달거북씨 2022. 7. 22. 03:02

1. Controller와 Service

MVC : Model, View, Controller

Spring Framework MVC : Model(DAO, DTO, Service), View, Controller

 

 

1-1. 서비스란?

  • 서비스레이어(Service Layer) 단에서 세분화된 비즈니스 로직을 처리하는 객체이다.
  • 서비스는 비즈니스 로직이 들어가는 부분이다.
  • Controller가 Request를 받으면 적절한 Service에 전달하고, 전달된 Service는 비지니스 로직을 처리한다.
  • 각 데이터는 DTO로 데이터를 전달받고, DAO로 데이터베이스를 접근한다.

 

1-2. Controller와 Service

  • Controller가 Service를 의존한다고 표현한다.
  • Service는 여러 컨트롤러에서 가져다 사용 가능하다.

 

 


 

 

2. DB없이 회원정보 저장하고 불러오기

어떤 데이터베이스를 사용할 지 정해지지 않았을 때, 우선 개발부터 시작할 수 있는 방법

 

 

2-1. Member DTO(VO) 만들기

회원정보를 받아오는 Member DTO(VO)

package com.koreait.test.member.dto;

public class Member {
	// 변수
	private int id;
	private String name;
	
	// getter, setter
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

 

 

2-2. Repository 생성

DB 대신 사용할 인터페이스를 만들어준다.

package com.koreait.test.member.repository;

import java.util.List;
import com.koreait.test.member.dto.Member;

// DB 대신 사용
public interface MemberRepository {
	// 회원 저장
	Member save(Member member);
	// 회원 찾기
	List<Member> findAll();
}

 

 

2-3. 임시저장용 클래스 생성

DB가 없기 때문에 메모리에 임시저장한다.

package com.koreait.test.member.repository;

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

import com.koreait.test.member.dto.Member;

@Repository
public class MemoryMemberRepository implements MemberRepository {

	// 메모리 사용 - static
	private static Map<Integer, Member> store = new HashMap<Integer, Member>();	// DB처럼 사용
	private static int sequence = 0;	// id에 해당
	
	@Override
	public Member save(Member member) {
		member.setId(++sequence);
		store.put(member.getId(), member);
		return member;
	}

	@Override
	public List<Member> findAll() {
		return new ArrayList<Member>(store.values());
	}
	
}

 

 

2-4. Service 생성

package com.koreait.test.member.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.koreait.test.member.dto.Member;
import com.koreait.test.member.repository.MemberRepository;
import com.koreait.test.member.repository.MemoryMemberRepository;

/*
* MemberService는 순수한 java Class(=POJO)라서 스프링이 인식하지 못한다
* 따라서 스프링이 찾을 수 있도록 적절한 어노테이션(@Service)을 선언해주어야 한다.
*/
@Service
public class MemberService {
	// 오른쪽은 인터페이스, 왼쪽은 실제 클래스
	MemberRepository memberRepository = new MemoryMemberRepository();
	
	// 생성자 주입
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	
	// 회원가입
	public int join(Member member) {
		memberRepository.save(member);
		return member.getId();
	}
	
	// 전체 회원 조회
	public List<Member> findMembers(){
		return memberRepository.findAll();
	}
}

 

POJO(Plain Old Java Object)
단순한 자바 오브젝트
객체 지향적인 원리에 충실하면서 환경과 기술에는 종속되지 않고
필요에 따라 재활용 될 수 있는 방식으로 설계된 오브젝트를 말한다.

POJO에 어플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO 프로그래밍이라고 한다.

 

 

2-5. Controller 생성

package com.koreait.test.member.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.koreait.test.member.dto.Member;
import com.koreait.test.member.service.MemberService;

@Controller
public class MemberController {
	// 기존 방식 : MemberService mService = new MemberService();
	// Spring 방식 :
	private final MemberService memberService;	
	// 선언만 해주면 할당은 스프링 프레임워크가 알아서 해준다. -> IOC(Inversion of Control)
    
	// 생성자 호출
	// MemberController가 생성될 때, 생성자를 호출해준다. 
   	// 즉, service까지 생성해서 자동으로 호출해준다.
    	// @Autowired를 선언해주면, MemberController를 생성하면서 스프링이 memberService와 연결해준다.
	// 단일 생성자에 한해 @Autowired 생략이 가능하다.	
    @Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
}

 

IoC(Inversion of Control)
개발자가 프레임워크의 기능을 호출하지 않고 프레임워크가 개발자의 코드를 호출한다.
때문에 개발자는 전체를 직접 구현하지 않고 부분적으로 코드를 "끼워넣는" 형태로 구현할 수 있다.

프레임워크가 객체의 라이프 사이클을 관리하며, 스프링으로부터 필요한 객체를 얻어올 수 있다.
객체의 의존성을 역전시켜 객체 간의 결합도를 줄인다. 또한 유연한 코드를 작성할 수 있게 하여 가독성 향상, 코드 중복 방지, 유지 보수 편리 등의 장점을 갖는다.

 

스프링 컨테이너
스프링은 실행 시 객체들을 담고 있는 컨테이너가 있고,
런타임 과정에서 이 컨테이너가 객체들 간의 의존관계를 알아서 만들어 준다.
스프링 컨테이너는 자바 객체의 생명주기를 관리하며,
생성된 자바 객체들에게 추가적인 기능을 제공하는 역할을 한다.
스프링에서는 이 자바 객체를 빈(Bean)이라고 부른다.
개발자는 객체를 생성하고 소멸할 수 있는데, 
스프링 컨테이너가 이 역할을 대신함으로써 제어의 흐름을 외부에서 관리하게 된다.

즉, 스프링 컨테이너에 객체들을 생성해 보관하고 있고,
그 관리를 스프링이 가져간 것을 Ioc(제어의 역전)이라고 한다.

 

스프링 빈 등록 방법
1. 컴포넌트 스캔 : @Component > 해당 어노테이션이 붙은 클래스를 스프링이 모두 스캔한다.

2. 자바 코드로 직접 스프링 빈을 등록하는 방법(거의 사용하지 않는다)

@Component를 붙여주지 않았는데도 빈이 등록된 이유는,
우리가 붙여준 @Controller, @Service, @Repository 등에 @Componenet가 포함되어 있기 때문이다.

참고로 스프링은 스프링 컨테이너에 빈을 등록할 때, 기본적으로 싱글톤으로 등록한다.
따라서 같은 스프링 빈이면 모두 같은 인스턴스이다.

 

@Autowired
필요한 의존 객체의 "타입"에 해당하는 빈을 찾아 주입한다.

의존성 주입을 할 대상을 찾지 못하면 애플리케이션 구동에 실패한다.

 

클래스 객체마다 역할에 맞는 어노테이션을 표시해주지 않으면
스프링이 찾지 못해 제대로 구동되지 않는다.

 

DI(Dependency Injection) : 의존성 주입
객체 간에 의존성이 존재할 경우, 개발자가 직접 객체를 생성하거나 제어하는 것이 아니라,

제어반전에 의하여 특정 객체에 필요한 다른 객체를 프레임워크가 자동으로 연결시켜주는 것을 말한다.
개발자는 객체를 선언만 할 뿐, 할당은 프레임워크에 의해서 자동으로 이루어진다.
따라서 개발자는 비지니스 로직 개발에만 집중할 수 있다.

 

DI의 3가지 방법
1. Field Injection(필드 주입)

2. Setter Injection(수정자 주입)
3. Constructure Injection(생성자 주입) < 우리가 사용하는 방법
// DI 1번 방법. Field Injection
@Autowired MemberService memberService;

// DI 2번 방법. Setter Injection
@Autowired
public void setMemberService(MemberService memberService) {
	this.memberService = memberService;
}

// DI 3번 방법. Constructor Injection
@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

 

Private를 붙이는 이유
application 로딩 시점에 조립하기 때문에 중간에 바꿀 일이 없다.

따라서 private를 붙여 중간에 바뀔 가능성을 닫아놓는다.

final을 붙이는 이유
선언된 레퍼런스 타입 변수는 반드시 선언과 함께 초기화가 되어야 한다.
객체가 불변하도록 하기 때문에, 누군가가 Controller 내부에서 Service 객체를 바꿔치기 할 수 없다.

 

 

2-6. 홈컨트롤러 생성

package com.koreait.test.member.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
	// 기본 localhost:9090으로 요청이 들어오면 home.html을 호출한다.
	@GetMapping("/")
	public String home() {
		return "home";
	} 
}

▶ 스프링부트 기본 웰컴페이지는 static 아래에 있는 index.html로 연결된다.

▶ @GetMapping("/")을 통해 웰컴페이지를 "home"으로 바로 연결해준다.

 

 

2-7. home.html 생성

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div>
		<h1>Spring Boot</h1>
		<p>회원관리</p>
		<p>
			<a href="/members/new">회원가입</a>
			<a href="/members">회원목록</a>
		</p>
	</div>
</body>
</html>

 

 

2-7. 회원가입 경로 만들기

위에서 만든 MemberController에 경로를 만들어준다.

@GetMapping(value = "/members/new")
	public String createForm() {
		return "members/createMemberForm";
	}

▶ /members/new로 주소가 들어오면 createForm 메서드가 받아 createMemberForm.html로 연결해준다.

 

 

2-8. 회원가입 페이지 만들기

- createMemberForm.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div>
		<form action="/members/new" method="post">
			<div>
				<label for="name">이름</label>
				<input type="text" id="name" name="name" placeholder="이름을 입력하세요">
			</div>
			<button type="submit">등록</button>
		</form>
	</div>
</body>
</html>

▶ DTO에서 id는 시퀀스로 만들어주도록 설정해 놓았기 때문에, 회원가입 페이지에서는 이름만 받아오면 된다.

 

 

2-9. 이름만 받는 DTO 만들기

- 기존 DTO를 사용할 수도 있지만 새로 만듦

▶ 왤까...? 필요한 변수만 사용하기 위해서일까? 이유를 설명해주지 않으셔서 잘 모르겠음

package com.koreait.core.member.dto;

public class MemberForm {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

 

 

2-10. 회원가입 처리

- MemberForm으로 받은 이름 Member로 보내 저장하기

- MemberController에서 경로를 만들어준다.

// 회원가입
@PostMapping(value = "/members/new")
public String create(MemberForm form) {
    Member member = new Member();
    member.setName(form.getName());

    memberService.join(member);

    // 홈화면으로 돌린다 -> redirect
    return "redirect:/";		
}

▶ home.html과 createMemberForm.html 두 곳에 각각 /members/new 경로로 보내지만 충돌이 생기지는 않는다.

▶ home.html에서는 Get으로, createMemeberForm.html에서는 Post 방식으로 경로가 들어오기 때문이다. 

▶ MemberForm에서 받은 이름을 Member로 보내주면 MemberService의 join메서드가 알아서 처리해준다.

 

 

2-11. 회원목록 받아오기

- Member DTO에서 회원목록을 받아 회원목록 페이지로 보내주는 경로를 MemberController에 만든다.

@GetMapping("/members")
public String list(Model model) {
    List<Member> members = memberService.findMembers();
    model.addAttribute("members", members);
    return "members/memberList";
}

▶ MemberService에 만든 findMembers 메서드로 전체 회원을 조회하여 Model에 저장해서 전달한다.

 

 

2-12. 회원목록 페이지 만들기

- memberList.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div>
		<table>
			<thead>
				<tr>
					<th>no</th>
					<th>이름</th>
				</tr>
			</thead>
			<tbody>
				<tr th:each="member : ${members }">
					<td th:text="${member.id}"></td>
					<td th:text="${member.name}"></td>
				</tr>
			</tbody>
		</table>
	</div>
</body>
</html>

▶ 테이블 형식으로 받아오도록 html을 짜준다.

▶ th:each는 반복문의 역할을 한다.

▶ 모델에 저장되어 있는 members에서 id와 name을 모두 출력한다. 

 

 

2-13. 시연

 

 

2-14. 구조 정리

728x90