스프링 핵심원리 기본편 요약

장원령·2021년 3월 31일

Backend(Java Spring)

목록 보기
3/6

1-2장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 프로젝트 생성

https://start.spring.io/
위 사이트에서 프로젝트를 생성할 수 있다. Gradle 외에는 설정을 그대로 따랐다.

  • dependencies등에서 lombok등을 추가할 수 있다. (7장의 내용)

2. 비즈니스 요구사항과 설계

비즈니스 요구 사항은 다음과 같다.

회원
회원을 가입하고 조회할 수 있다.
회원은 일반과 VIP 두 가지 등급이 있다.
회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)

주문과 할인 정책
회원은 상품을 주문할 수 있다.
회원 등급에 따라 할인 정책을 적용할 수 있다.
할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

주문과 할인 정책의 경우에는 객제치향 설계 방법으로 설계한다. 인터페이스를 만들고 구현체를 언제든지 갈아끼울 수 있도록 설계한다.

3. 회원 도메인 설계

: 회원 DB를 자체 구축 할 수도 잇고, 연동할수도 있기 때문에, 회원 데이터에 접근할 수 있는 계층을 따로 만들고, 이게 회원 저장소라는 인터페이스이다.
: 하위 계층의 3가지 (메모리 회원 저장소 db회원 저장소 외부시스템 연동 회원 저장소)를 선택하면 된다.
: 도메인 그림은 기획자도 볼 수 있는 그림, 바탕으로 클래스 다이어그램을 만들고(서버 실행 X 클래스들만 분석) 동적으로 발생하는 것들에 대해서 객체 다이어그램으로 계획한다.

4. 회원 도메인 개발

: 먼저, 회원 정보에 관련한 내용을 만든다.
: 생성자, getter/setter 관련한 단축키는 Alt+ insert이다.

package hello.core.member;

public class Member {
    private Long id;
    private String name;
    private Grade grade;

    public Member(Long id, String name, Grade grade) {//생성자
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Grade getGrade() {
        return grade;
    }
    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}

회원 저장소 인터페이스

package hello.core.member;

public interface MemberRepository {
    void save(Member member);

    Member findById(Long memberId);
}

회원 저장소 구현체

package hello.core.member;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();

    @Override
    public void save(Member member) {
        store.put(member.getId(), member);
    }

    @Override
    public Member findById(Long memberId) {
        return store.get(memberId);
    }
}

회원 서비스 인터페이스

package hello.core.member;

public interface MemberService {
    void join(Member member); //가입
    Member findMember(Long memberId); // 조회

}

회원 서비스 인터페이스 구현체


package hello.core.member;


public class MemberServiceImpl implements MemberService{
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    
    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

5. 회원 도메인 실행과 테스트

A. 실제로 동작해보는지 알기 위하여 MemberApp을 만든다.

: 리팩토링 중 변수 추출하기(ctrl + alt + v)

public static void main(String[] args) {

        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "pitchu", Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find member = " + findMember.getName());

    }
// 순수한 자바로 만들어낸 코드이다. 

: 애플리케이션 로직으로 하는건 한계가 있음

B. juint이라는 테스트 프레임워크를 활용해본다.

: 깨지면 오류를 보여주기 때문에 빠르게 캐치가 가능하다.

MemberService memberService = new MemberServiceImpl();

    @Test
    void join(){
        //given 주어졌을때
        Member member = new Member(1L, "pitchu",Grade.VIP);
        
        //when 이렇게 했을때
        memberService.join(member);
        Member findMember = memberService.findMember(1L);
        
        //then 이렇게 된다. 
        Assertions.assertThat(member).isEqualTo(findMember);          
        // 검증하는 내용
        // 조인 한거와 찾는게 똑같은지 확인한다. 

    }
  • 다만 이 코드는 DIP를 위반하고 있다.

6. 주문과 할인 도메인 설계

주문과 할인 정책
회원은 상품을 주문할 수 있다.
회원 등급에 따라 할인 정책을 적용할 수 있다.
할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있
다.)
할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을
미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)\

7. 주문과 할인 도메인 개발


public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member,itemPrice);
        // 단일 설계원칙을 잘 지킨 케이스 
        // 할인은 discountPolicy가 하고 나에게 결과만 전해줘. 
        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

