[Spring] 의존관계 주입 및 빈 생명주기

bin1225·2025년 3월 8일
0

Spring

목록 보기
16/16
post-thumbnail

Java는 Spring이라는 말을 꽤 많이 접했는데, 왜 라는 질문에 답할 수 없었다.

그래서 Spring을 왜 사용하는가에 조금 더 초점을 맞춰가며 공부했다.

프레임워크란

프레임워크는 코드의 덩어리인 것 같다.
라이브러리와 마찬가지로 어떠한 기능을 수행할 수 있는 코드들의 집합체이다.

라이브러리 vs 프레임워크

라이브러리와 구분되는 점은 제어의 역전(IOC: Inversion of control)이다. 코드의 흐름을 프로그래머가 직접 제어하는 것이 아니라 프레임워크에서 제어하는 부분이 존재한다.

Spring에는 DI가 대표적이다. Spring은 Bean이라는 개념을 도입해서 객체들을 스캔하고 직접 의존관계를 주입한다. 프로그래머는 의존관계 주입 실행을 명시하지 않는다.

프레임워크의 역할

프레임워크는 프로그래머가 실제 비즈니스 로직에만 집중할 수 있도록 도와주는 역할이다.

예를 들어,
웹 애플리케이션을 개발할 때 모든 기능을 직접 개발해야 한다면

TCP/IP 패킷을 확인하고 제어하는 것부터 HTTP로 도착한 데이터를 분석하고 필요한 데이터를 추출하고 또 응답 결과를 생성해서 보내는 등

실제 웹 애플리케이션에서 수행하길 바라는 기능 외에 그 과정이 정상적으로 작동하는데 필요한 기능이 더 많다.

자주 쓰이는 기능들이 이미 구현이 되어 있기 때문에 반복되는 코드를 줄이고 개발 생산성을 높일 수 있다.

Dependency Injection

객체지향을 잘 활용하기 위한 원칙 5가지 (SOLID) 중

OCP, DIP를 지키기 위해서는 클래스 외부에서 의존관계를 관리해주어야 한다.

이런 구조를 직접 구현한다면 복잡한 상황이 발생한다.


public class AppConfig {

	public Class1 class1() {
    	return new Class1();
    }
    
    
	public Class2 class2() {
    	return new Class2(class1());
    }
    
    
	public Class3 class3() {
    	return new Class3(class2());
    }
}

이런 식으로 외부 클래스에서 직접 의존관계를 설정하고, 해당 클래스를 통해 인스턴스를 사용해야 한다.

이렇게 사용자가 직접 의존관계를 설정하는 경우, 새로운 클래스를 추가하고 사용할 때마다 의존관계를 새롭게 구성해야 한다.
그 과정에서 누락이나 잘못된 설정 등의 위험성도 존재한다.

Spring 핵심 기능으로 DI 컨테이너가 존재한다. DI 컨테이너를 이용함으로써 이러한 문제를 해결할 수 있다.

Bean과 의존관계 주입

DI 컨테이너에 등록하는 객체를 Spring Bean이라고 부른다.

Bean은 하나의 객체로써, 자동으로 인스턴스화 되고 조립되어 의존관계를 형성한다.

  1. Configuration 객체를 이용한 등록
@Confinguration
public class BeanConfiguration {

	@Bean
    public Clazz1 clazz() {
    	return new Clazz();
    }
}
  1. ComponentScan을 사용한 등록
@Component
public class Clazz {
	...
}
@ComponentScan
public class Application {
	public void static main() {
    	...
    }
}

@ComponentScan 어노테이션이 붙은 클래스가 실행되면 @Component 어노테이션이 붙은 클래스를 Bean으로 등록한다.

싱글톤 패턴

싱글톤이란 객체 인스턴스를 1개만 생성하여 공유하는 패턴이다.
객체에 유지되는 데이터가 없는 경우 싱글톤 패턴을 구현하는 것이 메모리 사용 측면에서 효율적이다.

