SEB[Spring Container]

Jogi's 코딩 일기장·2021년 8월 12일
0


저번에는 Spring Boot 기초에 대해서 알아봤고, 이번 유닛에서는 Spring Container에 대해서 학습을 했다. 그 중에서도 DI(Dependency Injection)이라는 의존성 주입이라는 개념에 집중해서 학습했다. 학습했던 것들의 정리와 느낀점들을 다시 풀어보겠다.

Dependency Injection(DI) : 의존성 주입

객체 지향 설계

  • 우리가 객체지향 설계를 하는 이유는 기능을 분리하고 클래스의 독립성을 유지하여 단일 기능을 수정하는데 불필요한 클래스를 수정하는 일이 없도록 하는 것이 목적이다.

SOLID 원칙

  1. SRP : 단일 책임 원칙 (Single responsibility principle)
    한 클래스는 하나의 책임만 가져야 한다.

  2. OCP : 개방-폐쇄 원칙 (Open/closed principle)
    소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야 한다.

  3. LSP : 리스코프 치환 원칙 (Liskov substitution principle)
    프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

  4. ISP : 인터페이스 분리 원칙 (Interface segregation principle)
    특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

  5. DIP : 의존관계 역전 원칙 (Dependency inversion principle)
    프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안 된다.

  • 좋은 객체지향이란, 클래스의 기능이 하나로 명확하고 클래스를 사용하는 누구나 코드를 보지 않고도 기능을 사용할 수 있어야 한다.

Controller 기능 분산

스프링의 파일 구조

1) Controller

Controller는 API 요청을 매핑하는 클래스다. @Controller, @GetMapping 같은 어노테이션을 사용하는 클래스다. Controller 클래스의 단일 책임은 API 요청에 매핑하고 결과를 리턴하는 것이다. 이 과정에서 필요한 로직은 Service 클래스를 사용한다.

2) Service

Service는 비즈니스 기능을 수행하는 클래니다. 만약 회원가입 기능을 구현한다면 Controller 클래스를 통해 전달된 데이터의 유효성을 검증하고 중복회원 체크 등의 기능을 수행할 수 있다. 아래 Repository 클래스를 사용하여 데이터를 전달받고 로직 수행 후 결과를 Controller 클래스에 전달한다.

3) Repository

Repository는 DB에 원하는 데이터를 가지고 오고 저장하는 기능을 수행하는 클래스다. JPA 코드가 Repository에 작성이 되고 주로 SQL 관련 코드가 작성된다. Service에서 메소드를 호출하여 사용된다.

4) Domain

Domain은 앞서 배운 DTO, VO, Entity 코드를 모아두는 패키지이다.

5) Configuration

Configuration은 추후 배울 의존성 주입을 위한 스프링 어노테이션인 @Configuration을 사용하는 클래스다. 해당 클래스에서 스프링 컨테이너에 클래스를 등록한다.

Controller는 Service를 참조하고 Service는 Repository를 참조한다. 이 같은 참조 관계는 스프링 개발에서 매우 많이 등장하는 패턴이다. 그래서 위의 예시와 같이 스프링에서 기본으로 권장하는 패턴으로 만들고 이 참조 관계를 자동으로 정의해주기 위해서 @Service, @Repository를 만들었다.

싱글톤 패턴

new 키워드를 통한 참조가 일어난다면, API 요청이 올때마다 인스턴스를 새로 생성하게 된다. 객체지향의 특성상 객체를 쓰려면 생성을 해야 하기 때문에 요청마다 메모리에 새로운 인스턴스가 생성된다. 이를 해결하기 위해서는 인스턴스를 재사용한다. 그래서 new 키워드로 사용하는 것이 아닌 우리가 접근 가능한 곳에 미리 클래스를 인스턴스화 시켜두고 해당 인스턴스를 사용한다. 이것이 싱글톤 패턴이다.

싱글톤 패턴과 스프링

서버 자원을 안정적으로 사용하기 위해 싱글 톤을 적용하였지만, 싱글 톤 패턴의 남발은 여러 문제가 발생한다.

  • 싱글 톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 때, 다른 클래스의 인스턴스 간의 결합도가 높아져 "개방-폐쇄 원칙"을 위배하게 된다.

  • 멀티스레드 환경에서 동기화 처리를 하지 않으면 인스턴스가 두 개가 생성되는 이슈가 발생할 수 있다.

스프링에서는 스프링 컨테이너를 만들어 모든 클래스를 스프링 컨테이너에서 관리하게 했으며, 스프링 컨테이너를 사용하면 스프링 프레임워크가 싱글 톤도 적용해주고 객체에 인스턴스를 전달해준다.

스프링 컨테이너

  • 의존 관계
    자바 애플리케이션에서 행동 방식을 제공하기 위해 객체가 다른 객체와 상호작용하는 경우, 예를 들어 A class가 B class의 메소드를 사용한다면, A class는 B class에 의존 관계를 맺는다고 할 수 있다. 이는 생성자를 통한 참조 관계를 포함하는 개념이다.

    우리는 스프링 컨테이너를 사용해 의존 관계를 설정할 수 있다. 이를 통해서 개발의 생산성과 안정성을 보장받을 수 있다.

