Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

rudu_std

Spring DI && IoC 본문

Spring & Spring Boot

Spring DI && IoC

Ru_Du 2024. 8. 8. 23:20

IoC (Inversion of Control)?

IoC를 네이버 영어사전에서 번역해보면 제어 반전을 뜻하고 있다.

IoC(제어 반전)이란, 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다.

 

컴포넌트 의존관계 설정(Component dependency resoulution), 설정(Configuration) 및 생명주기(LifeCycle)을 

해결하기 위한 디자인 패턴(Design Pattern)이다.

IoC 컨테이너

컨테이너? - 컨테이너는 보통 객체의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것

 

스프링 프레임워크도 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너가 있는데,

그것이 바로 IoC 컨테이너(=스프링 컨테이너) 이다.

인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 한다.

객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있다.

  • IoC 컨테이너는 객체의 생성을 책임지고, 의존성을 관리한다.
  • POJO의 생성, 초기화, 서비스, 소멸에 대한 권한을 가진다.
  • 개발자들이 직접 POJO를 생성할 수 있지만 컨테이너에게 맡긴다.
  • 개발자는 비즈니스 로직에 집중할 수 있다.
  • 객체 생성 코드가 없으므로 TDD가 용이하다.

POJO(Plain Old Java Object)란?

주로 특정 자바 모델이나 기능, 프레임워크를 따르지 않는 Java Object를 지칭한다.

Java Bean 객체가 대표적이다. 

 

다른 클래스나 인터페이스를 extends  implements 받아 메서드를 구현해야 하는 클래스가 아닌, getter/setter 와 같이 기본적인 기능만 가진 자바 객체를 뜻한다.

더보기

POJO 를 지킨 예제

public class POJOMember {
    private String id;
    private String password;

    public String getId() {
        return id;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

POJO 를 지키지 않은 예제

public class MainKey extends KeyAdapter {

    @Override
    public void keyTyped(KeyEvent e) {
        super.keyTyped(e);
    }

    @Override
    public void keyPressed(KeyEvent e) {
        super.keyPressed(e);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        super.keyReleased(e);
    }
}

Key의 기능을 사용하기 위해 KeyAdapter 클래스를 상속받은 모습.
이렇게 사용하면 다른 솔루션을 사용하고자 할 때 많은 양의 코드를 리팩토링하는 문제가 발생한다.
위처럼 특정 기술이나 환경에 종속되어 의존하게 되면, 코드의 가독성 뿐 만이 아니라 유지보수나 확장에도 어려움이 생겨서 POJO라는 개념이 등장 한 것이다.

 

DI(Dependency Injection) 알아보기

DI(Dependency Injection)은 다른 Framework에서는 볼 수 없는 Spring에서 제공하는 의존 관계 주입 기능을 말한다. 즉, 필요할 때마다 객체를 생성해서 사용하는 것이 아닌 미리 생성 후 필요한 부분에 주입하여 사용할 수 있다. 이러한 기능을 사용하면 결과적으로 객체 간의 결합을 낮추고, 유지보수의 용이함을 가져온다.

 

이를 통해 객체간의 결합도를 줄이고 코드의 재활용성을 높일 수 있다.

@Autowired 는 Spring에게 의존성을 주입하는 지시자 역할로 쓰인다.

 

의존성 주입을 해야 하는 이유

  • Test가 용이해진다.
  • 코드의 재사용성을 높여준다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
  • 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다.

의존성 주입의 개념

  1. 의존성:
    • 객체 A가 객체 B의 기능을 사용하려고 할 때, 객체 A는 객체 B에 의존하게 된다. 이 경우, 객체 A는 객체 B를 필요로 하는 상태이다.
  2. 주입:
    • 의존성 주입은 객체 A가 직접 객체 B를 생성하거나 관리하지 않고, 외부(스프링 컨테이너)에서 객체 B를 제공받는 방식이다.
      즉, 객체 A가 필요로 하는 객체 B를 스프링 컨테이너가 생성하고 주입해준다.

@Autowired 어노테이션

 스프링 컨테이너( IoC 컨테이너 )에 어노테이션을 통해 빈을 등록하는 방법은 크게 두 가지다.

  1. @Bean 어노테이션을 통한 빈 등록 (메서드 레벨)
  2. @Component 어노테이션을 통한 빈 등록 (클래스 레벨)

@Bean의 경우 메서드에 붙여 해당 메서드를 통해 반환되는 객체를 Bean으로 관리할 때 사용한다. 이와 달리 @Component는 클래스에 붙여 해당 클래스 타입을 기반으로 빈을 관리하도록 한다. @Repository, @Service, @Controller 등의 어노테이션들은 @Component를 이미 포함한다.

@Component를 통해 등록된 빈은 다른 곳에서 @Autowired를 사용해 의존성을 주입할 수 있다.

스프링 컨테이너에 빈들을 모두 등록한 후에, 의존성 주입 단계가 이루어진다.

이 때 @Autowired 어노테이션이 부여된 메서드가 실행되며 필요한 인스턴스를 주입해준다.  

@Component
public class A {}

@Component
public class B {
    @Autowired
    private A a; // 컨테이너에서 A 타입의 빈을 찾아서 의존성을 주입한다  
    }
}

 

의존성 주입의 3가지 방법

1. 생성자 주입(Constructor Injection)
2. 필드 주입(Field Injection)

3. 수정자 주입(Setter Injection)

1. 생성자 주입(Constructor Injection)

● 방법 : Constructor 생성자를 통해 의존 관계를 주입하는 방법이다.

