개발공부/Spring

[Spring] IOC (제어의 역전)

jnnjnn 2024. 4. 25. 22:34

 

객체의 의존성

A 클래스가 B 클래스의 객체를 생성해 사용하는 것을 'A는 B에 의존한다'고 말한다

public class A{
	public static void main(String[] args){
    	B b = new B();
        b.action();
    }
}

class B {
	public void action(){
    	System.out.println("A는 B에 의존합니다");
    }
}

 

 

제어의 역전(Inversion Of Control)

스프링 컨테이너는 사용자가 new 생성자를 사용해서 객체를 생성하지 않고도 객체가 다른 객체를 사용할 수 있도록(의존할 수 있도록) 한다. 스프링은 객체를 빈으로 생성하고 관리한다. 이때 제어권이 사용자에게서 프레임워크로 이동되는 것을 제어의 역전(IoC)이라고 한다

빈(Bean)

스프링이 제어권을 가지고 생성하며 의존관계를 부여하는 객체

 

스프링 컨테이너

  • BeanFactory와 ApplicationContext가 있다
  • 클래스들이 서로 의존할 수 있도록 의존성 주입(Dependency Injection, DI)을 한다
  • 빈객체를 생성하고 생명 주기를 관리한다
  • 이때 생성되는 빈은 하나만 생성되는 싱글톤 방식이다

 

스프링 Bean 등록 방법

컴포넌트 스캔은 @Component가 붙은 클래스를 빈으로 등록한다

@Component

// Bean 이름은 dao로 자동 지정(lowerCamelCase)
@Component
class Dao{
}

// Bean 이름을 dao2로 지정
@Component("dao2")
class Dao3{
}

@Component("Bean 이름")

bean의 이름을 지정하지 않으면 클래스명을 lowerCamelCase로 변환한 것이 이름이 된다

bean의 이름을 지정해주고 싶다면 @Component("Bean 이름") 처럼 괄호 안에 빈의 이름을 적어주면 된다

 

 

@Configuration

: @Component를 붙일 수 없는 클래스의 빈을 생성할 때 사용한다

컴포넌트 스캔은 @Bean 어노테이션이 붙은 메소드가 반환하는 객체를 빈으로 등록한다

빈의 이름은 메소드명으로 결정된다

@Configuration
class MyConfiguration{
	@Bean
    public Myclass MyBean(){
    	return new MyClass();
    }
}

 

@Controller

@Controller는 @Component를 포함하고 있다

 

의존성 주입하는 방법

컴포넌트 스캔으로 생성한 스프링 빈을 @AutoWired를 사용해 의존성 주입(DI)하여 사용한다

@AutoWired

1. 필드 주입

 

필드를 사용해 의존성 주입을 하는 방법이다

외부에서 변경이 어렵기 때문에 추천하지 않는 방법이다

@Component
class MyClass91 {
}

@Component
class MyClass92 {
    @Autowired
    private MyClass91 myClass91;
}

 

2. 생성자 주입

생성자를 통해 의존성 주입을 하는 방법이다

@Component
@Getter
class MyClass112 {
    private MyClass111 field; // dependency

    // 생성자 주입
    @Autowired // 생성자가 하나일 때 생략 가능
    public MyClass112(MyClass111 field) {
        this.field = field;
    }
}

 

🌟 @AutoWired 생략 가능

 

생성자가 하나일 때, @AutoWired 어노테이션을 생략할 수 있다.

또 Lombok의 @RequiredArgsConstructor는 final 키워드가 붙은 필드를 파라미터로 받는 생성자를 자동 생성하므로 다음과 같이 간략하게 쓸 수 있다

@Component
@RequiredArgsConstructor // final인 필드를 파라미터로 받는 생성자 자동 생성
class MyClass122 {
    private final MyClass121 field;

//    // 필드가 final이고 생성자로 할당하면 자동으로 주입됨
//    MyClass122(MyClass121 field) {
//        this.field = field;
//    }
}

// 생략된 코드
@Component
@RequiredArgsConstructor
class MyClass131 {
    private final MyClass132 field;
}

 

생성자 주입을 사용할 경우 다음과 같은 장점이 있다

  1. 순환 참조 방지
  2. 객체의 불변성
  3. 테스트에 용이

3. setter 주입

setter 메소드가 public으로 열려있기 때문에 변경이 쉬워 추천하지 않는 방법이다

@Component
class MyClass142 {
    private MyClass141 field;

    @Autowired
    public void setField(MyClass141 field) {
        this.field = field;
    }
}

 

 

스프링 빈을 사용하는 이유?

 

객체의 의존성 주입을 받을 타입을 인터페이스로 설정하면 여러 인터페이스를 구현한 클래스를 주입받을 수 있습니다. 이러한 방법으로 다형성을 구현할 수 있습니다. 또한 코드의 결합도를 낮추고 관리가 용이해지는 등의 장점이 있습니다.

 

public class Application16 {
    public static void main(String[] args) {
        BeanFactory factory = SpringApplication.run(Application16.class);

        MyClass161 bean = factory.getBean(MyClass161.class);
        bean.action();
    }
}

@Component
@RequiredArgsConstructor
class MyClass161 {
    private final MyInterface161 field;

    public void action() {
        field.someMethod();
    }
}

// @Component
interface MyInterface161 {
    public void someMethod();
}