Dependency Injection(의존관계 주입)이란?
이 글은 IoC , Dependency 의 개념을 알고있다는 전제하에 작성된 글입니다.
원활한 이해를 위해서 아래글을 읽어주세요
https://masiljangajji-coding.tistory.com/51
IoC(Inversion Of Control)란
IoC/DI(Inversion Of Control/Dependency Injection)란 IoC(Inversion Of Control)란? IoC는 제어의 역전을 뜻합니다. 제어의 역전.. 제어가 역전된다.. 이게 어떤 의미일까요? 기존의 프로그램은 구현 객체가 프로그램
masiljangajji-coding.tistory.com
https://masiljangajji-coding.tistory.com/52
Dependency(의존관계)란?
Dependency(의존관계)란? 의존관계는 코드에서 두 모듈간의 연결을 의존관계라 합니다. 객체지향언어에서 두 클래스 간의 관계를 말하기도 합니다. 의존관계의 종류는 크게 4가지가 존재합니다. Dep
masiljangajji-coding.tistory.com
Spring Bean과 Context
Spring Bean
Spring Bean은 Spring Container에 등록된 객체를 의미합니다.
Java Beans 와는 다른것으로 Spring FrameWork에서 중요하게 관리하는 객체로 이해하면 됩니다.
Java Beans 의 조건
- public default (no argument) constructor
- getter/setter
- implement java.io.Serializable
Container에 Bean이 등록되기 위한 조건
- name(식별할 이름)
- class(타입)
- object(객체)
Java Beans와 다른 별개의 개념입니다.
Container/Context
Container는 Interface로 일종의 개념에 해당합니다.
프로그램의 구성 요소들을 담고 관리하는 환경을 나타내며 DI Container , 서블릿 컨테이너 , 웹 컨테이너 등등이 존재합니다.
Context는 Container라는 Interface의 구현체입니다.
(Container = DI Container = IoC Container)
스프링 프레임워크에서는 빈(Bean) 설정과 관련된 정보를 나타내는 Metadata에 따라
어떤 클래스의 ApplicationContext를 이용할지 정해집니다.
다음의 Git Repo를 참고하시길 바랍니다.
https://github.com/masiljangajji/DI_Exapmle
GitHub - masiljangajji/DI_Exapmle
Contribute to masiljangajji/DI_Exapmle development by creating an account on GitHub.
github.com
XML 을 이용한 DI
xml 파일을 이용해 의존관계를 주입하는 방식입니다.
Context에 등록을 돕는 메타데이터 형식이 xml형식이며 ClassPathXmlApplicationContext 클래스를 사용합니다.
<bean id="english" class="com.springframework.core.practice.language.domain.English"/>
id는 Bean에 등록될 이름을 , class는 클래스의 경로를 나타냅니다
이 코드는 다음과 같습니다.
- english라는 이름으로
- com.springframework.core.practice.language.domain.English 경로에있는
- 클래스파일을 reflection으로 읽어 객체를 생성하고 컨테이너에 등록해
public class XmlMain {
public static void main(String[] args) {
// 컨텍스트 등록을 돕는 메타데이터로 xml 선택시 ClassPathXmlApplicationContext 사용
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
// resources/beans.xml 파일을 읽어서 컨테이너에 객체 등록하겠다.
"beans.xml")) {
English english = context.getBean("english", English.class);
Korean korean = context.getBean("korean", Korean.class);
english.hello();
korean.hello();
}
}
}