8. 주문과 할인 도메인 실행과 테스트

A. 실제로 동작해보는지 알기 위하여 메인 메소드로 테스트


 MemberService memberService = new MemberServiceImpl();
         OrderService orderService = new OrderServiceImpl();

        Long memberId = 1L;
        Member member = new Member(memberId, "pitchu", Grade.VIP );
        memberService.join(member);
        Order order = orderService.createOrder(memberId, "pitchu", 10000);
        System.out.println("order = " + order);

B. juint이라는 테스트 프레임워크를 활용해본다.

MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();

@Test
    void createOrder(){
        Long memberId = 1L;
        Member member = new Member(memberId, "pitchu", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "pitchu",10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }

3장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

핵심 내용 : 스프링 컨테이너가 왜 탄생했는가?

1. 새로운 할인 정책 개발

: 새로운 할인 정책을 적용한다고 가정해보자
: 정책을 만들고 테스트 할 때, VIP인 경우, 아닌 경우 2가지로 나누어 테스트를 실행한다.
: 밑의 경우에서 보듯이, 옳은 답이 나오지 않을 경우 인텔리제이에서 주석처리한 에러메시지를 띄우기 때문에, 이를 통해 알 수 있다.


class RateDiscountPolicyTest {
    RateDiscountPolicy  discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10% 할인이 적용되어야 한다 ")

    void vip_o(){
        //given
        Member member = new Member(1L, "mem", Grade.VIP);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        Assertions.assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아닌 경우")
    void vip_x(){
        //given
        Member member = new Member(2L, "memberBASIC", Grade.BASIC);
        //when
        int discount = discountPolicy.discount(member, 10000);
        //then
        //Assertions.assertThat(discount).isEqualTo(1000); 일 경우
        //Expecting:
        // <0>
        //to be equal to:
        // <1000>
        Assertions.assertThat(discount).isEqualTo(0);
    }


}

2. 새로운 할인 정책 적용과 문제점

: 할인 정책을 변경하려면 OrderServiceImpl의 코드를 고쳐야 하는데 이는 다음과 같다.

public class OrderServiceImpl implements OrderService{
    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

: 이런 코드일 경우, 문제점이 생기는데 OCP와 DIP를 준수하지 못했기 때문이다.

  1. DIP의 문제
    : OrderServiceImpl이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy에도 의존하고 있기 때문이다.
  1. OCP 위반
    : 이 의존 관계를 변경하고자 할 경우 OCP도 위반하게 된다.

문제 해결법
: DIP를 위반하지 않도록 인터페이스만 의존하게 변경하면 된다.

//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private final DiscountPolicy discountPolicy;

주석처리된 코드를 아래와 같이 바꾸면 된다.

추가 문제
: 이렇게 하면 Null pointer exception 문제가 발생한다.

추가 문제 해결법
: 누군가가 클라이언트인 orderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다. 이에 관련한 내용은 밑에 더 자세하게 있다.

3. 관심사의 분리

: 지금 까지의 코드는, 애플리케이션이 공연이라고 가정 했을 때, 배우가 공연도 하고 여자 주인공도 공연에 초빙하는 다양한 책임을 지니는 코드이다.
: 이에 공연을 구성하고 섭외하는 공연 기획자를 만들어야 한다.
: 이것을 AppConfig로 가정하고 만들어본다.
: 이 클래스는 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스이다.

public class AppConfig {
 public MemberService memberService() {
 return new MemberServiceImpl(new MemoryMemberRepository());
 }
 public OrderService orderService() {
 return new OrderServiceImpl(
 new MemoryMemberRepository(),
 new FixDiscountPolicy());
 }
}

: AppConfig가 구현 객체를 생성하고, 생성한 객체의 참조를 통해 생성자를 통해서 주입한다.
: 이렇게 하면 DIP도 지키게 된다.
: appConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl 을 생성하면서 생성자로 전달한다.
: 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다

4. AppConfig 리팩터링

: 위의 문제였던 discountPolicy을 고친 모습이다.

public class OrderServiceImpl implements OrderService {
 private final MemberRepository memberRepository;
 private final DiscountPolicy discountPolicy;
 
 
 public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
 this.memberRepository = memberRepository;
 this.discountPolicy = discountPolicy;
 } // 생성자 주입 
 
 @Override
 public Order createOrder(Long memberId, String itemName, int itemPrice) {
 Member member = memberRepository.findById(memberId);
 int discountPrice = discountPolicy.discount(member, itemPrice);
 // 단일 설계원칙을 잘 지킨 케이스 할인은 discountPolicy가 하고 나에게 결과만 전해줘.
 return new Order(memberId, itemName, itemPrice, discountPrice);
 }
}

5. 새로운 구조와 할인 정책 적용

: 새로운 구조를 적용할 경우, 구성 영역은 당연히 변경되지만, 사용영역은 변경 될 필요가 없다.

@Configuration
public class AppConfig {
    //@Bean memberService -> new MemoryMemberRepository
    //@Bean orderservice -> new MemoryMemberRepository

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        //return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }


}
// 다음과 같이 Appconfig내 코드만 변경하면 되는 것을 알 수 있다. 

6. 전체 흐름 정리

7. 좋은 객체 지향 설계 5가지 원칙의 적용

1. SRP 단일 책임 원칙

: 한 클래스는 하나의 책임만 가져야 한다.
: 배역의 문제를 AppConfig를 통하여 나눔.

2. DIP 의존관계 역전 원칙

: 의존관계가 복합적이던 문제를 AppConfig를 통하여 해결

3. OCP 확장에는 열려있으나 변경에는 닫혀 있어야 한다.

: 애플리케이션을 사용영역과 구성영역으로 나눔.

8. IoC, DI, 그리고 컨테이너

1. IOC

: 프로그램의 제어 흐름을 클라이언트 구현 객체가 제어하지 않고, 외부의 구성 영역에서 가져가는 것을 제어의 역전 이라고 한다.

2. DI

: 의존 관계는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체 의존 관계로 나누어서 생각해야 한다.
: 정적은 실행하지 않고도 알 수 있는 것이다.
: 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 '의존관계 주입'이라고 한다.
: 의존 관계 주입을 사용하면 클라이언트 코드를 변경하지 않고 호출하는 대상의 타입 인스턴스를 변경 할 수 있다.
: 정적인 클래스 의존 관계를 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

3. IOC컨테이너 DI 컨테이너

: AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것

9. 스프링으로 전환하기

: @Configuration과 @Bean을 붙여준다
: AppConfig를 통해 기존에는 직접 객체를 생성하고 DI했지만, 스프링 컨테이너를 통해서 사용한다.

: 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정 정보로 사용한다. @Bean이 붙은 메소드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록하는데, 이렇게 등록된 객체를 스프링 빈이라 한다.
: 스프링 빈은 applicationContext.getBean() 메서드
를 사용해서 찾을 수 있다.

4장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 스프링 컨테이너 생성

//스프링 컨테이너 생성
ApplicationContext applicationContext =
 new
AnnotationConfigApplicationContext(AppConfig.class);

: 위의 코드에서 ApplicationContext 를 스프링 컨테이너이자 인터페이스이다.
: 파라미터로 구성 정보를 지정하고 파라미터의 설정 클래스 정보를 이용해서 스프링 빈을 등록한다.
: 빈의 이름은 메소드 이름을 사용한다.

2. 컨테이너에 등록된 모든 빈 조회

: ac.getBeanDefinitionNames()를 이용하면 모든 빈을 조회할 수 있고, (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION을 활용하면 직접 등록한 애플리케이션 빈만 추출할 수 있다.

3. 스프링 빈 조회 - 기본

: 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 방법은 아래와 같다.

ac.getBean(빈이름, 타입)
ac.getBean(타입)

: 다만, 조회 대상 스프링 빈이 없으면 예외 발생 하는데 이는 아래와 같다.
: 조회하는 자세한 방법은 아래와 같다.

NoSuchBeanDefinitionException: No bean named 'xxxxx' available


public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName(){
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("이름 없이 타입으로만 조회")
    void findBeanByType(){
        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2(){
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

    }

    @Test
    @DisplayName("빈 이름으로 조회 X") // 통과되지않는 경우
    void findBeanByNameX(){
        org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("XXXXX", MemberService.class));


    }

4. 스프링 빈 조회 - 동일한 타입이 둘 이상

: 같은 타입의 스프링 빈이 둘 이상이면 오류(NoUniqueBeanDefinition)가 발생하는데, 이럴때는 빈이름을 지정해주어야한다.


@Test
 @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
 void findBeanByName() {
 MemberRepository memberRepository = ac.getBean("memberRepository1",
MemberRepository.class);
 assertThat(memberRepository).isInstanceOf(MemberRepository.class);
 }

: 위와 같이 빈 이름을 지정하면 된다.

@Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBeanByType(){
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()){
            System.out.println("key = " + key + "value = " + beansOfType.get(key));
        }
        org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);

   }

: 다음과 같은 코드를 통하여 같은 타입의 빈을 모두 조회 할 수 있다.

5. 스프링 빈 조회 - 상속 관계

: 부모 타입을 조회할 경우, 자식 타입도 함께 조회된다.
: object타입으로 조회하면 모든 스프링 빈이 조회가 가능하다.
: 자식 타입이 둘 이사 있으면 중복 오류가 발생하기 때문에 마찬가지로 빈 이름을 지정해주면 된다.

6. Bean Factory와 ApplicationContext

1. Bean Factory

: 스프링 컨테이너의 최상위 인터페이스
: 스프링 빈을 관리하고 조회하는 역할을 담당한다. ex) getBean
: 직접 사용하지 않고 ApplicationContext를 사용한다.

2. ApplicationContext

: beanfactory의 기능을 모두 상속받아서 제공한다.
: 빈 관리 기능에 편리한 여러가지 부가기능들을 제공한다.

위의 두개를 스프링 컨테이너라고 한다.

7. 다양한 설정 형식 지원 - 자바코드 XML

: 스프링 컨테이너는 다양한 설정 정보를 받아들일 수 있게 설계되어 있다.

8. 스프링 빈 설정 메타 정보 - Bean Definition

: 스프링이 이렇게 다양한 설 정 형식을 지원하는 이유는 역할과 구현을 개념적으로 나누었기 때문이다.
: BeanDefinition이 빈 설정 메타 정보인데, @Bean 당 각각 하나씩 메타 정보가 생성 되고 이것을 기반으로 스프링 빈이 생성된다.
: BeanDefinition은 스프링이 다양한 형태의 설정 정보를BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 된다.

5장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 웹어플리케이션 & 싱글톤

: 대부분의 스프링 어플리케이션은 웹 어플리케이션인데, 여러 고객이 동시에 요청을 하는 경우가 많음.
: 매번 객체를 새로 생성함(스프링 없는 순수한 DI컨테이너의 경우)

이 문제를 해결하기 위하여 싱글톤이 등장

싱글톤이란?
: 해당 객체가 하나만 생성되고 공유되는 것

2. 싱글톤 패턴

: 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다
: 객체 인스턴스를 2개 이상 생성치 못하도록 막아야 한다.
: 싱글톤 패턴을 적용한 예제 코드는 다음과 같다.

package hello.core.singleton;
public class SingletonService {

 //1. static 영역에 객체를 딱 1개만 생성해둔다.
 private static final SingletonService instance = new SingletonService();
 
 //2. public으로 열어서 객체 인스터스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한
다.
 public static SingletonService getInstance() {
 return instance;
 }
 
 //3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
 private SingletonService() {
 }
 public void logic() {
 System.out.println("싱글톤 객체 로직 호출");
 }
}

: 싱글톤 패턴은 유연성이 떨어지는 단점이 있다.

3. 싱글톤 컨테이너

: 싱글톤 패턴의 문제점이었던 지저분한 코드 X DIP, OCP, 테스트 , private 생성자로부터 자유롭게 싱글톤 사용 가능
: 스프링 빈이 싱글톤으로 관리되는 빈이다.

4. 싱글톤 방식의 주의점

: 싱글톤 객체는 상태를 유지하게 설계하면 안된다.
: 무상태로 설계해야한다.
: 공유필드의 문제점이 있는데, 이는 다음 코드를 통해 알 수 있다.
: 싱글톤에 Stateful Service 클래스를 만들고,

package hello.core.singleton;

public class StatefulService {
    private int price; // 상태를 유지하는 필드

    public void order(String name, int price){
        System.out.println("name = " + name + "price = " + price);
        this.price = price;
    }

    public int getPrice(){
        return price;
    }
}

: 이어서 테스트를 다음과 같이 만들었을 경우,


class StatefulServiceTest {
    @Test
    void statefulServiceSingleton(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        //Thread A 사용자 10000주문
        statefulService1.order("user A ", 10000);
        //Thread B 사용자 30000주문
        statefulService2.order("user B ", 30000);

        //Thread A : 사용자 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println(price);

        org.assertj.core.api.Assertions.assertThat(statefulService1.getPrice()).isEqualTo(30000);
    }

    static class TestConfig{
        @Bean
        public StatefulService statefulService(){
            return new StatefulService();
        }
    }


}

: 사용자 A 의 값을 물었을때 사용자 B의 값이 나오는걸 볼 수 있음.

결론 : 스프링은 항상 무상태로 설계 해야함

5. configuration과 싱글톤

: configuration은 싱글톤을 위하여 존재함
: 아래 코드를 보자


void configurationTest(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
        OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
        MemberRepository memberRepository = ac.getBean("memberRepsitory", MemberRepository.class);

        MemberRepository memberRepository1 = memberService.getMemberRepository();
        MemberRepository memberRepository2 = orderService.getMemberRepository();

        // 1 
        System.out.println("memberService -> memberRepository = " + memberRepository1);
        // 2
        System.out.println("orderService -> memberRepository = " + memberRepository2);
        // 3 
        System.out.println("memberRepository = " + memberRepository);
    }

: 이 코드에서 1 2 3은 모두 같은 결과값을 나타낸다.(memberRepository가 한번만 호출된다.)

6. @Configuration과 바이트코드 조작의 마법

: 위의 이유는 @Configuration 때문이다. 아래의 코드의 실행 결과를 살펴보자.

@Test
    void ConfigurationDeep(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig bean = ac.getBean(AppConfig.class);
        System.out.println("bean = " + bean.getClass());
    }


: 위와 같이 CGLIB가 붙어서 나오는 것을 확인할 수 있다.
: 스프링은 싱글톤 보장을 위해 CGLIB라는 바이트코드 조작 라이브러리를 사용한다.
: 이에 @Configuration을 적용한 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 이를 스프링 빈으로 등록한 것이다.
: 로직상으로 빈이 존재할 경우, 존재하는 빈을 반환하고 없을 경우 생성해서 스프링빈으로 등록하고 반환하는 코드가 동적으로 만들어지기 때문이다.

만약 @Configuration을 사용하지 않고 빈만 적용한다면 어떻게 될까?


: 위와 같이 그대로 AppConfig가 나오는 것을 볼 수 있다.

: 위에서 만든 void configurationTest() 코드를 돌려보면, 세개가 일치하지 않음을 확인 할 수 있다.

: @Autowired MemberRepository memberRepository;
: 위 문장은 동적으로 의존관계를 자동 주입 시켜준다.

6장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 컴포넌트 스캔

: 스프링 빈을 등록할 때, @bean을 통하여 등록 했지만, 그 방법을 쓰지 않고 설정정보 없이 자동으로 스프링 빈을 등록하는 것이 컴포넌트 스캔이다.
: 컴포넌트 스캔은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
: 이렇게 할 경우 AppConfig와 달리 의존관계 주입을 한다고 명시할 수 없음

@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
        // AppConfig내의 수동으로 등록하는 @configuration
        // annotation 에도 component가 들어가기 때문에 임의로 제거함.
        // 예제코드를 살리기 위함
)

