1. IoC와 DI
IoC(Inversion of Control)은 객체 또는 프로그램의 일부에 대한 제어를 컨테이너 또는 프레임워크로 이전하는 방법이다.
IoC는 DI라고도 할 수 있는데, 이 프로세스는 객체가 생성자의 인자, 팩토리 메서드에 대한 인수 또는 객체 인스턴스가 팩토리 메서드로부터 생성되거나 반환된 후 객체 인스턴스에 설정된 속성을 통해서만 객체의 종속성을 정의하는 것이다.
이러한 구조의 장점
- 구현(implementation)으로 부터 작업 실행의 분리할 수 있다.
- 서로 다른 구현(implementation)으로 부터 쉽게 전환(switch)할 수 있다.
- 프로그램의 모듈성 (낮은 결합도)
- 컴포넌트를 구분하고, 종속성을 쉽게 바꿔가며 테스트를 쉽게 할 수 있다.
Factory Method (Pattern)
팩토리 메서드 패턴은 객체를 생성하기 위한 인터페이스 또는 추상 클래스를 정의하지만 실제 인스턴스화할 클래스(구체 클래스)는 인터페이스를 구현하거나 추상 클래스를 상속받는 하위 클래스에서 결정하도록 하는 패턴이다.
그럼 컨테이너가 빈(bean)을 생성할 때 이러한 종속성을 주입해준다.
여기서는 service 객체를 Controller 생성자에서 개발자가 직접 생성한다.
public class Controller {
private Service service;
public void Controller() {
this.service = new OrderService();
}
}
여기서는 Service 에 대한 구현체를 우리가 직접 지정하여 생성하지 않고, 컨테이너로부터 주입받는다.
public class Controller {
private Service service;
public void Controller(Service serivce) {
this.service = orderserivce;
}
}
2. IoC Container
IoC 컨테이너는 IoC를 구현하는 프레임워크의 공통적인 특성이다. Spring framework 에서는 ApplicationContext 인터페이스가 Ioc Container를 나타낸다. Spring Container는 bean들을 instantiating(인스턴스화), configuration(구성), assembling(조립) 의 책임이 있다.
Spring framework에는 ApplicationContext 인터페이스를 구현하는 다양한 구현체가 있지만, Web applications 에서는 WebApplicationContext가 사용된다.
컨테이너를 수동으로 인스턴스화 시키는 방법이다.
Application context = new ClassPathXmlApplicationContext("applicationContext.xml");
위의 예제에서는 metadata를 사용해서 Spring container가 어떻게 객체들을 instantiating, configuration, assembling 하라고 알려준 것이다. 그러면 container는 런타임시에 해당 metadata를 읽어서 사용한다.
스프링에서는 생성자, setter, field를 통해 의존성 주입(DI)를 할 수 있다.
3. 필드 기반 의존성 주입
public class Store {
@Autowired
private Item item;
}
필드 기반의 경우 @Autowired 어노테이션을 이용하여 의존성을 주입할 수 있다.
필드 기반 의존성 주입은 간결해보이지만, 몇 가지 이유로 추천하지 않는 방법이다.
- 순환참조가 발생할 수 있다.
- 리플렉션을 사용하여 의존성을 주입하는데, 이는 생성자 기반, 세터 기반 의존성 주입보다 비용이 비싸다.
- 프레임워크 종속적인 코드가 된다.
4. 세터 기반 의존성 주입
class Store {
private Item item;
public void setItem(Item item) {
this.item = item;
}
}
장점
- 객체를 재구성하거나 의존성을 재주입 할 수 있다.
단점
- 순환참조가 발생할 수 있다.
- 빈을 생성하는 필수조건이 아니므로 NPE가 발생할 수 있다.
5. 생성자 기반 의존성 주입
class Store {
private Item item;
//@Autowired
public Store(Item item) {
this.item = item;
}
}
장점
- 순환참조가 발생하지 않는다 -> 발생할 수 있지만 컴파일 시점에 에러가 발생한다.
6. 세터 기반 vs 생성자 기반
spring 공식적으로 생성작 기반 의존성 주입을 권장한다.
- 어플리케이션의 컴포넌트를 불변 객체로 구현할 수 있게 해준다.
- 필요한 종속성이 널이 안되게 해준다.
- 생성자 기반 컴포넌트들은 항상 완전한 초기 상태로 호출된다.
부가적으로, 많은 수의 생성자 인수를 갖는 코드는 나쁜 코드(bad code semll)이며, 이는 클래스가 너무 많은 책임을 가지고 있을 가능성이 있다.
7. 순환참조
순환참조는 클래스 A에서 클래스 B가 필요하고, 클래스 B는 클래스 A가 필요하여 서로가 서로를 의존성 주입하고 있는 상태다.
@Component
public class AComponent {
private BComponent bComponent;
public AComponent(BComponent bComponent) {
this.BComponent = bComponent;
}
}
@Component
public class BComponent {
private AComponent aComponent;
public BComponent(AComponent aComponent) {
this.aComponent = aComponent;
}
}
00:25:44.338 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@1b454c0
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.2)
2023-01-24 00:25:44.925 INFO 12208 --- [ restartedMain] duckjee.web.WebApplication : Starting WebApplication using Java 11.0.12 on DESKTOP-F6R7GOL with PID 12208 (C:\DUCK\duckjee\web\out\production\classes started by DUCKGYU in C:\DUCK\duckjee\web)
2023-01-24 00:25:44.927 INFO 12208 --- [ restartedMain] duckjee.web.WebApplication : The following 1 profile is active: "db"
2023-01-24 00:25:45.001 INFO 12208 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2023-01-24 00:25:45.001 INFO 12208 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2023-01-24 00:25:47.307 INFO 12208 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8083 (http)
2023-01-24 00:25:47.324 INFO 12208 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-01-24 00:25:47.324 INFO 12208 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
2023-01-24 00:25:47.436 INFO 12208 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-01-24 00:25:47.436 INFO 12208 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2434 ms
2023-01-24 00:25:47.507 WARN 12208 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AComponent' defined in file [C:\DUCK\duckjee\web\out\production\classes\duckjee\web\service\AComponent.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BComponent' defined in file [C:\DUCK\duckjee\web\out\production\classes\duckjee\web\service\BComponent.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AComponent': Requested bean is currently in creation: Is there an unresolvable circular reference?
2023-01-24 00:25:47.510 INFO 12208 --- [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2023-01-24 00:25:47.533 INFO 12208 --- [ restartedMain] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-01-24 00:25:47.567 ERROR 12208 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| AComponent defined in file [C:\DUCK\duckjee\web\out\production\classes\duckjee\web\service\AComponent.class]
↑ ↓
| BComponent defined in file [C:\DUCK\duckjee\web\out\production\classes\duckjee\web\service\BComponent.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
Process finished with exit code 0
클래스 AComponent와 BComponent가 생성자 주입을 통해 서로 주입되도록 구성되어 있는 경우 Spring IoC 컨테이너가 실행되면서(컴파일시) BeanCurrentInCreation 예외를 발생시켜준다.
생성자 주입이 아닌 field 인젝션, 세터 인젝션을 이용하거나 @Lazy 를 사용하면 이 문제를 해결할 수 있다.
하지만 가장 좋은 방법은 재설계이다.
순환 참조가 발생하는 것은 설계의 문제가 있고 책임 분리가 잘 되지 않았다는 것이다. 컴포넌트들의 계층 구조가 잘 설계되고 순환 참조가 필요하지 않도록 재설계해야한다.
참조
- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-metadata
- https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring