[Spring Study] - Dependency Injection

uHan2·2021년 1월 2일
0

TIL.BackEnd

목록 보기
5/12
post-custom-banner

비록 시작은 코딩일기지만, 그 끝은 창대하게
어엿한 개발자 블로그로 성장할 수 있도록.


Spring Study

본 게시글은
최범균 저자의 스프링 5 프로그래밍 입문
다른 자료들을 참고하여 정리한
Spring Study Contents 입니다.


개요 :: OOP

OOP

  1. 캡슐화

    • 변수와 함수를 클래스로 묶는 것.
    • 무작정 묶는 것이 아닌 연관된 목적을 가지고 있는 변수와 함수별로 캡슐화.
  2. 상속

    • 자식 클래스에서 부모 클래스로부터 모든 자원을 물려 받는 것.
    • 하나의 부모 클래스가 여러 자식 클래스를 가지는 것은 가능하지만 하나의 자식 클래스가 여러 부모 클래스를 상속받을 수는 없다.
    • 비효율적인 코드 중복을 피할 수 있다.
    • 유지 보수의 편리성도 얻을 수 있다.
  3. 다형성

    • 부모 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 해준다.
    • 예시 :: 큰 개념 Animal Class or Interface 에서 작은 개념 Bird, Dog, Cat 등을 구현
    • OverLoading, OverRiding 두 가지를 활용해서 다형성을 구현한다.
    • OOP의

OOP :: Polymorphism

유연하고 변경이 용이하다?
세상을 역할구현으로 구분해보자

  • 운전자자동차

운전자 역할자동차 역할이 있을 때, 자동차에는 어떠한 자동차가 와도 운전자에 영향을 끼치지 않는다. 차가 바뀌어도 운전자는 그대로 운전할 수 있다.
운전자는 자동차 인터페이스만 다룰 줄 알면 된다.
즉, 클라이언트에 영향을 끼치지 않으면서 새로운 기능을 제공할 수 있다.

  • 로미오줄리엣 (공연)

로미오와 줄리엣 공연에서 로미오, 줄리엣 역할에는 배우 아무나 배역이 가능하다.
어떤 배우든지 그 배역을 맡을 수 있고, 다른 배우로 대체도 가능하다.

이 밖에도 키보드, 마우스 등 표준 인터페이스, 정렬 알고리즘 등이 있다.

이렇게 역할구현으로 구분하면 세상이 단순해지고 유연해지며 변경이 용이해진다.
그리고 다음과 같은 장점을 얻는다.

  • 클라이언트는 대상의 역할, 인터페이스만 알면된다.
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
  • 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

그렇다면 Java에서는?

  • 역할인터페이스
  • 구현인터페이스를 구현한 클래스, 구현 객체

객체를 설계할 때 역할구현을 명확하게 분리하자.
인터페이스(역할)을 먼저 부여하고, 그 다음 이를 수행하는 객체를 구현하자.

  • 다형성의 본질
    • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 가능
    • 다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야함
    • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있음.

인터페이스를 안정적으로 잘 설계하는 것이 굉장히 중요하다.


OOP :: SOLID

Robert C. Martin 이 정리한 5가지 원칙

  • SRP :: 단일 책임 원칙 (Single Responsibility Principle)

  • OCP :: 개방 - 폐쇄 원칙 (Open / Closed Principle)

  • LSP :: 리스코프 치환 원칙 (Liskov Substitution Principle)

  • ISP :: 인터페이스 분리 원칙 (Interface Segregation Principle)

  • DIP :: 의존관계 역전 원칙 (Dependency Inversion Principle)


SOLID :: SRP

  • Single Responsibility Principle

정의 : 한 클래스는 하나의 책임만 가져야 한다.

하지만, 여기서 하나의 책임 은 모호하기 때문에 변경이 있을 때 파급효과가 적다면
SRP를 잘 따른것이다.

이 책임을 적당히 잘 배분하는 것이 능력


SOLID :: OCP

  • Open / Closed Principle

정의 : 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

다형성을 활용
-> 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능 구현
하지만 다음과 같은 문제가 있다.

public class MemberService
{
    MemberRepository m = new MemoryMemberRepository(); //기존
    MemberRepository m = new JdbcMemberRepository(); // 변경
}

MemoryMemberRepository()에서 jdbcMemberRepository()로 변경하려면 코드를 변경해야 하는데 이러면 OCP를 지킬 수 없다.
이를 해결하기 위해서 별도의 조립자, 설정자가 필요하다.
그것이 바로 Spring에서 제공하는 Spring Container


