지난 포스트에서 컴포넌트 스캔을 이용해 스프링 빈을 등록하고 사용해보았다.
https://debuggingworld.tistory.com/50
스프링 입문) 8. 스프링 빈과 의존관계 - 컴포넌트 스캔
지난 포스트에서 회원 관리 서비스인 MemberSerivce를 작성하고 테스트해보았다. 일반적으로 Cotroller를 이용해 외부 요청을 처리하고, Service에서 비즈니스 로직을 구현하며, Repository에서 데이터 저
debuggingworld.tistory.com
컴포넌트 스캔 방식이란, 스프링 애플리케이션이 구동되어 컨테이너를 생성할 때,
스프링이 자동으로 프로젝트 패키지 경로를 스캔에 @Component 어노테이션이 붙은 자바 클래스들을 스프링 빈으로 등록하는 방식이다.
이렇듯 컴포넌트 스캔을 이용해 스프링 빈을 등록하면 작성할 클래스에 @Component, @Service, @Repository, @Controller등의 어노테이션만 붙이면 되므로 사용하기 편리하지만, 빈으로 등록할 클래스를 교체하는 등
프로그램 구조에 변동이 생기면 코드를 직접 찾아 수정해야 되므로 유지보수하기 불편한 감이 있다.
프로젝트 규모가 커질수록 관리하는 스프링 빈도 많아지므로 이러한 불편함은 더욱 커진다.
이러한 스프링 빈들을 하나의 클래스에서 통합적으로 관리하는 방법이 있다.
우선 기존의 회원 서비스, 리포지토리 클래스의 @Service, @Repository, @Autowired 어노테이션을 제거한다.
MemberSerivce.java
package hdxian.hdxianspring.service;
import hdxian.hdxianspring.domain.Member;
import hdxian.hdxianspring.repository.MemberRepository;
import hdxian.hdxianspring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
public class MemberService {
// private final MemberRepository repository = new MemoryMemberRepository();
private final MemberRepository repository;
public MemberService(MemberRepository repository) {
this.repository = repository;
}
// 회원 가입 기능
public Long join(Member member) {
// 중복된 이름으로는 가입 불가.
checkNameDuplication(member);
repository.save(member);
return member.getId();
}
private void checkNameDuplication(Member member) {
repository.findByName(member.getName()) // findbyName()의 리턴 타입은 Optional<Member>
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
public List<Member> findMembers() {
return repository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return repository.findById(memberId);
}
}
MemoryMemberRepository.java
package hdxian.hdxianspring.repository;
import hdxian.hdxianspring.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream().
filter(member -> member.getName().equals(name)).findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
MemberController는 그대로 컴포넌스 스캔을 통해 등록되도록 한다.
이유에 대해 강의에서는 "Controller는 굳이 그럴 필요가 없다. 어쩔 수 없다." 정도로 짧게 설명하고 넘어갔다.
아마 MemberController와 같은 Controller는 Controller를 의존하는 다른 클래스도 없고,
다른 스프링 빈과 통합 관리하는게 외려 더 불편할 수 있기 때문이라 추측한다.
MemberController.java (이전과 그대로다)
package hdxian.hdxianspring.controller;
import hdxian.hdxianspring.repository.MemoryMemberRepository;
import hdxian.hdxianspring.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;
}
}
다음으로 프로젝트 패키지 경로 하위에 SpringConfig 클래스를 다음과 같이 생성한다.
SpringConfig.java
package hdxian.hdxianspring;
import hdxian.hdxianspring.repository.MemberRepository;
import hdxian.hdxianspring.repository.MemoryMemberRepository;
import hdxian.hdxianspring.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();
}
}
스프링 애플리케이션이 구동되어 스프링 컨테이너가 생성될 때,
스프링은 @Configuration 어노테이션이 붙은 클래스에서 @Bean으로 정의된 클래스들을 스프링 빈으로 등록한다.
SpringConfig에서 MemberService와 MemoryMemberRepository 클래스를 스프링 빈으로 등록했으므로 기존 클래스의 @Service, @Repository, @Aurowired 없이도 DI가 가능하다.
코드는 여기서 끝이다. 애플리케이션을 구동하면 정상적으로 실행된다.
구현해놓은 기능은 없지만, 직접 실행해서 Bean 관련 에러가 뜨지 않는지 확인해보길 권장한다.
단계별로 늘 확인을 해봐야 한다. 뒤에 가서 에러가 발생하면 어디서부터 잘못됐는지 찾기 힘들다.
DI의 3가지 구현 방식
DI의 구현 방법으로 다음 3가지가 있다.
1. 생성자 주입
클래스 생성자를 통해 의존관계를 주입하는 방식이다. (의존관계 주입이 DI다. 헷갈리면 공부를 다시 해야한다.)
예를 들면 다음과 같다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
의존하는 클래스의 참조 변수를 선언하고, 생성자를 통해 참조변수에 객체를 할당한다.
현재 가장 권장하는 방식이다.
2. 필드 주입
의존하는 클래스의 참조변수에 직접 의존관계를 설정하는 방식이다.
@Controller
public class MemberController {
@Autowired
private MemberService memberService;
}
의존 클래스의 객체에 대해 별도의 추가 작업을 수행할 수 없어 권장되지 않는 방식이다.
3. setter 주입
setter를 통해 의존관계를 설정하는 방식이다.
@Controller
public class MemberController {
private MemberService memberService;
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
}
setter를 통해 DI를 하려면 setter가 public으로 설정되어야 한다.
이럴 경우 인스턴스가 생성된 이후에도 외부에서 setter를 호출할 수 있으므로 그다지 좋지 못한 방법이다.
클래스 의존관계는 실행 도중 동적으로 변경될 일이 없다.
예컨데 예시 코드의 MemberController가 사용하는 MemberService 객체가 다른 객체로 바뀔 일이 없다는 것이다.
상식적으로 여기저기서 의존관계로 얽혀 있는 클래스를 아무때나 바꾼다니, 오류나면 어떻게 고치려고 그런 짓을 할까.
이러한 특성을 클래스는 변경에 대해 닫혀있어야 한다고 표현한다.
그런데 setter가 public으로 설정돼있으면 아무나 setter를 호출할 수 있으므로 이는 굉장히 좋지 못한 설계다.
즉 setter 주입 방식도 권장되지 않는다.
'[inflearn] 스프링 입문' 카테고리의 다른 글
11. H2 데이터베이스 설치 (0) | 2023.04.29 |
---|---|
10. 회원 관리 예제 - 웹 MVC 개발 (0) | 2023.04.16 |
스프링 입문) 8. 스프링 빈과 의존관계 - 컴포넌트 스캔 (0) | 2023.04.10 |
스프링 입문) 7. 회원 관리 예제 - 백엔드 개발(2) (0) | 2023.04.03 |
스프링 입문) 6. 회원 관리 예제 - 백엔드 개발(1) (0) | 2023.04.02 |