Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
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 29 30
Tags
more
Archives
Today
Total
관리 메뉴

블로그 이름 뭐하지

[Spring] IOC(제어의 역전)와 DI(의존성 주입) 본문

Spring

[Spring] IOC(제어의 역전)와 DI(의존성 주입)

가는말이고우면오는말은come 2024. 10. 10. 20:28

제어의 역전(Inversion of Control)

 

개발자가 직접 제어흐름을 제어하지 않고, 외부의 프레임워크나 라이브러리가 제어 흐름을 대신하게 되는 것이다.

// 클래스 A에서 new 키워드로 클래스 B의 객체 생성
public class A {
	b = new B();
}

// IOC : 코드에서 객체를 생성하지 않고
// 어디선가 받아온 객체를 b에 할당한다.
public class A{
	private B b;
}

 

쉽게 말하면 다른 객체를 직접 생성하거나 제어하지 않고, 외부에서 관리하는 객체를 가져와 사용한다는 뜻이다.

 

//Controller

@RestController
public class WebSpringController {

    private final ChessService chessService;

    @Autowired
    public WebSpringController(ChessService chessService) {
        this.chessService = chessService;
    }
}

//Service

@Service
public class ChessService {
    private final GameDao gameDao;
    private final BoardDao boardDao;

    @Autowired
    public ChessService(GameDao gameDao, BoardDao boardDao) {
        this.gameDao = gameDao;
        this.boardDao = boardDao;
    }
}

 

Service 클래스의 인스턴스를 직접 작성하지 않았지만 @Autowired 어노테이션을 통해

Controller에 Service의 인스턴스가 생성된 모습이다.

객체의 생명주기가 개발자에서 스프링 프레임워크로 위임된 것으로 제어의 역전이라 할 수 있다.

 

의존성 주입(Dependency Injection)

제어의 역전(IOC)을 구현하기 위해 사용하는 방법이 DI이다.

외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴으로, 어떤 클래스가 다른 클래스에 의존한다는 뜻이다.

// A에서 B를 주입받는다
public class A{
    @Autowired
    B b;
}

 

@Autowired는 스프링 컨테이너에 있는 Bean을 주입하는 애너테이션이다.

Bean은 스프링 컨테이너에서 관리하는 객체로,

해당 애너테이션을 사용하면 개발자가 직접 객체를 생성하지 않아도 스프링 컨테이너에서 객체를 주입받을 수 있다. 

 

의존성

한 객체가 다른 객체를 사용할 때, 의존성이 있다고 한다.

아래와 같이 Consumer 객체가 Chicken 객체를 사용하는 경우에는

Consumer객체가 Chicken 객체에 의존성이 있는 것이다.

public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat();
    }
}

class Chicken {
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

 

위의 코드에서는 Consumer와 Chicken이라는 클래스가 각각 존재하고,

Consumer 클래스에서는 eat 메서드에 Chicken 클래스를 생성한 뒤, Chicken의 eat() 메서드를 실행시킨다.

이러한 코드는 Consumer 클래스에서 Chicken이 아닌 다른 클래스(ex. Pizza)를 실행시키고 싶을 때

많은 코드 변경을 필요하게 하므로, 두 클래스가 강하게 결합되었다고 표현한다. 즉 의존성이 강한 것이다.

 

의존성을 약하게 만들기 위해서는 Interface를 이용한다.

public class Consumer {

    void eat(Food food) {
        food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat(new Chicken());
        consumer.eat(new Pizza());
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}

 

Chicken 클래스와 Pizza 클래스는 Food Interface에 구현되어, eat() 메서드를 오버라이딩했다.

Consumer 클래스는 Food Interface를 eat 메서드의 파라미터로 가져와 각 클래스의 eat 메서드를 실행시킨다.

이렇게 구현하면 기존 코드의 변경없이 새로운 클래스의 가감이 가능해진다.

이런 관계를 약한 결합, 약한 의존성이라고 한다.

 

주입

여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것을 뜻한다.

 

1) 필드에 직접 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        
        //필드에서 직접 생성하여 주입한다.
        consumer.food = new Chicken();
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

 

2) 메서드를 통한 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        
        //set 메서드를 통해 객체를 주입한다.
        consumer.setFood(new Chicken());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

 

3) 생성자를 통한 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
    
    	//Consumer의 생성자를 통해 주입한다.
        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

 

스프링에서는 @Autowired 어노테이션으로 의존성 주입을 명시 할 수 있고,

생성자를 주입 시 해당 어노테이션을 생략 할 수 있다.

생략 시에는, 필드에 final을 붙이고, @RequiredArgsConstructor 어노테이션으로 생성자를 자동 생성한다.

//생성자로 의존성 주입
@RestController
@RequiredArgsConstructor //final이 붙은 필드의 생성자를 자동으로 생성
//생성자
public class DIController {

    privite final MyService myService;
    
	//Controller Codes
}

 

IOC Container와 Bean

 

Bean은 Spring이 관리하는 객체를 뜻하고,

IOC Container는 해당 Bean을 모아둔 컨테이너를 뜻한다.

IOC 컨테이너에 Bean을 등록하면, Spring 프레임워크가 개발자 대신 필요한 객체를 생성하고 관리한다.

 

Bean 등록 방법

1. Component 사용

@Component

Bean으로 등록하고자 하는 클래스 위에 설정한다.

@Component
public class MemoService{...}

 //@Component가 설정된 클래스에 대해 Spring이 하는 일
 
 //1. MemoService 객체 생성
 Memoservice memoService = new MemoService();
 
 //2. Spring IOC Container에 Bean 저장
 memoService -> 클래스의 앞글자만 소문자로 변경하여 컨테이너에 저장.

 