2. @Autowired

: 의존관계를 자동으로 주입한다.
: 앞의 문제를 해결해주는 기능으로, 주로 컴포넌트 스캔과 같이 쓰임
: 생성자 위에 쓰임

ac.getBean(MemberRePository.class)// 와 같은 기능을 한다.

3. 컴포넌트의 탐색 위치와 기본 스캔 대상

: 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 좋다.
: 컴포넌트 스캔은 다음과 같은 내용도 대상에 포함한다.
: ex) @Component, @Controller, @Service, @Repository, @Configuration

basepackages = "hello.core.member"
//1. 탐색할 패키지의 시작 위치를 지정 이 패키지부터 하위 패키지로 찾아감
basePackageClasses 
// 2. 지정한 클래스의 패키지를 탐색 시작 위로 지정한다. 
// 3. 지정하지 않을 경우 : @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.  

4. 필터

: includeFilters / excludeFilters : 컴포넌트 스캔에서 추가 or 제외
: 아래와 같이 작동한다.

@ComponentScan(
            includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
    )

: FilterType은 5가지 옵션이 있다.

5. 중복 등록과 충돌

A. 자동 빈 등록 vs 자동 빈 등록

: 이름이 같을 경우 ConfilctingBeanDefinitionException오류 발생

B. 수동 vs 자동

