[Spring] DI (Dependency Injection) - 의존 주입

BoongDev·2021년 7월 20일
0

Spring

목록 보기
1/3

의존이란?

하나의 클래스가 다른 클래스의 메소드를 실행할 때 이를 '의존'한다고 표현합니다.

public class MemberRegisterService {
    private MemberDao memberDao = new MemberDao();

    public void register(RegisterRequest req) {
        Member member = memberDao.selectByEmail(req.getEmail());
        if (member != null) {
            throw new DuplicateMemberException("dup email " + req.getEmail());
        }
        Member newMember = new Member(
                req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now()
        );
        memberDao.insert(newMember);
    }
}

여기서 MemberRegisterService가 DB처리를 하기위해 MemberDao의 메소드 사용하여 처리를 하고 있습니다.
그렇기에 '의존'한다고 표현할 수 있습니다. '의존'은 변경에 의해 영향을 받는 관계를 의미하기도 합니다. memberDao.insert() 에서 insert()insertMember() 로 바꾸면 이 메소드를 사용하는 MemberRegisterService 또한 코드를 변경해 주어야 합니다.


'의존'은 의존하는 대상이 있으면 그 대상을 구하는 방법이 필요합니다.

  • 첫 번째로 직접 의존 대상을 객체로 생성하여 필드에 할당합니다.
  • 두 번째는 의존 대상을 객체로 생성하는 대신 생성자로 의존 객체를 전달 받는 방식입니다. = DI
  • 세 번째로는 서비스 로케이터가 있지만 여기서는 다루지 않겠습니다. 추후에 따로 포스팅 할 예정입니다.

첫 번째 방법인 직접 의존 대상을 객체로 생성하는 방법은 그다지 추천하지 않습니다. 나중에 유지보수 관점에서 보면 문제점을 유발할 수 있기 때문입니다.

간단하게 직접 생성 하였을 경우 의존 대상이 변경점이 많이 생기면 직접 생성한 모든 곳을 똑같이 변경해주어야 하기 때문입니다.

그렇기에 DI와 서비스 로케이터 방식을 씁니다. 이 중 스프링과 관련된 DI에 대해서 설명합니다.


DI(Dependency Injection) 의존 주입

DI 방식

  • 생성자 방식
    생성자로 객체를 전달 받는 대표적인 방식입니다.
  • 세터 메소드 방식
    생성자 외에 세터(setter) 메소드를 이용해서 객체를 주입받는 방식입니다
    (평소에 사용하는 그 setter 메소드가 맞습니다.)
    일반적인 세터 메소드는 자바빈 규칙에 따라 다음과 같이 작성합니다.
    • 메소드 이름이 set으로 시작한다.
    • set 뒤에 첫 글자는 대문자로 시작한다.
    • 파라미터가 1개이다.
    • 리턴 타입이 void이다

그럼 무엇을 써야 하나. 답은 없다 상황별로 골라 사용하면 된다. 각 방식의 장점을 보자

  • 생성자 방식: 빈 객체를 생성하는 시점에 모든 의존 객체가 주입
  • 세터 메소드 방식(설정 메소드 방식): 세터 메소드 이름을 통해 어떤 의존 객체가 주입되는지 알 수 있다.

각 방식의 장점은 곧 단점이 된다. 파라미터의 개수가 많다면 생성자로 한번에 의존 객체 주입이 가능하지만 어떤 의존 객체가 들어있는지는 생성자 코드를 확인해야하는 번거로움이 있다. 반면 세터 방식은 어떤 의존 객체가 들었는지 쉽게 유추할 수 있지만 파라미터가 많은 만큼 많은 set 메소드를 작성하게 될 것이다.


싱글톤

의존 주입 예제로 회원 관련된 DAO를 많이 작성하게 됩니다. 다음과 같은 설정 코드가 있습니다.

@Configuration
public class AppCtx{
   @Bean
   public MemberDao memberDao() {
      return new MemberDao();
   }

   @Bean
   public MemberRegisterService memberRegSvc() {
      return new MemberRegisterService(memberDao());
   }

   @Bean
   public ChangePasswordService changePwdSvc() {
      ChangePasswordService pwdSvc = new ChangePasswordService();
      pwdSvc.setMemberDao(memberDao());
      return pwdSvc;
   }
 }

위 코드에서 memberRegSvcchangePwdSvc 메소드 둘 모두 memeberDao 메소드를 실행하고 memberDao메소드는 매번 새로운 MemberDao 객체를 리턴합니다.

각각 다 다른 곳에서 메소드를 부르고 객체를 매번 리턴받으면 다른 객체가 아닌가 하는 의문점이 생깁니다. 실제로 만일 새로운 회원을 저장을하고 비밀번호를 바꾸게 되면 다른 memberDao 객체를 쓰게 되는 것이 아니냐는 말입니다.

당연한 의문이지만 스프링 컨테이너가 생성하는 빈 객체는 싱글톤 객체라고 하며, 스프링 컨테이너는 @Bean이 붙은 메소드에 대해 한 개의 객체만을 생성합니다.

어떻게 가능한가? 스프링은 설정 클래스를 그대로 사용하지 않습니다. 대신 설정 클래스를 상속한 새로운 설정 클래스를 만들어서 사용합니다. 만들어진 새로운 설정 클래스는 매우 복잡합니다만, 간단하게 말하자면 한번 만들어진 빈 객체를 보관했다가 이후에는 동일한 객체를 리턴합니다. 다음은 새로 만들어진 가상의 유사코드입니다.

public class AppCtx extends AppCtx {
 private Map<String, Object> beans = ...;
    
 public MemeberDao memberDao() {
  if(!beans.containsKey("memberDao")) {
   beans.put("memberDao", super.memberDao());
  }
  return (MemberDao) beans.get("memberDao");
}

어디까지나 이해를 돕기위한 유사한 가상의 코드이다. 실제 스프링 코드는 이것보다 훨씬 더 복잡합니다.

profile
욕심 많은 주니어 개발자입니다.

0개의 댓글