2025. 6. 20. 15:03ㆍSpring Framework/Spring IoC
일반적인 어플리케이션 시나리오에서, Spring 컨테이너에 등록된 대부분의 Bean은 싱글톤이다. 싱글톤 Bean이 다른 싱글톤 Bean과 협업하거나, 프로토타입(Non-singleton) Bean이 또 다른 프로토타입 Bean과 협업하는 경우, 보통은 한 Bean을 다른 Bean의 프로퍼티로 정의하여 의존성을 처리한다.
🔨 Method Injection란?
Method Injection은 싱글톤 빈이 프로토타입 빈을 참조할때, 새로운 인스턴스를 주입받아야 하는 경우 발생하는 문제를 해결하는 주입방법이다.
❔ 왜 필요할까
Spring의 대부분의 빈은 디폴트로 싱글톤으로 관리된다. 하지만 때때로 싱글톤 빈이 프로토타입 빈을 사용해야 하는 상황이 발생할 수 있다.
| 상황 | 문제 |
| 매번 새로운 인스턴스가 필요할 때 | 생성자/필드 주입은 항상 같은 객체 주입됨 |
| 선택적 의존성 필요할 때 | 생성자에 넣으면 너무 복잡해짐 |
| 런타임에 객체를 바꿔야 할 때 | 생성자 주입은 고정적 |
💥 문제 상황 발생 💥
싱글톤 빈이 프로토타입 빈을 사용해야 한다고 가정
싱글톤 빈은 한 번만 생성되므로, 프로토타입 빈도 한번만 주임됨
config 패키지
@Configuration
@ComponentScan(basePackages = "dependency.methodinjection.lookupexample.service")
public class AppConfig {}
service 패키지
@Component
@Scope("prototype")
public class Task {
public void execute() {
System.out.println("Executing task");
}
}
@Component
public class TaskService {
private final Task task;
public TaskService(Task task) {
this.task = task;
}
public void run() {
task.execute();
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
TaskService taskService = context.getBean(TaskService.class);
// 싱글톤 빈은 한번만 생성되므로 PrototypeBean도 한 번만 주입됨
// → 새로운 인스턴스를 받을 수 없음
// @Lookup 안쓸시
taskService.run(); // Task@1e1a0406
// TaskService@3cebbb30
taskService.run(); // Task@1e1a0406
// TaskService@3cebbb30
taskService.run(); // Task@1e1a0406
// TaskService@3cebbb30
}
}
실행시
Executing task // Task@1e1a0406
// TaskService@3cebbb30
Executing task // Task@1e1a0406
// TaskService@3cebbb30
Executing task // Task@1e1a0406
// TaskService@3cebbb30
→ run()을 여러 번 호출해도 항상 같은 객체 사용됨
→ 매번 새로운 객체 사용과 불일치(문제 발생!!)
🛠️ 해결 방법 1 : ApplicationContext(컨테이너)를 활용한 Bean 조회(❌ 비추천!! ❌)
Spring 컨테이너를 직접 사용하여 필요할 때마다 새로운 빈을 가져오는 방식
@Component
public class TaskService {
private ApplicationContext context;
@Autowired
public TaskService(ApplicationContext context) {
this.context = context;
}
public void run() {
Task task = context.getBean(Task.class); // 매번 새 Bean 요청
task.execute();
}
}
실행시
Executing task // Task@36916eb0
// TaskService@7bab3f1a
Executing task // Task@437da279
// TaskService@7bab3f1a
Executing task // Task@23c30a20
// TaskService@7bab3f1a
- ⚠️ DI 원칙 위배 (IOC 컨테이너를 직접 참조)
- ❌ Spring 프레임워크에 종속적(비즈니스 로직이 Spring API에 의존하게 됨)
- ❌ 테스트 및 유지보수 어려움
- ✔️ 구현은 간단해서 빠른 해결이 필요할 땐 임시로 사용 가능
🛠️ 해결 방법 2 : Lookup Method Injection (@Lookup 사용)
Spring 컨테이너가 특정 메서드를 오버라이드하여, 새로운 프로토타입 빈을 반환하도록 한다.
@Component
public class TaskService {
public void run() {
Task task = new Task(); // 매번 새로운 Task 인스턴스 생성
task.execute();
}
@Lookup
protected Task getTask() {
// Spring이 런타임에 이 메서드를 오버라이드하여 새로운 Task 인스턴스를 반환함
return null;
}
}
실행시
Executing task // Task@36916eb0 TaskService$$SpringCGLIB$$0@21282ed8 프록시 클래스
Executing task // Task@7bab3f1a
Executing task // Task@437da279
💡 @Lookup을 쓰는 이유
| 프로토타입 빈의 의미를 살리기 위해 | @Autowired로는 싱글톤처럼 동작해버림 |
| 매번 새로운 인스턴스를 얻기 위해 | @Lookup 메서드를 호출할 때마다 새로운 빈 반환 |
| 런타임 메서드 오버라이딩 제공 | Spring이 프록시를 만들어 해당 메서드를 오버라이딩함 |
| 코드 의존성 최소화 | ApplicationContext나 ObjectProvider 없이 깔끔한 방식 |
'Spring Framework > Spring IoC' 카테고리의 다른 글
| DI에서 Depend on, Lazy, Autowiring 사용 (0) | 2025.06.18 |
|---|---|
| Dependency Injection(DI, 의존성 주입) (0) | 2025.06.18 |
| Bean의 인스턴스화 방법 (0) | 2025.06.11 |
| Bean 이름 지정 (Naming Beans) (0) | 2025.06.11 |
| Bean Definition(정의) (0) | 2025.06.11 |