A가 B를 의존한다?
A가 B를 의존한다는 말은 B의 변화가 감지된다면 A에게까지 파급 효과가 미친다는 것을 말한다.
요리사와 레시피의 관계를 예시로 들 수 있다.
요리사는 레시피를 참고하여 요리를 만들 수 있다. 만약 레시피가 변화되었다면 요리사는 변화된 레시피에 따라 요리를 만들 수 있게 된다. 레시피의 변화가 요리사의 요리에 영향을 미쳤기 때문에 요리사는 레시피에 의존한다.라고 설명할 수 있게 되는 것이다.
package di;
public class Chef {
private Recipe recipe;
public Chef() {
recipe = new Recipe();
}
}
위와 같이 클래스에 직접적으로 의존하게 되면 결합도가 높아져 이렇게 되면 유지보수가 어려워진다는 단점이 생긴다. 레시피는 여러 가지가 존재할 수 있는데 그 때마다 레시피를 바꾸면 많은 수정을 필요로 하게 된다.
또 다른 예시로 핸드폰을 들어보자. 우리가 실생활에서 갤럭시, 아이폰, LG 등등 여러 회사의 제품을 사용하고 있다. 핸드폰을 교체할 때마다 사용자가 사용하는 핸드폰의 기종을 그 때마다 교체한다면 코드의 수정은 불가피하다. 갤럭시, 아이폰, LG 등등의 핸드폰 제품의 공통점을 추출해 상위 타입으로 보았을 때 상위 타입은 바로 핸드폰이 되는 것이다. 특정 핸드폰에 의존하는 것이 아닌 핸드폰이라는 상위 타입의 공통점에 의존하는 것이다.
다시 위의 예시로 돌아와서 현재 코드를 보면 요리사는 Recipe
클래스에만 의존하고 있는 것을 볼 수 있다. 요리사가 다양한 레시피에 의존할 수 있도록 하려면 코드를 아래와 같이 수정하면 된다.
package di;
public class Chef {
private FoodRecipe foodRecipe;
public Chef() {
foodRecipe = new FoodA();
// foodRecipe = new FoodB();
}
}
레시피의 변화에 따라서 전체 코드를 수정할 필요없이 다형적 참조를 활용해 결합도를 낮추고 유지보수의 용이성을 높일 수 있다.
의존관계 주입을 수행하는 방법은 크게 2가지가 있다.
[1]. 어노테이션을 이용한 컴포넌트 스캔과 자동 의존관계 주입 방법
[2]. 자바 코드 설정 파일을 작성해 직접 스프링 빈으로 등록하는 방법
Ex1. MemberRepository
인터페이스를 구현한 구현체 클래스 MemoryMemberRepository
를 사용 & 어노테이션을 이용한 컴포넌트 스캔과 자동 의존관계 주입 방법
@Controller
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
// ...
}
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// ...
}
@Repository
public class MemoryMemberRepository implements MemberRepository{
// ...
}
@Component
어노테이션이 있으면 스프링 빈으로 자동 등록이 된다.@Controller
, @Service
, @Repository
어노테이션만 있을 뿐 @Component
어노테이션은 붙어 있지 않았는데 어떻게 스프링 빈으로 등록이 되었을까...@Component
어노테이션이 있는 것을 볼 수 있는데 이 때문에 스프링 빈으로 등록이 된다고 볼 수 있다.Ref1. @Controller 코드
// ...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 확인
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
Ref2. @Service 코드
// ...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 확인
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
Ref3. @Repository 코드
// ...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 확인
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
Ex2. MemberRepository
인터페이스를 구현한 구현체 클래스 MemoryMemberRepository
를 사용 & 자바 코드로 설정 파일을 작성하여 직접 스프링 빈으로 등록하는 방법
❗앞서 어노테이션을 이용한 컴포넌트 스캔 때 작성했던 @Service
, @Repository
, @Autowired
어노테이션을 제거(단, @Controller
는 남겨둠)
package hello.springboot;
import hello.springboot.repository.MemberRepository;
import hello.springboot.repository.MemoryMemberRepository;
import hello.springboot.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
어노테이션이 붙은 메서드를 전부 찾아서 생성된 객체로 빈을 등록한다.
Ex3. 스프링 JPA를 활용 & 어노테이션을 이용한 컴포넌트 스캔과 자동 의존관계 주입 방법
Ex1
과 같이 다시 어노테이션을 붙여주고 스프링 JPA 인터페이스를 작성
package hello.springboot.repository;
import hello.springboot.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
Optional<Member> findByName(String name);
}
JPA의 경우 JpaRepository 인터페이스를 상속받아 사용한다.
❗인터페이스를 상속(extends)?, 구현(implements)?
인터페이스를 구현한 구현체 클래스의 경우에는
implements
를 사용하는 것이 맞고 인터페이스가 인터페이스를 상속받는 경우에는extends
를 사용한다.
또한 인터페이스는 다중 상속을 지원하므로JpaRepository
와MemberRepository
를 상속받는다.
대개 인터페이스를 사용면 인터페이스의 추상 메서드를 구현한 구현체 클래스가 존재해야하는데 그 구현체가 없다. 하지만 JpaRepository
를 상속받고 있으면 구현체 클래스를 자동으로 만들고 스프링 빈으로 등록해준다.(개발자가 등록하는 것이 아니다.)
Ex4. 스프링 JPA를 활용 & 자바 코드로 설정 파일을 작성하여 직접 스프링 빈으로 등록하는 방법★
이 과정을 공부하면서 아래와 같은 에러가 계속 발생했었다.
Parameter 0 of constructor in hello.springboot.service.MemberService required a single bean, but 2 were found:
이 에러는 스프링 빈이 2개 이상 등록되어 발생하는 에러다. @Repository
어노테이션이 붙은 부분도 제거를 했고 자바 코드로 작성한 설정 파일인 SpringConfig
에서 @Configuration
과 @Bean
어노테이션을 붙여 직접 스프링 빈으로 등록하도록 했는데 스프링 빈이 2개 이상 등록이 되었다는 문제가 계속 발생해 매우 난감했다.
MemberRepository
인터페이스를 구현한 메모리 Repository 구현체 클래스의 이름을 처음에 MemberRepositoryImpl
로 작성해서 했다. 근데 이것이 문제가 되었던 것이다.
사용자 정의 구현 클래스 규칙에 해당하여 뜻하지 않게 스프링 빈으로 등록이 되어버린 것이다.
사용자 정의 구현 클래스
- 규칙 : 리포지토리 인터페이스 이름 +
Impl
위와 같은 규칙으로 이름을 붙이면 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록
그래서 이 부분을 감안하여 MemberRepository
인터페이스를 구현한 구현체 클래스 이름을 다른 이름으로 바꾸었더니 정상적으로 동작하는 것을 확인할 수 있었다.
스프링 JPA를 사용할 때 자바 코드로 직접 설정 파일을 작성할 때 스프링 JPA를 개발자가 직접 스프링 빈으로 등록하는 것이 아니다. 따라서 이 부분을 고려하여 SpringConfig
파일을 작성한다.
package hello.springboot;
import hello.springboot.repository.MemberRepository;
import hello.springboot.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
SpringConfig 파일에서 생성자를 통한 의존관계 주입이 이루어지는 것을 볼 수 있다.
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// ...
}
❗private 접근 제어자 : 외부로부터의 접근을 제어
❗final 키워드 : 재할당을 불가능하게함으로써 객체의 불변을 보장할 수 있다.
이 때, 생성자에 @Autowired
어노테이션이 있는 것을 볼 수 있는데 이 @Autowired
어노테이션은 생성자가 만약 하나만 존재한다면 생략할 수 있다.
JDBC나 JdbcTemplate, JPA 등은 스프링을 사용하는 환경이므로 @Repository
를 통한 스프링이 관리하는 객체를 등록(생성)하고 생성자 주입(@Autowired MemberRepository memberRepository) 혹은 필드 주입으로 의존관계를 주입하여 사용한다.
인프런 - 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
인프런 커뮤니티 - Parameter 0 of constructor in hello.springboot.service.MemberService required a single bean, but 2 were found:
https://mangkyu.tistory.com/125