[스프링 핵심 원리 - 기본편] 강의 내용 정리

HJ·2022년 12월 24일
0

김영한 님의 스프링 핵심 원리 - 기본편 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard


1. 객체 지향 원리

1-1. 다형성

  • 다형성을 이용하면 부품을 갈아 끼우듯이 컴포넌트를 쉽고 유연하게 변경할 수 있다

  • 부모(인터페이스)로 선언하고 구체화는 클래스의 생성자를 이용한다


1-2. DIP, OCP 위반

public class MemberService {

    private MemberRepository m = new MemoryMemberRepository();
}
  • 하지만 위처럼 작성하게 되면, 구현 객체 변경을 위해 클라이언트 코드를 변경해야하기 떄문에 OCP를 위반하게 된다

  • 또한 인터페이스에만 의존해야하는데 인터페이스에 의존하면서 동시에 구현 객체에도 의존하고 있기 때문에 DIP를 위반하게 된다


1-3. 해결 방안


public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

< AppConfig >

@Configuration
public class AppConfig {

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

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
  • 그러므로 구현 클래스를 작성할 때 인터페이스에만 의존하도록 변경하고, 의존 관계가 있는 객체는 생성자를 통해 외부에서 주입받아야 한다

  • AppConfig가 객체의 생성과 연결을 설정하며 프로그램의 제어 흐름을 관리하는데 설정 파일임을 알 수 있도록 @Configuration 을 붙인다

  • 이처럼 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 하고, 외부에서 구현 객체를 주입받는 것을 DI(의존관계 주입)라고 한다

  • MemberService도 다른 곳에서 의존하고 있을 수도 있기 때문에 MemberService 인터페이스를 통해 구현 객체(MemberServiceImpl)가 생성될 수 있도록 한다




2. 싱글톤

  • 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리

  • 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태(stateless)로 설계해야 한다

  • 클라이언트에 의존적인 필드나, 클라이언트가 값을 변경할 수 있는 필드가 존재하면 안된다

  • @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다 ( 객체가 여러 개 생성된다 )

  • 설정 정보에 @Configuration를 붙여줘야 바이트코드를 조작하는 CGLIB 기술을 사용해 싱글톤을 보장할 수 있다




3. 스프링 컨테이너

3-1. 스프링 컨테이너 생성

public class MemberApp {
    public static void main(String[] args) {
        
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
    }
}
  • AnnotationConfigAPplicationContext 을 통해 스프링 컨테이너를 생성하였다

  • 생성 시 설정 정보인 AppConfig 파일을 넘겨주었다

  • 설정 파일에 명시된 빈들이 등록되면 getBean() 메서드를 통해 빈을 꺼낼 수 있다


3-2. BeanFactory & ApplicationContext

  • BeanFactory : 스프링 컨테이너의 최상위 인터페이스 ( 빈을 관리하고 조회하는 기능을 가짐 )

  • ApplicationContext : BeanFactory 의 기능을 상속받아 제공

  • AnnotationConfigApplicationContext : ApplicationContext 의 구현 클래스


3-3. 다양한 형식의 설정 정보

  • ApplicationContext 는 스프링 컨테이너를 생성할 때 다양한 형식의 설정 정보를 받을 수 있다

    • 어노테이션 기반 자바 코드

    • xml 문서 기반

    • 임의로 구현한 설정 정보

  • 3-1 번에서 스프링 컨테이너를 생성할 때 AppConfig 를 넘겨주었는데 이는 어노테이션 기반 자바 코드이기 때문에 AnnotationConfigApplicationContext 클래스를 사용했다

  • xml 은 GenericXmlApplicationContext




4. 스프링 빈

4-1. 스프링 빈 설정 메타 정보 ( BeanDefinition )

  • 3-3에서 설명한 것처럼 스프링이 다양한 형식의 설정 정보 ( 자바 코드, xml )을 받아들일 수 있는 이유 : BeanDefinition

  • BeanDefinition : 빈 설정 메타정보로 @Bean, <bean> 마다 하나씩 메타 정보가 생성된다

