아래 내용은 김영한 님의 스프링 핵심 원리 기본편의 내용에 기반한다.
스프링 빈을 등록할때 자바 코드의 @Bean이나 XML의 등을 통해서 설정 정보에 직접 등록을 하는 방식은 규모가 커지면 다음과 같은 문제가 있다.
하지만 컴포넌트 스캔 방식으로 하면 의존관계 자동 주입을 위해 @Autowired를 사용해야 한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@Configuration
@ComponentScan
public class AutoAppConfig {
}
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
@Component
public class MemberRepositoryImpl implements MemberRepository {}
이렇게 설정하면 component scan을 통해 스프링이 @Component가 붙은 모든 클래스를 빈으로 등록한다. 그리고 @autowired가 있으면 스프링은 컨테이너에서 등록된 빈 중에서 타입으로(기본조회 전략) 조회하여 주입한다.
모든 자바 클래스를 스캔하면 시간이 오래걸릴 수 있기 때문에 필요한 위치에서 사용해야 한다.
@ComponentScan(basePackages = "hello.core",}
@Component
@Controller (MVC 컨트롤러로 인식)
@Service (핵심 비즈니스 로직 위치 인식)
@Repository (스프링 데이터 접근 계층으로 인식)
@configuration (스프링 설정 정보로 인식)
사실상 @Component만 인식이 되는것이고 다른 애노테이션은 내부에 @Component를 포함하고 있다.
참고로 에노테이션은 상속의 개념이 없어 특정 애노테이션이 다른 에노테이션을 포함하고 있다는 것을 인식하는건 스프링의 지원하는 기능이다.
includeFilters,excludeFilters 옵션을 사용해 자동 등록할 빈을 추가 또는 제외 시킬 수 있다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class)
)
자동 등록 and 자동 등록 -> ConflictingBeanDefinitionException 예외 발생
수동 등록 and 자동 등록 -> 수동 등록 우선순위
최근 스프링 부트는 수동 등록 and 자동 등록에도 오류가 나도록 기본값을 변경하였고 프로퍼티 값(spring.main.allow-bean-definition-overriding)으로 기본설정을 변경할 수 있도록 하였다.
항상 코딩할때 코드가 길어지고 깔금하지 않더라고 명확성을 유지하는게 나은 선택이다. 안그러면 잡기힘든 버그가 될 가능성이 높다.
의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야만 한다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- 불변,필수 의존관계에 사용, 1번 호출 보장
- 좋은 개발 습관은 제약을 잘 두자 모두 변경에 열어두면 에러/변경 추적이 힘들다. 예) 객체를 불변으로 만들면(여기서 제약은 생성할 때만 내부 값을 세팅할 수 있음) 중간에 어디서 바뀔지 걱정하지 않아도 된다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
- 자바빈 프로퍼티 규약(getXxx,setXxx)의 수정자 메서드 방식을 사용
- 선택 변경 가능서이 있는 의존관계에만 사용
- @Autowired(required = false)로 지정해야 주입할 대상이 없어도 작동한다.
- 스프링에서 빈을 모두 생성후 @autowired가 있는 메서드에 의존성을 주입하는 사이클을 따른다. (생성과 의존관계가 나누어진 단계)
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
- 간결하다.
- 외부에서 변경이 힘들어 테스트 하기 힘들다.
- DI 프레임워크 없이는 아무것도 못한다.
- @Configuration이나 테스트 코드와 같이 실제 어플리케이션과 관계없는 곳에만 사용 권장
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- 수정자 주입과 똑같다고 볼 수 있다.
- 생성자와 수정자 주입으로 다 해결할 수 있기 때문에 잘 사용되지 않는다.
Member가 스프링에서 관리되지 않는 빈이면 해당 메서드는 호출이 안됨 하지만 required=true(default)면 exception 터짐
@Autowired(required = false)
public void setNoBean(Member member) {}
Member가 스프링에서 관리되지 않는 빈이면 null값으로 들어옴
@Autowired
public void setNoBean(@Nullable Member member) {
}
Member가 스프링에서 관리 여부에 따라 적절한 optional값으로 들어옴
@Autowired
public void setNoBean(Optional<Member> member)) {
}
@RequiredArgsConstructor를 사용 (final keyword가 붙은 필드들에 대한 생성자를 자동으로 생성해준다.)
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Autowired
// RateDiscountPolicy가 주입된다.
public OrderServiceImpl(MemberRepository memberRepository,DiscountPolicy discountPolicy ) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier가 우선순위가 @Primary보다 높다.
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public OrderServiceImpl(Map<String, DiscountPolicy> policyMap,List<DiscountPolicy> policies ) {
this.policyMap = policyMap;
this.policies = policies;
}
}
Map에서 key값이 String이고 빈이름으로 자동으로 등록되다면 불편하지 않을까? enum처럼 확실한 값으로 지정할려면 수동 등록으로 하면 될까?
자동등록 사용
수동등록 사용
- 기술 지원 빈 (데이터베이스 연결 ,공통 로그 처리)
- 기술 지원 빈은 문제가 발생하면 어디서 발생했는지 파악하기 어려움으로 수동 등록으로 명확하게 보여줘야 한다.(이게 정확하게 어떤 뜻일까?🙄)
- 다형성을 적극 활용할 때
- List, Map과 같은 타입으로 여러객체를 자동등록 받는다면 어떤 객체들이 들어오는지 map의 key값으로 들어오는 빈의 이름은 무엇인지가 쉽게 파악하기 어렵다. 자동 등록을 하려면 최소 특정 패키지에 묶어두어야 한다.
스프링 컨테이서 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
생성자 의존성 주입은 빈 생성과 동시에 의존관계를 주입힌다.
객체 생성과 초기화 분리
외부 커넥션 연결등과 같은 무거운 동작을 수행하는 초기화 작업은 객체 생성과 분리하는게 유지보수 관점에서 좋다.(SRP) 하지만 🙄 그렇다면 항상 생성 후 초기화 메서드를 호출하고 다른 메서드를 사용하도록 디자인하는게 더 좋을까? 특히 스프링을 사용하지 않는 상황이라면 개발자가 직접 초기화 메서드를 호출하는게 번거롭지 않을까?
인터페이스
설정 정보에 초기화 메서드, 종료 메서드 지정
@PostConstruct, @PreDestroy 애노테이션
고로 최대한 @PostConstruct, @PreDestroy 애노테이션을 사용하고 외부 라이브러리 경우에만 @Bean 의 initMethod , destroyMethod를 사용하자