: 수동 빈 등록이 우선권을 가진다. (수동빈이 자동 빈 오버라이딩)

7장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 다양한 의존관계 주입 방법

크게 4가지 방법이 있다.
A. 생성자 주입
B. 수정자 주입(setter)
C. 필드 주입
D. 일반 메소드 주입

A. 생성자 주입

: @Autowired
: 생성자 호출 시점에 딱 1번만 호출 되는 것이 보장된다.
: 주로 불변과 필수 의존관계에 쓰인다.
: 스프링 빈의 경우 생성자가 하나일 경우에는 자동적으로 @Autoqired가 적용된다.

B. 수정자 주입

: setter라 불리는 필드의 값을 변경하는 수정자 메소드를 통하여 의존관계를 주입하는 방법이다.
: 주로 선택과 변경 의존관계에 쓰인다.
: 자바빈 프로퍼티 규약의 수정자 메소드 방식을 사용하는 방법이다.
: 스프링 빈 의존관계 설정 - 완료 단계에서 주입함.

@Autowired(required = false //선택적임)
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    } 

C. 필드 주입

: 이름 그대로 필드에 바로 주입하는 방법이다.
: 간단하고 좋으나, 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있다.
: DI프레임워크가 있어야 함
: 다만, 애플리케이션의 실제 코드와 관련없는 테스트코드, 스프링 설정을 목적으로 하는 @Configuration과 같은 곳에서만 특별한 용도로 사용된다.

