업데이트:

Spring 입문

지난 시간 까지 도메인과 서비스, 레포지토리를 만들어 보았는데, 이번에는 컨트롤러를 만들어 보며 컨트롤러에서 서비스를 호출하는 등의 의존관계에 대한 내용을 학습할 것이다.

스프링에서는 스프링 컨테이너로 스프링 빈의 생명주기를 관리하며, 객체들을 관리해준다. 이 컨테이너를 통해 관리받는 객체를 스프링 빈이라고 하는데, 이 스프링 빈의 등록 방식은 어노테이션을 통한 컴포넌트 스캔에 의해 자동으로 의존관계를 생성시켜 주거나, 자바 코드로 직접 등록해 주는 것이다.

컴포넌트 스캔과 자동 의존관계 설정

멤버 컨트롤러 생성

멤버 컨트롤러를 만들며 의존관계를 설정해 보자. 예시로 만들었던 HelloController 과 같은 디렉토리에 MemberController 를 다음과 같이 만들어 주자.

java/hello/hellospring/controller/MemberController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

위 코드에서 memberService 객체를 다음과 같이 new 를 통해 생성해 줄 수도 있을 것이다.

1
private final MemberService memberService = new MemberService();

하지만 위와 같은 방식에서는 컨트롤러가 호출 될 때 마다 새로운 객체가 생성되고, 레포지토리 등 다른곳에서 생성된 객체가 있는데 다시 생성하는 등의 문제로 바람직하지 못하다. 따라서 우리는 스프링 컨테이너에 memberService 객체를 스프링 빈으로 등록하고, 이를 끌어 쓰기 위해 @Controller, @Autowired 어노테이션을 사용해 주게 된다.

위와 같이 멤버 컨트롤러를 만들어 주고 실행을 하게 되면 오류가 발생하는데, 조금 생각해 보면 당연한 결과다. 우리가 만들어 주었던 서비스와 레포지토리는 순수 자바 클래스이고, 어노테이션을 통해 스프링 빈이 되어 컨트롤러의 통체를 받는 컴포넌트가 아니기 때문에 @Autowired 를 통해 가져올 것이 없기 때문이다.
따라서 MemberServiceMemoryMemberRepository 를 다음과 같이 수정해 주어야 한다.

MemberService.java

1
2
3
4
5
6
7
8
9
@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

MemoryMemberRepository.java

1
2
3
4
5
@Repository
public class MemoryMemberRepository implements MemberRepository{

   private static Map<Long, Member> store = new HashMap<>();
   private static long sequence = 0L;

컴포넌트 스캔

이름 그대로 스프링 컨테이너에서 @Component 가 달린 객체를 스캔해 자동으로 의존관계를 생성해 주는 개념이다.

하지만 우리는 @Service, @Repository, @Controller 의 어노테이션을 사용했는데, 어떻게 된 일일까?

예시로, @Service 의 구현체를 살펴보면

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
31
32
/**
 * Indicates that an annotated class is a "Service", originally defined by Domain-Driven
 * Design (Evans, 2003) as "an operation offered as an interface that stands alone in the
 * model, with no encapsulated state."
 *
 * <p>May also indicate that a class is a "Business Service Facade" (in the Core J2EE
 * patterns sense), or something similar. This annotation is a general-purpose stereotype
 * and individual teams may narrow their semantics and use as appropriate.
 *
 * <p>This annotation serves as a specialization of {@link Component @Component},
 * allowing for implementation classes to be autodetected through classpath scanning.
 *
 * @author Juergen Hoeller
 * @since 2.5
 * @see Component
 * @see Repository
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

이러한 내용을 살펴볼 수 있다. 보다싶이, 그 안에 @Component 가 포함되어 있기에 스캔되어 작동하게 된다.

의존성 주입 Dependency Injection

1
2
3
4
    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

위와 같이 객체를 외부에서 넣어주어 의존성을 만들어 주는 것을 의존성 주입이라 한다.

자바 코드로 직접 스프링 빈 등록하기

main 과 같은 레벨에 SpringConfig 를 통해 컴포넌트 스캔을 통하지 않고 직접 스프링 빈으로 등록이 가능하다.

java/hello/hellospring/SpringConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

방식 별 장단점

단순 편의의 측면에서는 컴포넌트 스캔 방식 이 더 편리해 보이는데, 다뤘던 두 방식의 장단점을 짚어보자.

의존성 주입에는 세가지 방식이 있다고 한다.

  • 생성자 주입 (이번 실습의 방식)
  • 필드 주입
    생성자 없이 바로 주입
    1
    
    @Autowired private final MemberService memberService;
    

    이 경우 실행 즉시 결정되며 중간에 바꿀 방법이 없어 선호되지 않는다.

  • setter injection 방식

    1
    2
    3
    4
    5
    6
    7
    
      private MemberService memberService;
    
      @Autowired
      public void setMemberService(MemberService memberService) {
          this.memberService = memberService;
      }
    
    

    이 경우 setter가 public으로 풀려있어야 하고, 따라서 어딘가에서 잘못 바뀔 가능성이 존재한다.

따라서 위 세가지 방법 중 생성자 주입이 가장 권장된다.

실무에서는 정형화된 컨트롤러, 서비스, 레포지토리와 같은 코드는 컴포넌트 스캔을 사용하고, 정형화되지 않거나, 상황에 따라 구현체를 변경해야 하는 경우 설정을 통해 스프링 빈으로 등록하는 방법을 사용하게 된다.

지난 시간의 시나리오에 따라 앞으로 진행하며 레포지토리의 구현체를 MemoryMemberRepository 에서 DBMemberRepository 로 변경할 예정이기에 설정을 통해 스프링 빈으로 등록하는 방법을 사용하자.

Spring 입문

댓글남기기