@ComponentScan

Spring 서버가 돌 때 @ComponentScan에 설정해준 패키지 위치와 하위 패키지들을 모두 확인하여

@Component가 설정된 클래스들을 Bean으로 등록한다.

@Configuration
@ComponentScan(backPackages = "com.sparta.memo")
class BeanConfig {...}

 

Spring Boot 를 사용할 경우 @ComponentScan을 사용하지 않는다.

SpringBootApplication에 의해 default로 설정되어 있기 때문이다.

 

2. ApplicationContext 활용

ApplicationContext는 Spring Ioc Container의 인스턴스이다.

BeanFactory의 하위 인터페이스로 BeanFactory 등을 상속하여 기능을 확장한다.

//Bean을 수동으로 가져오는 방법

@Component
public class MemoService {

    private final MemoRepository memoRepository;

    public MemoService(ApplicationContext context) {
        // 1.'Bean' 이름으로 가져오기
        MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");

        // 2.'Bean' 클래스 형식으로 가져오기
        // MemoRepository memoRepository = context.getBean(MemoRepository.class);

        this.memoRepository = memoRepository;
    }

		...		
}

 

3. 3 Layer Annotation

3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스를 Bean으로 등록할 때,

해당 Bean 클래스의 역할을 명시하기 위해 사용한다.

보통 Bean으로 등록할 때 사용하는 @Component는 해당 Annotation에 포함되어 있다.

 

종류는 @Controller, @RestController, @Service, @Repository가 있다.

 

Controller와 RestController은 반환값에서 차이가 난다.

기본적으로 Controller는 view. 그러니까 html, css 같은 프론트엔드 단을 반환한다.

Data를 반환하기 위해서는 @ResponseBody 어노테이션으로 json 형식의 데이터 처리를 해야한다.

@Controller
@RequiredArgsConstructor
public class TestController {

    private final MemberService memberService;
	
    @GetMapping("api/board/member")
    public @ResponseBody ResponseEntity<Member> findMember(@RequestParam("id") String id) {
        
        return ResponseEntity.ok(memberService.findMember(member));
    }
}

 

RestController는 @ResponseBody와 Controller가 결합된 상태로,

@ResponseBody를 붙이지 않더라도 데이터를 처리할 수 있도록 한다.

@RestController
@RequiredArgsConstructor
public class TestController {

	private final MemberService memberService;
	
    @GetMapping("api/board/member")
    public ResponseEntity<Member> findMemberResponseEntity(@RequestParam("id") String id) {
        
        return ResponseEntity.ok(memberService.findMember(member));
    }
}

 

Bean 사용 방법

@Autowired

필드, 메서드, 생성자 위에 @Autowired를 설정한다.(의존성주입)

객체의 불변성을 위해 일반적으로는 생성자 위에 설정한다.

set...Method를 만들고 @Autowired를 적용할 수도 있다.

//필드에 DI
@Component
public class MemoService {
		
    @Autowired
    private MemoRepository memoRepository;	
}

//생성자에 DI
@Component
public class MemoService {

    private final MemoRepository memoRepository;

    @Autowired
    public MemoService(MemoRepository memoRepository) {
        this.memoRepository = memoRepository;
    }	
}

 

@Autowired 적용 조건

Spring Ioc Container에 의해 관리되는 클래스에서만 가능하다.

또한 Spring Ion Container에 의해 관리되는 Bean 객체만 의존성을 주입할 수 있다.

 

@Autowired 생략 조건

1. 생성자 선언이 1개일 때

public class A {
	@Autowired // 생략 불가
	public A(B b) { ... }

	@Autowired // 생략 불가
	public A(B b, C c) { ... }
}

 

2. 클래스가 @RequiredArgsConstructor을 사용할 때

@Component
@RequiredArgsConstructor
// final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성
public class MemoService {

    private final MemoRepository memoRepository;
    
//    public MemoService(MemoRepository memoRepository) {
//        this.memoRepository = memoRepository;
//    }

}

 

 

 

참고한 링크

 

DI는 IoC를 사용하지 않아도 된다

Dependency Injection(DI)을 얘기할 때 빠지지 않는 글이 있다. Martin Folwer의 Inversion of Control Containers and the Dependency Injection pattern이다. 이 글에서 DI란 용어가 생겨나게된 배경을 볼 수 있다. 관련한 내용

jwchung.github.io

 

스프링의 콘셉트(IoC, DI, AOP, PSA) 쉽게 이해하기

❕ 들어가며 이번 글에서는 스프링의 중요한 콘셉트인 IoC(제어의 역전), DI(의존성 주입), AOP(관점 지향 프로그래밍), PSA(이식 가능한 서비스 추상화)에 대해 알아보겠습니다. 💡 이 글을 읽게 됨

shinsunyoung.tistory.com

 

[Spring] @Controller와 @RestController 차이

Spring에서 컨트롤러를 지정해주기 위한 어노테이션은 @Controller와 @RestController가 있습니다. 전통적인 Spring MVC의 컨트롤러인 @Controller와 Restuful 웹서비스의 컨트롤러인 @RestController의 주요한 차이점

mangkyu.tistory.com

 

'Spring' 카테고리의 다른 글

[Spring] Bean  (0) 2024.11.13
[Spring] JPA  (0) 2024.11.13
[Spring] 3 Layer Architecture  (0) 2024.10.10
[Spring] JDBC  (0) 2024.09.30
[Spring] Spring MVC  (0) 2024.09.30