scone-lemon
2022 01 09 일요일 공부 (김영한 스프링 입문 day3 : 스프링 빈과 의존관계, 회원 관리 예제 - 웹 MVC 개발) 본문
2022 01 09 일요일 공부 (김영한 스프링 입문 day3 : 스프링 빈과 의존관계, 회원 관리 예제 - 웹 MVC 개발)
lemon-scone 2022. 1. 9. 12:091. 스프링 빈과 의존관계
스프링 빈과 의존관계
- 화면을 붙이는 과정 ??? : 컨트롤러, 뷰템플릿
- 회원가입 기능을 구현하고, 결과를 html로 뿌려주는 작업을 하기 위해서 ???
Concept Review (by using my words)
1) 패키지 구조
- hello.hellospring.controller @Controller @Autowired
- hello.hellospring.domian
- hello.hellospring.repository @Repository
- hello.hellospring.service @Service @Autowired
2) 각각 클래스의 역할
- HelloSpringApplication.java : Main Class
- domain/Member.java : Member 객체 생성
- controller/MemberCotroller.java : 멤버 서비스를 통해 기능을 수행하도록 해준다
- repository/MemberRepository.java : 디비가 정해지지 않거나 바꿔야 하는 상황이 될 때 코드의 융통성을 높이기 위해 인터페이스로 함수들을 선언해주고 임플리먼트 해준다
- repository/MemoryMemberRepository.java : (개발스럽게 네이밍) 인터페이스 구현체 --> 디비에 어떻게 데이터를 저장하고 데이터를 어떻게 꺼내오고 등, "데이터와 관련된 작업들을 수행하는 로직"을 개발 스럽게 네이밍 된 함수에 구현하는 것 같다
- service/MemberService.java : (비즈니스 의존적으로 네이밍) 각 기능을 담당하는 함수를 repository를 활용하여 구현한다 --> 보통 memeberRepository.함수명() 이런식으로 간단간단하게, "서비스를 수행하는 로직"을 비즈니스 스럽게 네이밍 된 함수를 구현해주는 것 같다
MemberController / MemoryMemberRepository / MemberService 연결해주는 방법
- @ : Annotation --> Spring에게 관리해야 할 객체라는 것을 알려줌
- @Controller : Controller 라는 객체를 생성해서 Contrainer에 가지고 있어줘 라는 뜻
- @Autowired : Spring Container 에서 MemberService를 가져와줘 라는 뜻
- Controller --> Service 를 Autowired를 통해서 연결, 컨트롤러에 서비스 주입 (== Dependency Injection)
- Service --> Repository 를 Autowired를 통해서 연결, 저비스에 레포지토리 구현체를 주입
스프링 빈을 등록하는 2가지 방법
1) 컴포넌트 스캔과 자동 의존관계 설정 (@Controller, @Service, @Repository, @Autowired 이용하는 방법 !!!)
2) 자바 코드로 직접 스프링 빈 등록하기 (SpringConfig, @Configuration, @Bean 이용하는 방법)
- 그렇다면 아무데나 @Component를 지정해주면 빈으로 등록될까? : No (HelloSpringApplication을 실행시키면, hello.hellospring 패키지를 포함한 해당 패키지의 하위 패키지 일 경우에만 스프링이 뒤져서 스프링 빈으로 등록한다)
- 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 (대부분, 거의 모든 경우에서) 싱글톤으로 등록한다 (유일하게 하나만 등록해서 공유한다) !!! 따라서 같은 스프링 빈이면 모두 같은 인스턴스이다
컴포넌트 스캔과 자동 의존관계 설정
- 각 클래스에 어노테이션만 지정해주면 되기 때문에 간편한 방법
package hello.hellospring.controller;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
// Spring 이 처음에 뜰 때 Spring Container 라는 통이 생기는데
// @Controller 에노테이션이 있으면 MemberController 객체를 생성해서 Container 통에 넣어둔다
// 그리고 Spring 이 이것을 관리
// ==> Spring Bean 이 관리된다고 표현
@Controller
public class MemberController {
/* MemberController 가 MemberService 를 가져다가 써야함
다 스프링 컨테이너에 등록, 받아서 쓰도록 바꾸어야 한다
아래와 같이 하게 되면, 다른 여러 컨테이너들이 MemberService 를 가져다 쓰게 됨
==> Spring Container 에 등록을 하고 공용으로 가져다 쓰는것이 좋은 방법 !!! */
//private final MemberService memberService = new MemberService();
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
/* Dependency Injection : 3 Way
field injection --> not recommend 수정이 안된다...?
setter injection --> not recommend 아무나 호출할 수 있다...?
constructor injection --> recommend !!!
*/
}
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
// service : 비즈니스에 의존적으로 네이밍
// repository : 개발스럽게 네이밍
//@Service SpringConfig 파일을 생성해서 주석처리 해 놓음 (컴포턴트 스캔 부분에선 여기에 포커스하면 됨)
public class MemberService {
//private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
//@Autowired
public MemberService(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
// 이하 생략
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
//@Repository SpringConfig 파일을 생성해서 주석처리 해 놓음 (컴포턴트 스캔 부분에선 여기에 포커스하면 됨)
public class MemoryMemberRepository implements MemberRepository {
// 메모리를 저장
private static Map<Long, Member> store = new HashMap<>();
// key 값을 생성
private static long sequence = 0L;
// 이하 생략
}
자바 코드로 직접 스프링 빈 등록하기
- 상황에 따라 구현 클래스를 변경 ??? : ex 아직 디비가 정해지지 않은 가상의 시나리오 --> 나머지 코드에 일절 손대지 않고 구현체를 바꿔치기 할 수 있는 것처럼 코드 수정이 용이하다(?)
- MemberService, MemberRepository 가 스프링 컨테이너에 스프링 빈으로 등록되었다
package hello.hellospring.service;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
// memberService, memberRepository 를 Spring Bean 등록
// Controller --> Service --> Repository 를 각각 Spring Bean 에 올리고 엮어주어야 함
// 그러나 Controller 는 어쩔수 없이 어차피 Spring 이 관리하는 거기 때문에 Component Scan 으로 Container 에 올라 감
@Bean
public MemberService memberService(){
// Service 랑 Repository 를 엮어줌
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
// 인터페이스는 new 안되니까 MemoryMemberRepository
return new MemoryMemberRepository();
}
}
의존성 주입 (DI : Dependency Injection) 의 3가지 방법
1) 필드 주입 : 중간에 바꿀 수 있는 방법이 없다(?)
2) 세터 주입 : 누가 컨트롤러를 호출했을 때 setter가 퍼블릭으로 열려있어야 하는데 이게 노출되서 아무나 호출할 수 있는 위험성
3) 생성자 주입 ★ : 처음에 셋팅이 되는 시점에 (어플리케이션이 조립되는 시점에) 한번만 호출되고 그 다음에는 변경을 못하도록 막음
2. 회원 관리 예제 - 웹 MVC 개발
회원 웹 기능 - 홈 화면 추가 (HomeController.java, Home.html)
회원 웹 기능 - 등록 (MemberController.java, createMemberForm.html)
회원 웹 기능 - 조회 (MemberController.java, memberList.html)
1) HomeController.java, MemberController.java, MemberForm.java
package hello.hellospring.controller;
import hello.hellospring.domain.Member;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class HomeController {
// 우선순위 !!! localhost8080 요청이 왔는데 왜 index.html 로 안갈까? :
// 매핑된 컨트롤러가 았는지 먼저 찾고 없으면 static 을 뒤지기 때문에
// 관련 컨트롤러가 있으면 호출하고 바로 끝내고 정적 리소스 index.html 은 무시된다
@GetMapping("/") // == localhost:8080/
public String Home(){
return "home"; // home.html 로 가 !!!
}
}
package hello.hellospring.controller;
import hello.hellospring.domain.Member;
import hello.hellospring.service.MemberService;
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 java.util.List;
// Spring 이 처음에 뜰 때 Spring Container 라는 통이 생기는데
// @Controller 에노테이션이 있으면 MemberController 객체를 생성해서 Container 통에 넣어둔다
// 그리고 Spring 이 이것을 관리
// ==> Spring Bean 이 관리된다고 표현
@Controller
public class MemberController {
/* MemberController 가 MemberService 를 가져다가 써야함
다 스프링 컨테이너에 등록, 받아서 쓰도록 바꾸어야 한다
아래와 같이 하게 되면, 다른 여러 컨테이너들이 MemberService 를 가져다 쓰게 됨
==> Spring Container 에 등록을 하고 공용으로 가져다 쓰는것이 좋은 방법 !!! */
//private final MemberService memberService = new MemberService();
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
/* Dependency Injection : 3 Way
field injection --> not recommend 수정이 안된다...?
setter injection --> not recommend 아무나 호출할 수 있다...?
constructor injection --> recommend !!!
*/
// 회원가입 --------------------------------------------------
// GetMapping vs PostMapping :
// url 은 똑같지만 get 이냐 post 이냐 에 따라서 다르게 mapping 가능
// get 방식 : 데이터를 조회할 때 사용 (url 창에다가 입력 후 엔터 치는 건 겟매핑)
// @GetMapping : 아래 함수를 이런 url 이 호출될 때 매핑해
@GetMapping("/members/new")
public String CreateForm(){
// members/createMemberFrom 여기로 이동하는 기능만 수행
// templates 에서 문서 찾음
return "members/createMemberFrom";
// 뷰 리졸버를 통해서 얘가 선택되고
// 타임리프가 탬플릿 앤진이 얘를 랜더링, 브라우저에 html 을 뿌림
// form tag 설명은 createMemberFrom.html 에 정리함
}
// post 방식 : 데이터를 폼 같은데에 넣어서 등록하고 전달할 때 사용
@PostMapping("/members/new")
// create 메소드 호출
// --> MemberForm.name 에 /members/new (CreateMemberForm.html) 페이지에 입력한 값이 저장됨
// Spring 이 Memberform.setName() 을 통해서 값을 넣어줌
public String create(MemberForm form){
Member member = new Member();
// member 객체 생성
member.setName(form.getName());
// 생성된 member 객체 setName 해주는데
// Memberform 에서 받아온 이름으로 set 해주기기
// Spring이 setName 한 데이터를 getName 으로 꺼냄
System.out.println("Member : " + member.getName());
memberService.join(member);
// set 된 이름으로 join 하기
return "redirect:/";
}
// 회원조회 --------------------------------------------------
// 메모리 안에 있기 때문에 자바를 내려버리면(?)(==서버를 껏다가 다시 키면) 회원 데이터가 다 사라진다
// --> 데이터를 파일이나 데이터베이스에 저장해야 한다
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberlist";
}
}
package hello.hellospring.controller;
// 회원 등록 폼
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2) home.html, createMemberForm.html, memberlist.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<h1>Hello Spring</h1>
<p>회원 기능</p>
<p>
<a href="/members/new">회원 가입</a>
<a href="/members">회원 목록</a>
</p>
</div>
</div> <!-- /container -->
</body>
</html>
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<!-- form 을 작성하고 버튼을 누르면
action url 로 post 방식으로 데이터가 넘어옴
MemberController.java 의 @PostMapping("/members/new") 요기로 감 -->
<form action="/members/new" method="post">
<div class="form-group">
<label for="name">이름</label>
<input type="text"
id="name"
name="name"
placeholder="이름을 입력하세요">
<!-- name : server 로 넘어올 때 key 가 된다 -->
</div>
<button type="submit">등록</button>
</form>
</div> <!-- /container -->
</body>
</html>
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<table>
<!-- 표 상단 : 속성명 -->
<thead>
<tr>
<th>#</th>
<th>이름</th>
</tr>
</thead>
<!-- 표 하단 및 내용 : 속성 -->
<thead>
<tbody>
<!-- 탬플릿 앤진이 랜더링하는 부분 :
each == 루프를 돈다
${members} == model 안에 있는 값을 꺼냄
${member.id} == getId 에 접근 값을 가져와서 출력
${member.name} == getName 에 접근 값을 가져와서 출력
-->
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
'PROJECT > 2학기 공통' 카테고리의 다른 글
2022 01 10 월요일 공부 (김영한 스프링 입문 day4 : 스프링 DB 접근기술) (0) | 2022.01.10 |
---|---|
2022 01 09 일요일 공부 (김영한 스프링 입문 day3 : 스프링 DB 접근 기술) (0) | 2022.01.09 |
2022 01 08 토요일 공부 (김영한 스프링 입문 day2 : 스프링 웹 개발 기초, 회원 관리 예제 - 백엔드 개발) (0) | 2022.01.08 |
2022 01 07 금요일 회의 (기능 구체화) (0) | 2022.01.07 |
2022 01 06 목요일 공부 (랩탑 초기설정 이슈) (0) | 2022.01.06 |