반응형

Dependency Injection

설명

  • A가 B에 의존할 경우 B가 변경이 발생하면 A에 영향을 미침
  • 직접 의존 관계를 맺는 것보다 인터페이스를 통해 의존 관계를 맺어 결합도를 낮추는 것이 중요함
  • 컨테이너는 의존 관계를 동적으로 설정해주는 역할을 수행
  • 의존성 주입 방법에는 생성자 주입, 필드 주입, setter 주입이 있음

Constructor-Based Dependency Injection

설명

  • 생성자를 통해 의존성을 주입받는 방식 (권장)
  • 장점
    • 필수적으로 사용해야하는 의존 객체가 없이는 인스턴스를 만들지 못하도록 강제할 수 있음
    • 순환 참조 의존성을 빠르게 확인할 수 있음
    • 의존 객체 필드를 final로 지정하여 불변함을 강제화할 수 있음
    • 테스트 코드 작성시 생성자를 통해 의존성 주입이 쉬움
    • 의존 관계가 복잡해지는 것을 쉽게 알아차려 리팩토링 시점(Single Response Principle이 깨지는 시점)을 확인하기 쉬움
  • 단점
    • 어쩔 수 없이 순환 참조가 필요한 경우 사용 불가

기본 예시

@Service
public class StudentService {
    private final UserService userService;

    public StudentService(UserService userService) {
        this.userService = userService;
    }
}

순환 참조 예시

@Service
public class StudentService {
    private final UserService userService;

    public StudentService(UserService userService) {
        this.userService = userService;
    }
}
@Service
public class UserService {
    private final StudentService studentService;

    public UserService(StudentService studentService) {
        this.studentService = studentService;
    }
}
  • 서버 실행시 아래 로그 발생 후 종료
***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  studentService defined in file [D:\workspace\spring-example\target\classes\com\example\springexample\StudentService.class]
↑     ↓
|  userService defined in file [D:\workspace\spring-example\target\classes\com\example\springexample\UserService.class]
└─────┘

Field-Based Dependency Injection

설명

  • 필드에 @Autowired 어노테이션을 달아 주입 받는 방식 (비권장)
  • 장점
    • 가장 간단하게 사용 가능
  • 단점
    • 의존 관계 설정을 쉽게 할 수 있기 때문에 의존 관계가 복잡해질 수 있음
    • DI Container와 강한 결합을 갖는 방법이기 때문에 컨테이너 외의 다른곳에서 사용시 주입하기 어려움
    • 단위 테스트시 의존성 주입이 어려움
    • 의존성 주입 대상 필드를 final로 지정할 수 없어 의존 객체가 변할 수 있는 위험이 있음

기본 예시

@Service
public class StudentService {
    @Autowired
    private UserService userService;
}

순환 참조 예시

@Service
public class StudentService {
    @Autowired
    private UserService userService;

    public void test() {
        userService.test();
    }
}
@Service
public class UserService {
    @Autowired
    private StudentService studentService;

    public void test() {
        studentService.test();
    }
}
@Component
public class TestComponent {
    @Autowired
    private StudentService studentService;

    @PostConstruct
    public void init () {
        studentService.test();
    }
}
  • 서버 실행은 문제 없이 진행되고, 순환 호출 관계에 있는 메소드 실행시 아래 오류 발생
2020-12-11 20:38:16.772 ERROR 16936 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testComponent': Invocation of init method failed; nested exception is java.lang.StackOverflowError
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.1.jar:5.3.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429) ~[spring-beans-5.3.1.jar:5.3.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1780) ~[spring-beans-5.3.1.jar:5.3.1]

Setter-Based Dependency Injection

설명

  • setter 메서드에 @Autowired 어노테이션을 달아 주입 받는 방식
  • 장단점 모두 필드 주입 방식과 동일함
  • 다만 setter 주입 방식은 일반 자바 문법을 사용하는 방식이기 때문에 DI Container 종류 상관 없이 사용 가능하다는 장점이 추가됨

기본 예시

@Service
public class StudentService {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

DI Annotations

설명

  • 주입 받을 대상에 어노테이션을 달아 설정하는 방식을 사용함
  • 종류로는 @Autowired, @Resource, @Inject가 있음

차이점

  • 지원
    • @Resource : java
    • @Autowired : spring
    • @Inject : javax
  • 사용 가능 위치
    • @Resource : 필드, setter
    • @Autowired : 생성자, 필드, setter
    • @Inject : 생성자, 필드, setter
  • Bean 강제 지정 방식
    • @Resource : @Resource(name = "id")
    • @Autowired : @Autowired @Qualifier("id")
    • @Inject : @Inject @Named("id")
  • Bean 검색 순위
    • @Resource : 이름 → 타입
    • @Autowired : 타입 → 이름
    • @Inject : 타입 → 이름

테스트 공통코드

public interface Animal {
}
public class Dog implements Animal {
}
public class Cat implements Animal {
}

@Resource 테스트

  • 이름 → 타입순으로 빈을 탐색하기 때문에 아래같은 경우 찾은 빈이 다른 타입을 가지므로 오류 발생
@Configuration
public class AnimalConfig {
    @Bean
    public Cat cat() {
        return new Cat();
    }

    @Bean
    public Dog dog() {
        return new Dog();
    }
}
@Service
public class AnimalService {
    @Resource
    private Dog cat; // error
}
  • cat이라는 이름의 빈이 없으므로 타입 탐색에 의해 주입하기 때문에 오류 발생 없이 Dog 타입 객체가 주입된다.
@Configuration
public class AnimalConfig {
    @Bean
    public Dog dog() {
        return new Dog();
    }
}
@Service
public class AnimalService {
    @Resource
    private Dog cat; // Dog 타입 객체
}

@Autowired 테스트

  • 타입 → 이름순으로 매칭된 객체를 주입하기 때문에 아래 케이스는 Dog 객체가 주입됨
@Configuration
public class AnimalConfig {
    @Bean
    public Cat cat() {
        return new Cat();
    }

    @Bean
    public Dog dog() {
        return new Dog();
    }
}
@Service
public class AnimalService {
    @Autowired
    private Dog cat; // Dog 객체
}

참고

반응형

'Development > Spring' 카테고리의 다른 글

[Spring] ORM (with JPA, Hibernate)  (0) 2020.12.27
[Spring] WebSocket  (0) 2020.12.27
[Spring] Distributed Lock (with MySQL, Redis)  (4) 2020.12.27
[Spring] Transactional  (0) 2020.12.27
[Spring] AOP  (0) 2020.12.27

+ Recent posts