korean은 컨테이너에 등록이 안됐기 때문에 다음과 같은 에러가 발생 No bean named 'korean' available
(English와 같은 방식으로 xml에 추가하면 에러가 해결됩니다.)
생성자 주입
public class LanguageSpeakService {
private final Language language;
public LanguageSpeakService(Language language) {
this.language = language;
}
public void speak() {
System.out.println("Speak Service");
language.hello();
}
}
LanguageSpeakService 클래스는 Language에 대한 의존관계를 갖습니다.
Field로 갖고있는 타입이 Interface기 때문에 실질적인 동작은 Language의 SubType인
English,Korean 중 하나를 받아 동작할 것입니다.
따라서 기존과 같은 방법으로 DI시켜주게 된다면
LanguageSpeakService languageSpeakService =
context.getBean("languageSpeakService", LanguageSpeakService.class);
<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService"/>
스프링은 Service가 필요한 Language가 English , Korean 중 무엇인지 알 수 없음으로 에러가 발생하게 됩니다.
이런 경우 Constructor Injection(생성자 주입) 방식을 사용합니다.
<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
<constructor-arg ref="korean"/>
</bean>
이 코드는 다음과 같습니다.
- languageSpeakService라는 이름으로
- com.springframework.core.practice.language.service.LanguageSpeakService 경로에있는
- 클래스파일을 reflection으로 읽어 객체를 생성할때 korean 이름으로 등록된 Bean을 참고해
- 만들어진 languageSpeakService 객체를 컨테이너에 등록해
public static void main(String[] args) {
// 컨텍스트 등록을 돕는 메타데이터로 xml 선택시 ClassPathXmlApplicationContext 사용
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
// resources/beans.xml 파일을 읽어서 컨테이너에 객체 등록하겠다.
"beans.xml")) {
English english = context.getBean("english", English.class);
Korean korean = context.getBean("korean", Korean.class);
english.hello();
korean.hello();
LanguageSpeakService languageSpeakService =
context.getBean("languageSpeakService", LanguageSpeakService.class);
languageSpeakService.speak();
}
생성자 주입 사용시 코드가 문제없이 동작하게 됩니다.
setter 주입
public class LanguageSpeakService {
private Language language;
// setter 주입을 위한 기본 생성자
public LanguageSpeakService(){
}
public LanguageSpeakService(Language language) {
this.language = language;
}
public void setLanguage(Language language) {
this.language = language;
}
public void speak() {
language.hello();
}
}
<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
<property name="language" ref="english"/>
</bean>
이 방식은 language 에 대한 표준 명명규칙을 따르는 set메서드를 찾아갑니다.
실질적으론 setLanguage 메서드를 찾아 동작하는 것이죠 따라서 name을 바꾼다면 정상동작 하지 않습니다.
<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
<property name="language2" ref="english"/>
</bean>

