본문 바로가기

Backend/spring

Spring DI

DI는 Dependency Injection의 약자로 '의존 주입'이라고 번역할 수 있다. 여기에서 의존이란 객체 간의 의존을 의미하고, 한 클래스가 다른 클래스의 메서드를 실행할 때 이를 '의존'한다고 표현한다. 의존하는 방법에는 여러가지가 있다.

 

가장 쉬운 방법은 의존 대상 객체를 직접 생성하는 것이다. 클래스의 필드에 대상 클래스 생성자를 통해 객체를 직접 생성한 뒤 사용할 수 있다. 하지만 Spring에서의 DI는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다.

객체 조립기

DI를 사용하는 이유는 코드 변경에 유연하게 대처하기 위해서이다. 변경된 코드를 주입하는 경우 객체를 주입하는 코드 한 곳만 변경하면 된다. 그렇다면 객체를 생성하는 코드는 어디에 두어야 할까?

 

한 가지 방법으로 객체를 생성하고 의존 객체를 주입해주는 클래스를 따로 작성하는 방법이 있다. 의존 객체를 주입한다는 것은 서로 다른 두 객체를 조립한다고 표현하는데, 이런 맥락에서 이러한 클래스를 조립기라고 표현한다.

 

Assembler는 자신이 생성하고 조립한 객체를 리턴하는 메서드를 제공한다.위 코드처럼 Assembler가 제공하는 메서드를 이용해서 필요한 객체를 구하고 그 객체를 사용하는 것은 전형적인 Assembler 사용법이다.

스프링의 DI 설정

의존, DI, 조립기 등의 개념을 먼저 살펴본 이유는 스프링이 DI를 지원하는 조립기의 역할을 하기 때문이다. 스프링을 사용해 의존을 주입하기 위해서는 이에 대한 설정 정보를 코드로 작성해야 한다. 해당 클래스 위에 @Configuration 어노테이션을 붙여 스프링 설정 클래스로 이용할 수 있도록 하자. 

 

갹채를 생성하고 의존 객체를 주입하는 것은 스프링 컨테이너이므로 설정 클래스를 이용해서 컨테이너를 설정해야 한다.

DI를 위한 두 가지 방식

생성자 방식

public class MemberRegisterService {
	private MemberDao memberDao;
    
    public MemberRegisterService(MemberDao memberDao) {
    	this.memberDao = memberDao;
    }
    
    public Long regist(RegisterRequest req) {
    	Member member = memberDao.selectByEmail(req.getEmail());
        ...
        memberDao.insert(newMember);
        return newMember.getId();
    }
}

위 코드를 보면 생성자를 통해 의존 객체를 주입받아 필드에 할당했다.

세터 메서드 방식

생성자 외에 세터 메서드를 이용해서 객체를 주입받을 수도 있다. 일반적인 세터 메서드는 자바빈 규칙에 따라 다음과 같이 작성한다. 

  • 메서드 이름이 set으로 시작한다.
  • set 뒤에 첫 글자는 대문자로 시작한다.
  • 파라미터가 하나이다.
  • 리턴 타입이 void이다.

생성자 방식은 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다는 장점이 있으며, 세터 메서드 방식은 메서드 이름을 통해 정확히 어떤 객체가 주입되는지 확인할 수 있다는 장점이 있으나, 세터 메서드 방식을 사용하는 경우 NullPointerException이 발생할 위험이 있기 때문에 생성자를 통한 주입이 권장된다.

https://reflectoring.io/constructor-injection/

 

Why You Should Use Constructor Injection in Spring

Dependency injection is a common approach to implement loose coupling among the classes in an application. There are different ways of injecting dependencies and this article explains why constructor injection should be the preferred way.

reflectoring.io

@Configureation 설정 클래스의 @Bean 설정과 싱글톤

의존성이 주입된 메서드를 호출할 때 마다 주입된 클래스의 생성자를 호출하는데 이에 대한 내부 동작은 어떻게 작동될까? 스프링 컨테이너가 생성한 Bean객체는 싱글톤 객체이며, @Bean이 붙은 클래스에 대해 하나의 객체만 생성한다. 이는 다른 설정 메서드에서 주입된 생성자를 몇 번을 호출하더라도 항상 같은 객체를 리턴한다는 것을 의미한다.

 

어떻게 이게 가능할까? 스프링은 설정 클래스를 그대로 사용하지 않는다. 대신 설정 클래스를 상속한 새로운 설정 클래스를 만들어 사용한다. 이 설정 클래스에서 매번 새로운 객체를 생성하지 않고 한 번 생성한 객체를 보관했다가 이후에는 동일한 객체를 리턴한다.