영운's 블로그

스프링 입문(2) - 회원 관리 예제, 스프링 빈과 의존관계 설정 본문

스프링 강의 정리/스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

스프링 입문(2) - 회원 관리 예제, 스프링 빈과 의존관계 설정

오영운(you88) 2022. 5. 24. 17:11

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

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

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

해당 글은 김영한님의 스프링 입문을 강의를 들으며 개인적으로 정리한 내용입니다.

모든 자료의 출처는 김영한님의 스프링 입문 강의임을 미리 밝힙니다.

 

실습 코드: https://github.com/you88311/spring-example1

 

회원 관리 예제

 

1. 비즈니스 요구사항 정리

 

 

service는 보통 비즈니스에 의존적으로 설계 & naming도 join(), findMembers() 같이 비즈니스 단 용어

repository는 비즈니스보다는  개발스럽게(?) & naming도 findByName(), findByID(), findAll()

 

 

 2. 회원 도메인과 리포지토리 만들기

 

..pdf참조바람. 자세히 설명되어 있음

회원 도메인은 말 그대로 회원이라는 객체

리포지토리는 '저장소'니까 회원 정보를 저장, 관리하는 함수들 save() findByName(), findByID(), findAll() 등등

 

3. MemoryMemberRepository 테스트 케이스 작성

 

  • 테스트 케이스 작성해야 하는 이유?

 

개발한 기능을 실행해서 테스트 할 때 테스트 케이스 말고도 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행하는 방법도 있다. 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다

 

=>이를 해결하기 위해 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.

 

방법1) 개발하고 => test 케이스

 

방법2) test케이스 먼저 만들고 => 개발

이건 TDD(Test Driven Development)

 

  • 구체적인 테스트 방법

1. main에 만들었던 것과 동일한 package구조 만들고

2. 거기에 test할 클래스 이름 + Test를 붙여서 클래스 생성

   이번에는 MemeoryMemberRepository 클래스 test할거니까 MemoryMemberRepositoryTest라는 클래스를 만들었음

 

1+2를 할필요 없고 그냥 main에 있는 테스트하려는 클래스에서

ctrl+alt+T 누르면 자동으로 test에 디렉토리는 물론 전체적인 구조까지 만들어줌

 

 

3. @Test라는 annotation 활용하기

 

테스트하고자 하는 메소드들 별로 @test annotation 붙여주면 자동으로 org.junit.jupiter.api.Test를 import하여 테스트 가능

 

 

  •  @AfterEach

AfterEach는 메소드 별로 test할 때 이전의 test하기 위한 변수값 들이 이후 다른 메소드 test할 때 영향 주지 않도록 하기위한 것

굉장히 중요한 것이 test는 우리가 @Test로 선언한 메소드 순서대로 test되는 것이 아님.

따라서 위에서 아래로는 문제가 없어 보여도 전체를 test시 문제 발생함.

+

이를 위해서는 별도로 test하고자 하는 클래스인 MemoryMemberRepository 클래스에 clearStore()함수 만들고 이를 @AfterEach 어노테이션으로 MemoryMemberRepositoryTest 클래스에 저렇게 선언시 @Test마다 @AfterEach를 선언하여 Test 순서가 상관없게 함

 

MemoryMemberRepository 클래스에 clearStore()함수

store가 Map 인터페이스 형식인데 Map에내장된 clear()함수로 그냥 저장된 내용을 지우는 함수임

 

  • Assertions.assertThat은 import에 주의할 것

 

import static org.assertj.core.api.Assertions.*; 을 해야지

import org.junit.jupiter.api.Assertions를 import하면 안됨

 

+

import static으로 선언시 Assertions.assertThat이 아니라

assertThat()바로 선언 가능

이건 특별한 기능이 아니라 그냥 기본 자바 문법이다.

이를 통해 Assertions.assertThat.isEqualTo() 보다는 

assertThat(actual).isEqualTo(expected)로 선언시 그냥 영어 문장 읽는 것처럼

