[인프런]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술4

23759 단어 SpringSpring

4. 스프링 빈과 의존관계

  • 화면 합치기: 컨트롤러(멤버 서비스를 통해서 회원가입, 데이터 조회 가능해야함: 컨트롤러가 멤버 서비스를 의존함) + 뷰 템플릿 => 스프링 빈 등록과 의존관계를 설정해야 함.

  • 스프링 빈을 등록하는 2가지 방법

    • 컴포넌트 스캔과 자동 의존관계 설정
    • 자바 코드로 직접 스프링 빈 등록하기
  • DI(Dependency Injection, 객체 의존관계를 외부에서 넣어주는 것)의 3가지 방법

    1. 생성자 주입: 처음 애플리케이션이 조립되는 시점에 설정되고 끝나서 변경되지 않도록 막을 수 있음
    2. 필드 주입: 중간에 바꿀 수 있는 방법이 아예 없기 때문에 별로 안좋음
    3. setter 주입: set함수가 호출되기 위해 public으로 열려 있어야 하기 때문에 모든 사용자에게 노출되어 보안상 좋지 않음

[참고]
helloController의 경우 스프링에서 제공하는 컨트롤러이므로 @Controller가 있으면 자동으로 등록됨

4.1 컴포넌트 스캔과 자동 의존관계 설정(중요)

  • memberController에 의존관계 추가

    • java/hello.hellospring/controller/MemberController.java
    package hello.hellospring.controller;
    
    import hello.hellospring.service.MemberService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    
    // Controller : 외부 요청 받음
    @Controller
    public class MemberController {
    
        // 스프링 컨테이너에 등록해서 쓰기 - 하나만 등록됨
        private final MemberService memberService;
    
        // 스프링이 스프링 컨테이너에 있는 MemberService를 가져다 연결을 시켜줌 (Controller랑 Service를 연결시켜줌, dependency injection)
        @Autowired
        public MemberController(MemberService memberService){ // 오류 발생
            this.memberService = memberService;
        }
    }
    • 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌(DI)
  • 오류 발생

    'Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.'

  • memberService와 memberRepository 스프링 컨테이너의 스프링 빈으로 등록

    Controller 클래스 선언 위에 @Controller를 Service 클래스 선언 위에 @Service를 Repository 클래스 선언 위에 @Repository를 추가하는 것 => 각 @Controller, @Service, @Repository 내에 @Component가 들어 있기 때문에 이것을 컴포넌트 스캔이라고 함

    회원 서비스 스프링 빈 등록
    @Service
    public class MemberService {}
    
    회원 리포지토리 스프링 빈 등록
    @Repository
    public class MemoryMemberRepository implements MemberRepository {}
    • @Component: Annotation이 있으면 스프링 빈으로 자동으로 등록됨
    • @Component를 포함하는 Annotation(@Controller, @Service, @Repository)은 컴포넌트 스캔이기 때문에 스프링 빈으로 자동 등록됨
    • @Autowired의 역할: 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아 주입(DI), 생성자가 하나일 경우 생략가능
    • 스프링 빈 등록 이미지

[참고]

  • 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본으로 싱글톤(유일하게 하나만 등록해 공유)으로 등록함 (메모리 절약 가능)
  • 따라서, 같은 스프링 빈이면 모두 같은 인스턴스임

4.2 자바 코드로 직접 스프링 빈 등록하기(중요)

  • MemberService와 MemverRepository의 @Service, @Repository, @Autowired Annotation을 제거하고 진행, MemberController 부분은 그대로 두고 진행(회원 컨트롤러에 의존관계 추가 코드를 의미)

  • 자바 코드로 직접 스프링 빈 등록

    • java/hello.hellospring/SpringConfig.java
    package hello.hellospring;
    
    import hello.hellospring.repository.MemberRepository;
    import hello.hellospring.repository.MemoryMemberRepository;
    import hello.hellospring.service.MemberService;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class SpringConfig {
    
        // MemberService를 스피링 빈에 등록하는 코드
        @Bean
        public MemberService memberService(){
            return new MemberService(memberRepository()); // 스프링 빈에 등록되어 있는 MemberRepository를 MemberService에 넣어줌
        }
    
        // MemberRepository를 스피링 빈에 등록하는 코드
        @Bean
        public MemberRepository memberRepository(){
            return new MemoryMemberRepository();
        }
    
    }

[참고]
1. DI에는 필드 주입, setter 주입, 생성자 주입 3가지 방법이 있는데, 의존 관계가 실행중에 동적으로 변하는 경우는 거의 없기 때문에 생성자 주입을 권장함
2. 정형화된(일반적으로 사용하는) 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용함
3. 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정(SpringConfig.java)을 통해 스프링 빈으로 등록함

  • 현재 만든 메모리 리포지토리가 나중에 선정되는 데이터 저장소에 따라서 변경되어하기 때문에, 기존 코드에 일절 손대는 것 없이 메모리 구현체를 바꿔치기가 가능 해야함. => 자바 코드로 스프링 빈으로 설정해야하는 이유 (컴포넌트의 경우 고쳐야하는 코드가 많음)