직접 구현하는 방법은 생성자 접근을 막아두고, 미리 생성해둔 인스턴스를 반환하는 것이다.

직접 구현

public class Clazz {
		
     private Clazz clazz = new Clazz();
     
     private Clazz() {
     }
     
     public Clazz getInstance() {
     	return this.clazz;
     }
     
}

생성자의 접근 제어자를 private로 설정한다.
getInstance()로 미리 초기화된 인스턴스를 반환하여 사용한다.

싱글톤 패턴은 클래스에 유지되는 데이터가 존재하는 경우 의도와 다르게 작동할 가능성이 높다.

애플리케이션 특성 상 객체들을 싱글톤으로 유지하는 것이 효율적인 경우가 많다. 그런데 이렇게 모든 객체에 대해 싱글톤 패턴을 적용하는 것은 중복 코드가 많이 발생하며, 누락의 위험이 존재한다.

스프링 DI컨테이너를 사용하면 객체를 싱글톤으로 관리해준다.

생명주기 콜백

Spring은 Bean의 생명주기 (생성, 삭제) 에 맞춰 콜백 메서드를 제공한다.

@Component
public class helloBean {
	@PostConstruct
	public void postConstruct() {
		System.out.println("bean created");
	}

	@PreDestroy
	public void postConstruct() {
		System.out.println("bean destroy");
	}
}

@PostConstruct : Bean 생성 후 의존관계 주입 완료 시 호출

PreDestroy : 빈 삭제 직전 호출

직접 어노테이션으로 지정할 수 없는 메서드(ex: 수정 불가능한 라이브러리 메서드)의 경우는 다음과 같은 방법으로 지정할 수 있다.

public class BeanOne {

	public void init() {
		// initialization logic
	}
}

public class BeanTwo {

	public void cleanup() {
		// destruction logic
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public BeanOne beanOne() {
		return new BeanOne();
	}

	@Bean(destroyMethod = "cleanup")
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

@Bean어노테이션 속성을 활용하여 initMethod, destroyMethod 의 이름을 지정할 수 있다.

빈 스코프

빈 스코프는 빈의 생명주기를 의미한다.
직접 빈의 생명주기를 설정할 수 있다.
기본값은 singleton이다.

prototype scope

매 번 새로운 bean instance를 생성해서 반환한다.

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}

container의 getBean()메서드를 통해 받아온다.

주의할 점은 prototype bean과 singleton bean이 의존관계에 있을 때이다.


@Component
public class SingleTonBean {
	@Autowired
	private PrototypeBean prototypeBean;

	...
}

이 경우 PrototypeBean의 초기화는 SingletonBean의 생성 시점에 진행된다.
즉 SingletonBean이 인스턴스화 되고 호출될 때마다 새로운 PrototypeBean이 생성되지 않는다.

Provider를 사용해 해결

Provider는 지정한 Bean을 컨테이너에서 대신 찾아주는 기능을 가지고 있다.

Provider를 주입받아서 PrototypeBean이 필요한 시점에 직접 컨테이너에서 찾아서 사용한다.

@Autowired
private Provider<PrototypeBean> provider;

public int logic() {
 	PrototypeBean prototypeBean = provider.get();
 	prototypeBean.addCount();
 	int count = prototypeBean.getCount();
 	return count;
}

request scope

HTTP 요청이 들어올 때마다 생성된다.
SpringContext가 웹 관련 정보를 알고 있는 구현체일 경우만 사용할 수 있다.

하나의 httpRequest를 처리하는 과정에서 존재하며 공유된다.

3개의 댓글

comment-user-thumbnail
2025년 3월 9일

정리 훌륭하네... 실무에서는 스코프를 변경하는 일은 거의 없고 각 api 마다 다른 네트워크 설정 값 (read timeout, connection timeout) 이 필요해서 Configuration 을 통해 수동 bean 등록하고 사용하는 경우가 종종 있더라구

1개의 답글