Developer/Spring eGov4.0 (Java11, Tomcat9)

Spring , 의존성 처리 방법

단님 2024. 9. 24. 01:28
728x90
Spring 의 의존성 처리 ?

 

➡️스프링에 대해 다시보기

➡️스프링 컨테이너에 대해 다시보기

 

Spring 의 의존성 처리는 객체간의 결합도를 낮추고 유연한 설계를 하기 위해 중요한 개념이다.

의존성 주입 (Dependency Inhection , DI)은

객체를 직접 생성하는 대신 외부에서 주입받는 방식을 통해 의존성을 해결하는 방법을 말한다.

의존성 주입에는 크게 두가지 방법이 있는데 생성자 주입방식setter 주입 방식이다.

이 두가지 외에도 고전적 방법으로 직접 객체를 생성하는 방법도 있다

이 세가지 방식에 대해 알아보자.

 

1. 고전적 방법
직접 new 키워드로 객체 생성
고전적인 방법은 클래스가 자신의 의존성을 직접 관리하는 방식.
즉, 필요한 객체를 내부에서 new 키워드를 사용하여 생성하는 방식.
public class ServiceA {
    private ServiceB serviceB;

    public ServiceA() {
        // 고전적 방법: 직접 객체를 생성
        this.serviceB = new ServiceB();
    }

    public void performAction() {
        serviceB.doSomething();
    }
}

 

고전적 방법의 문제점들
  • 강한 결합: ServiceA는 ServiceB의 구체적인 구현에 강하게 결합되어 있다.
    ServiceB를 다른 구현으로 바꾸고 싶을 때 수정해야 할 코드가 많다.
  • 테스트 어려움: 유닛 테스트 시 ServiceB를 목(mock) 객체로 대체하기 어려워진다.
  • 재사용성 낮음: ServiceA는 항상 ServiceB의 구체적인 구현체를 사용하므로, 유연성이 떨어진다.
2. IoC/DI: 생성자 주입
생성자 주입( Constructor Injection )은 객체가 생성될 때 필요한 의존성을 생성자의 매개변수로 주입받는 방법.
이 방법은 의존성 주입 방식중 가장 추천되는 방식이다.
클래스가 생성될 때 의존성이 완전히 주입되므로 , 의존성 누락 문제를 방지할 수 있다.
public class ServiceA {
    private final ServiceB serviceB;

    // 생성자를 통해 의존성 주입
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void performAction() {
        serviceB.doSomething();
    }
}
생성자 주입의 특징

 

  • 불변성 보장: 의존성이 생성자를 통해 주입되면, final 키워드를 사용하여 객체가 불변하게 유지될 수 있다.
  • 테스트 용이: 테스트 시 모의 객체(Mock)를 생성자에 전달하여 쉽게 주입할 수 있어 유닛 테스트에 적합하다.
  • 완전한 초기화: 객체가 생성될 때 모든 의존성이 주입되므로, 의존성 누락으로 인한 런타임 에러를 방지할 수 있다.

[✔️ XML - 어노테이션 사용 예시]

    <bean id="lgtv" class="iocDI01_xml.LgTVs" lazy-init="true" >
    <constructor-arg name="color" value="Gold"/>
    <constructor-arg name="price" value="12345000"/>
    <constructor-arg index="0" ref="sp"/>
    </bean>
	<bean id="sp" class="iocDI01_xml.Speaker" lazy-init="true"/>

인자를 이름으로 찾기 때문에 순서는 중요하지 않음.

value 는 항상 쌍따옴표를 사용해줘야한다

index는 인자를 배열로 바라보고 위치에 맞춰 지정해준다.

참조자료형의 경우 참조할 빈을 만들어 id 값을 ref를 통해 등록해준다.

<bean id="serviceA" class="com.example.ServiceA">
    <constructor-arg ref="serviceB" />
</bean>
<bean id="serviceB" class="com.example.ServiceB" />

 

 

@Component
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}​

 

IoC/DI: Setter 주입
Setter 주입(Setter Injection)은 객체를 생성한 후에 의존성을 Setter 메서드를 통해 주입받는 방식.
생성자 주입과 달리 선택적인 의존성 주입이 가능하지만,
주입되지 않은 경우 NullPointerException 등의 문제가 발생할 수 있다.
주로 필수적인 의존성보다는 선택적인 의존성 주입에 적합하다.
public class ServiceA {
    private ServiceB serviceB;

    // Setter 메서드를 통해 의존성 주입
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void performAction() {
        serviceB.doSomething();
    }
}

 

Setter 주입의 특징

 

  • 유연성: 생성된 객체에 나중에 의존성을 주입할 수 있어 좀 더 유연한 구조를 가질 수 있다.
  • 의존성 누락 위험: 의존성을 주입하지 않으면 런타임 시점에 문제가 발생할 수 있다.
    따라서 필수 의존성은 생성자 주입이 더 안전하다.
  • 테스트 가능: 테스트 시 Setter 메서드를 통해 모의 객체를 주입할 수 있어 테스트가 용이하다.

 

[✔️ XML - 어노테이션 사용 예시]

<bean id="serviceA" class="com.example.ServiceA">
    <property name="serviceB" ref="serviceB" />
</bean>
<bean id="serviceB" class="com.example.ServiceB" />

property 가 해당하는 setter 를 찾아 값을 넣어준다.

위와같이 참조자료형일 경우 bean을 ref를 통해 참조하고 value를 넣어줌.

@Component
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

 

의존성 처리방식의 비교