Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch3
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch9
- 스프링 mvc2 - 검증
- 게시글 목록 api
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch14
- 스프링 mvc1 - 서블릿
- 스프링 mvc1 - 스프링 mvc
- 스프링 고급 - 스프링 aop
- 자바 기본편 - 다형성
- 자바의 정석 기초편 ch4
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch11
- 스프링 db1 - 스프링과 문제 해결
- 스프링 db2 - 데이터 접근 기술
- 스프링 입문(무료)
- 자바의 정석 기초편 ch8
- @Aspect
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 수제비 실기
- jpa - 객체지향 쿼리 언어
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch12
- jpa 활용2 - api 개발 고급
Archives
- Today
- Total
나구리의 개발공부기록
의존관계 자동주입, 조회 빈이 2개 이상 - 문제, @Autowired 필드명/@Qualifier/@Primary, 애노테이션 직접 만들기, 조회한 빈이 모두 필요할 때(List/Map), 자동과 수동의 올바른 실무 운영 기준 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 핵심원리 - 기본편
의존관계 자동주입, 조회 빈이 2개 이상 - 문제, @Autowired 필드명/@Qualifier/@Primary, 애노테이션 직접 만들기, 조회한 빈이 모두 필요할 때(List/Map), 자동과 수동의 올바른 실무 운영 기준
소소한나구리 2024. 1. 31. 17:11 출처 : 인프런 - 스프링 핵심원리 - 기본편(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 조회 빈이 2개 이상 - 문제
1) 문제 발생
- @Autowired는 타입(Type)으로 조회하기 때문에 ac.getBean(DiscountPolicy.class)처럼 동작함(실제로는 더 많은 기능을 제공)
- 스프링 빈 조회에서 학습했듯이 타입으로 조회하면 선택된 빈이 2개 이상일 때 에러가 발생함
- 아래의 예시의 코드처럼 FixDiscountPolicy에도 @Component를 적용하여 의존관계 자동 주입을 실행하면 하나의 빈을 기대했는데 fixDiscountPolicy, rateDiscountPolicy 2개가 발견되었다는 메시지와 함께 NoUniqueBeanDefinitionException 에러가 발생함
- 전체 테스트를 돌려보면 AutoAppConfigTest의 basicScan에서 에러가 발생
@Autowired
private DiscountPolicy discountPolicy
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
- 이때 하위 타입으로 지정하여 해결 할 수도 있지만 DIP를 위배하고 유연성이 떨어지게 되며 이름만 다르고 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안됨
- 스프링 빈을 수동 등록해서 문제를 해결해도 되지만 의존관계 자동 주입으로 해결하는 여러 방법이 존재함.
2. @Autowired 필드명, @Qualifier, @Primary
1) @Autowired필드명 매칭으로 해결
- @Autowired는 타입 매칭을 시도하고 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가로 매칭 함
(1) 사용예시
- 필드명이나 생성자의 파라미터명을 지정하고자하는 빈으로 직접 지정하면 정상적으로 rateDiscountPolicy가 주입됨
- 필드명 매칭은 먼저 타입 매칭을 시도하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능임
- 물론 필드 주입은 실무에서 사용하면 안됨
// @Autowired 필드명 매칭 - 필드명을 빈 이름으로 변경
@Autowired private DiscountPolicy rateDiscountPolicy;
// @Autowired 필드명 매칭 - 파라미터명을 빈 이름으로 변경
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
(2) @Autowired 매칭 정리
- 타입 매칭
- 타입 매칭의 결과가 2개 이상일 때 필드명, 파라미터명으로 빈 이름을 매칭함
2) @Qualifier 사용
- 추가 구분자를 사용하는 방법
- 주입 시 빈 이름을 변경하는 것이 아니라 추가적인 방법을 제공 하는 것임
(1) 사용 예시
- 빈 등록시 @Qualifier("등록한 이름")를 붙여줌
- 만약 @Qualifier("mainDiscountPolicy")를 못 찾으면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 검색함
- 그러나 Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 명확하고 좋음
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
(2) 생성자 자동 주입 예시
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
(3) 수정자 자동 주입 예시
public void setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
(4) 빈 직접 등록 시 예시
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
(5) @Qualifier 정리
- @Qualifier끼리 매칭
- 빈 이름 매칭
- 그럼에도 못찾으면 NoSuchBeanDefinitionException 예외 발생
3) @Primary사용
- 우선순위를 정하는 방법
- @Autowired시 여러 빈이 매칭이 될 경우 @Primary가 붙은 곳이 우선권을 가짐
(1) 사용예시
- 별다른 코딩이 필요없이 우선권을 가져야하는 곳에 @Primary만 붙여 주면 되며 전체 테스트를 실행해보면 정상적으로 Primary가 동작함
@Component
@Primary // 우선권
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
(2) @Primary와 @Qualifier의 활용
- @Primary는 코드의 수정이 없지만 @Qualifier는 주입 받을 때 모든 코드에 @Qualifier를 붙여 줘야함
- 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary를 적용해서 편리하게 조회하고 서브 데이터베이스의 커넥션을 획득을 할 때는 @Qualifier를 지정해서 명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지 할 수 있음
- 이때 메인 데이터베이스의 스프링 빈을 등록할 때@Qualifier를 이용하는 것은 상관 없음
(3) 우선순위
- @Primary는 기본값 처럼 동작하는 것이고 @Qualifier는 매우 상세하게 동작함
- 스프링은 자동보다는 수동이, 넓은 범위보다는 좁은 범위의 선택권이 우선순위가 높기에 @Qualifier가 우선권이 높음
3. 애노테이션 직접 만들기
1) 타입 체크 문제 해결
- @Qualifier("mainDiscountPolicy") 이렇게 문자를 적으면 컴파일시 타입 체크가 안되는데 애노테이션을 만들어서 문제를 해결할 수 있음
(1) @MainDiscountPolicy 애노테이션을 직접 생성
- annotation 패키지를 생성하여 작성
- @Qualifier에 들어가서 작성 되어있는 애노테이션들을 전부 직접 생성한 MainDiscountPolicy에 복사하고 @Qualifier()로 이름을 지정
- 애노테이션은 상속이라는 개념이 없으며 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능임
- @Qualifier뿐만 아니라 다른 애노테이션도 함께 조합해서 사용할 수 있음(@Autowired 같은 경우도 재정의 가능)
- 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의하는 것은 유지보수에 더 혼란만 가중 할 수 있으므로 사용에 주의해야함
package hello.core.annotation;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
// @Qualifier의 내부에 들어가서 붙어있는 모든 애노테이션을 복사
@Qualifier("mainDiscountPolicy") // 구분자 지정
public @interface MainDiscountPolicy {
}
(2) 직접 만든 애노테이션 적용 및 사용
- 적용할 클래스에 만든 애노테이션 적용
- 사용할 때는 @Qualifier를 사용한 것처럼 생성자, 수정자의 파라미터에 애노테이션 이름 적용하면 됨
@MainDiscountPolicy // 직접 생성한 애노테이션을 적용
public class RateDiscountPolicy implements DiscountPolicy {}
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy;
// 생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@MainDiscountPolicy DiscountPolicy discountPolicy) {
// ...
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
4. 조회한 빈이 모두 필요할 때 - List, Map
- 위에서 제시한 여러가지 방법 중 한가지로 조회 빈이 2가지 이상일 때의 문제를 해결한 후 진행해야 함
1) 조회한 빈이 모두 필요할 때
- 의도적으로 해당 타입의 스프링 빈이 다 필요한 경우가 발생할 수 있음
- 예를 들어 모든 할인 서비스를 제공할 때 클라이언트가 할인의 종류를 선택할 수 있다고 가정
(1) AllBeanTest
- List와 Map을 모두 사용할 수 있지만 여기서는 Map으로만 사용
- 실행해보면 fixDiscountPolicy, rateDiscountPolicy의 할인정책이 모두 정상적으로 동작하여 테스트로직이 통과함
package hello.core.autowired;
public class AllBeanTest {
@Test
void findAllBean() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "memberA", Grade.VIP);
assertThat(discountService).isInstanceOf(DiscountService.class);
int fixDiscountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
assertThat(fixDiscountPrice).isEqualTo(1000);
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
assertThat(rateDiscountPrice).isEqualTo(2000);
}
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
}
(2) 로직 분석
- DiscountService 클래스는 Map으로 모든 DiscountPolicy(할인정책)를 주입 받으며 이때 fixDiscountPolicy와 rateDiscountPolicy가 주입됨
- discount()메서드는 discountCode로 넘어온 할인정책을 Map에서 동일한 스프링을 찾아 실행함
(3) 주입 분석
- Map<String, DiscountPolicy> : map의 키에 스프링 빈의 이름을 map의 값에는 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아줌
- List<DiscountPolicy> : DiscountPolicy타입으로 조회한 모든 스프링 빈을 담음(현재 테스트코드에선 주입만 받고 사용하지 않음)
- 만약 해당하는 타입의 스프링 빈이 없으면 빈 컬렉션이나 Map을 주입함
** 참고 - 스프링 컨테이너를 생성하면서 스프링 빈 등록하기
- 스프링 컨테이너는 생성자에 클래스 정보를 받는데 여기에 클래스 정보를 넘기면 해당 클래스가 스프링빈으로 자동으로 등록됨
- 즉, 해당코드는 2가지로 나누어서 이해할 수 있음
- 1. new AnnotationConfigApplicationCentext() 를 통해 스프링 컨테이너를 생성
- 2. AutoAppConfig.class, DiscountService.class 파라미터의 클래스를 자동으로 스프링 빈으로 등록
- 정리하면 스프링 컨테이너를 생성하면서 동시에 파라미터의 클래스를 자동으로 스프링빈으로 등록함
ApplicationContext ac = new AnnotationConfigApplicationContext
(AutoAppConfig.class, DiscountService.class);
5. 자동, 수동의 올바른 실무 운영 기준
1) 편리한 자동 기능을 기본으로 사용
- 스프링이 나오고 시간이 갈수록 점점 자동을 선호하는 추세임
- 스프링은 @Component뿐 아니라 @Controller, @Service, @Repository처럼 계층에 맞춰 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원함
- 최근 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록하도록 설계 되어있음
- 설정 정보를 기반으로 애플리케이션을 구성하는 부분과 실제 동작하는 부분을 명확히 나누는 것이 이상적이지만 @Component 하나만 넣어주면 끝나는 것을 @Configuration 설정 정보에 @Bean을 적고, 객체를 생성하고, 주입할 대상을 적는 것은 상당히 번거로운 작업이며 관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 됨
- 자동으로 빈 등록을 사용해도 일부 애노테이션을 수정하는 등의 작업만하면 OCP, DIP를 어느정도 지킬 수 있음
2) 수동 빈 등록은 언제 사용해야 할까?
- 애플리케이션에 광범위하게 영향을 미치는 기술지원 객체는 수동 빈으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수하기에 좋음
(1) 업무 로직 빈
- 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리 등이 모두 업무 로직임
- 보통 비즈니스 요구사항을 개발할 때 추가 되거나 변경이 됨
- 숫자도 매우 많고 한번 개발해야 하면 컨트롤러, 서비스, 리포지토리처럼 어느정도 유사한 패턴이 있으며 보통 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉬움
- 이런 경우 자동 기능을 적극적으로 사용하는 것이 좋음
(2) 기술 지원 빈
- 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용
- 데이터베이스 연결이나, 공통 로그 처리처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들
- 업무로직과 비교해서 그 수가 적고 보통 애플리케이션 전반에 걸쳐서 영향을 미침
- 적용이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많아 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋음
(3) 비즈니스 로직 중에서 다형성을 적극 활용할 때
- 조회한 빈이 모두 필요할 때 작성했던 테스트코드를 보면 DiscountPolicy가 의존관계 자동 주입으로 Map에 어떤 빈들이 주입 될지, 각 빈들의 이름은 무엇인지 한번에 파악할 수 없음(물론 타고 들어가서 보면 알 수 있으나 한눈에 파악이 어렵고 명확하지 않음)
- 이런 경우 수동 빈으로 등록하거나 자동으로 할 경우 특정 패키지에 묶어 두는 것이 좋음, 핵심은 딱 보고 이해가 되어야 함
- 아래의 코드처럼 할인 정책 관련된 별도의 설정 정보로 만들어 수동으로 빈 등록을 하면 한눈에 파악하기가 좋으며 만일 자동으로 등록하고자 할 경우 파악하기 좋게 DiscountPolicy의 구현 빈들만 모아서 특정 패키지에 모아두어야 파악하기가 좋음
@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
** 참고
- 스프링과 스프링 부트가 자동으로 등록하는 수 많은 빈들은 예외임
- 이런 부분들은 스프링 자체를 이해하고 의도대로 잘 사용하는 것이 중요함
- 스프링부트의 경우 DataSource같은 데이터베이스 연결에 사용하는 기술 지원 로직까지 내부에서 자동으로 등록하는데 이런부분은 매뉴얼을 잘 참고해서 스프링 부트가 의도한 대로 편리하게 사용 하면 됨
- 스프링부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록하면 수동으로 등록해서 명확하게 드러내는 것이 좋음
(4) 정리
- 편리한 자동 기능을 기본으로 사용
- 직접 등록하는 기술 지원 객체는 수동 등록
- 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민하면 됨