 객체가 생성될 때 딱 한 번 호출되는 것이 보장된다 → 의존관계가 변하지 않는 경우, 필수 의존관계에 사용

 의존 관계에 있는 객체들을 final로 선언할 수 있다는 장점 → 생성자에서 무조건 설정해주어야 함 → 누락 발생 X

● 생성자가 하나일 경우 @Autowired를 생략할 수 있다.

@Controller 
class CocoController {  
	//final을 붙일 수 있음    
	private final CocoService cocoService;  
	
	@Autowired     
	public CocoController(CocoService cocoService) {        
		this.cocoService = cocoService;    }
	}

의존해야하는 객체를 우선 private final로 선언한다.

그리고 생성자를 통해 해당 객체들을 주입받도록 하고, 생성자의 위에 @Autowired 어노테이션을 추가한다.


2. 필드 주입(Field Injection)

@Controller
public class CocoController {

    @Autowired
    private CocoService cocoService;
}

필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입된다.

사용법이 매우 간단하기 때문에 가장 많이 접할 수 있는 방법.

 

필드 주입은 간단하게 사용할 수 있지만, 테스트와 코드 유지보수 측면에서 단점이 있으므로, 생성자 주입 또는 setter 주입을 사용하는 것이 더 나은 경우가 많다. 특히 의존성이 명확히 드러나고, 객체의 불변성을 유지할 수 있는 생성자 주입이 권장된다.

 

단점

  • 코드가 간결하지만, 외부에서 변경하기 힘들다.
  • 프레임워크에 의존적이고 객체지향적으로 좋지 않다.
  • final 로 선언 불가하기에 불변성 유지가 어렵다.
더보기

 

  • 테스트 어려움:
    • 필드 주입을 사용하면 테스트를 위해 의존성을 주입하기가 복잡해질 수 있다. 테스트 코드에서 의존성을 직접 설정해야 하므로, 테스트가 복잡해질 수 있다.
  • 의존성 불투명성:
    • 클래스의 의존성이 명시적으로 나타나지 않아서, 클래스의 설계를 이해하는 데 어려움이 있을 수 있다 . 생성자 주입을 사용하면 의존성을 명확히 확인할 수 있지만, 필드 주입은 클래스 외부에서 의존성을 명확히 알 수 없다.
  • 불변성 유지 어려움:
    • 필드 주입을 사용하면 객체의 필드를 final로 선언할 수 없다. 이로 인해 객체가 생성된 후 의존성이 변경될 수 있다 . 생성자 주입을 사용하면 객체의 불변성을 보장할 수 있다 .
  • 리팩토링 어려움:
    • 필드 주입을 사용하면 클래스의 의존성을 변경하거나 교체할 때, 코드 리팩토링이 복잡해질 수 있다 . 생성자 주입을 사용하면 의존성을 변경할 때 명확하게 클래스의 생성자를 수정할 수 있다 .
  • 초기화 문제:
    • 객체가 생성된 후에 필드가 주입되므로, 필드가 초기화되지 않은 상태에서 메서드를 호출할 수 있는 위험이 있다 . 이로 인해 NullPointerException과 같은 문제를 발생시킬 수 있다 .

 

3. Setter 주입(Setter Injection)

 방법 : setter를 생성하고, 그 위에 @Autowired를 적는다.

 스프링 빈을 모두 등록한 후에 @Autowired가 붙은 수정자를 모두 찾아서 의존관계를 주입한다.

 "선택적"이고, "변화 가능"한 의존 관계에 사용한다.

@Controllerpublic 
class CocoController {    

	private CocoService cocoService;     
    
	@Autowired    
	public void setCocoService(CocoService cocoService) {    	
	this.cocoService = cocoService;    
	}
    
}

의존해야하는 객체를 우선 private final로 선언한다.

자바 빈 프로퍼티 규약에 따라 해당 변수에 대한 Setter 메서드를 생성하고, 해당 setter에 @Autowired를 붙여서 사용한다.

단점

