Spring

Spring의 Component scan과 자동 Dependency Injection

2donny 2021. 12. 21. 13:51

 

"우리는 왜 Dependency injection를 쓰나요?"

위 같은 질문은 대답하기 참 어려웠다.

 

 

 

면접 자리라면 그래도 말은 해야하니 아래와 같이 말하곤했다

"객체의 생성과 사용의 관심사를 분리하기 위함이죠. 또한 객체 생성의 책임을 IoC 컨테이너에게  전가함으로써 프레임워크 단에서 디자인 패턴을 만드는데에도 의의가 있습니다."

 

다시 보니까, 위 두 이유가 되게 비슷한 맥락이라서 거의 같은 말 같기도하다. 아무리 찾아봐도 당시 저 상황에서 공감이 잘 안됐고 그나마 저게 가장 공감됐던 DI를 사용하는 이유였다. 물론 위 근거도 충분히 이유가 될 수 있다.

 

근데, 오늘 Spring의 컴포넌트 스캔을 배우게 되면서 DI를 사용하는 또하나의 이유를 알게되어 정리하고자 한다.

 

 

 

Spring의 Layered 아키텍쳐는 다음의 3가지를 포함한다.

1. Controller Layer, 톰캣으로부터 요청을 받는 Layer

2. Service Layer, Controller Layer에 의해 실행되고 Business logic을 담당하는 Layer

3. Repository Layer, Service Layer에 의해 실행되고 데이터를 저장하고 조회하는 Layer

 

 

 

위 아키텍쳐의 이해를 바탕으로, 간단한 예를 들어보자.

아래와 같은 간단한 User 도메인이 있다.

public class User {
    private Long id;
    private String name;
}

 

 

그리고 User의 회원가입(join)의 컨트롤러 메서드가 있다.

아래서는 UserController의 종속 객체인 UserServiceDI 없이 new 키워드를 통해 직접 클래스 내부에서 생성하였다. 

 

@Controller
public class UserController {

    private final UserService UserService = new UserService;
    
    @GetMapping("join")
    public void join(@RequestParam("name") String name) {
    	User newUser = new User();
    	newUser.setName(name);
    	userService.join(newUser);
    }
}

 

 

이렇게 하면 되긴 한다. 근데 큰 문제점이 있다.

 

바로 UserService에 종속적인 객체가 UserController 만이 아닐 수가 있는 것이다.

기획이 추가되다보면 상품 서비스, 결제 서비스, 예약 서비스 등 많은 곳에서도 UserService에 종속적일 수 있게되고, 그럼 그때마다 위와 같이 new 키워드로 새로운 인스턴스를 생성해야한다. 그럼 하나의 인스턴스를 공유하는 것이 아니라, 같은 객체가 중복생성되어 Memory leak를 초래하게된다. 또한 그 종속객체가 Repository라면 Repository 인스턴스를 생성할 때마다 그 안에있는 DB를 새로 생성해버릴수도 있는 것이다. (물론 static 메서드로 공유해서 쓰겠지만 그렇게 좋은 System은 아닌 것 같다.)

 

 

Component Scan

Spring은 이러한 문제를 Component Scan으로 해결해준다.

Component Scan이란 스프링이 서버를 실행할 때 @Component 어노테이션이 붙은 모든 클래스를 찾아서 Spring IoC 컨테이너에 객체를 저장하는 액션을 말한다. 이 때 저장된 객체를 Bean이라고 부른다.

 

 

@Controller, @Service, @Repository 어노테이션들은 모두 @Component로 정의되어있다. (아래 그림 참조)

 

@Service의 정의 인터페이스

 

 

그럼 각각 Layer에 맞는 Annotation을 붙여서 Bean 객체를 생성해 컨테이너에 등록해보자.

 

@Controller
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("join")
    public void join(@RequestParam("name") String name) {
    	User newUser = new User();
        newUser.setName(name);
        userService.join(newUser);
    }

}

 

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Long join(User user) {
        // 같은 이름이 있는 중복 회원 X
        validateDuplicateMember(user);

        userRepository.save(user);
        return user.getId();
    }

    private void validateDuplicateMember(User user) {
        userRepository.findByName(user.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }
}

 

@Autowired는 자동으로 두 객체를 연결시켜주는 역할을 한다.

 

위 UserService 예시에서 Flow를 자세히 살펴보면 다음과 같다.

1. 스프링이 뜰 때 컴포넌트 스캔을 하다가 @Service 어노테이션 발견한다.

2. Spring Ioc 컨테이너의 Bean 객체로 등록하기 위해 생성자를 실행한다.

3. @Autowired를 보고 Spring IoC 컨테이너에 등록된 UserRepository Bean 객체를 Container가 Dependency Injection한다.

4. 생성된 UserService 객체를 Spring IoC 컨테이너에 Bean 객체로 등록한다.

 

 

최종적으로 아래와 같이 스프링 컨테이너에는 빈 객체들이 등록됨으로써 앞으로 모든 종속적인 객체에서 Depdency injection을 통해 재사용할 수 있게 된다. 

 

 

 

 

정리를 하자면..

 우리가 종속 객체를 생성하고 싶을 때, new 키워드를 통해 직접 생성하는 것이 아니라, 생성자 내에서 외부의 DI를 통해 종속 객체를 넘겨받는 것. 또 이것을 하기 위해 Component Scan을 하여 사전에 Spring IoC 컨테이너Bean 객체로서 모두 등록을 하는 것. 이렇게 함으로써 인스턴스를 최초에 한번만 생성해놓고 재사용하여 메모리 누수를 방지할 수 있고, 또 부가적으로 springframework 에서 제공하는 Annotation을 씀으로써 Spring의 가이드라인을 잘만 따른다면 DI를 잘 쓸 수 밖에 없는것, 즉 Layer의 디자인패턴을 어느정도 프레임워크 단에서 강제하는 것. 이것들이 바로 두 마리의 토끼를 다 잡는것이 아닐까?

 

 

 

"Dependency injection을 왜 사용하는가?" 에 대해서 얘기하다가 Spring의 Component Scan까지 오게되었다. 사실 비슷한 개념이 아닐수도 있지만 개인적으로는 Component Scan을 알게되면서 DI를 더 잘 이해하게 되었고 왜 탄생하게 되었는지 드디어 공감하게 되었다. (이 글을 통해 내 공감이 전파되었으면..) 

 

 

 

 

참조글

영한님의 Spring 인프런 강의

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88Bean%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%83%9D%EC%84%B1-%EC%9B%90%EB%A6%AC

 

[Spring] 스프링 빈(Bean)의 개념과 생성 원리

[Spring] 스프링 빈(Bean)의 개념과 생성 원리 빈(Bean) Spring IoC 컨테이너가 관리하는 자바 객체를 빈(Bean)이라는 용어로 부른다. 우리가 new 연산자로 어떤 객체를 생성했을 때 그 객체는 빈이 아니

atoz-develop.tistory.com