DI, IoC

Jung·2021년 11월 10일
0

TIL

목록 보기
49/77
post-thumbnail

스프링을 사용하면 DI(Dependency Injection), IoC(Inversion of Controll)라는 용어를 많이 들어봤을 것이다.

IoC(Inversion of Control, 제어의 역전): 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것, 프레임워크는 IoC라는 특징을 갖는다. 프레임워크가 프로그램의 흐름을 제어하기 때문!

DI(Dependency Injection, 의존관계/의존성 주입)라는 것은 스프링에서만 사용되는 것은 아니다.

JAVA로 예를 들겠다 !

JAVA

✅ v1: DI 적용 X

Domain

public class Person {

    private Long id;
    private String name;
    private int age;

    public Person(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Repository

public class PersonRepository {

    private static Map<Long, Person> store = new HashMap<>();

    public void save(Person person) {
        store.put(person.getId(), person);
    }
}

DI에 집중하기 위해 DB에 저장하지 않고 HashMap을 사용했다.
TMI - HashMap을 사용하면 동시성 이슈가 발생할 수 있기 때문에 실무에서는 ConcurrentHashMap을 사용한다고 한다.

  • ConcurrentHashMap: Hashtable 클래스의 단점을 보완하면서 멀티 쓰레드 환경에서 사용할 수 있도록 나온 클래스

Service

public class PersonService {

    private final PersonRepository personRepository = new PersonRepoitory();

    public void join(Person person) {
        personRepository.save(person);
    }
}

Application

public class PersonApp {

    public static void main(String[] args) {
        PersonService personService = new PersonService();
        Person person = new Person(1L, "KJ", 25);
        personService.join(person);
        
        System.out.println(person.toString()); // Person{id=1, name='KJ', age=25}

    }
}

PersonService 클래스에서 PersonRepository 클래스 객체를 사용해야 하는데 PersonService 클래스에서 PersonRepository 객체를 생성하게 되면 이 둘 사이에 강한 결합이 맺어진다. 결합도가 강해지는 건 좋은 방법이 아니다. 결합도는 낮게, 응집도는 높게 구성하는 것이 좋은 방법이다.

기존의 강결합을 생성자를 통한 DI로 약하게 만들어보자 !

✅ v2: DI 적용

Domain

위와 같으므로 생략

Repository

위와 같으므로 생략

Service

public class PersonService {

    private final PersonRepository personRepository;

    public PersonService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public void join(Person person) {
        personRepository.save(person);
    }
}

PersonService 클래스의 생성자를 만들었다. 생성자만 만들면 어떡하라고?? 외부에서 뭐라도 넣어줘야 되지 않나??
그렇다! AppConfig 클래스에서 주입시켜줄 것이다.

💉 AppConfig 💉

public class AppConfig {

    public PersonService personService() {
        return new PersonService(new PersonRepository());
    }

}

Application

public class PersonApp {

    public static void main(String[] args) {
    
        AppConfig appConfig = new AppConfig();
        PersonService personService = appConfig.personService();

        Person person = new Person(1L, "KJ", 25);

        personService.join(person);

        System.out.println(person.toString()); // Person{id=1, name='KJ', age=25}
        
    }
}

이처럼 애플리케이션 실행 시점(런타임)에 외부(AppConfig 클래스)에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 DI(의존관계 주입)이라 한다.
이렇게 함으로써 이전에 강했던 결합이 약해졌다 !!


Spring으로 전환

AppConfig

@Configuration
public class AppConfig {

    @Bean  // Bean은 스프링 컨테이너에 등록이 된다
    public PersonService personService() {
        return new PersonService(new PersonRepository());
    }
    
    /*
     * 이 주석 부분은 위 코드와 같다
    @Bean
    public PersonService personService() {
        return new PersonService(personRepository());
    }

    @Bean
    public PersonRepository personRepository() {
        return new PersonRepository();
    }
    */

}

AppConfig라는 클래스를 생성했다.
@Configuration Annotation을 추가한다. 하나 이상의 @Bean을 제공하는 클래스의 경우에는 @Configuration을 넣는다.
스프링에서 기본적으로 Bean 이름은 메서드명(personService)으로 등록된다.
@Bean(name="빈 이름")을 통해 직접 이름을 정해줄 수도 있다.

Application

public class PersonApp {

    public static void main(String[] args) {
    
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        // AppConfig 클래스의 빈을 스프링 컨테이너에 등록해 관리한다.
        PersonService personService = ac.getBean("personService", PersonService.class);
        // personService이라는 이름을 가진 빈을 가져와 변수에 할당

        Person person = new Person(1L, "KJ", 25);

        personService.join(person);

        System.out.println(person.toString()); // Person{id=1, name='KJ', age=25}
        
    }
}

여기서 나오는 ApplicationContext가 바로 IoC 컨테이너(DI 컨테이너, 스프링 컨테이너)다. 스프링 컨테이너는 Annotation 기반의 자바 설정 클래스로 만들 수 있고, XML을 기반으로도 만들 수 있다.
위에 있는 AppConfig가 Annotation 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이고, 아래처럼 XML로도 만들 수 있다.

AppConfig - XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="personRepository" class="com.kjkim.kj.PersonRepository" />

    <bean id="personService" class="com.kjkim.kj.PersonService">
        <constructor-arg name="personRepository" ref="personRepository" />
    </bean>
    
</beans>

XML로 Bean 등록을 했을 시 Application 코드도 아래처럼 변경해야 한다.

Application - XML

public class PersonApp {

    public static void main(String[] args) {
    
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        PersonService personService = ac.getBean("personService", PersonService.class);

        Person person = new Person(1L, "KJ", 25);

        personService.join(person);

        System.out.println(person.toString()); // Person{id=1, name='KJ', age=25}
        
    }
}

위 방법처럼 Bean을 직접 등록해도 되지만 이것보다 더 간편하게 @Component, @ComponentScan 이라는 Annotation으로 스프링 기능을 사용하기도 한다.

@Controller, @Service, @Repository 등 여러 Annotation에 @Component가 포함되어 있고 @SpringBootApplication@ComponentScan이 포함되어 있다.

그래서 수동적으로 @Bean을 넣지 않아도 @Component, @ComponentScan 이 두 가지 Annotation이 자동으로 해준다.

그리고 의존관계 주입은 @Autowired로 편하게 사용할 수 있다. @Autowired를 사용하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 생성자, Setter, 필드, 일반 메서드를 통한 의존관계 주입이 있지만 생성자를 통해 DI를 사용하는 것이 권장된다.

스프링이 어렵지만 스프링에 대해 알면 알수록 개발자를 엄청 편하게 해준다는 것을 알 수 있다 !

참고
인프런 김영한님 강의, https://inf.run/bvKz

profile
97kim.github.io

0개의 댓글