표준 명명규칙을 따르지 않기 떄문에 오류가 발생 (property name 을 language로 변경하면 정상동작 합니다.)
public void setLanguage2(Language language) {
this.language = language;
}
<bean id="languageSpeakService" class="com.springframework.core.practice.language.service.LanguageSpeakService">
<property name="language2" ref="english"/>
</bean>
이렇게 set메서드 이름을 맞춰줄시 정상동작 합니다.
(name = setLanguage2 를 부르겠다 , 넣어줄 인자는 english 이름을 가진 빈)
이렇듯 setter주입은 명명규칙을 지켜줘야 하며 기본 생성자가 필요합니다.
Autowired Injection
autowired injection 은 다음의 방식을 사용할 수 있습니다.
- byType
- byName
<bean id="koreanChef" class="com.springframework.core.practice.domain.chef.KoreanChef"/>
<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService" autowire="byType"/>
public class ChefCookService {
private Chef chef;
public ChefCookService() {
}
public void setChef(Chef chef) {
this.chef = chef;
}
public void makeFood() {
chef.cook();
}
}
autowired byType설정을 사용하려면 일치하는 빈이 하나여야 합니다.
지금은 일치하는 빈이 KoreanChef 하나기 때문에 정상동작 합니다.
하지만 AmericanChef가 추가된다면 일치하는 빈에 중복이 생기며 에러가 발생합니다.
<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService" autowire="byName"/>
// 양식 조리 출력
public void setAmericanChef(Chef chef) {
this.chef = chef;
}
// 한식 조리 출력
public void setKoreanChef(Chef chef) {
this.chef = chef;
}
byName을 사용하게되면 set메서드의 이름을 따라갑니다.
XML 이용한 DI With Annotation
XML 방식으로 Bean 의존성 주입을 Annotation으로 구현할 수 있습니다.
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
(Autowired는 생성자 , 메서드 , 파라미터 , 필드 등 대부분에 사용가능)
<bean id="koreanChef" class="com.springframework.core.practice.domain.chef.KoreanChef"/>
<bean id="americanChef" class="com.springframework.core.practice.domain.chef.AmericanChef"/>
<bean id="chefCookService" class="com.springframework.core.practice.service.ChefCookService"/>
public class ChefCookService {
//// Autowired 어노테이션 기반 설정
@Autowired
// americanChef 이름을 가진 빈을 사용하겠다
@Qualifier("americanChef")
private Chef chef;
public ChefCookService() {
}
public void makeFood() {
chef.cook();
}
}
Annotation 을 이용하지만 XML방식을 기반으로 두고있기 떄문에
등록하고자 하는 객체들은 기존과 동일하게 전부 XML 파일안에 정의되있어야 합니다.
이러한 방법은 설정을 아예 분리함으로써 프레임워크와의 의존성을 최소화합니다.
하지만 만약 클래스가 5천개쯤 된다면?
지금은 프로그램이 간단해 xml을 읽고 설정하는게 쉽지만 프로그램이 커지는 경우 XML방식은 큰 부담이 됩니다.
따라서 프레임워크와의 의존성이 강화되더라도 순수 자바코드로 해결하는 Java Configuration 방식이 도입됩니다.
Java Configuration 을 이용한 DI
기존에는 XML파일을 메타데이터로 사용했지만 이제는 순수자바를 사용하기 떄문에
AnnotationConfigApplicationContext를 사용합니다.
Java Configuration 이용한 방법은 크게 2가지가 존재합니다.
- 자바코드로 Bean등록
- Component Scan
자바코드로 Bean등록
// 이건 설정하는 파일이야
@Configuration
// JavaConfiguration 에서 XML 설정을 사용할 수 있습니다.
@ImportResource("classpath:/beans.xml")
public class JavaConfig {
@Bean
public AmericanChef americanChef() {
return new AmericanChef();
}
@Bean
public KoreanChef koreanChef() {
return new KoreanChef();
}
// 메서드 주입방식이라 합니다.
@Bean
ChefCookService chefCookService() {
return new ChefCookService(americanChef());
}
}
여기서 Bean에 등록될 이름은 메서드의 이름과 같습니다.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.springframework.core.practice");
이 코드는 다음과 같습니다.
- com.springframework.core.practice 경로 아래에있는 모든 파일에서
- @Configuration , @Component , @Controller , @Service , @Repository 에 해당하는
- 어노테이션이 붙은 클래스들을 자동으로 빈에 등록해.
Component Scan
컴포넌트 스캔은 다음의 어노테이션을 포함하는 클래스를 읽어 자동으로 빈에 등록시킵니다.
- @Component : 컴포넌트 스캔에서 사용
- @Controller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
(Configuration,Controller,Service,Repository 모두 내부적으로 @Component을 상속하고있습니다.)
@Component
public class English implements Language{
@Override
public void hello() {
System.out.println("Hello");
}
}
@Component
public class Korean implements Language {
@Override
public void hello() {
System.out.println("안녕하세요");
}
}
생성자 주입
@Component
public class LanguageSpeakService {
private final Language language;
// 생성자 주입
@Autowired
public LanguageSpeakService(@Qualifier("english") Language language) {
this.language = language;
}
public void speak() {
language.hello();
}
}
생성자를 이용하기 떄문에 Field를 final로 선언 가능해 불변성을 지킬수 있습니다.
가장 권고되는 주입방식입니다.
setter 주입
@Component
public class LanguageSpeakService {
private Language language;
// setter 주입
@Autowired
public void setLanguage(@Qualifier("english") Language language) {
this.language = language;
}
public void speak() {
language.hello();
}
}
기본생성자 필요 , final 선언 불가
필드주입
@Component
public class LanguageSpeakService {
// 필드주입
@Autowired
@Qualifier("english")
private Language language;
public void speak() {
language.hello();
}
}
코드가 가장 간결하지만 외부에서 변경이 불가능해 테스트가 어렵습니다.
또한 setter와 마찬가지로 final 선언이 불가합니다.
(이러한 단점들 때문에 거의 사용하지 않음이 권고됩니다.)
Bean Vs Component
자바코드를 이용한 Bean등록은 주로 System 전체에서 공통적으로 사용되야 하는 것들을 대상으로 합니다.
예를들어 전체에서 공통적으로 사용되는 Thread Pool , Connection Pool의 경우
설정파일(JavaConfig.class)에 모아서 셋팅하게되면 "아 이것들은 공통적으로 전체 System에서 사용하는 구나" 이런 이해를 돕습니다.
반대로 Business Model들은 각각의 것들이 고유한 성질을 갖습니다.(ChefCookService , LanguageSpeakService...)
이런경우 @Service , @Controller 같은 Component Scan 방식을 이용합니다.
도움이 되셨으면 좋겠습니다.
'Spring > Spring Core' 카테고리의 다른 글
Dependency(의존관계)란? (1) | 2024.01.04 |
---|---|
IoC(Inversion Of Control)란 (1) | 2024.01.02 |