훨씬 가독성이 상승하는 것을 볼 수 있음. 뭐가 actual이고 뭐가 expected인지도 쉽게 알 수 있다.

 

 

4. 회원 서비스 개발

 

 

result.ifPresent는 Optional에서 제공하는 함수이다.

요즘은 이렇게 값이 null이 될 수 있는 것은 Optional을 이용하여 제어한다.

만약 result가 null이 아니라면 다음 해당 람다식을 실행하는 것.

여기서는 그냥 null이 아니면 예외를 발생하도록 만든 것

 

 

사실 함수 하나에는 하나의 기능이 있는 것이 좋기에 회원가입 함수와 별도로

중복검사는 별도의 함수로 만들 수도 있다.

신기한게 ctrl+alt+shift+T => extract method하면 

저렇게 자동으로 함수형태로 만들어줌. 우리는 함수 이름만 별도로 설정하면 된다.

 

 

5. 회원 서비스 테스트 

 

앞에서 했던 것 처럼 main에 있는 테스트하려는 클래스에서

ctrl+alt+T 누르면 자동으로 test에 디렉토리는 물론 전체적인 구조까지 만들어준다.

그리고 테스트 코드는 주석처리된 부분처럼

given, when, then 형태로 만들자. 물론 복잡한 상황에서는 저게 안통하는

경우도 있지만 우리처럼 그냥 토이 프로젝트 만드는 경우는 저걸 기반으로

웬만한 것은 모두 만들 수 있음

 

테스트 케이스의 경우 실제 main의 함수 이름과 다르게 해도 됨. 심지어 보자 직관적으로 알아볼 수 있도록 한글로 해도 됨. void 회원가입(), void 중복_회원_예외()이렇게

실제 배포시에는 test는 포함되지 않기에 여기는 한글로 함수이름도 많이 사용함

회원가입 테스트 케이스인데 아직 부족함. 회원가입의 핵심 로직은 동일한 이름으로

회원가입이 되는지 안되는지가 아닌 우리가 설계한 중복시 예외처리가 제대로 작동하는지 이기에 별도로 회원이름 중복시 예외처리 관련 테스트 케이스 확인 필요.

 

 

회원가입의 경우 핵심은 회원가입이 제대로 저장되는지가 아니라 중복회원에 대해 예외처리가 제대로 되고 있는지 확인하는 것이 핵심. 따라서 아래와 같이 중복처리 관련 test를 추가함.

 

예외처리 확인의 경우 주석처럼 try-catch문을 사용할 수도 있으나 역시  Assertions에서 

보다 편리한 예외처리 test 함수를 제공함.

 

  • 의존성 주입(Dependency injection)

또다른 짚어볼 점은 main에서 사용하는 memberRepository와 실제 test시 사용하는 memberRepository가 다르다는 점.

내부적으로는 memberRepository의 회원정보를 저장하는 map 형식의 'store'가 static으로 사용되어 있기는 하지만 static으로 선언 안되어있을 경우 서로 다른 repository로 테스트하는 셈. 이를 아예 구조적으로 static으로 선언하지 않더라도 동일한 repository로 테스트하는 형태가 바람직함.

 

그래서 아예 service부분에서 new를 하지 않고 외부에서 받아오게 설정

그리고 테스트단에서 BeforeEach로 Test마다 그때 그때 만들어 test하게

이러면 memberService의 memberRepository와 MemberServiceTest의 memberRepositor가 동일하게 됨 

 

근데 스프링은 실제로는 이렇게 안하고 @Autowired로 의존관계를 자동으로 주입함

 

 

 

스프링 빈과 의존관계 설정

크게 두 가지 방법이 있다.

방법1. 컨포넌트 스캔과 자동 의존관계 설정

방법2. 자바 코드로 직접 스프링 빈 등록하기

 

1. 컨포넌트 스캔과 자동 의존관계 설정

 

스프링 컨테이너: 자바 객체 관리 및 기능 제공

스프링 빈: 자바 객체

 

컴포넌트 스캔 원리

