스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 스프링 빈, 의존관계

jkky98·2024년 6월 20일
2

Spring

목록 보기
1/77

컴포넌트 스캔

스프링은 지정된 패키지 내에서 @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스를 자동으로 스캔하여, 해당 클래스를 스프링 컨텍스트에 빈(Bean)으로 등록한다.

직접만든 회원 컨트롤러에 의존관계를 추가해보도록 한다.

import java.util.List;

@Controller
public class MemberController {

    private final MemberService memberService;

    // spring container에 등록
    // MemberController가 생성될 때 Spring Bean에 있는 MemberService 객체를 넣어준다.
    // 이것이 DI다.(스프링이 넣어주는거다.)
    @Autowired // 연결
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

@Autowired

@Autowired는 스프링 프레임워크에서 의존성 주입(Dependency Injection)을 위해 사용하는 어노테이션으로, 필드, 생성자, 세터 메서드에 적용할 수 있다. 이 중 생성자 주입이 권장되며, 이는 객체 생성 시 모든 의존성이 주입되어 객체를 불변 상태로 유지하고, 필수 의존성을 보장하며, 순환 참조를 방지할 수 있기 때문이다. 또한, 생성자 주입은 의존성을 명시적으로 전달하므로 테스트 시 Mock 객체를 주입하기 용이하다. 이러한 이유로 생성자 주입은 코드의 안정성과 예측 가능성을 높이는 좋은 선택으로 간주된다.

@Controller

@Controller가 붙은 MemberController 클래스는 스프링 컨테이너에 Bean으로 등록되며, 스프링은 이 클래스의 인스턴스를 하나만 생성하여 관리한다. 이를 싱글톤(Singleton) 스코프라고 하며, 빈에 등록되는 대부분의 객체는 기본적으로 싱글톤으로 사용된다.

MemberController의 생성자@Autowired를 적용하면, 생성자에서 필요한 MemberService 인스턴스를 스프링이 자동으로 주입한다. @Autowired는 스프링 컨테이너에서 MemberService 빈을 찾아 생성자에 전달하며, 이를 통해 의존성 주입이 이루어진다.

@Service
public class MemberService {

    private final MemoryMemberRepository memberRepository;

    @Autowired
    public MemberService(MemoryMemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

@AutowiredMemberService를 주입하려면, MemberService도 스프링 컨테이너에 Bean으로 등록되어야 한다. 이를 위해, MemberService 클래스에 컴포넌트 스캔이 가능하도록 적절한 어노테이션(예: @Service)을 추가한다.

MemberService를 컨트롤러에 자동으로 의존 관계를 주입하려면, MemberService 자체도 생성자를 통해 리포지토리를 주입받아야 한다. 이 과정에서 @Autowired를 사용하여 스프링 컨테이너에서 리포지토리를 가져온다.

또한, 리포지토리가 스프링 Bean으로 등록되어야 하기 때문에, @Repository를 사용해 컴포넌트 스캔 대상으로 지정한다. 이를 통해, MemberController → MemberService → 리포지토리로 이어지는 의존 관계가 스프링에 의해 자동으로 설정된다.

@Repository
public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

직접 스프링 빈 등록하기

Main실행 자바 파일과 같은 경로에 SpringConfig 클래스 파일을 생성한다.

package hello.hello_spring;

import hello.hello_spring.repository.MemoryMemberRepository;
import hello.hello_spring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

위와 같이 컴포넌트 스캔 방식을 사용하지 않고, 직접 Config 파일을 구성하여 Bean을 등록할 수도 있다. 이를 통해 개발자가 명시적으로 의존성을 설정할 수 있다.

하지만, @Controller@Autowired는 컨트롤러 클래스에서 유지해야 하며, 컨트롤러는 컴포넌트 스캔 방식으로 등록된다. 이는 컨트롤러가 웹 요청을 처리하는 스프링 MVC의 특성과 역할을 유지하기 위함이다. 따라서, Config 파일을 사용한 의존성 주입은 서비스와 리포지토리와 같은 핵심 로직 구성 요소를 설정하는 데 주로 사용된다.

회원 웹 기능 - 홈 화면 추가

홈 컨트롤러를 만들어 baseurl에서의 기본 화면이 index.html이 아니도록 구성한다.

package hello.hello_spring.controller;

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

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "home";
    }

}

이렇게 구성하고 template에 home.html을 적당히 잘 만들면,
이와 같이 설정한 환경에서 우리의 목적은 다음과 같다:

  1. 회원 가입 버튼 클릭

    • 사용자가 회원 가입을 누르면 회원 가입 페이지로 이동한다 (GET 요청).
  2. 회원 가입 페이지 동작

    • 가입 페이지에서 로그인 폼에 이름을 입력한 후, 등록 버튼을 누르면 POST 요청으로 데이터를 전송한다.
  3. 데이터 처리 및 저장

    • 서버는 POST 요청으로 전달된 데이터를 받아 Member 객체로 변환하고, 이를 리포지토리에 등록한다.

이 과정을 통해 회원 데이터를 등록하는 기능이 구현된다.

//MemberForm

package hello.hello_spring.controller;

public class MemberForm {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
//////////////////////////////////
    // MemberController
    @GetMapping("/members/new")
        public String createForm() {
            return  "members/createMemberForm";
        }

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

        memberService.join(member);

        return "redirect:/";
    }

회원가입이 성공한다면 다시 홈 화면으로 돌아오고 회원 목록에 들어가면 우리는 리포지토리에 넣은 member들을 확인할 수 있다.

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

    }

모델(Model)은 데이터를 HTML에서 사용하기 위해 전달하는 역할을 한다.

  1. 회원 데이터 조회

    • memberService.findMembers()를 호출하여 모든 회원을 조회하고, 결과를 List로 저장한다.
  2. 모델에 데이터 추가

    • 조회된 회원 리스트를 Model 객체에 추가하여, 데이터를 HTML 템플릿으로 전달한다.
    • 예: model.addAttribute("members", memberList)
  3. HTML 렌더링

    • 데이터를 포함한 ModelmemberList.html로 전달되며,
      타임리프(Thymeleaf)를 사용해 동적으로 데이터를 렌더링하여 화면에 표시한다.

타임리프를 활용해 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>

${}로 하여금 모델에서 데이터를 뽑을 수 있다.
물론 현재 사용 중인 리포지토리는 메모리 기반 리포지토리이기 때문에, 스프링 애플리케이션 실행을 종료하면 모든 데이터가 삭제된다. 이러한 한계를 보완하기 위해 파일 저장소데이터베이스를 도입할 수 있다.

파일 저장소를 사용하면 데이터를 로컬 파일에 영구적으로 저장할 수 있으며, 데이터베이스를 사용하면 대량의 데이터를 효율적으로 관리하고 복잡한 쿼리를 통해 조회와 조작이 가능해진다. 이러한 변경은 기존 리포지토리 인터페이스를 유지하면서, 구현체만 교체하는 방식으로 유연하게 적용할 수 있다.

profile
자바집사의 거북이 수련법

0개의 댓글