스프링 컨테이너의 몇 가지 개념

  • Bean
    빈은 스프링 컨테이너에서 생성하고 관리하는 애플리케이션 객체를 뜻한다. @Configuration 클래스 안에 구현한 메소드에서 @Bean 어노테이션을 붙여 특정 객체를 스프링 컨테이너에 등록할 수 있다.

  • IoC (Inversion of Control : 제어의 역전)
    제어 권한이 누구에게 있는지를 나타내는 용어다.
    기존의 객체의 관리(생성과 의존 관계 설정)는 개발자가 직접 제어했으나, 스프링을 사용하면 객체 제어 권한을 스프링 컨테이너가 가지게 되어 어노테이션에 따라 자동으로 객체를 관리해주기 때문에 제어 주체가 역전됐다고 표현한다.

  • DI (Dependency Injection : 의존성 주입)
    스프링 컨테이너를 통해 의존 관계를 설정하는 방식을 뜻한다. 기존의 new 키워드나 직접 싱글톤 패턴을 사용하지 않고 컨테이너를 통해 Bean 객체를 주입 받아 사용한다. 생성자세터를 통해 의존 관계를 주입한다.

Bean 객체

Bean 객체 설정

  • 우리가 만든 클래스를 Bean으로 만들어 주기 위해서는 스프링 컨테이너에서 어떤 클래스를 Bean으로 관리해야 하는지 알려줘야 한다. Bean 객체로 설정하는 방법은 2가지가 존재한다.

    • @Configuration 클래스의 @Bean 메소드를 통해 인스턴스를 등록하는 방법

      //Controller class
      @RestController
      class UserController{
      	private UserService userSerivce; 
      	@Autowired
      	public UserContorller(UserService userService){
      		this.userService = userService;
              }
      }
      
      //Service class
      public class UserService{
      	private UserRepository userRepository;
      	public UserService(UserRepository userRepository){
      		this.userRepository = userRepository;
      	}
      }
      
      //Service class
      public class UserService{
      	private UserRepository userRepository;
      	public UserService(UserRepository userRepository){
      		this.userRepository = userRepository;
      	}
      }
      //Repository class
      public class UserRepository{
      	...
      }
      //Configuration
      @Configuration
      public class SpringConfig {
      	@Bean 
      	public UserService userService(){
      		return new UserService(userRepository());
      	}
      	@Bean 
      	public UserRepository userRepository(){
      		return new UserRepository();
      	}
      }
    • @Component를 클래스에 직접 명시해서 설정하는 방법이 있다. (컴포넌트 스캔을 이용한다.) @Autowired를 사용해서 주입받는다.

      //Controller class
      @RestController
      class UserController{
      	private UserService userSerivce; 
      	@Autowired
      	public UserContorller(UserService userService){
      		this.userService = userService();
      	}	
      }
      //Service class
      @Service
      public class UserService{
      	private UserRepository userRepository;
      	@Autowired
      	public UserService(UserRepository userRepository){
      		this.userRepository = userRepository;
      	} 	
      }
      //Repository class
      @Repository
      public class UserRepository{
      		//...
      }

@RestController 또한 스프링 빈에 등록해주고 있다. 내부 구현을 보면 @Component가 작성되어 있어 @RestController를 사용하면 스프링 컨테이너의 등록과 더불어 API 요청을 위한 부가 기능을 손쉽게 사용할 수 있다.

@Service@Repository 또한 마찬가지로 @Component를 포함하고 있어서 스프링 빈으로 등록할 수 있고 각 기능에 맞는 부가 기능을 쉽게 얻을 수 있다.

컴포넌트 스캔

컴포넌트 스캔은 @Component가 선언된 클래스 파일을 전부 찾아 스프링 빈으로 설정해주는 기능이다.

  • 생성자 주입
    생성자 인자를 통해 Bean 객체를 받아오는 방식이다. 보통 private final을 이용해서 사용하는데 이 이유는 전역 변수로 사용하기 위해 이와 같이 선언한다.

    생성자 주입의 경우 @Autowired를 생략해도 되지만 생성자가 2개 이상일 때는 명시해주어야 한다.

    final 키워드는 생성자 주입에서만 사용할 수 있다. 생성자 주입은 스프링에서 권장하는 DI 방법이며, 생성자 주입을 권장 하는 이유는 다음과 같다.

    • 객체 불변성 확보
    • 순환 참조 방지
    • final 키워드 사용 가능
    • 테스트 코드 작성에 용이
  • 세터 주입
    세터 주입은 전역 변수에 setter 메소드의 인자로 Bean 객체를 받아오는 방식이다. 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다.

  • 필드 주입
    변수에 바로 @Autowired 키워드를 사용해 Bean 객체를 받아오는 방식이다. 테스트 코드를 작성하는 경우에만 제한적으로 사용하는 것을 권장한다.

  • 컴포넌트 스캔 범위
    Application 클래스가 있는 폴더부터 그 하위 폴더까지 컴포넌트 스캔을 진행한다.

느낀점

저번 유닛에서는 API 요청에 대한 매핑에 대한 구현만을 했다면 지금은 객체지향적으로 더 생각을 해서 의존성도 주입하는 방법과 함께 실습도 진행했었다. 실습을 하면서 기능과 목적에 맞게 클래스를 나누고 그에 따른 스프링 어노테이션을 같이 사용하는 것을 실습을 했는데 생각보다 구현에 있어서는 어려움이 없었으며 오히려 객체 지향적으로 생각을 해서 기획을 하고 어떻게 나누어야 하는지에 대한 생각을 할 수 있었던 유닛이었다. 앞으로 스프링에 대해 더 많이 배울텐데 이 점들을 잘 참고하여 나중에 잘 기억해서 써먹을 수 있도록 하자. 습관화를 하는 것이 좋을 것 같다.

Reference

  • 코드스테이츠 강의자료
profile
프로그래머로서의 한걸음

0개의 댓글