해당 내용의 출처는 김영한 님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술의 강의 내용이고,본 포스트는 수강후 개인적으로 공부한 내용을 필기하고 다시 복습하기 위하여 작성된 것임을 밝힌다.
Spring bean과 의존관계
- MVC 패턴에서, 컨트롤러는 서비스와 데이터를 주고받고, 서비스는 리포지터리와 데이터를 주고받는다. 이것을 상호 의존관계에 있다고 표현한다.
- Controller - Service의 의존관계 설립은 하기의 예제와 같다.
// MemberController
@Controller
public class MemberController {
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
// Autowired라는 어노테이션을 삽입하면, 스프링 컨테이너가 생성자의 인자로 알아서 memberService를 주입하여준다.
}
// MemberService
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 스프링에서는 상기와 같이, @Controller라는 어노테이션을 삽입하면, 서블릿에서의 서블린 컨테이너와 마찬가지로, 스프링 컨테이너라는 곳에다가 스프링이 어노테이션을 통하여 객체를 생성한 후 저장을 해둔다. 그 후 스프링이 관리를 시작하게 되는 것이다.
- 상기와 같은 상황을 스프링 컨테이너에서 스프링 빈이 관리된다라는 표현을 사용한다.
- 그러나 상기의 코드는 문제가 있다. MemberController에서는 스프링 컨테이너에 컨트롤러를 등록하고, Service를 찾아서 주입해달라고 요청하였지만, 정작 Service는 스프링에 등록이 되어있지 않아 스프링이 관리할 수 없는 상태이기 때문이다.
- 따라서 해당 코드를 정상적으로 사용하기 위해서는 당연하게도 Controller와 Service를 모두 컨테이너에서 관리될 수 있도록 어노테이션을 삽입해두어야 한다. 하기와 같다.
// MemberController
@Controller
public class MemberController {
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
// Autowired라는 어노테이션을 삽입하면, 스프링 컨테이너가 생성자의 인자로 알아서 memberService를 주입하여준다.
}
// MemberService
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 기술한 내용과 같은 같은 맥락으로서 서비스와 리포지터리 역시 스프링 컨테이너에 등록하고 연결해주어야 한다.
- 이러한 패턴은 정형화된 패턴이다. 컨트롤러에 외부 요청이 들어오고, 서비스에서 비즈니스 로직을 생성하고, 리포지터리에서 데이터를 저장하는 것이 가장 정형화되어있는 패턴이라고 할 수 있다.
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
- 그리고 예제와 같이 생성자를 통하여 데이터를 주고 받을 객체를 주입받는 것, 이것이 바로 DI이다.
- !! 참고 !!
- 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다( 유일하게 하나만 등록하여 공유한다는 의미이다) 따라서 같은 스프링 빈이면 모두 같은 인스턴스를 사용한다는 의미가 된다.
자세한 내용은 Java Singleton Pattern에서 다시 정리하겠다. - 이 역시 설정을 이용하면 싱글톤을 사용하지 않도록 바꿀 수 있지만 특수한 경우를 제외하고는 대부분 싱글톤을 사용한다고 하였다
- 컴포넌트가 아닌 자바 코드로서 직접 컨테이너에 빈을 등록하는 방법은, 본 예제의 가정과 같이 아직 DB가 설정되지 않고 임시로 개발을 진행하였을때, 개발이 되고있는 시점 혹은 해당 기능의 개발이 완료된 시점에 DB선정이 완료가되었을 때, 코드를 변경하지 않고 그대로 적용할 수 있는 방법이 있기 때문에 사용하는 것이다.
스프링 컨테이너에 스프링 빈을 등록하는 방법
- 이전 장에서는 스프링 컨테이너에 스프링 빈을 등록하는 방법을 예제를 통하여 다루었다. 본 장에서는 본격적으로 스프링 컨테이너에 스프링 빈을 등록하는 방법에 대해서 기술하였다.
- 스프링 컨테이너(이하 컨테이너)에 스프링 빈(이하 빈)을 등록하는 방법은 크게 두가지가 있다고 하였다.
- 첫번째는 컴포넌트 스캔과 자동 의존관게 설정이고, 두번째는 자바 코드로 직접 빈 등록을 하는 방법이다.
- 과거에는 다음의 방법 의외에도 XML로 등록을 하는 등의 방법이 존재하였는데 현재는 잘 사용하지 않는 방법이다.
- DI 주입의 방법에는 필드주입, Setter주입, 생성자 주입 이렇게 세가지가 존재하는데, 의존관계가 실행중에 동적으로 변하는 경우가 거의 없기 때문에 생성자 주입이 권장된다 하였다.
// ArticleController
@Controller
public class ArticleController {
ArticleService articleService;
// 필드 주입 방법 : 그다지 권장되지 않음
// @Autowired ArticleService articleService;
/*
Setter 주입 방법
객체를 생성하고 난 다음 Setter로서 Controller에 Service를 주입하는 방법 (해당 메소드는 public이 필수)
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
*/
/*
생성자 주입 방법
@Autowired
public ArtcileController(ArticleService articleService) {
this.articleService = articleService;
}
*/
- 예제에서 Setter의 단점은 setter 메소드가 public인 것에 있다. Setter의 접근제어가 public이기 때문에, 이후에 MemberController가 생성되고 Setter로서 다른 값이 주입된다면 어플리케이션에 오류 혹은 혼란이 야기될 수 있는 가능성이 존재하기 때문에 생성자에 주입하는 방법이 가장 권장되고 안전한 방법이라고 하였다.
컴포넌트 스캔과 자동 의존관계 설정
- 첫번째로 다룰 컴포넌트 스캔과 자동 의존관계 설정은, 이전 장에서 다룬 어노테이션을 통한 컨테이너의 등록과 생성자로서 등록을 하는 방법이다 예제는 다음과 같다.
// ArticleController
@Controller
public class ArticleController {
ArticleService articleService;
@Autowired
public ArtcileController(ArticleService articleService) {
this.articleService = articleService;
}
}
// ArticeService
@Service
public class ArticleService {
ArticleRepository articleRepositry;
@Autowired
public ArticleService(ArticleRepository articleRepositry) {
this.articleRepositry = articleRepositry;
}
}
// ArticleRepository
@Repository
public class ArticleRepository {
...
}
- Article과 관련된 컨트롤러, 서비스, 리포지터리를 생성하였다는 가정하에 예제를 작성하였다. 상기의 예제와 같은 방법으로, 스프링에서 지원하는 어노테이션인 @Controller, @Service, @Repository 를 이용하여 컨테이너에 빈을 등록하고, @Autowired 어노테이션을 사용하여 서로를 연결할 수 있도록 하는 것이 컴포넌트 스캔과 자동 의존관계를 설정한 것이다.
- 한가지 의문이 생길 수 있다. 예제에서는 분명 각각의 역할에 맞는 어노테이션을 추가한 것인데 어째서 컴포넌트를 스캔하였다고 한 것인가?
@Controller
public class ArticleController {}
@Component
public class ArticleController {}
- 기술하였던 예제는 상기 예제와 일맥상통하다. 이것은 컴포넌트 스캔의 원리를 알면 이해할 수 있는데 다음과 같다.
- @Controller, @Service, @Repository 각각의 어노테이션에 엑세스 해보면 공통적으로 @Component라는 어노테이션이 포함되어있는 것을 확인할 수 있을 것이다.
- @Component 어노테이션을 보유하고 있으면 컨테이너에 빈이 자동적으로 등록될 수 있도록 스프링은 설계되어있고, 각각의 어노테이션들이 전부 해당 어노테이션을 보유하고 있기 때문에, 역할에 맞는 어노테이션을 삽입한 후, 그것과 연결을 해주는 Autowired 어노테이션을 쓰면 자동적으로 연결이 되어 사용할 수 있도록 설계되어있는 것이다.
자바 코드로 직접 빈 등록을 하는 방법
- 두번쨰로 다룰 자바 코드로서 직접 빈 등록을 하는 방법은 컴포넌트 스캔을 이용한 방법이 아닌(@Component와 @Autowired를 사용하는 방법이 아닌) 직접 컨테이너에 빈을 등록하는 방법인데 다음과 같다.
- 본 방법은 빈으로 등록될 클래스를 직접 컨테이너에 등록하는 방법으로서 하기와 같이 작성하면 된다.
// SpringConfig // SpringConfig라는 클래스를 하나 생성한다.
@Configuration // Configuration 이라는 어노테이션을 삽입한다.
public class SpringConfig {
@Bean // bean으로 등록하고자 하는 메서드위에 해당 어노테이션을 삽입한다.
public MemberService memberService() {
return new MemberService(memberRepository()); // 리턴값으로 빈으로 등록하여 관리하고자 하는 객체를 생성하여준다.
// 해당 프로젝트에서 MemberService의 생성자는 MemberRepository를 요구하고 있는데, 이 상황에서는
// 하기에 bean으로서 객체를 생성하고, 생성하는 메소드를 상태로서 주입해주면 오류를 해결할 수 있다.
// 이렇게 하면 컨테이너에 등록된(하기의 절차로서) MemberRepository를 MemberService에 알아서 주입한다.
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
// MemberService
public class MemberService {
}
// MemberRepository
public class MemberRepository{
}
스프링 컨테이너가 스프링 빈을 검색하는 범위
- 그렇다면, 모든 패키지에 @Component를 삽입하면 컨테이너에 자동적으로 등록이 되는 것일까?
- 정답은 아니다이다. 컨테이너에 빈으로서 등록하기 위해서는 규칙이 하나 있는데 다음과 같다.
// 패키지 디렉토리
com.wcs.springpj / SpringAPplication
- 스프링 프로젝트를 생성하면, 기본적으로 생성되는 패키지가 있고, 그 안에 자동적으로 기본 SpringApplication 이라는 class가 생성될 것이다.
- 스프링 컨테이너에 등록하여 자동으로 빈을 관리하고 싶을 때, 그리고 스프링 프레임워크를 이용하여 프로젝트를 하고자 할때는 반드시 상기 패키지의 하위 패키지에 생성을 하여 작업을 하여야 한다.
- 즉 스프링을 이용하여 개발을 하고자 한다면, 기본적으로 Application이라는 패키지를 상위 패키지로 두어야 한다는 이야기가 된다.
- 이것은 절대적인 이야기는 아니다. 특정한 설정을 한다면 그 외의 패키지 역시 참조가 가능 하지만, 기본적으로는 상기의 규칙을 지켜야지만 컨테이너에 빈을 등록할 수 있는, 스프링 프레임워크를 이용한 프로젝트를 구성할 수 있는 것이다.
@SpringApplication 어노테이션에 엑세스 해보면
@ComponentScan이라는 어노테이션을 발견할 수 있을 것이다. 그렇기 때문에 SpringApplication하위에 있는 패키지 들이 기본적으로 컨테이너에서 관리되어질 수 있는 것이다.
'Framework > Spring' 카테고리의 다른 글
Spring 오류 정리 1. Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. (0) | 2021.08.05 |
---|---|
Spring 공부내용 정리 : 3. Spring 에서의 JDBC #3 (0) | 2021.08.04 |
Spring 공부내용 정리 : 3. Spring 에서의 JDBC #2 (0) | 2021.08.01 |
Spring 공부내용 정리 : 3. Spring 에서의 JDBC #1 (0) | 2021.08.01 |
Spring 공부내용 정리 : 1. 테스트 코드 (0) | 2021.07.25 |