SOLID :: LSP

  • Liskov Substitution Principle

정의 : 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서
하위 타입의 인스턴스로 바꿀 수 있어야 한다.

다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것.
다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요하다.

예)

  • 자동차 인터페이스가 있을 때 그 구현체의 엑셀은 느리더라도
    반드시 앞으로 가야하는 기능이어야 한다.

SOLID :: ISP

  • Interface Segregation Principle

정의 : 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

예)

  • 건담 인터페이스 -> 조종 인터페이스, 정비 인터페이스 로 분리
  • 사용자 클라이언트 -> 조종사 클라이언트, 정비사 클라이언트 로 분리

인터페이스가 명확해지고, 대체 가능성이 높아진다.


SOLID :: DIP

  • Dependency Inversion Principle

정의 : 구현 클래스에 의존하지 말고, 인터페이스에 의존하라

역할구현 중 역할에 의존하게 해야 한다.
객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하게 되면 변경이 아주 어려워 진다.

public class MemberService
{
    MemberRepository m = new MemoryMemberRepository(); //기존
    MemberRepository m = new JdbcMemberRepository(); // 변경
}

사실 앞서 예로 든 이 코드는 DIP어긋난다.
MemberServiceMemberRepository 에만 의존해야 하는데
MemoryMemberRepository(또는 JdbcMemberRepository)
에 까지 의존하기 때문이다.

객체 지향의 핵심은 다형성이다. 그런데 다형성 만으로는 OCPDIP를 지킬 수 없다.
무언가 더 필요하다. 그것이 바로 Spring 에서 제공하는 DI 이다.


Spring

Spring :: DI

Dependency Injection, 즉 의존성 주입.
참고로 IoC의 한 영역으로 마틴 파울러가 주장한 스프링에 사용할 새로운 개념.

  • 잠깐, 의존이란?
    쉽게 말해 한 클래스에서 다른 클래스를 사용하는, 코드를 아는 등등

의존하는 객체를 직접 생성하지 않고 외부에서 주입 받는 것.


//의존 객체 직접 생성
public class MemberRegisterService
{
    private MemberDao memberDao = new MemberDao();
}

//의존 객체 직접 생성하지 않고 생성자를 통해 주입받음. DI 적용
public class MemberRegisterService
{
    public MemberRegisterService(MemberDao memberDao)
    {
        this.memberDao = memberDao;
    }
}

의존 객체를 외부에서 주입받으면 나중에 MemberDao() 가 아닌 예를 들어
CachedMemberDao() 로 변경할때 클라이언트의 코드를 변경하지 않아도 된다.
그리고 의존 객체간의 결합도를 낮춘다.
(교재 p57 ~ p58 참고)


Spring :: Container

MemberDao Class or Interface 의 실제 구현체 생성과 의존 객체 주입을 누군가 해줘야 한다.
(코드는 교재 p66 ~ p67 참고)

교재에서 작성한 Assembler Class 는 사실
Spring Container에서 DI Assembler 로 제공된다.

Spring Container 는 개발자 대신에 Bean을 생성하고 관리하고 제거한다. Spring Container 에서 관리하기 위해서는 객체를 Bean 으로 등록해야 한다.

  • Bean ?
    • Spring Container (IoC Container)가 관리하는 자바 객체를 빈(Bean)이라고 한다. 우리가 new 연산자로 어떤 객체를 생성했을 때 그 객체는 빈이 아니다.
      ApplicationContext.getBean()으로 얻어질 수 있는 객체는 빈이 맞다. Spring 에서의 빈은 ApplicationContext가 알고있는 객체, 즉 ApplicationContext가 만들어서 그 안에 담고있는 객체를 의미한다.

Spring Container 의 종류

  1. Bean Factory
    Bean Factory는 스프링 설정파일에 등록된 Bean 객체를 생성하고 관리하는 기본적인 기능만 제공한다.
    컨테이너가 구동될 때 Bean 객체를 생성하는 것이 아니라 클라이언트의 요청에 의해서 Bean 객체가 사용되는 시점(Lazy Loading) 에 객체를 생성하는 방식을 사용하고 있다.
    일반적으로 스프링 프로젝트에서는 사용될 일이 없지만,
    Application ContextBean Factory상속받고 있다는 것을 알아두자.

  2. Application Context
    Bean Factory와 마찬가지로, Bean 객체를 생성하고 관리하는 기능을 가지고 있다.
    뿐만 아니라 트랜잭션 관리, 메시지 기반의 다국어 처리, AOP 처리 등 DIIoC 외에도 많은 부분을 지원하고 있다.
    컨테이너가 구동되는 시점에 객체들을 생성하는 Pre-Loading 방식이 Bean Factory가장 큰 차이점이다.


