본문 바로가기
프로그래밍/스프링 자바

[Spring] 스프링 개념 정리

by 커피는아아 2021. 6. 30.
반응형

Spring

11. 스프링은 무엇인가?

  • 스프링은 좋은 객체지향 프로그래밍을 할 수 있게 도와주는 프레임워크이다.

12. 프레임워크 vs 라이브러리

  • 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다.
    • JUnit 라이프 사이클 속에서 내코드를 작성
    • 어플리케이션의 flow를 누가 제어하는냐가 프레임워크인지 라이브러리인지 구분할 수 있다.
  • 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.
    • public static void main(String[] args) { new 함수 호출 등}

13. 스프링과 객체지향은 어떤 관계인가?

  • 스프링의 IOC, DI는 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
  • 스프링은 다음 기술로 다형성OCP,DIP를 가능하게 지원한다
    • DI(Dependency Injection): 의존관계, 의존성 주입
    • DI 컨테이너 제공
    • 결과 : 클라이언트 코드의 변경 없이 기능 확장 -> 쉽게 부품을 교체하듯이 개발
  • 위 기술을 활용하여 클라이언트의 코드 변경 없이 기능 확장할 수 있다
  • 쉽게 부품을 교체하듯이 개발할 수 있다.

14. 스프링은 왜 만들어졌을까?

  • 좋은 객체 지향 개발을 하려고 순수한 자바로 OCP DIP 원칙들을 지키면서 개발을 하기는 너무 할 일이 많다. 그래서 스프링프레임워크 (DI 컨테이너)를 만들게 된다.

15. 스프링의 특징은 무엇인가?

16. IOC란 무엇인가?

  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라 한다.

17. DI란 무엇인가?

  • 애플리케이션 실행 시점(런타임) 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것의존관계 주입이라 한다.

18. IOC 컨테이너 DI컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC컨테이너 또는 DI 컨테이너라 한다.
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
  • 스프링 컨테이너 (ApplicationContext)가 DI 컨테이너의 역할을 한다.

19. 스프링 컨테이너란 무엇인가?

  • ApplicationContext(인터페이스) 를 스프링 컨테이너라 한다.
    • 스프링컨테이너를 BeanFactory, ApplicationContext로 구분해서 이야기 하지만 BeanFactory를 직접 사용하는 경우는 BeanFactory(인터페이스)를 상속받고, 그외 여러가지 인터페이스(메세지소스, 환경변수)를 상속 받는다.
  • 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이 너를 통해서 사용한다.
  • 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
  • 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
  • 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService )
  • 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너 를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드 를 사용해서 찾을 수 있다.
  • 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다.
  • 코드가 약간 더 복잡해진 것 같은데, 스프링 컨테이너를 사용하면 어떤 장점이 있을까?

20. 스프링 빈이란 무엇인가?

  • 스프링 컨테이너에서 관리하는 객체를 스프링 빈이라고 한다.
  • 스프링 빈 조회시 상속관계에 주의한다.
    • 부모 타입으로 조회하면, 자식 타입도 함께 조회된다.
    • 그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
    스크린샷 2021-06-29 오후 8 49 28

21. 스프링 컨테이너의 생성

  • 스프링 컨테이너 생성
    • new AnnotationConfigApplicationContext(AppConfig.class)
    • 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 여기서는 AppConfig.class 를 구성 정보로 지정했다.

스크린샷 2021-06-29 오후 8 10 26

  • 스프링 빈 등록
    • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
    • 빈이름은 항상 중복되지 않아야 한다.
    • @Bean은 메소드 이름을 빈이름으로 사용한다.

스크린샷 2021-06-29 오후 8 11 50

  • 스프링 의존관계 주입
    • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
    • 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이 차이는 뒤에 싱글톤 컨테이너에서 설명한다.

스크린샷 2021-06-29 오후 8 13 12

다만 자바 코드로 스프링 빈(@Bean) 을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리

22. 싱글톤 컨테이너

  • 스프링은 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
    지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
  • 스프링컨테이너는 등록된 빈 객체들을 싱글톤으로 관리한다.
  • 싱글톤 패턴
    • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴

싱글톤 방식의 주의점

  • 스프링빈은 무상태로 설계해야한다.
  • @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.
    memberRepository() 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다
  • 스프링 설정 정보는 항상 @Configuration 을 사용하자.

23. 컴포턴트 스캔

컴포넌트 스캔과 의존관계 자동 주입

  • 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 등을 통해서 설정 정보에 직 접 등록할 스프링 빈을 나열했다.
  • 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공
    • @ComponentScan -> @Component로 등록된 클래스들을 찾아 자동으로 빈으로 등록해준다.
  • 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다
    • Component로 등록 된 Bean들의 의존관계를 주입해 줘야하기 때문에 Autowired로 주입해 준다.
  • @Component 빈 이름은 class이름을 사용한다 (앞 글자만 소문자)
  • @Bean은 메소드 이름을 빈이름으로 사용한다.

스크린샷 2021-06-30 오전 11 03 57

  • @Autowired (의존관계 자동 주입)

스크린샷 2021-06-30 오전 11 04 10

  • 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
  • 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.
    • 타입이 같은 빈이 여러개라면 에러가 날 것이다 ex) DiscountPolicy <- rate, fixed

