같은 타입의 여러 빈이 정의되어 있을 때 의존성 주입

이지호·2022년 2월 14일
0

스프링부트

목록 보기
3/4

스프링은 설정정보를 참고하여 어플리케이션을 구성하는 객체들을 생성하고 관리하는 어플리케이션 컨텍스트를 제공한다.

그리고 프레임워크(Framework)는 어플리케이션을 구성하는 오브젝트가 생성되고 동작하는 방식에 대한 틀을 제공하며, 소스코드가 어떻게 작성되어야 하는지에 대한 기준을 제시한다.

이를 프로그래밍 모델이라고 하며, 스프링은 다음과 같은 3가지 프로그래밍 모델을 제공한다.

  • IOC/DI(제어의 역전, 의존성 주입)
  • 서비스 추상화
  • AOP

먼저, 책의 예제에서는 UserDao, Connection, DConnection가 있다.
UserDao클래스는 Connection인터페이스와 의존관계를 가지고 있으며, DConnection클래스는 Connection의 구현체클래스이다.

다음으로 구현체를 설정클래스에서 정의하여 스프링컨테이너가 관리하는 빈으로 바꾸어준다.

@Configuration
public class ConnectionConfig {
	@Bean
    public Connection dConnection() {
    	return new DConnection();
    }
}

다음으로 Client인 UserDao에서 @Autowired를 이용하여 의존성 주입을 받으면 된다.

public class UserDao {
	@Autowired
	private Connection connection;
}

프로그램이 시작하면, 설계시점 의존관계의 실체화인 런타임 오브젝트 의존관계는 UserDao클래스가 DConnectionMaker클래스를 의존하게 된다.

이런식으로 구현체가 하나일 경우에는 별 무리없이 사용이 가능하다.

만약에 타입이 같은 여러 구현체들이 존재하는 경우 빈주입은 어떻게 될까??

AConnection, BConnection, DConnection으로 총 세개가 있다.
이걸 Client인 UserDao에서는 Autowired로 빈주입을 받을 경우 당연하게도 에러 메시지가 뜬다.

Parameter 0 of constructor in com.example.demo.web.service.ConnectionService required a single bean, but 3 were found:
이 에러는 스프링컨테이너에서 어떤 빈을 주입해야할 지 몰라서 에러메시지를 내뱉는 것이다.

그럼 이제 스프링이 어떤 빈을 주입을 해줘야할지 알려줘보자.
위에서와 같이 각각 구현체에 대해서 스프링컨테이너가 관리하는 빈으로 설정해준다.

public class ConnectionConfig {
	// dConnection
    
	@Bean
    public Connection aConnection() {
    	return new AConnection();
    }
    
    @Bean
    public Connection bConnection() {
    	return new BConnection();
    }
}

UserDao에서는 이제 @Qualifier를 이용하여 원하는 빈을 주입시켜주면된다. 이때 @Qulifier의 속성으로 설정클래스에서 정의했던 메서드의 이름을 이용하면 된다.

public class UserDao {
	@Qualifier("aConnection")
    private Connection aConnection;
    
    @Qualifier("bConnection")
    private Connection bConnection;
}

개인적으로 설정클래스를 생성하여 빈을 주입하기보다는 각 구현체 클래스에 @Component를 선언하여 스프링 컨테이너가 관리하는 빈으로 만들어 의존성을 주입하고 싶다.

스프링은 같은 타입의 여러 빈들을 Collection으로 관리할 수 있다.

ConnectionService에는 연결이라는 기능이 있다. 만약 연결말고도 Connection관련한 다른 기능을 구현해야 하면, 그 기능안에는 해당 빈이 담겨있는 Collection을 조회하여 꺼내와야할 것이고, ConnectionService클래스 내에는 반복코드가 생길 것이다.
해당 구현체 클래스의 인스턴스를 가져오는 반복코드를 메서드로 만들수도 있겠지만, 나는 이 책임을 팩토리 패턴을 이용하여 제거할 것이다.

@Component
@RequiredArgsConstructor
public class ConnectionFactory {
	private final Map<String, Connection> connections;
    
    public Connection getInstance(ConnectionType type) {
    	switch (type) {
        	case ACONNECTION:
            	return connections.get("ACONNECTION");
            case BCONNECTION:
            	return connections.get("BCONNECTION");
        }
        // 예외 던지기
    }
}

// client
public class ConnectionService {
	private final ConnectionFactory connectionFactory;
    
    public String getConnection(ConnectionType type) {
    	Connection connection = connectionFactory.getInstance(type);
        return connection.makeConnection();
    }
}

코드가 한결 더 깔끔해졌다. Factory클래스를 생성하여 @Component를 선언하여 해당 Factory클래스를 스프링 컨테이너가 관리하는 빈으로 만들어 Connection구현체들을 Map타입으로 의존성주입을 받는다. 그리고 클라이언트인 ConnectionService는 Factory클래스를 통하여 Connection의 구현체를 사용하게 된다. 어떤타입의 Connection을 사용할 것인지만 넘기고 Client는 그대로 사용하기만 하면된다.

이렇게 스프링에서 같은 타입의 여러 빈들이 존재하는 경우 어떤식으로 사용하는지 또 결합도는 어떻게 낮추어야 할 지에 대해서 알아보았다.

위에서 다뤘던 예제를 생각하면서 좋은 예시는 아니지만 어떤식으로 결합도를 낮추고 재사용이 가능한 코드에 대해서 좀 더 생각할 수 있었던 시간이라고 생각한다.

0개의 댓글