DongDD's IT

[Spring] Dependency Injection(DI) 본문

프로그래밍/Spring

[Spring] Dependency Injection(DI)

DongDD 2019. 3. 18. 21:01

[Spring] Dependency Injection(DI)



Dependency Injection



DI 배경


- 컴포넌트를 생성자에서 직접 생성하는 방식 —> 생성한 클래스를 교체하는 것이 어려움

→ 클래스의 결합도가 높음

→ 클래스의 결합도를 낮추기 위해 생성자의 인수로 컴포넌트를 넘기고 초기화하는 방식이 있음

- 이러한 방식으로 클래스의 결합도를 낮추더라도 변경이 발생하는 경우 재작업을 피할 수 없음

→ 외부에서 컴포넌트를 생성하고 내부에서 사용 가능하게 해주는 DI(Injection) 개념 나옴

DI를 자동으로 처리하는 기반을 DI Container라고 부름



DI 장점


- 의존성 해결

- 인스턴스의 scope 관리

- AOP 기능 추가



IoC(Inversion of Control)


- 인스턴스 관리를 DI Container가 해줌(제어가 역전되었다고 표현)



DI Container



ApplicationContext


- Spring Framework에서의 DI Container


1
2
3
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
                                                    AppConfig.class);
applicationContext.getBean(aa.class);
cs


- AppConfig에 @Configuration과 @Bean을 사용해 bean 정의

- getBean을 이용해 Bean을 불러와 사용

- Look up : DI Container가 Bean을 찾는 작업



Bean 설정 방법


1. 자바 기반 설정

- @Configuration과 @Bean을 사용하여 설정


2. XML 기반 설정


3. Annotation 기반 설정

- @Component 등을 사용하여 설정

- ComponentScan : DI Container가 Component를 탐색하는 과정 (@ComponentScan("com.example.prj"), default = 설정 클래스가 들어있는 패키지 이하)

- Autowiring : DI 컨테이너가 자동으로 필요로 하는 의존 컴포넌트 주입(@Autowired)



의존성 주입 - 1


1. 설정자 기반 의존성 주입(Setter-based dependency injection)

1) 자바 기반 세터 인젝션

1
2
3
4
5
6
7
@Bean
TestBean testBean() {
    TestBeanImpl testBean = new TestBeanImpl();
    testBean.setTestBean1(testBean1());
    testBean.setTestBean2(testBean2());
    return testBean;
}
cs


2) 자바 기반 매개변수 세터 인젝션

1
2
3
4
5
6
7
@Bean
TestBean testBean(TestBean1 testBean1, TestBean2 testBean2) {
    TestBeanImpl testBean = new TestBeanImpl();
    testBean.setTestBean1(testBean1());
    testBean.setTestBean2(testBean2());
    return testBean;
}
cs


3) 어노테이션 기반 세터 인젝션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class TestBeanImpl implements TestBean {
    private TestBean1 testBean1;
    private TestBean2 testBean2;
 
   @Autowired
    public void setTestBean1(TestBean1 testBean1) {
        this.testBean1 = testBean1;
    }
 
   @Autowired
    public void setTestBean2(TestBean2 testBean2) {
        this.testBean2 = testBean2;
    }
}
 
 
cs


2. 생성자 기반 의존성 주입(Constructor-based dependency injection)

1) 어노테이션 기반 컨스트럭터 인젝션

1
2
3
@ConstructorProperties({"testBean1""testBean2"})
public TestBeanImpl(TestBean1 testBean1,  TestBean2 testBean2) {
}
cs



3. 필드 기반 의존성 주입(Field-based dependency injection)

- 생성자나 설정자 method를 사용하지 않고 DI Container를 통해 의존성을 주입하는 방식

- @Autowired 사용


1) 어노테이션 기반 필드 인젝션

1
2
3
4
5
6
7
8
9
10
11
@Component
public class TestBeanImpl implements TestBean {
 
    @Autowired
    TestBean1 testBean1;
 
    @Autowired
    TestBean2 testBean2;
}
 
 
cs


- Autowiring : @Bean이나 XML을 사용하지 않고 자동으로 주입


의존성 주입 - 2


1. Type 기반 → setter, constructor, field injection 사용 가능

- Bean을 찾지 못하는 경우, NoSuchBeanDefinitionException 발생(@Bean(required=null) 사용 → null)

- 같은 타입의 여러 Bean 존재할 경우, NoUniqueBeanDefinitionException

    → @Bean(name=""), @Qualifer("name") 사용

    → @Primary : 우선적으로 선택될 bean 설정

- Qualifier를 Annotation으로 구현


어노테이션 구현


