https://sundaland.tistory.com/285
Dependencies
일반적인 엔터프라이즈 애플리케이션은 단일 객체(또는 Bean)로 구성되지 않는다. 가장 단순한 애플리케이션조차도 최종 사용자가 이 애플리케이션을 일관된 애플리케이션으로 인식할 수 있도록 하기 위해 함께 작동하는 몇 가지 객체가 있다.
의존성 주입 (Dependency Injection)
객체들이 그들은 의전성(그들이 함께 작업하는 다른 객체들)을 오직 생성자 아규먼트, 팩토리 메서드로의 아규먼트, 또는 객체가 생성되거나 팩토리 메서드에서 리턴된 후에 객체 인스턴스에 설정된 속성을 통해서만 정의하는 과정이다.
그런 다음 컨테이너는 빈을 생성할 때 그 의존성을 주입한다. 이 과정은 근본적으로 빈이 자신의 의존성의 인스턴스화나 위치를 직접 클래스의 생성 또는 서비스 로케이터 패턴을 사용하여 스스로 제어하는 것관 반대이다. (제어의 역전)
▼ 생성자 아규먼트
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
UserDao dao =
new UserDao(connectionMMaker()); // Dependency Injection
// :Constructor argument
return dao;
}
@Bean
public ConnectionMaker connectionMMaker() {
ConnectionMaker connectionMaker =
new MConnectionMaker();
return connectionMaker;
}
@Bean
public ConnectionMaker connectionHMaker() {
ConnectionMaker connectionMaker =
new HConnectionMaker();
return connectionMaker;
}
}
▼ 팩토리 메서드로의 아규먼트
// Repository 클래스
public class Repository {
public void save(String data) {
System.out.println("Data saved: " + data);
}
}
// Service 클래스
public class Service {
private final Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public void process(String data) {
System.out.println("Processing data: " + data);
repository.save(data);
}
}
// 스프링 설정 클래스
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Repository repository() {
return new Repository();
}
@Bean
public Service service(Repository repository) {
return new Service(repository);
}
// Repository 클래스
public class Repository {
public void save(String data) {
System.out.println("Data saved: " + data);
}
}
// Service 클래스
public class Service {
private final Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
public void process(String data) {
System.out.println("Processing data: " + data);
repository.save(data);
}
}
// 스프링 설정 클래스
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Repository repository() {
return new Repository();
}
@Bean
public Service service(Repository repository) {
return new Service(repository);
}
//@Bean
//public Service service() {
//return new Service(repository());
//}
}
객체가 생성되거나 팩토리 메서드에서 리턴된 후에 객체 인스턴스에 설정된 속성을 통해서만 정의하는 과정에 대한 예시는 아래와 같다.
@Configuration
public class DaoFactory {
@Bean
public DataSource dataSourceM() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource ();
dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
dataSource.setUrl("jdbc:mysql://localhost/sbdt_db?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("1234");
return dataSource;
}
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
////////////////////////////////////
// 객체가 생성된 후, 속성에 의해 의존성이 정의되고 있음!!!
userDao.setDataSource(dataSourceM());
return userDao;
}
}
의존성 주입 (DI)에서 객체가 생성되거나 팩토리 메서드에서 리턴된 후에 객체 인스턴스에 설정된 속성을 통해서만 정의하는 과정은 주로 Setter Injection으로 불리는 방식과 관련이 있다. 이 방식은 객체가 생성된 후에 필요한 의존성을 설정자(Setter) 메서드를 통해 주입하는 방법이다.
Spring에서는 보통 @Autowired 어노테이션을 사용하여 Setter Injection을 구현한다.
▼ 1. 의존성 클래스를 정의
import org.springframework.stereotype.Component;
@Component
public class Repository {
public void save() {
System.out.println("Data saved!");
}
}
@Componenet 어노테이션은 이 클래스를 Spring의 빈으로 등록하는데 사용된다. 이제 이 빈은 다른 클래스에서 주입받을 수 있다.
▼ 2. 의존성을 주입받는 클래스 정의
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service {
private Repository repository;
// 기본 생성자
public Service() {}
// Setter 메서드를 통해 의존성 주입
@Autowired
public void setRepository(Repository repository) {
this.repository = repository;
}
public void performAction() {
repository.save();
}
}
service 클래스를 정의하고 Repository 의존성을 주입받도록 설정한다.
여기서 @Autowired 어노테이션은 Spring 컨테이너가 Repository 빈을 찾아 Service 빈에 주입하도록 지시한다. 이 과정에서 setRepository 메서드를 통해 의존성을 주입하게 된다.
의존성 주입의 원칙으로 코드는 더 깔끔해지며, 객체에 의존성이 제공될 때 결합이 더 효과적으로 해소된다. 객체는 자신의 의존성을 찾지 않으며, 의존성의 위치나 클래스를 알지 못한다. 결과적으로, 특히 의존성이 인터페이스나 추상 기본 클래스인 경우, 단위 테스트에서 stub이나 mock 구현을 사용할 수 있으므로 클래스가 테스트하기 더 쉬워진다.
Constructor-based Dependency Injection
생성자 기반 의존성 주입은 컨테이너가 의존성을 나타내는 여러 아규먼트들을 가진 생성자를 호출함으로써 이루어진다. 특정 아규먼트를 가진 static 팩토리 메서드를 호출하여 빈을 구성하는 것도 거의 동일하다.
▼ 생성자 주입으로만 의존성이 주입될 수 있는 클래스
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
이 클래스는 컨테이너 특정 인터페이스, 베이스 클래스 또는 어노테이션에 대한 의존성이 없는 POJO (Plain Old Java Object)이다.
Constructor Argument Resolution
생성자 아규먼트 해석은 아규먼트의 타입을 사용하여 일치시킨다. Bean 정의의 생성자 아규먼트에 잠재적인 모호성이 존재 (아규먼트가 기본 타입인 경우)하지 않는 경우, 빈 정의에서 생성자 아규먼트가 정의된 순서가 빈이 인스턴스화될 떄 적절한 생성자에 그 아규먼트들이 제공되는 순서이다.
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
ThingTwo와 ThingThree 클래스가 상속 관계에 있지 않다고 가정하면, 잠재적인 모호성이 존재하지 않는다. 따라서 아래의 빈 구성은 잘 작동하며, <constructor-arg/> 요소에서 생성자 인자의 인덱스나 타입을 명시적으로 지정할 필요가 없다.
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
@Configuration
public class AppConfig {
@Bean
public ThingThree thingThree() {
ThingThree thing = new ThingThree();
return thing;
}
@Bean
public ThingTwo thingTwo() {
ThingThree thing = new ThingTwo();
return thing;
}
@Bean
public ThingOne thingOne() {
ThingOne thing = new ThingOne(thingTwo(), thingThree());
return thing;
}
}
다른 빈을 참조할 때는 타입이 알려져 있어 일치시킬 수 있다. (앞의 예시와 같은 경우)
<value>true</value>와 같은 간단한 타입을 사용할 떄, Spring은 값을 타입을 결정할 수 없으므로 도움 없이는 타입에 의한 일치를 할 수 없다.
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
type 속성을 사용하여 생성자 인자의 타입을 명시적으로 지정하면, 컨테이너는 간단한 타입에 대해 타입 매칭을 사용할 수 있다.
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
아래 예시와 같이 index 속성을 사용하여 생성자 인자의 인덱스를 명시적으로 지정할 수 있다.
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
여러 간단한 값의 모호성을 해결하는 것 외에도, 인덱스를 지정하면 생성자에 동일한 유형의 두 인지가 있는 경우의 모호성도 해결할 수 있다.
Setter-based Dependency Injection
세터 기반 의존성 주입은 컨테이너가 아규먼트가 없는 생성자나 아규먼트가 없는 static 팩토리 메서드를 호출하여 빈을 인스턴스화한 후, 빈의 세터 메서드를 호출하여 수행된다.
아래의 예제 클래스는 전통적인 자바이며 컨테이너별 인터페이스, 베이스 클래스 또는 어노테이션에 대한 종속성이 없는 POJO이다.
▼ 순수 세터 주입을 사용하여 종속성 주입이 가능한 클래스
public interface MovieFinder {
void findMovies();
}
public class SimpleMovieFinder implements MovieFinder {
@Override
public void findMovies() {
System.out.println("Finding movies...");
}
}
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MovieFinder movieFinder() {
return new SimpleMovieFinder();
}
@Bean
public SimpleMovieLister simpleMovieLister() {
SimpleMovieLister lister = new SimpleMovieLister();
lister.setMovieFinder(movieFinder()); // MovieFinder 주입
return lister;
}
}
ApplicationContext는 관리하고 빈에 대해 생성자 기반 DI와 세터 기반 DI를 지원한다. 또한 일부 의존성이 생성자 방식으로 이미 주입된 후에도 세터 기반 DI를 지원한다. 의존성은 BeanDefinition의 형태로 구성되며, 이를 PropertyEditer 인스턴스와 함꼐 사용하여 속성을 한 형식에서 다른 형식으로 변환한다. 그러나 대부분의 Spring 사용자들은 이러한 클래스들을 직접적으로(프로그래밍 방식으로) 작업하지 않으며, 대신 XML, 빈 정의, 어노테이션이 달린 구성 요소(@Component, @Controller 등으로 주석이 달린 클래스), 또는 자바 기반 @Configuration 클래스의 @Bean 메서드로 작업한다. 이러한 소스들은 내부적으로 BeanDefinition 인스턴스로 변환되어 전체 Spring IoC 컨테이너 인스턴스를 로드하는데 사용된다.
생성자 기반과 세터 기반 DI를 혼합하여 사용할 수 있으므로, 필수 의존성에는 생성자를 사용하고 선택적 의존성에는 세터 메서드나 구성 메서드를 사용하는 것이 좋은 지침이다.
세터 메서드에 @Autowired 어노테이션을 사용하며 속성을 피수 의존성으로 만들 수 있지만, 아규먼트에 대한 프로그래머틱 검증을 포함하는 생성자 주입이 선호된다.
Spring 팀은 일반적으로 생성자 주입을 권장한다. 이는 애플리케이션 구성 요소를 불변 객체로 구현할 수 있게 하며 필수 의존성이 null이 아님을 보장한다. 또한 생성자로 주입된 구성 요소는 항상 완전히 초기화된 상태로 클라이언트(호출) 코드에 반환된다. 한편, 생성자 아규먼트가 많은 것은 나쁜 코드로, 클래스가 너무 많은 책임을 가지고 있으며 관심사의 적절한 분리를 더 잘 다루기 위해 리팩토링되어야 함을 나타낸다. 세터 주입은 클래스 내에서 합리적인 디폴트 값을 할당할 수 있는 선택적 의존성에 주로 사용되어야한다. 그렇지 않으면 의존성을 사용하는 코드 곳곳에 null이 아닌지 확인해야 한다.
세터 주입의 이점 중 하나는 세터 메서드가 해당 클래스의 객체를 나중에 재궁성하거나 재주입하기 적합하게 만든다는 것이다. 따라서 JMX MBeans를 통한 관리는 세터 주입을 위한 강력한 사용 사례이다. 특정 클래스에 가장 의미 있는 DI 스타일을 사용한다. 떄떄로 소스가 없는 타사 클래스를 다룰 떄 선택이 이미 결정될 수 있다. 예를 들어 타사 클래스가 세터 메서드를 노출하지 않는 경우, 생성자 주입이 유일하게 가능한 DI 형태일 수 있다.
Dependency Resolution Process
- ApplicationContext는 모든 빈을 설명하는 구성 메타데이터로 생성되고 초기화된다. 구성 메타데이터는 XML, 자바 코드 또는 어노테이션을 통해 지정될 수 있다.
- 각 빈의 종속성을 속성, 생성자 아규먼트 또는 static 팩토리 메서드에 대한 아규먼트(일반 생성자 대신 사용하는 경우)의 형태로 표현된다. 이러한 종속성은 빈이 실제로 생성될 떄 빈에 제공된다.
- 각 속성 또는 생성자 아규먼트는 설정할 값의 실제 정의거나 컨테이너의 다른 빈에 대한 참조이다.
- 값으로 되어 있는 각 속성 또는 생성자 아규먼트는 지장된 형식에서 해당 속성 또는 생성자 아규먼트의 실제 타입으로 변환된다. 기본적으로 Spring은 문자열 형식으로 제공된 값을 int, long, String, boolean 등과 같은 모든 내장 타입으로 변환할 수 있다.
Spring 컨테이너는 컨테이너가 생성될 떄 각 빈의 구성을 검증한다. 그러나 빈 속성 자체는 빈이 실제로 생성될 떄까지 설정되지 않는다. 싱글톤 범위로 지정되고 사전 인스턴스화되도록 설정된 (디폴트 값) 빈들은 컨테이너가 생성될떄 생성된다. 범위는 Bean Scope에서 정의된다. 그렇지 않으면 빈은 요청될때만 생성된다. 빈의 생성은 빈의 의존성과 그 의존성의 의존성 (그리고 이어지는 의존성)이 생성되고 할당됨에 따라 빈의 그래프가 생성될 수 있다. 이러한 의존성 간의 해결 불일치는 늦게 나타날 수 있다. 즉 영향을 받는 빈을 처음 생성할 떄 나타날 수 있다.
Circular Dependencies
주로 생성자 주입을 사용하는 경우, 해결할 수 없는 순환 의존성 시나리오를 만들 수 있다. 예를 들어 클래스 A가 생성자 주입을 통해 클래스 B의 인스턴스를 필요로하고, 클래스 B가 생성자 주입을 통해 클래스 A의 인스턴스를 필요로 하는 경우이다. 클래스 A와 B에 대한 빈을 서로 주입하도록 구성하면, Spring IoC 컨테이너는 런타임에 이 순환 참조를 감지하고 BeanCurrentlyCreationException을 던진다.
한 가지 가능한 해결책은 일부 클래스의 소스 코드를 수정하여 생성자가 아닌 세터를 통해 구성하도록 하는 것이다. 또한 생성자 주입을 피하고 세터 주입만 사용한다.
권장되지는 않지만, 세터 주입으로 순환 의존성을 구성할 수 있다. 일반적인 경우 (순화 의존성이 없는 경우)와 달리. 빈 A와 빈 B 간의 순환 의존성은 빈 중 하나가 완전히 초기화되기 전에 다른 하나에 주입되도록 강제한다.
Spring은 컨테이너 로드 시점에 존재하지 않는 빈에 대한 참조와 순환 의존성과 같은 구성 문제를 감지한다. Spring은 빈이 실제로 생성될 때 가능한 늦게 속성을 설정하고 의존성을 해결한다.
이는 정확하게 로드된 Spring 컨테이너가 나중에 객체를 요청할 때 문제가 있는 객체나 그 의존성을 생성하는 데 문제가 있을 경우 예외를 발생시킬 수 있다는 것을 의미한다.
예를 들어, 스프링 컨테이너는 빈이 누락되었거나 잘못된 속성으로 인해 예외를 던진다. 일부 구성 문제에 대한 가시성이 지연될 가능성이 있기 때문에 ApplicationContext 구현은 기본적으로 싱글톤 빈을 사전에 인스턴스화한다. 실제로 필요하기 전에 이러한 빈을 만드는데 약간의 사전 시간과 메모리를 희생하지만 나중에 아니라 ApplicationContext가 생성될 떄 구성 문제를 발견한다. 이와 같이 싱글톤 빈이 적극적으로 미리 인스턴스화되는 대신, 지연 (Lazy) 초기화되도록 이 기본 동작을 재정의할 수도 있다.
순환 의존성이 존재하지 않는 경우, 하나 이상의 협력 빈이 의존 빈에 주입될 떄, 각 협력 빈은 의존 빈에 주입되기 전에 완전히 구성된다. 이는 빈 A가 빈 에 의존하는 경우 Spring IoC 컨테이너가 빈 A의 세터 메서드를 호출하기 전에 빈 B를 완전히 구성한다는 것을 의미한다. 즉 빈이 인스턴스화된다 (사전에 인스턴스화된 상글통이 아니라면). 그 의존성이 설정되고 관련 생명주기 메서드가 호출된다.
▼ 세터 기반 의존성 주입
package examples;
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
// 세터 메서드들
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
public class AnotherBean {
// 관련된 속성과 메서드들...
}
public class YetAnotherBean {
// 관련된 속성과 메서드들...
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public AnotherBean anotherExampleBean() {
return new AnotherBean();
}
@Bean
public YetAnotherBean yetAnotherBean() {
return new YetAnotherBean();
}
@Bean
public ExampleBean exampleBean() {
ExampleBean exampleBean = new ExampleBean();
exampleBean.setBeanOne(anotherExampleBean()); // 세터 기반 주입
exampleBean.setBeanTwo(yetAnotherBean()); // 세터 기반 주입
exampleBean.setIntegerProperty(1); // 세터 기반 주입
return exampleBean;
}
}
▼ 생성자 기반 의존성 주입
package examples;
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
// 생성자
public ExampleBean(AnotherBean beanOne, YetAnotherBean beanTwo, int i) {
this.beanOne = beanOne;
this.beanTwo = beanTwo;
this.i = i;
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public AnotherBean anotherExampleBean() {
return new AnotherBean();
}
@Bean
public YetAnotherBean yetAnotherBean() {
return new YetAnotherBean();
}
@Bean
public ExampleBean exampleBean() {
// 생성자 기반 주입
return new ExampleBean(anotherExampleBean(), yetAnotherBean(), 1);
}
}
▼ static 팩토리 메서드를 통한 의존성 주입
package examples;
public class ExampleBean {
// private 생성자
private ExampleBean(AnotherBean beanOne, YetAnotherBean beanTwo, int i) {
this.beanOne = beanOne;
this.beanTwo = beanTwo;
this.i = i;
}
// 정적 팩토리 메서드
public static ExampleBean createInstance(AnotherBean beanOne, YetAnotherBean beanTwo, int i) {
return new ExampleBean(beanOne, beanTwo, i);
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public AnotherBean anotherExampleBean() {
return new AnotherBean();
}
@Bean
public YetAnotherBean yetAnotherBean() {
return new YetAnotherBean();
}
@Bean
public ExampleBean exampleBean() {
// 정적 팩토리 메서드를 사용한 주입
return ExampleBean.createInstance(anotherExampleBean(), yetAnotherBean(), 1);
}
}
세터 기반 DI : setBeanOne, setBeanTwo, setIntegerProperty 메서드를 통해 ExampleBean의 의존성을 주입한다.
생성자 기반 DI : ExampleBean의 생성자를 통해 의존성을 주입한다.
정적 팩토리 메서드를 통한 DI : ExampleBean 클래스 내의 정적 팩토리 메서드 createInstance를 사용하여 의존성을 주입한다.
'스프링 프레임워크' 카테고리의 다른 글
The IoC Container [6] (0) | 2024.08.14 |
---|---|
The IoC Container [4] (0) | 2024.08.14 |
The IoC Container (2) (0) | 2024.08.09 |
메이븐 (Maven) Build System (0) | 2024.08.08 |
The IoC Container (1) (0) | 2024.08.08 |