  • 생성 시점 이후에 메서드를 호출하여 의존성을 변경할 수 있으나 수정자 주입을 사용하면 setXXX 메서드를 public으로 열어두어야 하기 때문에 필드가 외부에서 변경될 수 있다는 점과 의존성이 필요한 시점에 주입되지 않을 수 있다는 위험이 존재한다.

Bean이 무었인가?

먼저 Bean을 이해하기 위해 스프링 컨테이너 (Spring Container 또는 IoC 컨테이너)에 대해서 알 필요가 있다.

자바 어플리케이션은 어플리케이션 동작을 제공하는 객체들로 이루어져 있다.

이때, 객체들은 독립적으로 동작하는 것 보다 서로 상호작용하여 동작하는 경우가 많다.

이렇게 상호작용하는 객체를 '객체의 의존성'이라고 표현한다.

스프링에서는 스프링 컨테이너에 객체들을 생성하면 객체끼리 의존성을 주입(DI; Dependency Injection)하는 역할을 해줍니다.

스프링 컨테이너에 등록한 객체들을 '빈'이라고 한다.

 

 

Bean: Spring에서 관리되는 객체를 의미. Bean은 Spring 컨테이너에 의해 생성, 초기화, 관리됩니다.

Bean으로 등록되기 전 그것들은 단순한 자바의 객체이다.(클래스, 메서드, 필드)

 

빈 등록: 클래스를 Spring 컨테이너에 등록하여 Bean으로 만들기 위해 다음 방법을 사용할 수 있다.

  • @Component 어노테이션: 일반적인 빈으로 등록한다.
  • @Service, @Repository, @Controller 어노테이션: 각각 서비스, 데이터 액세스, MVC 컨트롤러에 적합한 빈으로 등록한다.
  • @Configuration과 @Bean 메서드: 설정 클래스 내에서 @Bean 메서드를 사용하여 빈을 등록한다.

( IoC 컨테이너에게 "이거 이제 너가 관리해" 라고 알려주는 어노테이션)

 

  • @Component: 일반적인 Bean으로 등록한다.
  • @Service: 서비스 레이어의 Bean을 등록할 때 사용한다. @Component의 특수화된 형태이다.
  • @Repository: 데이터 액세스 레이어의 Bean을 등록할 때 사용한다 . @Component의 특수화된 형태이다.
  • @Controller: MVC 컨트롤러의 Bean을 등록할 때 사용한다 . @Component의 특수화된 형태이다.
  • @Configuration: Spring 설정 클래스를 정의할 때 사용한다 . 이 클래스 내에 정의된 @Bean 메서드는 Spring 컨테이너에 의해 관리되는 Bean을 반환한다.

 

@Configuration과 @Bean 개념

  • @Configuration 클래스: Spring에서는 @Configuration 어노테이션이 붙은 클래스를 설정 클래스라고 부른다.
    Spring 애플리케이션의 설정을 정의하는 데 사용되는 클래스에 붙이는 어노테이션.
    이 클래스는 Spring 컨테이너에 의해 관리될 빈들을 정의하는 역할을 한다. 일반적으로 애플리케이션의 설정 정보를 제공하는 데 사용된다.
  • @Bean 어노테이션: @Configuration 클래스 내에서 @Bean 어노테이션이 붙은 메서드는 Spring 컨테이너에 의해 호출되어, 반환된 객체를 빈으로 등록한다. 이 메서드는 빈의 생성, 초기화, 설정을 담당하며, 반환값이 Spring 컨테이너에 등록된 빈이 된다.

@Configuration, @ ComponentScan  역할

빈 정의

  • @Configuration 클래스 내에서 @Bean 어노테이션을 사용하여 빈을 정의하고 등록한다.
  • 예를 들어, 데이터베이스 연결 설정이나 외부 라이브러리 설정을 빈으로 정의할 수 있다.
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyService();
    }
}

 

컴포넌트 스캔 설정

  • @ComponentScan을 사용하여 특정 패키지를 스캔하고,
    그 패키지 내의 @Component, @Service, @Repository, @Controller 등을 자동으로 빈으로 등록한다.
  • @ComponentScan가 사용된 패키지 and 하위패키지를 스캔
  • basePackages 속성을 사용하여 스캔할 패키지를 지정할 수 있다.
  • @ComponentScan을 명시하지 않으면, @Configuration이나 @SpringBootApplication이 붙은 클래스의 패키지와 그 하위 패키지가 스캔된다.
@Configuration
@ComponentScan(basePackages = "com.example")
// ComponentScan[(basePackages = "com.example")]  [] 생략가능
public class AppConfig {
}

