1. 의존이란?

의존(dependency)이란 변경에 의해 영향을 받는 관계를 의미한다. 즉, 객체 간 의존관계는 한 클래스가 다른 클래스의 메서드를 실행하면서 발생한 변경에 따른 영향이 그 메서드를 사용한 클래스로 전파되는 관계를 나타낸다.
이러한 의존 객체를 구하는 방법으로는 아래의 것들이 있다.

  • 클래스 내부에 의존 대상인 객체를 직접 생성: 가장 쉬운 방법
  • DI(Dependency Injection, 의존 주입)
  • 서비스 로케이터: DI를 사용할 수 없는 상황에서 활용

위 방식 중 Spring과 연관된 것은 두번째 방법이다.


2. DI를 통한 의존 처리

DI(Dependency Injection)의 개념과 장점

DI(Dependency Injection, 의존 주입)은 클래스 내부에서 의존하는 객체를 직접 생성하는 대신 그 외부에서 생성된 의존 객체를 전달받는 방식이다.
그리고 DI의 이러한 의존 처리 방식은 코드의 유지보수가 간편해진다는 장점이 있다. 의존 주입 대상인 1개의 객체를 여러 개의 클래스가 필요로 하는 상황에서 해당 의존 객체를 사용할 클래스를 변경하는 등의 수정이 필요할 때, DI의 대상이 되는 객체를 생성하는 코드 한 곳만 수정하면 되기 때문이다.

객체 조립기(Assembler)란?

조립기(Assembler)는 객체를 생성한 다음 다른 객체에 주입하여 객체를 서로 연결해주는 역할을 수행하는 클래스를 의미한다.

  • 객체 조립기 사용 예시
package assembler;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

public class Assembler {

    private final MemberDao memberDao;
    private final MemberRegisterService regSvc;
    private final ChangePasswordService pwdSvc;

    public Assembler() {
    	/* Assembler 기능 1: 객체 생성 */
        memberDao = new MemberDao();
        regSvc = new MemberRegisterService(memberDao);
        pwdSvc = new ChangePasswordService();
        /* Assembler 기능 2: 생성한 객체를 다른 객체에 주입(여기서는 setter 활용) */
        pwdSvc.setMemberDao(memberDao);
    }

    public MemberDao getMemberDao() {
        return memberDao;
    }

    public MemberRegisterService getMemberRegisterService() {
        return regSvc;
    }
    public ChangePasswordService getChangePasswordService() {
        return pwdSvc;
    }
}

3. Spring에서의 DI 설정

Spring을 이용한 객체 조립과 사용

Spring은 위의 Assemble 객체처럼 필요한 객체를 생성한 다음 생성한 객체에 DI를 수행하는데, 이러한 객체 조립기로서의 기능을 Spring은 클래스의 타입에 구애받지 않고 수행할 수 있다.

Spring에서의 DI 방식

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

@Configuration
public class AppCtx {
	/* memberDao() 메서드: 생성한 Bean 객체를 "memberDao"라는 이름으로 Spring에 등록 */
    @Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }

	/* Spring의 DI 방식 1: 생성자를 통한 의존 객체 주입 */
    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDao()); // DI 수행
    }

	/* Spring의 DI 방식 2: setter 메서드를 통한 의존 객체 주입 */
    @Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao());	// DI 수행
        return pwdSvc;
    }
}

생성자 DI 방식

  • 장점: Bean 객체 생성 시점부터 모든 의존 객체를 주입하므로 처음부터 완전한 상태로 Bean 객체를 활용할 수 있다.
  • 단점: 생성자로 주입하는 의존 객체의 수가 많아지면 각 인자별로 어떤 의존 객체를 설정했는지를 파악하기 위해 생성자의 코드를 일일이 확인해야 하는 번거로움이 존재한다.

setter 메서드 DI 방식

  • 장점: setter 메서드명을 통해 어떤 의존 객체를 주입했는지 쉽게 유추 가능하고, 처음부터 Bean 객체에 모든 의존 객체를 주입하지 않고도 사용할 수 있다.
  • 단점: 필요한 의존 객체를 전달하지 않고도 Bean 객체를 생성하므로 객체 사용 시점에서 NullPointException이 발생할 수 있다.

4. 여러 개의 설정 클래스 사용 방법

@Configuration, Bean, @Autowired 활용 방법

  • @Autowired
    해당 어노테이션이 사용된 필드와 클래스 타입이 같은 Bean을 찾아서 자동으로 해당 필드에 할당한다.

@Autowired을 활용하여 Spring Bean에 의존하는 다른 Bean을 자동으로 주입할 수 있다. 그리고 Spring 컨테이너는 @Configuration 어노테이션이 붙은 설정 클래스 또한 Spring Bean으로 등록하기 때문에 Spring 설정 클래스 타입의 필드에도 @Autowired를 사용할 수 있다.
따라서 설정 클래스에 관한 객체를 Bean으로 등록하고, 해당 설정 클래스 타입을 갖는 필드에 @Autowired 어노테이션을 사용하는 방식으로 2개 이상의 설정 클래스를 사용할 수 있다.

@Import 활용 방법

  • @Import([사용할 설정 클래스명].class)
    해당 어노테이션에서 지정한 설정 클래스를 @Import 어노테이션이 사용된 설정 클래스에서도 함께 사용할 수 있다. 또한 @Import({클래스명1.class, 클래스명2.class})와 같이 배열을 이용하여 사용할 설정 클래스를 2개 이상 지정하는 것도 가능하다.

5. getBean() 메서드

getBean() 메서드는 BeanFactory 인터페이스에 정의되어 있으며, 이를 실제로 구현한 클래스는 AbstractApplicationContext이다.

  • getBean("Bean의 이름", "Bean의 타입")
    Bean 객체를 가져오는 메서드이다. 메서드의 첫번째 인자인 Bean의 이름은 두번째 인자로 지정된 타입의 Bean을 생성하는 @Bean 메서드의 이름과 같다.
    만약 첫번째 인자로 존재하지 않는 @Bean 메서드 이름을 사용한다면 NoSuchBeanDefinitionException이, 두번째 인자로 지정한 타입과 Bean의 실제 타입이 다르면 BeanNotOfRequiredTypeException이 발생한다.
  • getBean("Bean의 타입")
    Bean의 타입만 지정하여 이 메서드를 호출했을 경우, 해당 타입을 갖는 Bean 객체가 오직 1개만 존재하면 그 객체를 반환한다.
    만약 해당 타입의 Bean이 존재하지 않으면 NoSuchBeanDefinitionException이, 여러 개 존재한다면 NoUniqueBeanDefinitionException이 발생한다.

6. 주입 대상 객체를 Bean으로 설정하는 경우

Spring Bean으로 등록된 객체는 Spring 컨테이너의 관리 대상이 된다. 그리고 Spring 컨테이너는 Bean으로 등록된 객체에 한해 객체 생성뿐만 아니라 자동 주입, Bean 생명주기 관리 등 객체 관리를 위한 다양한 기능을 제공한다.
따라서 Spring 컨테이너가 제공하는 객체 관리 기능이 필요하거나 getBean() 메서드를 통해 의존 객체를 주입해야 하는 경우 주입 대상 객체를 Bean으로 등록해놓는 것이 좋다.


Reference

  • 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문(최범균)

0개의 댓글