컴포넌트 스캔 기본 대상

  • @Component : 컴포넌트 스캔에서 사용
  • @Controlller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용
    • 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

24. 의존관계 자동 주입

의존관계 주입 방법

  1. 생성자 주입
  2. 수정자 주입(Setter 주입)
  3. 필드 주입
  4. 일반 메서드 주입

생성자 주입

  • 생성자를 통해서 의존 관계를 주입 받는 방법이다.
  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용
  • 생성자가 딱 1개만 있으면 @Autowired를 생략 해도 자동 주입 된다. 물론 스프링 빈에만 해당한다.
    • 빈 등록시점에 생성자를 호출하기 때문에 빈등록과 호출이 동시에 일어난다.
  @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;
  }

수정자 주입

  • setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다. 특징
  • 선택 변경 가능성이 있는 의존관계에 사용
    • 참고: @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.
    • 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.
  • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.
    • 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서 드를 통해서 값을 읽거나 수정하는 규칙
@Component
    public class OrderServiceImpl implements OrderService {
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
        @Autowired
        public void setMemberRepository(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        @Autowired
        public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
        }
    }

자바빈 프로퍼티 규약 예시
class Data {
      private int age;
      public void setAge(int age) {
        this.age = age;
      }
      public int getAge() {
        return age;

      }
}

필드 주입 (안티 패턴)

  • 이름 그대로 필드에 바로 주입하는 방법이다.
  • 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명 적인 단점이 있다.
  • DI 프레임워크가 없으면 아무것도 할 수 없다.
    • 순수자바 코드로 하는 테스트를 못함.
  • 사용처 (사용하지 말자)
    • 애플리케이션의 실제 코드와 관계 없는 테스트 코드 (SpringbootTest)
    • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용 (수동으로 @bean등록할 때?)
@Component
    public class OrderServiceImpl implements OrderService {
        @Autowired private MemberRepository memberRepository;
        @Autowired private DiscountPolicy discountPolicy;
    }

참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스 프링 컨테이너를 테스트에 통합한 경우에만 가능하다.

일반 메서드 주입

  • 일반 메서드를 통해서 주입 받을 수 있다
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다. (Setter 주입이랑 유사하며, 다른 방식으로 사용하면 되기 때문)
    @Component
    public class OrderServiceImpl implements OrderService {
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;

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

참고: 어쩌면 당연한 이야기이지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동 작한다. 스프링 빈이 아닌 Member 같은 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.

옵션 처리

  • 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
  • 그런데 @Autowired 만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없 으면 오류가 발생한다.
  • 자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
    • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
    • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
    • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

결론: 생성자 주입을 사용해라

생성자 주입을 선택해라!

  • 과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.
  • 생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다

불변

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
  • 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
  • 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다. 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

누락

  • 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에 다음과 같이 수정자 의존관계인 경우
    public class OrderServiceImpl implements OrderService {
        private MemberRepository memberRepository;
                private DiscountPolicy discountPolicy;
      @Autowired
      public void setMemberRepository(MemberRepository memberRepository) {
          this.memberRepository = memberRepository;
      }
      @Autowired
      public void setDiscountPolicy(DiscountPolicy discountPolicy) {
          this.discountPolicy = discountPolicy;
      }
    }
  • @Autowired 가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만, 지금은 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다. 이렇게 테스트를 수행하면 실행은 된다.
  @Test
  void createOrder() {
      OrderServiceImpl orderService = new OrderServiceImpl();
      orderService.createOrder(1L, "itemA", 10000);
  }
  • 그런데 막상 실행 결과는 NPE(Null Point Exception)이 발생하는데, memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.
  • 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
  @Test
  void createOrder() {
    OrderServiceImpl orderService = new OrderServiceImpl(); () <- 오류가 발생하는 부분, 생성자를 주입하라
    orderService.createOrder(1L, "itemA", 10000);
  }

final 키워드

  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되 지 않는 오류를 컴파일 시점에 막아준다.
  • 참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
    @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;
             }
        }
  • 자바는 컴파일 시 점에 다음 오류를 발생시킨다.
  java: variable discountPolicy might not have been initialized

롬복 라이브러리 적용

  • 최근에는 생성자를 딱 1개 두고, @Autowired 를 생략하는 방법을 주로 사용한다.
  • 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.

25. 빈 생명주기 콜백

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

  • 스프링컨테이너생성 -> 스프링빈생성 -> 의존관계주입 -> 초기화콜백 -> 사용 -> 소멸전콜백 -> 스프링 종료
  • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
  • 소멸전 콜백: 빈이 소멸되기 직전에 호출
  • @PostConstruct(생성 전), @PreDestory(소멸전) 애노테이션 특징
    • 최신 스프링에서 가장 권장하는 방법이다. 애노테이션 하나만 붙이면 되므로 매우 편리하다.
    • 패키지를 잘 보면 javax.annotation.PostConstruct 이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.
    • 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
    • 컴포넌트 스캔과 잘 어울린다.
    • 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 initMethod , destroyMethod 기능을 사용하자.

참고:

  • 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애 플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다
  • 객체의 생성과 초기화를 분리하자
    • 생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다.

참조

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

계속 업데이트 예정