  • ApplicationContext ( 스프링 컨테이너 인터페이스 )는 BeanDefinition( 인터페이스 )에만 의존하고 이 메타 정보를 기반으로 스프링 빈을 생성하기 때문에 다양한 형식의 설정 정보를 받아들일 수 있는 것이다

  • 즉, 스프링 컨테이너 구현 객체가 설정 정보를 읽어서 BeanDefinition 을 생성하고 스프링은 이 BeanDefinition를 기반으로 스프링 빈을 생성한다


4-2. 스프링 빈 수동 설정

  • 설정 정보 ( 어노테이션 기반 자바 코드, xml 문서 )를 받아 스프링 컨테이너를 생성하는데 바로 이 설정 정보에 스프링 빈에 대한 정보가 있다

  • 강의에서는 AppConfig라는 설정 정보 클래스에서 객체 생성과 연결을 담당하는데 @Configuration@Bean을 이용해서 빈을 수동으로 등록하였다


4-3. 스프링 빈 자동 설정 ( 컴포넌트 스캔 )

  • 컴포넌트 스캔 : 설정 정보에 직접 빈을 등록하는 것이 아닌 자동으로 스프링 빈을 등록하는 방식

  • 컴포넌트 스캔을 사용하려면 @ComponentScan 을 설정 정보에 붙여야 하는데 이는 @Component를 가진 클래스를 찾아서 자동으로 스프링 빈으로 등록해준다

  • @Controller, @Repository 등의 어노테이션이 붙은 클래스도 스프링 빈으로 등록하는데 그 이유는 위 어노테이션들이 @Component이 붙어있기 때문이다

  • 컴포넌트 스캔 사용 시, 의존관계 주입을 위해 생성자에 @Autowired 붙여주면 스프링이 타입에 맞는 객체를 자동으로 주입해준다


4-4. 스프링 빈 등록 및 조회

@Configuration
static class SameBeanConfig {

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

    @Bean
    public MemberRepository memberRepository1() {
        return new MemoryMemberRepository();
    }

    @Bean
    public MemberRepository memberRepository2() {
        return new MemoryMemberRepository();
    }
}
  • MemberService 가 스프링 빈으로 등록될 때 MemberService 가 빈의 타입이 되고, 메서드 이름인 memberService가 빈의 이름이 된다

  • MemberRepository 는 두 개 다 MemberRepository 로 타입은 동일하지만 빈의 이름이 memberRepository1, memberRepository2 로 다르다

  • 빈 이름 + 타입으로 조회 : getBean("memberService", MemberServiceImpl.class);

  • 타입으로 조회 : getBean(MemberRepository.class);

    • 위처럼 동일한 타입이 둘 이상인 경우, 타입으로 조회하면 오류가 발생한다



5. 의존관계 주입

5-1. 개념

  • @Autowired 를 통해 의존관계를 주입받는다

  • 일반적인 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다

  • 생성자 의존관계 주입 방법은 스프링 빈을 등록하면서 자동으로 의존관계까지 주입되는 방식이다

  • 생성자 주입 외에도 setter 주입, 필드 주입, 일반 메서드 주입이 있다


5-2. 의존관계 옵션처리

@Autowired(required = false)
public void setNoBean1(Member noBean1) { ... }

@Autowired
public void setNoBean2(@Nullable Member noBean2) { ... }

@Autowired
public void setNoBean3(Optional<Member> noBean3) { ... }
  • @Autowired에는 required 속성이 있는데 default가 true로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다

  • 오류가 발생하지 않도록 하기 위해 required 속성 이용, @Nullable 혹은 Optional<>를 사용하는 방법이 있다

  • @Nullable : 자동 주입할 대상이 없으면 null 이 입력된다

  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다




6. 의존관계 주입 오류

6-1. 설명

  • @Autowired는 타입에 맞는 객체를 자동으로 주입해주는데 이는 빈을 타입으로 조회한다는 의미이다

  • 타입으로 조회했을 때, 동일한 타입의 스프링 빈이 2개 이상이라면 NoUniqueBeanDefinitionException 오류가 발생한다

  • 즉, 동일한 타입이 2개 이상이라면 의존관계 자동 주입이 이루어지지 않는다


6-2. 해결 1 : 필드명 매칭

// 오류
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
}