[주의]
@Autowired를 통한 DI는 helloController, memberService등과 같이 스프링이 관리하는 객체에서만 동작, 스프링 빈으로 등록되지 않고 내가 직접 생성한 객체에서는 동작하지 않음

5. 회원 관리 예제 - 웹 MVC 개발

5.1 회원 웹 기능 - 홈 화면 추가

  • 홈 컨트롤러 생성

    • java/hello.hellospring/controller/HomeController.java
    package hello.hellospring.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @Controller
    public class HomeController {
        @GetMapping("/")
        public String home(){
            return "home";
        }
    }
  • 회원 관리용 홈 생성

  • resources/templates/home.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>
  • 실행

    • localhost:8080
  • 결과

[참고]

  • 실행에 우선 순위가 정해져 있음
    • 정전 컨테츠 이미지
      • 스프링 컨테이너에 Mapping 되는 것이 있는 경우 Mapping되는 파일을 실행함(따라서, templates/hello.html이 실행됨)
      • 없는 경우에만 static 파일을 찾아서 실행시킴(따라서, 기존에 만들었던 static/index.html이 무시됨)

5.2 회원 웹 기능 - 등록

  • 회원 등록 폼 컨트롤러 추가

    • java/hello.hellospring/controller/MemberController.java
    package hello.hellospring.controller;
    
    import hello.hellospring.service.MemberService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    // Controller : 외부 요청 받음
    @Controller
    public class MemberController {
    
        // 스프링 컨테이너에 등록해서 쓰기 - 하나만 등록됨
        private final MemberService memberService;
    
        // 스프링이 스프링 컨테이너에 있는 MemberService를 가져다 연결을 시켜줌 (Controller랑 Service를 연결시켜줌, dependency injection)
        @Autowired
        public MemberController(MemberService memberService){ // 오류 발생
            this.memberService = memberService;
        }
    
    		  // 회원 등록 폼 컨트롤러 추가  
        @GetMapping("/members/new")
        public String createForm(){
            return "/members/createMemberForm"; //createMemberForm이라는 곳으로 이동
        }
    }
  • 회원 등록 폼 HTML 생성

    • resources/templates/members/createMemberForm.html
    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org">
    <body>
    <div class="container">
     <form action="/members/new" method="post">
     <div class="form-group">
     <label for="name">이름</label>
     <input type="text" id="name" name="name" placeholder="이름을
    입력하세요">
     </div>
     <button type="submit">등록</button>
     </form>
    </div> <!-- /container -->
    </body>
    </html>
  • 실행

    • localhost:8080/members/new
  • 결과

  • 회원 등록 컨트롤러

  1. 웹 등록 화면에서 데이터를 전달 받을 폼 객체 생성

    • java/hello.hellospring/controller/MemberForm.java
    package hello.hellospring.controller;
    
    public class MemberForm {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
  2. 회원 컨트롤러에서 회원을 실제 등록하는 기능 추가

    • java/hello.hellospring/controller/MemberController/java
    @PostMapping("/members/new")
     public String create(MemberForm form){ // 등록을 누르면 MemberFrom의 객체에 setName()함수로 전달된 name 값이 들어감
         Member member = new Member();
         member.setName(form.getName());
    		
         memberService.join(member);
    
         return "redirect:/"; // 홈 화면으로 돌아감
     }
  • 결과
    • 등록을 눌렀을 때(home 화면으로 돌아감)

[참고]

  • post 방식: 데이터 전달(등록)
  • get 방식: 데이터 조회

5.3 회원 웹 기능 - 조회

  • 회원 컨트롤러에서 조회 기능 추가
    • java/hello.hellospring/controller/MemberController.java
    @GetMapping("/members")
      public String list(Model model){
          List<Member> members  = memberService.findMember();
          model.addAttribute("members", members); //member list를 model에 담아서 화면에 넘김
          return "members/memberList";
      }
  • 회원 리스트 HTML 생성
    • resources/templates/members/memberList.html
    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org">
    <body>
    <div class="container">
        <div>
            <table>
                <thead>
                <tr>
                    <th>#</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>
    </div> <!-- /container -->
    </body>
    </html>
    • thymeleaf가 본격적으로 동작함(for문으로 동작함)
  • 결과
  • spring 등록 후
  • 소스 코드 보기
  • sprign2 등록 후
  • 소스 코드 보기

[참고]

  • 등록 결과가 메모리에 있기 때문에 프로그램을 실행 종료했다가 다시 실행시키면 등록했던 데이터는 사라지게 됨
  • 따라서, 데이터들을 파일이나 데이터베이스에 저장해야 함

참고문헌

[인프런] 김영한 강사님, 「스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술」

좋은 웹페이지 즐겨찾기