@Autowired private MemberRepository memberRepository

D. 일반 메서드 주입

: 아무 메소드에나 @Autowired 가능
: 한번에 여러 필드를 주입받는다.

@Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

2. 옵션처리

: 주입할 스프링 빈이 없어도 동작해야 할 때가 있는데, @Autowired만 사용하면 자동 주입 대상이 ㅇ벗을시 오류가 발생 할 수 있다.

자동주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
A. @Autowired(required = false) 자동 주입 대상이 없을 시 수정자 메서드가 호출이 되지 않는다.
B. org.springframework.lang.@NUllable 자동 주입 대상이 없을 시 null이 입력됨
C. Optional<> 자동 주입 대상이 없을 시 Optional.empty가 입력됨
package hello.core.autowired;


public class AutowiredTest {
    @Test
    void AutowiredOption(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);

    }
    static class TestBean{
        @Autowired(required = false)
        public void setNoBean1(Member noBean1){ // 스프링 컨테이너 관리에 없는걸 아무거나 집어 넣었을때
            System.out.println("no Bean1 = " + noBean1); // 호출이 안됨 메소드가
        }

        @Autowired
        public void setNoBean2(@Nullable Member noBean2){
            System.out.println("no Bean2 = " + noBean2); // no Bean2 = null
        }

        @Autowired
        public void setNoBean3(Optional<Member> noBean3){ // java 8
            System.out.println("no Bean3 = " + noBean3); // no Bean3 = Optional.empty
        }
    }
}

