• [인프런 김영한 로드맵1]스프링 입문 강의 정리(2)

    2021. 12. 12.

    by. 웰시코더

    반응형

    -인프런 김영한 강사님의 스프링 입문 강의 핵심 위주로 정리한다.
    -모든 소스는 깃허브에서 관리한다.(https://github.com/coderahn/Spring-Lecture1)


    4.회원 관리 예제 - 백엔드 개발


    1)비지니스 요구사항 정리

    간단한 회원관리 예제를 만든다. 회원등록과 조회 기능을 만든다. 데이터 저장소는 선정되지 않았다는 가정하에 메모리에 데이터를 저장하고 조회하는 방식으로 만든다.

    데이터베이스 저장소 미선정으로 인터페이스로 구현클래스 변경 가능하도록 설계(김영한님 인프런 참고자료)


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

    인텔리제이로 하고 있는데 VSCODE랑 이클립스를 주로 써서 단축키를 항상 까먹게 된다. 주요 단축키는 체득이 필요할듯.(윈도우기준)

    • Getter & Setter : Alt + Insert
    • import 정리 : Alt + Shift + O
    • implements Methods : Ctrl + I


    Repository를 만들 때 임시적으로 메모리 저장 방식을 사용한다. MemberRepository를 구현한 MemoryMemberRepository를 만든다. static한 HashMap 인스턴스를 생성하여 임시저장소를 만든다.

    회원등록, 조회와 관련된 메모리Repository


    쓰레드 관련된 ConcurrentHashMap,  AtomicLong 등에 대해서도 공부해야될 것 같다. 따로 쓰레드 박살내는 포스팅을 하든지 해야겠다.

    3)회원 리포지토리 테스트 케이스 작성

    인텔리제이 관련 단축키는 다음과 같다.

    • static import : static import할 메소드에 커서 있는 상태에서 Alt + Enter


    테스트 케이스를 작성한다.  테스트 케이스 작성시 테스트할 데이터를 만들고, 실제로 테스트할 메소드를 호출한 후 assertThat이나 Assertions.assertEquals()를 사용하여 검증한다.

    테스트끼리는 순서에 의존적이면 안 된다.  다음의 경우는 순서에 의존적이게 되어 테스트가 실패한 케이스다.


    위의 화면에 findByName()이 실패했다.  순서를 보면 findAll()부터 테스트가 작동한 것을 확인할 수 있다. 이 때 name이 'spring1'인 member1이 참조하는 Member인스턴스가 만들어졌다.  이후 findByName() 테스트 메소드에서도 'spring1' name이 저장된 member1 객체를 만들어 save하여 중복이 발생한다. 둘의 sequence ID는 다르다. hashMap은 value 중복 저장이 가능하기 때문에 name으로 검색하면 원치 않는 결과를 가져올 것이기 때문에 정확한 테스트가 되지 않는 것이다.

    순서 의존도를 없애기 위해 clear를 해줘야 한다.


    실제 MemoryMemberRepository에 메소드 clearStore()를 만든 후, @AfterEach 어노테이션을 적용한 메소드를 만든다. @AfterEach를 붙이면 각 테스트 실행 후 콜백하는 메소드 함수를 만들 수 있다. 이후 테스트 하면 순서 의존도 없이 테스트가 성공하는 것을 확인할 수 있다.

    4)회원 서비스 개발

    인텔리제이 관련 단축키는 다음과 같다.

    • 메소드 추출(Extract Method) : Ctrl + Alt + m

    리포지토리 테스트가 끝난 후, 실제 서비스단을 만든다.


    5)회원 서비스 테스트

    인텔리제이 관련 단축키는 다음과 같다.

    • 테스트 만들기 단축키 : 테스트 만들 클래스에서 Ctrl + Shift + t

    MemberServiceTest를 단축키로 만든다. 기존 MemberService에서 MemberRepository를 new로 바로 인스턴스 생성하던 것을 DI방식으로 변경한다.

    변경 전

    변경 후


    테스트 클래스에서 MemberService가 DI를 받을 수 있게 초기화한다.


    Service의 테스트 중, 중복회원예외 테스트를 하였다. 같은 이름을 가지는(getName("spring")) Member 인스턴스를 만든다음 join()을 시도해본다. 중복 회원이 있으면 throw할 수 있도록 처리해놨기 때문에 이를 확인하는 테스트가 필요하다.

    예외발생시 catch로 넘겨 처리하는 방법과 assertThrows를 사용하는 방법 2가지가 있다.


    그리고 테스트시에는 given-when-then 패턴을 기본적으로 사용하자. 인터넷에 검색해보니 자주 사용하는 방식인 것 같다.

    5.스프링 빈과 의존관계


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

    MemberController를 만들어서 MemberService, MemberRepository를 의존하여 사용하는 방법을 배운다. 단순히 다음과 같은 초기화 방법은 지양한다.

    private final MemberService = new MemberService();

    MemberService를 스프링컨테이너가 관리하는 빈으로 만들어 공유하여 사용하도록 한다. 그러기 위해 의존하는 주체와 의존 당하는 주체 모두 스프링컨테이너가 관리하는 '빈'이 되어야 한다.

    MemberController위에 @Controller를 붙여 스프링컨테이너 관리 빈으로 만든다. 그리고 다음과 같이 @Autowired 어노테이션을 붙여 초기화한다.

    • @Autowired : 스프링 빈으로 관리되고 있는 객체를 DI(의존성주입) 해줌


    하단에 보면 'Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.'이라는 오류가 콘솔로 확인된다. MemberService를 빈타입으로 정의해라 라고 해석할 수 있는데 말그대로 스프링컨테이너가 관리하는 빈으로 설정하라는 의미이다. MemberService에도 @Controller와 같은 역할을 하는 어노테이션을 붙여주는 작업이 필요하다.

    서비스에는 @Service 어노테이션을 붙여준다. Repository 구현체에는 @Repository 어노테이션을 붙여주면 스프링컨테이너가 관리하는 빈이 된다. 그러면 Controller에서 @Autowired를 통해 의존할 수 있다.

    @Service, @Controller 등은 모두 내부에 @Component 어노테이션을 갖고 있는데, 이 어노테이션이 붙은 애들만 스프링 빈으로 관리된다. 이런식으로 컴포넌트들을 스캔하는 방식을 컴포넌트 스캔 방식이라고 한다.

    추가적으로, hello.hellospring 밖에 패키지를 만들고 @Component어노테이션을 붙인 클래스 파일을 만들면 컴포넌트 스캔이 될까? 기본적으로 안 된다. 다음 화면과 같이, HelloSpringApplication이라는 구동클래스를 포함하는 패키지 안에서만 스캔한다.

    HelloSpringApplication이 포함된 패키지 안에서 스캔 가능. @SpringBootApplication안에 @ComponentScan이 있다.


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

    컴포넌트 스캔 방식 이외에도 자바설정파일을 만들어서 빈 등록이 가능하다. 기존 @Service, @Repository, @Autowired를 제거한 후, 다음과 같은 자바 설정파일을 만든다.


    SpringConfig 파일의 디렉토리가 어디까지 스프링컨테이너 설정정보로 이용될 수 있는지 궁금해서 이곳저곳에 만들며 테스트해봤다. hello.hellospring.config 패키지 하위에 두어도 인식이 가능했다. 반면 hell.hellospring을 벗어난 패키지(java/test/SpringConfig.java 등)로 설정하면 인식하지 못한다. 항상 HelloSpringApplication이 속한 패키지 하위에 설정정보 파일을 넣어둬야 한다.

    그리고 DI주입 방식에는 3가지가 있다. (setter방식, 필드방식, 생성자방식)

    //1.setter방식
    @Autowired
    public setMemberRepository(MemoryRepository memoryRepository) {
        this.memoryRepository = memoryRepository
    }
    
    //2.필드방식
    @Autowired
    public void MemberRepository memberRepository;
    
    //3.생성자방식(추천)
    @Autowired
    public MemberService memberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository
    }

    일반적으로 컴포넌트 스캔은 정형화된 MVC패턴 파일들을 빈등록할 때 사용한다. 반면 정형화되지 않거나 상황에 따라 구현 클래스 변경 필요한 경우 자바설정을 통해 스프링 빈으로 등록한다.(MemoryMemberRepository를 DBMemberRepository로 바꿔야할 경우)

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


    1)회원 웹 기능 - 홈 화면 추가

    home으로 갈 수 있는 기본적인 getMapping 컨트롤러 메소드를 추가한다. 그리고 viewResolver로 만들어진 home.html을 만든다.

    HomeController 일부

    home.html

    home.html 구동 화면


    2)회원 웹 기능 - 등록

    아래의 회원등록 폼에서 이름 입력 후 등록을 클릭하면 /members/new POST로 요청된다.

    회원등록폼


    다음과 같이 컨트롤러의 @PostMapping("/members/new")에 매핑된다. MemberForm에는 name프로퍼티가 있고 setter, getter가 구현되어 있다. 폼에서 입력한 name값이 담긴다. getName()으로 값을 가져올 수 있다. 코드는 아래와 같다.

    가입 완료 후 root(/)로 redirect한다.



    3)회원 웹 기능 - 조회

    홈 화면에서 목록을 누르면 @GetMapping("/members")로 이동한다. 구현한 findMembers()를 호출한 후 model에 담아 /members/memberList로 이동한다. ViewResolver로 인하여 memberList.html을 보내준다.


    타임리프 문법을 사용하여 each 반복문을 통해 모든 member들을 가져온다.


    다음과 같이 가입한 회원들의 목록을 확인할 수 있다.

    다음과 같이 회원 목록을 확인할 수 있다.
    반응형

    댓글