느리더라도 꾸준히

회고록(28)-스프링 컨테이너와 DI 본문

(CodeStates)Daily memoir

회고록(28)-스프링 컨테이너와 DI

테디규 2022. 12. 11. 20:10

1. 데일리 일기

이런 생각이 들었다. 전부다 레퍼런스에 있는 내용인데 왜 블로깅을 하는 것일까? 내가 세세하게 아무리 적어도 레퍼런스의 지식은 따라 갈수 없을 것이다.

다만 레퍼런스에서는 지식만 알려주고 있을뿐, 지혜를 제공하지 않는다. 지헤란 "이치를 빨리 꺠우치고, 사물을 정확하게 처리하는 정신적 능력" 을 말한다. 예를 들어 스프링이란 큰 레퍼런스 지식은 내가 다 담을 수 없다. 설명해 보라고 하면 그날 공부한 단편적인 부분만 설명 할 수 있을 것이다.

그러나 지혜는 다르다. 스프링의 핵심 원리 및 철학등을 깨우치게 되면, 정확하게 처리해야 할 부분은 레퍼런스를 참고하면 된다. 이전의 나는 래퍼런스에 있는 지식들을 어떻게든 정리해서 내 노션에 전부 적어두려고 했었다. 이는 불가능하며, 비효율적이다.
해당 래퍼런스는 내가 모른다고 느낄때 다시 읽으면 된다. 오히려 이치와 핵심부분을 캐치하고 적어둠으로써 더 빨리 읽을 수 있도록 도와만 주면 된다.

정리글 이든, 블로그 글이든 다른 사람에게 설명할 수 있을 정도로 이치와 핵심을 파악한 지혜를 제공하려는 연습을 해보자.

그리고 정리한 글 자체가 중요한게 아니라 내가 성장하는게 중요한것이다. 결과보다는 과정을 즐기려고 다시 노력해보자.!!!

2. 오늘 배운 내용

Spring 컨테이너(Container)와 빈(Bean)의 의미를 이해할 수 있다

Spring 컨테이너란, Spring 컨테이너로 DI를 하는 이유

컨테이너란 왜 탄생했을까?

컨테이너란 객체의 생명주기를 관리(생성, 조회, 삭제등)해주는 컴포넌트(기능 실행의 단위)를 말합니다. DI를 이용하면 객체의 생명주기를 관리해주는 컨테이너를 생성할 수 있습니다. 컨테이너를 통해 우리는 비즈니스 로직 실행과 객체를 생성,삭제와 같이 관리해도록 관심사를 분리할 수 있게 되었습니다. 이를 통해 객체지향의 특징인 SRP를 만족시키며, 변화와 확장에도 유연한 코드를 작성 할 수 있게 되었습니다.

  • 인터페이스등을 이용하면 OCP, DIP를 만족하도록 변화와 확장에 유연한 코드 작성이 가능하다.

스프링 컨테이너란 왜 탄생했을까?

사실 순수 Java코드로도 DI를 통해 객체간의 관계를 설정해줄 수 있습니다. 앱내에서 사용하는 객체들의 생명주기를 관리하는 AppConfig.class를 만들어 비즈니스 로직 실행과 설정부분으로 관심사를 분리하면 객체지향적인 코드도 작성할 수 있다. 그럼에도 불구하고 스프링 컨테이너를 사용하는 이유는 무엇일까요?

스프링 컨테이너도 Java로 만든 DI 컨테이너와 마찬가지로, 객체의 생명주기를 외부(스프링 컨테이너)에서 관리 할 수 있도록 해줍니다. 그러므로 DI를 통해 낮은 결합도, 높은 캡슐화를 가진 객체지향적인 코드를 작성 할 수 있습니다.

위 Java 기반 컨테이너는 Java 코드를 기반으로만 IOC를 동작 시킬 수 있었지만, 사실 스프링 컨테이너는 Java뿐만 아니라, xml 파일, 또는 다른 파일들의 설정정보를 읽어와 객체간의 생명주기 관리 및 의존관계를 설정해 줄수 있습니다.

어떻게 여러 설정파일들을 가지고 컨테이너를 작성 할 수 있을까?

바로 BeanFactory라는 인터페이스 때문입니다. 해당 인터페이스는 설정 메타데이터(여러가지 설정정보)들이 오면 해당 정보를 참고하여 Bean(스프링 컨테이너에서 사용하는 객체)의 생성과 의존관계등을 설정해줍니다. 해당 인터페이스는 ApplicationContext라는 하위 인터페이스를 가지고 있는데, 해당 인터페이스는 조금 더 많은 부가기능들을 가지고 있기에 주로 이 인터페이스를 사용합니다.