3. 생성자 주입을 선택해라

생성자 주입을 권장하는 이유 (주를 생성자로, 옵션을 수정자로 선택하라. 필드주입은 쓰지 마라)

A. 불변
: 대부분의 의존관계 주입은 변경할 필요가 없고, 하면 안된다.
: 수정자 주입 사용시 set Xxx메소드가 public으로 열림
: 누군가가 변경할 수 있는 메서드를 열어두는 것은 좋은 방법이 아니다.
: 생성할때 한번 호출되기 떄문에 불변하게 설계할 수 있다.

B. 누락
: 프레임 워크 없이 순수한 자바 코드를 테스트 할 경우에 빠르게 의존관계를 detect 할 수 있다.

C. final 키워드
: 생성자 주입관계를 써야 final 키워드를 쓸 수 있고, 이는 생성자에서만 값을 세팅할 수 있음을 의미
: 컴파일러가 값이 설정되지 않는 오류가 있을시에 알려준다.

public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
         //밑의 두 코드가 없으면 알려준다 
        //this.memberRepository = memberRepository;
        //this.discountPolicy = discountPolicy;
    }

4. 롬복과 최신 트렌드

: 롬복이란? 자동으로 getter와 setter를 만들어주는 것을 뜻한다.

// 롬복이란 이 코드를 
@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    //@Autowired // 하나만 있어서 생략
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
// 이것과 같이 바꾸는 것을 뜻한다. 
package hello.core;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class HelloLombok {
    private String name;
    private int age;

    public static void main(String[] args) {
        HelloLombok helloLombok = new HelloLombok();
        helloLombok.setName("gerrard");
        System.out.println("name = " + helloLombok);
    }
}