Bean 등록 방법 3가지

  1. XML 파일에 직접 등록하기
<bean id="aaa" class="xxx.yyy.zzz.AAA">
   <property name="prop"></property>
</bean>

위와 같은 방법으로 XML 파일에 직접 등록할 수 있다.

하지만 다음의 문제점이 있다.

  • 빈의 성격 구분을 하기 힘들어진다.

  • 어플리케이션의 크기가 커지면 빈의 양이 늘어 관리하기 버거울 수 있다.

그래서 스프링에서는 클래스에 특정 Annotation을 부여하고 부여한 클래스를 찾아 자동으로 빈 등록을 해주는 빈 스캐닝 이라는 기능을 제공해준다.

  1. .java 파일을 이용하여 등록하기
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class ApplicationConfig 
{
 
    @Bean
    public MemberRepository memberRepository() 
    {
        return new MemberRepository();
    }
 
    @Bean
    public MemberService memberService() 
    {
        MemberService memberService = new MemberService();
        memberService.setMemberRepository(memberRepository());
        return memberService;
    }
}

별도의 xml 파일을 생성하지 않고 config.java 를 만들어 빈으로 등록할 수 있다.
Class 레벨에는 @Configuration 을 붙이고 Method 레벨에는 @Bean 을 붙이면 된다.

  1. Component Scan 을 이용하여 등록하기

필요한 Annotation 을 달면 SpringComponent Scanner 가 쭉 훑은 다음 빈으로 등록시킨다.


DI 방법 3가지

  1. Constructor Injection (권장)
public class MemberRegisterService
{
    public MemberRegisterService(MemberDao memberDao)
    {
        this.memberDao = memberDao;
    }
}

권장하는 이유

  • 단일 책임의 원칙
    생성자의 인자가 많을 경우 코드량도 많아지고, 의존관계도 많아져 단일 책임의 원칙에 위배된다.
    그래서 생성자 주입 방식을 사용함으로써 의존관계, 복잡성을 쉽게 알수 있어 리팩토링의 실마리를 제공한다.

  • 테스트 용이성
    DI Container에서 관리되는 클래스는 특정 DI Container에 의존하지않고 POJO 여야 한다.
    DI Container를 사용하지 않고도 인스턴스화 할 수 있고, 단위 테스트도 가능하다.

  • Immutability
    Constructor Injection 에서는 필드를 final 로 선언할 수 있다.
    반면에 Field Injection 방식은 final을 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다.

  • 강제
    필수적으로 사용해야하는 의존성 없이는 인스턴스를 만들지 못하도록 강제할 수 있다.
    MemberServiceMemberRepository 없이 제대로 동작할 수 없다면 MemberService 입장에서 MemberRepository반드시 있어야 하는 객체이다.

  • 순환 의존성
    생성자 주입 방식에서 순환 의존성을 가질 경우 BeanCurrentlyCreationExcepiton 을 발생시켜 순환 의존성을 알 수 있다.
  1. Setter Injection
@Service
public class MemberService
{
    private MemberRepository memberRepository;
 
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) 
    {
        this.memberRepository = memberRepository;
    }
}
  1. Field Injection
@Service
public class MemberService
{
    @Autowired
    private MemberRepository memberRepository;
}

Field Injection 에서는 같은 타입이 2개일때 문제가 발생한다.

@Configuration
public class AppConfig 
{

    @Bean(name = "modelMapperMain")
    public ModelMapper modelMapper() 
    {
        return new ModelMapper();
    }

    @Bean(name = "modelMapperSub")
    public ModelMapper modelMapperSub()
    {
        return new ModelMapper();
    }
}

다음과 같이 타입을 기준으로 주입을 받을 때 같은 타입이 2개라면 에러가 난다.
따라서 다음과 같이 @Qualifier 를 붙여 해결해야 한다.

@Controller
public class XXXController 
{
    @Qualifier("modelMapperMain")
    private ModelMapper modelMapper;
    
}

@Autowired 관련해서는 chapter04 의존 자동 주입 단원에 자세히 나와있다.

profile
For the 1% inspiration.
post-custom-banner

0개의 댓글