// 해결
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = rateDiscountPolicy;
}
  • @Autowired 로 조회 시, 결과가 두 개 이상인 경우 필드 이름, 파라미터 이름으로 빈 이름을 추가로 매칭한다

  • 타입이 DiscountPolicy 인 FixDiscountPolicy , RateDiscountPolicy 가 둘 다 등록되어 있는 경우 첫 번째 코드는 오류가 발생

  • 두 번째 코드에서 필드명 매칭은 결과가 두 개 이상인 경우 빈 이름을 추가로 매칭하기 때문에 rateDisCountPolicy 를 찾아서 의존관계를 주입한다


6-2. 해결 2 : @Qualifier

// 스프링 빈 등록
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy { ... }

// 의존관계 주입
public OrderServiceImpl(MemberRepository memberRepository,
                            @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) { ... }
  • 스프링 빈 등록 & 의존관계 주입 시 @Qualifier 를 사용해서 추가 구분자를 붙여주는 방식

  • @Qualifier 로 주입할 때 @Qualifier("mainDiscountPolicy") 를 못찾으면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다


6-2. 해결 3 : @Primary

// 스프링 빈 등록
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy { ... }

// 의존관계 주입
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { ... }
  • 스프링 빈을 등록할 때 @Primary로 우선순위를 지정한다

  • 위의 방식처럼 따로 생성자 코드를 맞추지 않아도 @Primary 가 붙은 객체가 잘 주입된다




7. 빈 생명주기 콜백

  • 스프링 빈( 싱글톤 )의 이벤트 라이프사이클

    • 스프링 컨테이너 생성 ➜ 스프링 빈 생성 ➜ 의존관계 주입 ( setter, 필드 ) ➜ 초기화 콜백 ➜ 사용 ➜ 소멸 전 콜백 ➜ 스프링 종료
  • 스프링 빈의 초기화 작업을 수행하려면 스프링 빈이 생성되고, 의존관계 주입까지 끝난 후에 진행해야한다

  • 의존관계 주입이 끝난 시점을 알 수 있도록 스프링은 스프링 빈에게 초기화 콜백 메서드롤 통해 초기화 시점을 알려준다

  • 또한 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다

  • 스프링은 인터페이스, 메서드, 어노테이션 3가지 방법으로 초기화 콜백, 소멸 콜백을 지원한다

    • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출

    • 소멸 전 콜백: 빈이 소멸되기 직전에 호출

    • 가장 간단한 방법은 @PostConstruct, @PreDestroy 어노테이션을 사용하는 것이다




8. 빈 스코프

8-1. 싱글톤

  • 스프링 빈은 기본적으로 싱글톤 스코프로 생성
  • 스프링 컨테이너의 시작과 종료까지 유지되는 스코프

8-2. 프로토타입

  • 스프링 컨테이너가 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 스코프

  • 의존관계를 주입하고 초기화 메소드까지 호출하지만 @PreDestroy와 같은 종료 메서드가 호출되지 않는다

  • 요청을 할 때마다 새로운 프로토타입 빈이 생성되어 반환된다

  • 단> 의존관계 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 프로토타입 빈을 사용 할 때마다 새로 생성되는 것이 아니다

  • 지정방법 : @Scope("prototype")


8-3. 웹 관련 스코프

  • 스프링 웹과 관련된 기술이 들어가야 사용할 수 있는 스코프

    • spring-boot-starter-web 라이브러리를 추가해야한다
  • request

    • 웹 요청이 들어오고 나갈때 까지 유지되는 스코프

    • 요청이 들어올 때 생성, 응답이 나갈 때 destroy

    • 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성된다

  • application : 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프


8-4. request 스코프

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;    // 이 부분이 오류

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        ...
    }
}
  • @RequiredArgsConstructor가 있기 때문에 스프링 컨테이너가 생성될 때 MyLogger 객체 의존관계 주입이 자동으로 이루어진다

  • but> MyLogger 는 request 스코프이기 때문에 고객의 요청이 들어올 때까지 생성되지 않기 때문에 없는 객체를 주입하려고 해서 오류가 발생하한다

  • 이를 해결하기 위해 ObjectProvider 나 프록시를 이용할 수 있는데 이들의 핵심은 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다

0개의 댓글