@RequiredArgsConstructor
: final 이 붙은 필드를 모아 자동으로 생성자를 만들어줌 

5. 조회 빈이 2개 이상 - 문제

: @Autowired는 타입으로 조회한다.
: 스프링 빈 조회와 유사하게, 빈이 2개 이상일 경우에 문제가 발생한다.
: 'NoUniqueBeanDefinitionException' 오류가 발생하며, 하나의 빈을 기대했는데 두개의 빈이 발견되었다고 알려준다.

6. 조회 빈이 2개 이상 - 해결 방법

1. @Autowired 필드 명 매칭

: @Autowired는 처음에 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.

2. @Qualifier 사용

: @Qualifier는 추가 구분자를 붙여주는 방법이고, 주입시 추가적인 방법을 제공한다.
: Qualifier끼리 매칭한다. 빈이름을 매칭 하고, 예외가 터진다.

3. @Primary

: 자주 사용하는 것.
: 우선순위를 지정하는데, @primary가 지정되어 있을 경우 우선권을 가진다.

7. 애노테이션 직접 만들기

: @Qualifier("mainDiscountPolicy") 이렇게 문자를 적으면 컴파일시 타입 체크가 안되기 때문에 Annotation을 만든다.

//Annotation을 만들고 
package hello.core.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
        ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

//다음과 같이 사용한다. 
@Autowired
    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

8. 조회할 빈이 모두 필요할 때, list , map

: 해당 타입의 스프링 빈이 다 필요한 경우가 있다. 이럴 경우 사용한다.

9. 자동, 수동의 올바른 실무 운영 기준

: 자동으로 웬만하게 쓰되, 직접 등록하는 기술 지원 객체는 수동 등록하자.
: 다형성을 활용하는 비즈니스 로직은 수동등록이 좋다.

8장

인프런 김영한님의 '스프링 핵심 원리 기본편' 강의를 요약정리한 내용입니다

1. 빈 생명주기 콜백 시작

: 여러 작업들에서, 객체의 초기화와 종료 작업이 필요하다.
: 스프링 빈은 객체 생성 -> 의존관계 주입 이라는 사이클을 가진다.
: 의존관계 주입 뒤에야, 필요한 데이터를 사용할 수 있는 준비가 완료된다.
: 이에 초기화 작업은 의존 관계 주입 후 호출해야한다.
: 스프링은 의존 관계 주입이 완료되는 시점을 알려주는 다양한 기능이 있고, 스프링 컨테이너 종료 전에는 소멸 콜백도 준다.

스프링 빈의 이벤트 라이프사이클

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백(빈 의존 관계 주입 완료) -> 사용 -> 소멸전 콜백(빈 소멸 직전)-> 스프링 종료

스프링은 아래의 2, 3,4 번을 통해서 빈 생명주기 콜백을 지원한다.

2. 인터페이스 InitializingBean, DisposableBean

2-1) implements InitializingBean

: 의존 관계 주입이 끝나면 호출하는 메소드 를 생성

public void afterPropertiesSet() throws Exception {
        
    }

2-2) implements DisposableBean

@Override
    public void destroy() throws Exception {
        disconnect();
    }

다만 이 방법에는 단점이 있는데,
1. 스프링 전용 인터페이스에 의존한다
2. 외부 라이브러리에 적용이 불가능하고, 메소드 이름 변경이 불가능.