이러한 인터페이스의 구현체로 애노테이션 기반으로 Java파일에서 설정 파일을 가져오도록 하거나 , xml 파일의 설정정보를 읽어 가져오도록 하여 스프링 컨테이너를 생성할 수 있습니다. 바로 다형성을 사용하는 모습을 보여주는 것이죠. 그래서 위의 둘 인터페이스를 스프링 컨테이너의 종류로 보기도 합니다.

빈이란?

Bean은 스프링 컨테이너 안에 등록된 객체입니다.빈은 이전에 말한 설정 정보 클래스(AppConfig.class)에서 @Bean을 통해 만 들 수 있습니다. 이러한 빈은 설정정보 안에서 메서드를 통해 생성한 객체들을 빈 객체로 생성합니다. 이전에 설정 정보는 클래스 뿐만 아니라, xml이나 다른 파일들도 가능하다는 점을 기억하시나요? 이런 파일들이 오면 어떻게 빈을 생성할 까요?

빈은 어떻게 다형적으로 만들어지는 것일까?

스프링 컨테이너는 위의 설정정보들을 가지고 BeanDefinition(설정 메타데이터) 인터페이스의 구현체를 만듭니다. 스프링 컨테이너는 이 설정 메타데이터를 이용하여 각각의 Bean들을 생성하는 것이죠. 이 설정 메타데이터에는 설정할 빈의 이름, 빈의 생명주기, 의존관계를 어떻게 생성할것인지 등 여러 정보를 담고 있습니다.

스프링 컨테이너는 이렇게 생성된 Bean들을 이용하여 객체의 생명주기를 관리하고, POJO로 작성한 비즈니스 로직과 함께 완전히 사용할 준비가 된 APP을 생성할 수 있습니다.

빈 스코프(Bean Scope)의 의미를 이해할 수 있다.

아니 자바 객체들을 그냥 사용하면 되지 왜 굳이 컨테이너에서는 Bean이라는 새로운 개념을 만들어서 사용할까요? 바로 Bean Scope라는 개념이 존재하기 때문입니다. 스프링 컨테이너의 존재하는 빈은 BeanDefinition의 구현체로 Bean의 Scope에 해당하는 설정정보도 같이 담게 됩니다.

scope 값을 넣으면 어떻게 되는거죠?

스프링에서는 Bean을 기본적으로 싱글톤scope로 관리하고 있습니다. 이 외에도 프로토타입, request scope, session scope, application scope 등 여러 scope를 가지고 있습니다. 쉽게 말해 어느 시점까지 해당 객체를 존재할 수 있도록 하겠는가를 정할 수가 있습니다.

  • ex) 클라이언트의 요청시부터 응답까지만 살려놓겠다. , 쿠키 세션동안만 살려놓겠다 등등

그중 기본적으로 제공하는 싱글톤 scope는 Bean 객체를 단 하나의 인스턴스만 가지고 관리하게 됩니다. 사실 일반적으로 Java에서 사용하는 싱글톤 패턴들은 코드 작성이 길고, 구체 클래스에 의존하고, 인스턴스값을 건드리면 안돼기 떄문에 테스트하기 어렵다는 불편함이 있습니다. 또한 속성 공유라도 하는 날엔 모든 클래스 객체의 속성값이 바뀌므로 큰 문제가 발생 할 수 있습니다.

스프링의 싱글톤은 다르다?

스프링 컨테이너는 자체에서 싱글톤 패턴을 제공하므로 코드 작성을 할 필요가 없으며, 실제 객체가 아닌 컨테이너 객체(AnnotationConext의 구현체)에 의존하므로 DIP 도 만족 시킬수 있습니다. 또한 Bean을 사용할때 마다 새로 생성하도록 하는등의 무상태설계로 작성시 속성공유의 문제도 피할 수 있습니다.

어떻게 무상태로 설계하나요?

값을 getter(읽기)만 가능하도록 설정하며, 공유필드 대신 지역변수, 파라미터, ThreadLocal 변수를 사용하면 싱글톤 객체를 무상태로 설계할 수 있습니다.

Java 기반 컨테이너(Container) 설정에 대해 이해할 수 있다.

스프링 컨테이너는 여러 설정 파일들을 통해 컨테이너를 생성할수 있다고 했었습니다. 그중 가장 많이 사용하는 자바 애노테이션 기반의 컨테이너 생성에 대하여 알아봅시다.