@Component 애노테이션이 있으면 스프링 빈으로 자동 등록된다.

@Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다.

 

@Component 를 포함하는 다음 애노테이션도 스프링 빈으로 자동 등록된다.

@Controller

@Service

@Repository

 

Repository, Service, Controller마다 annotation으로 

@Repository, @Service, @Controller로 선언을 해준다.

@Repository, @Service, @Controller 이들이 모두 Componenet에 해당

 

Controller에서 Service 사용하려면 스프링 빈에 @로 선언된 걸 가져와서 @Autowired로 사용함

@Autowired를 만나면 컨포넌트 스캔을 통해 스프링 빈에 저장된 Component를 불러오는 것. 

 

기본적으로 memberRepository는 스프링 빈에 하나만 저장됨.

memberService, memberController도 마찬가지. 이걸 '싱글톤'이라고 한다.

 

이건 다른 서비스, 예를 들어 orderService에서 memberRepository를

스프링 컨테이너에 요구할 때도 마찬가지. 그때도 동일한 memberRepository를 반환한다.

 

컨포넌트 스캔의 기본 범위는 main에 해당하는 @SpringBootApplication이

존재하는 package 및 그 하위 package들

 

 

2. 자바 코드로 직접 스프링 빈 등록하기

 

 

package springpractice.hellospring;

이 main에 해당하는 경로 밑에 클래스 만들고

@Configuration으로 annotation주고 직접 필요한 것들 @Bean으로 연결

 

MemberService의 경우 MemoryMemberRepository가 필요하니 차례대로..

 

단, Controller는 이렇게 수동으로 설정 불가하다.

Controller만은자동 의존관계 설정하는 걸로 @Controller, @Autowired로 해줘야 함.

 

 

 

  • 그럼 컨포넌트 스캔의 자동 의존관계 설정이 훨씬 편한데 자바 코드로 직접 스프링 빈 등록하는 방법을 왜 사용하는가?

=> 처음에 어떤 저장소를 쓸지 모르겠어서 MemberRepository 인터페이스만 만들고 

MemeoryMemberRepository 클래스를 만들어서 사용했다.

이렇게 직접 스프링 빈에 등록하는 방법의 경우 이 MemeoryMemberRepository가

다른 DB로 바뀌는 경우 단순히 그 이름으로만 바꿔주면 의존관계가 주입됨.

물론 컴포넌트 스캔 방식도 바꿀 수는 있지만 그러한 경우 여러 개를 바꿔야 하는 번거로움이 있음.

 

이러한 경우를 제외하고는 대부분 컴포넌트 스캔을 이용함.

 

 

 

DI(의존성 주입)에는 

  1. 필드 주입 
  2. setter 주입
  3. 생성자 주입
     

생성자 주입은 우리가 했던 방식

생성자 통해서 inject하는 방식

 

필드 주입은  이런식으로 필드 자체에 @Autowired를 주는 거

 

 

setter주입은

alt+insert로 setter를 만들고 거기다 @Autowired 설정하는 것

이 방식의 경우 setMemberService를 public으로 설정해야 하기때문에 다른 곳에서 MemberService를

변경할 수 있다 단점이 있다.

이렇게 외부에서 생성할 수 잇다는 것은 위험하다. 의존관계를 실행중에(runtime에) 동적으로 변하는 경우는 없기 때문.

따라서 이렇게 setter에 직접 autowired를 설저하는 것은 바람직하지 않음.

 

결론: 우리가 기존에 사용하던 생성자 주입으로 의존성을 주입하는 것이 바람직하다.

 

@Autowired로 의존성 주입하는 방법은 스프링 링이 관리하는

controller, service 등의 객채에만 해당되는 것이지 

우리가 별도로

 

public static void main{

        @Autowired
        MemeberService memberservice = new Memberservice();

}

이렇게 만든 memberservice 객체는 MemberService 내부에서 @Autowired를 제대로 설정했다고 의존성이 주입되지 않는다. 이건 스프링이 관리하는 객체가 아니기 때문이다.

 

 

Comments