이 때문에 지금은 거의 사용하지 않는다.

3. 빈 등록 초기화, 소멸 메소드 지정

: 자유롭게 메소드를 만들고, 빈에 아래와 같이 메소드를 저장.


@Bean(initMethod = "init",destroyMethod = "close")

장점
: 스프링 빈이 스프링 코드에 의존하지 않게 됨
: 코드가 아니라 설정 정보를 사용하기 때문에, 외부 라이브러리에도 초기화 종료 메소드를 적용할 수 있다.

  • destroymethod에는 특별한 기능이 있는데, 디폴트가 "(Inferred)"로 잡혀있다.
    : 이는 close shutdown등의 메소드를 자동으로 호출해준다.
    : 이에 종료 메소드는 따로 적어줄 필요가 없다.

4. Annotation @PostConstruct, @PreDestroy

: @PostConstruct, @PreDestroy 어노테이션을 활용한다.
: 가장 편리하게 초기화와 종료를 실행 할 수 있다.
: 다만 외부 라이브러리에는 적용하지 못하는 단점이 있다.
: 위의 경우에는 3번을 사용하면 된다.

9장

1. 빈 스코프란?

: 싱글톤 스코프에서는, 스프링 빈이 스프링 컨테이너가 시작할때 생성되고, 종료할때까지 유지된다. 스프링빈은 기본적으로 싱글톤 스코프로 생성된다.

  • 스코프란?
    : 빈이 존재할 수 있는 범위
  • 스코프의 종류
  1. 싱글톤
    : 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위
  2. 프로토타입
    : 프로토타입 빈의 생성과 의존관계 주입 초기화 까지하고 관리하지 않는 매우 짧은 범위의 스코프
  3. 웹 관련
    A. request : 웹 요청이 들어오고 나갈때까지
    B. session : 웹 세션의 생성과 종료
    C. application : 웹이 서블릿 컨텍스와 같은 범위로 유지

2. 프로토타입 스코프

: 싱글톤 스코프에서는, 스프링 컨테이너가 항상 같은 인스턴스의 스프링 빈을 반환한다.(5장의 내용)
: 프로토타입 스코프의 경우에는 항상 새로운 인스턴스를 생성해서 반환한다.
: 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화 까지만 처리한다.
: 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다. @predestory와 같은 종료 메소드는 호출되지 않는다.

  • import static = alt + enter

3. 싱글톤 빈과 사용시 문제점

: 같이 사용할 경우, 프로토타입 빈은 과거에 이미 주입이 끝난 빈이고(주입 시점에만 생산이 되고), 사용할 때마다 새로 생성이 되지 않는다.

4. Provider로 문제 해결

  1. 싱글톤 빈이 프로토타입을 사용할때마다 새로 요청하는 것이다.
    : 의존관계를 주입받는게 아니라 필요한 의존관계를 찾는데, 이것이 Dependency Lookup이다.
    : 다만 더욱 정확한 해결을 위해 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL정도의 기능만 제공하는 무언가가 있어야 한다.

  2. ObjectFactory, ObjectProvider

@Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;
  1. JSR-330 Provider
    : get()을 통하여 빈을 찾아 반환한다.

5. 웹 스코프

: 웹 환경에서만 동작하고, 스프링이 해당 스코프의 종료 시점까지 관리하여 종료 메소드가 호출되는 스코프이다.
: 요청에 맞춰 각각 다른 스프링 빈을 생성하여 전용 객체를 만들어 사용된다.

  • 웹 스코프의 종류
    1. request
    2. session
    3. application
    4. websocket

6. request 스코프 예제 만들기

  • request : 동시에 여러가지 HTTP 요청이 왔을 때, 로그가 남도록 활용하는 것.
    : request 스코프 빈이 생성될때에 주입을 해야함

7. 스코프와 Provider

위의 문제를 해결하기 위하여

1. provider를 활용한다.

: objectProvider를 활용하여 request생성까지 지연할 수 있다.

8. 스코프와 프록시

2. 프록시를 활용한다.

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

: 가짜 프록시 클래스를 만든 후 request와 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해둘 수 있다.
: 위의 코드보다 훨씬 간단하다.

0개의 댓글