컨테이너 생성 종류

  • @Configuration 과 @Bean 으로 생성
  • @ComponentScan과 @Component로 생성
  • JSR-330 메타데이터

왜 이렇게 구분하고 있을까?

Java 애노테이션 기반으로 스프링 컨테이너를 생성하는 방법은 위의 세가지가 존재합니다. @Component와 JSR의 경우 클래스레벨에서 Bean을 정의하는 방식으로 Bean을 등록합니다. 그러므로 일반적으로는 이 방식을 사용합니다.

@Configuration의 경우 설정정보클래스에 있는 @Bean이 적힌 메서드 레벨에서 빈을 등록하기 때문에, 외부 라이브러리의 기능을 사용하기 위해 직접 클래스를 변경할수 없는 외부객체를 Bean으로 등록할 때 사용됩니다. 또한 수동적으로 Bean을 등록하는 경우에 사용하기도 합니다.

Component 스캔에 대해 이해할 수 있다.

Java 애너테이션 기반으로 스프링 컨테이너를 작성하는 방법중 @Component방식은 설정 파일에 @ComponentScan을 입력함으로써 자동으로 Bean을 등록 할 수 있습니다.

컴포넌트 스캔은 아래의 아래의 애노테이션들을 모두 스캔합니다.(안에 @Component가 들어 있음)모든 클래스를 @Component로 생성하기 보다는 각 목적에 맞게 아래와 같은 내용으로 설정 하는 것이 좋습니다.

  • @Controller & @RESTController
  • @Service
  • @Repository
  • @Configuration

컴포넌트 스캔은 매개변수로 필터를 이용해 가져올/가져오지않을 컴포넌트를 설정할 수 있습니다. 또한 아래 나올 @Autowired로 Bean 객체를 주입할떄 Bean객체가 비어있는 경우, 어떻게 처리할 지에 대한 옵션 처리 기능도 가지고 있습니다.

그리고 @컴포넌트를 통해 생성된 Bean들 사이의 의존관계를 나타내기 위해서 생성자에 @Autowired 명령어를 붙일 수 있습니다.

  • 필드 주입
  • 수정자 주입
  • 생성자 주입
  • 메서드 주입

셋중 대부분은 생성자 주입으로 처리하며, 수정이 필요한 부분만 수정자 주입을 사용합니다. (나머지는 사용 거의 X, 아니 안하는게 좋음) 생성자 주입을 사용하는 이유는 아래와 같습니다.

  • 불변
  • 누락방지
  • final 키워드 사용 가능
  • 순환 참조 방지

Spring DI(Dependency Injection)의 의미를 이해할 수 있다.

우리가 Java DI 컨테이너를 만들어서 사용할수 있음에도 Spring DI를 사용하는 이유가 무엇일까? 결국 위에서 말한 모든 스프링 컨테이너가 제공해주는 기능들을 이용하기 위해서다.

위의 기능들을 이용하면 객체들의 생명주기를 더욱 더 효율적으로 관리할 수 있으며, 의존관계를 설정해 줄수 있고 무엇보다 스프링 프레임워크가 여러 기능들을 동작시켜주면서 작성해야 하는 코드수가 매우 감소한다. 그러므로 개발 생산성을 높일 수 있다.

  • ApplicationContext interface를 통한 다형적 설정 파일을 통해 컨테이너 생성 가능
  • Bean을 통한 관리와 Bean Scope를 이용하여 객체의 생명주기(정확히는 Bean의 존재할수 있는 범위)를 제어 가능
    • 싱글톤 Scope를 통한 편리함
  • 애노테이션 기반으로 으로 스프링 컨테이너를 편리하게 생성하므로써, 개발 생산성을 높일 수 있음.

그리고 당연히 DI를 통한 IOC 를 구현해 냄으로써 Spring 컨테이너가 제어권을 가질 수 있게된다.

  • 객체들의 생명주기를 관리 할수 있게 된다.
  • 그러므로 관심사의 분리( 객체 관리, 서비스로직)가 일어난다.
  • 낮은 결합도, 높은 응집력을 가진다.
  • 변화와 확장에 유연한 객체지향적인 코드 작성이 가능해진다.

정리후 모르겠는 내용

Bean을 생성하고 POJO 비즈니스 로직 소스코드를 이용해서 , full figured Sysyem를 만든다고 한다. 이게 뭔지 모르겠다.? 앱으로써 사용한다는 의미인가?

Comments