관리 메뉴

나구리의 개발공부기록

스프링 빈과 의존관계 - 컴포넌트스캔과 자동 의존관계 설정, 자바 코드로 직접 스프링 빈 등록 / 회원 관리 예제 - 웹 MVC개발(홈 화면 추가, 회원등록, 회원조회) 본문

인프런 - 스프링 완전정복 코스 로드맵/코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술(무료)

스프링 빈과 의존관계 - 컴포넌트스캔과 자동 의존관계 설정, 자바 코드로 직접 스프링 빈 등록 / 회원 관리 예제 - 웹 MVC개발(홈 화면 추가, 회원등록, 회원조회)

소소한나구리 2024. 1. 24. 11:42

컴포넌트스캔과 자동 의존관계 설정

 

  • 회원 컨트롤러가 회원서비스를 통해서 회원가입 및 데이터 조회를 해야 함 (서로 의존관계가 있다고 표현)

멤버 컨트롤러 작성

 

  • @Controller 애노테이션을 생성하면 스프링이 작성된 클래스의(컨트롤러) 객체를 생성해서 가지고 있음
  • 해당 작업을 스프링 컨테이너에서 스프링 빈이 관리 된다고 표현 함
  • 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌
    -> 의존 관계를 외부에서 넣어주는 것을(Dependency Injection) 의존성 주입이라고 함
package start.startspring.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import start.startspring.service.MemberService;

@Controller
public class MemberController {
    // new 연산자로 객체 생성을 하지않고 spring 컨테이너에서 받아서 쓰도록 해야함

    private final MemberService memberService;

    // 생성자에 @Autowired 애노테이션을 붙이면 매개변수를 스프링이 컨테이너에 있는 멤버 서비스를 가져다가 연결시킴
    // 매개변수의 타입 클래스에 @Service 애노테이션을 붙여 줘야 함
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

 

컴포넌트 스캔과 자동 의존관계 설정으로 스프링 빈 등록

 

  • @Component 애노테이션이 있으면 스프링 빈으로 자동 등록
    • @Controller 컨트롤러가 스프링 빈으로 자동 등록 된 이유도 컴포넌트 스캔 때문 
    • @Controller, @Service, @Repository도 스프링 빈으로 자동 등록
    • 각 애노테이션에 들어가보면 모두 @Component가 등록 되어 있음

 

멤버 서비스와 멤버리포지토리의 구현체인 메모리멤버리포지토리의 클래스에 각각 애노테이션을 작성

 

  • memberService와 memberRepository가 스프링컨테이너에 스프링 빈으로 등록 됨
@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
@Repository
public class MemoryMemberRepository implements MemberRepository {

    // 실무에서는 공유되는 변수일 경우 ConcurrentHashMap을 써야하지만 예제는 단순하게 HashMap사용
    private Map<Long, Member> store = new HashMap<>();
    // 위와 마찬가지로 실무에서는 AtomicLong을 사용
    private static long sequence = 0L;

PS.

  • 생성자에 @Autowired를 사용 하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입
    (생성자가 1개인 경우 애노테이션 생략 가능)
  • 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본적으로 싱글톤으로 등록하여 유일한 인스턴스를 공유함
  • 같은 스프링 빈이면 모두 같은 인스턴스
  • 설정으로 싱글톤이 아니게 할 수 있으나 특별한 경우를 제외하면 대부분 싱글톤을 사용

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

 

  • 직접 코드로 스프링 빈을 등록 한다고해도 Controller는 클래스에 @Controller와 생성자에 @Autowired를 그대로 두고 나머지 애노테이션들만 제거하고 진행
package start.startspring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import start.startspring.repository.MemberRepository;
import start.startspring.repository.MemoryMemberRepository;
import start.startspring.service.MemberService;

@Configuration
public class SpringConfig {
    // Bean을 등록
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
  • XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않음
  • DI(Dependency Injection)에는 필드주입, setter주입, 생성자 주입 3가지 방법이 있음
    -> 의존관계가 실행 중 동적으로 변하는경우는 없으므로 생성자 주입을 권장
  • 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용
  • 정형화 되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈 등록

필드 주입 방식 - 권장하지 않음

 

  • 중간에 바꿀 수 있는 방법이 전혀 없음
@Controller
public class MemberController {
	// 인텔리제이에서 경고를 표시
   @Autowired private MemberService memberService;
   
}

 

 

setter 주입 방식 - 권장하지 않음

 

  • 메서드가 public으로 노출 되어있기 때문에 위험성이 높음
  • 변경 되지 말아야할 메서드가 호출이 될 수 있으면 위험
@Controller
public class MemberController {
	// final이 없음
    private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
}

 

주의사항

  • @Autowired를 통한 DI는 스프링이 관리하는 객체에서만 동작(스프링 빈에 등록)
  • 내가 직접 객체를 생성하거나 스프링 빈에 등록하지 않으면 동작하지 않음

회원 웹 기능 - 홈 화면 추가

 

  • HomeController class 작성
package start.startspring.controller;

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

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home";
    }
}
  • home.html templates 작성
<!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>

</body>
</html>
  • 실행 후 localhost:8080 접속 -> 정상

리마인드

  • 웹 브라우저 요청 -> 톰캣 서버 -> 맵핑 된 컨트롤러 확인 후 반환 -> 없으면 static파일 찾아서 반환

회원 웹 기능 - 등록

 

  • MemberForm class 작성 (웹에서 받은 값을 저장)
package start.startspring.controller;

public class MemberForm {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • createMemberForm templates작성 (회원 등록을 받을 페이지)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<div class="container">

    <form action="/members/new" method="post"> <!--전달방식 = post-->
        <div class="form-group">
            <label for="name">이름</label>
            <input type="text" id="name" name="name" placeholder="이름을 입력하세요">
        </div>
        <button type="submit">등록</button>
    </form>

</div>

</body>
</html>
    •  MemberController에 웹에서 입력된(createMemberForm)값을  Post형식으로 받아서 MemberService에 저장
@Controller
public class MemberController {
    // new 연산자로 객체 생성을 하지않고 spring 컨테이너에서 받아서 쓰도록 해야함

    private final MemberService memberService;

    // 생성자에 @Autowired 애노테이션을 붙이면 매개변수를 스프링이 컨테이너에 있는 멤버 서비스를 가져다가 연결시킴
    // 매개변수의 타입 클래스에 @Service 애노테이션을 붙여 줘야 함
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

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

    // 보통 data를 등록할때는 post 조회할 땐 get을 사용
    @PostMapping("/members/new")
    public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return "redirect:/";
     }
}

 


회원 웹 기능 - 조회

 

  • MemberController class에 GetMapping 방식의 메서드 추가
    @GetMapping("/members")
    public String list(Model model) {
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
}
  • memberList template 작성
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
    <div>
        <table>
            <thead>
            <tr>
                <th>#</th>
                <th>이름</th> </tr>
            </thead>
            <tbody>
            <!--thymeleaf 문법-->
            <tr th:each="member : ${members}"> <!--Model안에 있는 값을 꺼냄-->
                <!--getId메서드를 통해 Member에 있는 id값을 호출해서 꺼냄-->
                <td th:text="${member.id}"></td>
                <!--getName메서드를 통해 Member에 있는 name값을 호출해서 꺼냄-->
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>
  • 실행 결과

좌) 회원등록 / 중) 회원목록 / 우) html 소스보기


출처 : 인프런 - 스프링 입문(무료) / 김영한님

https://inf.run/hivx6