@Configuration과 @ComponentScan은 자주 함께 사용되지만, 항상 함께 사용되는 것은 아니다.

독립적으로 사용될 경우

@ComponentScan만 사용: 메인 애플리케이션 클래스에 @SpringBootApplication붙어 있는 경우, @SpringBootApplication은 내부적으로 @ComponentScan을 포함하여 자동으로 빈을 스캔한다. 이 경우 @ComponentScan을 별도로 명시할 필요는 없다.

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@SpringBootApplication

@SpringBootApplication은 @Configuration, @ComponentScan, @EnableAutoConfiguration의 조합이다.

기본적으로 MyApplication 클래스가 위치한 패키지와 그 하위 패키지에서 @Component, @Service, @Repository, @Controller 어노테이션이 붙은 클래스를 스캔하여 빈으로 등록한다.

 


@Configuration 어노테이션이 붙은 클래스는 빈을 정의하는 설정 클래스로 사용된다.

이 클래스 내에 정의된 @Bean 메서드는 Spring 컨테이너가 빈을 생성하고 초기화할 때 사용된다.

 

@Bean 어노테이션이 붙은 메서드는 빈을 생성하고 Spring 컨테이너에 등록하는 역할을 한다.

이 메서드는 일반적으로 객체를 생성하고 설정한 후 반환한다. 반환된 객체는 Spring 컨테이너에서 관리하는 빈이 된다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyRepository myRepository() {
        return new MyRepository();
    }
}

 

  • myService 메서드: MyService 객체를 생성하고 반환한다. 이 객체는 Spring 컨테이너로 관리되는 빈으로 등록된다.
  • myRepository 메서드: MyRepository 객체를 생성하고 반환한다. 이 객체 역시 Spring 컨테이너의 빈으로 등록된다.

 

@Bean @Autowired 차이

@Bean

 

용도

@Bean 어노테이션은 @Configuration 클래스 내에서 사용되며, 메서드가 반환하는 객체를 Spring 컨테이너에 빈으로 등록한다.

 

작동 방식

  • 빈 등록: @Bean 어노테이션이 붙은 메서드는 호출 시 객체를 생성하고 반환한다. 반환된 객체는 Spring 컨테이너에 의해 관리되는 빈으로 등록된다.
  • 명시적 빈 정의: 개발자가 메서드를 통해 빈을 명시적으로 정의할 수 있다. 이 방법은 객체 생성과 설정을 명시적으로 제어할 수 있다.
  • 설정 클래스: @Configuration 어노테이션이 붙은 클래스 내에서 사용되며, 애플리케이션의 설정을 담당한다.

@Autowired

용도 

@Autowired 어노테이션은 Spring 컨테이너에서 빈을 자동으로 주입할 때 사용된다. 주입될 빈의 타입을 기준으로 자동으로 의존성을 해결한다.

작동 방식:

  • 의존성 주입: @Autowired 어노테이션이 붙은 필드, 세터 메서드, 생성자에 대해 Spring 컨테이너가 빈을 자동으로 주입한다.
  • 빈 주입: 빈이 생성되고 초기화된 후, 해당 빈의 인스턴스가 @Autowired가 붙은 필드나 메서드에 주입된다.

주요 차이점

  1. 목적:
    • @Bean: 빈을 정의하고 등록하는 데 사용된다. 설정 클래스 내에서"만" 사용되어 빈의 생성과 설정을 직접 제어한다.
    • @Autowired: 빈을 주입받는 데 사용된다. 주입될 빈을 자동으로 찾아서 주입한다.
  2. 사용 위치:
    • @Bean: @Configuration 클래스 내의 메서드에 붙여 사용한다.
    • @Autowired: 클래스의 필드, 세터 메서드, 생성자에 붙여 사용한다.
  3. 빈의 역할:
    • @Bean: 빈을 생성하고 등록하여 Spring 컨테이너가 관리하게 한다.
    • @Autowired: 이미 Spring 컨테이너에 등록된 빈을 주입받아 사용하는 데 사용한다.
  4. 빈 관리:
    • @Bean: 빈의 생성과 초기화 시점을 명시적으로 제어할 수 있다.
    • @Autowired: 빈의 주입 시점을 자동으로 관리한다.

 

 

 

 

출처 : https://dev-coco.tistory.com/80

'Spring & Spring Boot' 카테고리의 다른 글

ResponseEntity  (0) 2024.08.25
@RestController, @Controller  (0) 2024.08.20
REST API란 무엇인가?  (0) 2024.08.20
@RequestParam과 @PathVariable  (0) 2024.08.20
@PathVariable과 @RequestParam  (0) 2024.08.20