1
2
3
4
5
6
7
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface TestAnnotation {
}
cs

-  @Target : 어노테이션 적용 위치 결정

-  @Retention : 어노테이션 적용 범위(어떤 시점까지 적용할지)

-  @Documented : 문서에도 어노테이션 정보 표시

-  @Inherited : 자식 클래스가 어노테이션 상속 받을 수 있음


2. Name 기반 → setter, field injection 사용 가능

- @Resource(name="") 사용

→ name이 없을 경우, 변수명이 name으로 지정

→ 해당 name이 없을 경우, type 기반으로 autowiring 시도


Component Scan


- Class Loader 스캔 → 클래스 발견 → DI Container에 등록


1. 기본 설정 컴포넌트 스캔

- Annotation이 붙은 클래스가 탐색 태상

- @ComponentScan(basePackages = "package"), @ComponentScan(basePackages = "package")

- 탐색 범위가 넓고 처리하는 시간이 길기 때문에 적절한 범위가 필요


2. 필터 적용 컴포넌트 스캔

- @ComponentScan(includeFilters ={ })

- useDefaultFilters = false : 기본 스캔 대상 제외

- excludeFilters={} : @Exclude 어노테이션이 설정된 컴포넌트를 제외


Bean Scope


- DI Container가 bean scope 관리

- DI Container가 관리하는 default scope는 single tone


1. Scope 종류

- singleton

- prototype

- request

- session

- globalSession

- application

- custom

-> @Bean, @Component 밑에 @Scope("type")로 Scope 설정


2. 서로 다른 bean scope 주입

- prototype의 bean을 singleton bean으로 주입할 경우, singleton으로 취급

- Lookup method injection으로 해결(prototype)

    - DI Container가 bean을 lookup하는 메소드를 만들고 메소드를 의존할 bean에게 주입

    - @Lookup annotation 이용

    

1) Lookup method injection

1
2
3
4
5
6
7
8
9
10
11
@Component
public class TestBeanImpl implements TestBean {
    public void test() {
        ProtoBean protoBean = protoBean();
    }
 
    @Lookup
    ProtoBean protoBean() {
        return null;
    }
}
cs

- value 속성에 bean 이름 지정(없을 경우, 메소드의 반환 값 타입을 보고 룩업 빈 결정)


2) Scoped Proxy(request, session scope, globalSession)

- prototype은 효율성 측면에서 lookup method injection 사용

- Bean을 proxy로 감싸고, proxy를 다른 bean에 주입, 주입된 bean에서 proxy 메소드를 호출하면 proxy 내부적으로 bean을 lookup하고 메소드 수행

- @Scope(proxyModes = '')

- proxyModes

- ScopedProxyMode.INTERFACES : 인터페이스 기반 proxy

- ScopedProxyMode.TARGET_CLASS : 서브클래스 기반 proxy


3. Custom Scope

- 사용자가 직접 정의하여 scope 사용

1
2
3
4
5
6
@Bean
static CustomScopeConfigurer customScopeConfigurer() {
    CustomScopeConfigurer configurer = new CustomScopeConfigurer();
    configurer.add("thread"new SimpleThreadScope());
    return configurer;
}
cs

- @Scope("thread")로 사용



Bean



Bean 생명주기


1. 초기화

- Bean 설정

- DI

- 생성 후 처리(@PostConstruct)


2. 사용


3. 종료

- 전처리(@PreDestroy, @Bean(destoryMethod="method_name")



Bean Config 분할

Bean Config 분할 이미지

- @Import를 통해 AConfig에서 BConfig, CConfig를 import하여 config 정의



Profile 별 Config


- phase : (test1, test2), test3, test4 


1. 자바 기반 Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  Java-based Configuration  //
@Configuration
@Profile({"test1","test2"})
public class test1Config {
    //config
}
 
@Configuration
@Profile({"test3")
public class test3Config {
}
 
@Configuration
@Profile({"test4")
public class test4Config {
}
cs



2. application.yml Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// application.yml file  //
---
spring:
    profiles: test1, test2
    //test1, test2 config
---
spring:
    profiles: test3
    //test3 config
---
spring:
    profiles: test4
    //test4 config
---
cs



3. 여러 개의 properties 파일 생성

- application-test1.properties

- application-test2.properties

- application-test3.properties

- application-test4.properties

-> application-${spring.profiles.active}.properties로 지정


Spring Boot Profile

- Spring Boot의 경우, edit configurations.. 에서 Active profiles 설정


Tomcat Profile

- Tomcat의 경우, VM options에 "-Dspring.profiles.active=test"와 같은 옵션을 주어 실행(또는 환경변수 설정)

Comments