기존 Spring에서는 자바 코드의 @Bean
이나, xml파일의 <bean>
태그 등을 통해서 직접 등록할 스프링 빈을 나열했다.
실무에서는 코드가 수십, 수백개가 될텐데 이 Bean들을 모두 일일히 등록해주려면 귀찮기도하고 실제로 누락되는 정보도 많을 것이다.
그래서 Spring은 설정 정보가 없어도 자동으로 Spring Bean을 등록해주는 @ComponentScan
기능을 제공한다.
@ComponentScan
Annotation은@Component
및 @Controller
, @Service
, @Repository
, @Configuration
등 과 같은 Annotation을 스캔하여 해당 Annotation이 부여된 Class(객체)들을 자동으로 Scan하여 스프링 빈(Bean)에 등록 해주는 역할을 한다.
그리고 @Autowired
를 통해 의존 관계도 자동으로 주입(Dependency Injection)해준다.
@ComponentScan
Annotation은 따로 basePackages를 지정해 주지 않는 이상 자기가 위치한 곳부터 패키지 아래의 모든 @Component
와 @Controller
, @Service
, @Repository
, @Configuration
와 같은 Annotation 들을 Scan하여 객체를 @Bean
으로 등록해준다.
그렇기 때문에 패키지의 가장 바깥쪽에 위치하는 곳에 두고 Scan을 한다.
@ComponentScan
을 적용한 예시
@Configuration
@ComponentScan
public class AppConfig {
}
@ComponentScan
내 Scan 패키지 범위 지정하기basePackages 를 이용하면 탐색할 패키지의 시작 위치를 지정할 수 있다. basePackages에 지정한 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.
com.kyh.heera.member 패키지와 그 하위 패키지 내 @Component
객체 Scan하기
@ComponentScan(
basePackages = "com.kyh.heera.member",
}
@ComponentScan
은 말 그대로 @Component
Annotation이 붙은 Class를 Scan해서 스프링에 Bean으로 등록한다.
@Controller
, @Service
, @Repository
, @Configuration
와 같은 Annotation들을 들어가보면 @Component
가 등록되어 있는 것을 확인할 수 있다.
@Controller
Annotation 내부
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Service
Annotation 내부
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Repository
Annotation 내부
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Configuration
Annotation 내부
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
boolean enforceUniqueMethods() default true;
}
Bean 등록 테스트
package com.kyh.heera.scan;
import com.kyh.heera.AutoAppConfig;
import com.kyh.heera.member.MemberRepository;
import com.kyh.heera.member.MemberService;
import com.kyh.heera.order.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
public class AppConfigTest {
@Test
void basicScan() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
OrderServiceImpl bean = ac.getBean(OrderServiceImpl.class);
MemberRepository memberRepository = bean.getMemberRepository();
System.out.println("memberRepository = " + memberRepository);
}
}
Test 결과
@Autowired
를 지정하면, Spring Container가 해당 스프링 빈을 찾아서 주입한다.
참고로 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 Bean이어야 동작한다. Member와 같은 DTO에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.
기본적으로 Type 기반으로 조회를 하며, 여러 빈이 있다면 필드이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
@Autowired
는 4가지의 경우에 적용할 수 있다.
생성자 주입은 생성자에 의존성 주입을 받고자 하는 field를 나열하는 방법으로, 3가지 경우 중 가장 권고되는 방법이다.
특징
Constructor(생성자) 주입 예시
@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;
}
}
수정자 주입은 setter라 불리는 field의 값을 변경하는 수정자 Method를 통해 의존관계를 주입하는 방법이다.
특징
참고
선택적으로 의존성을 주입할 경우에 @Autowired의 기본동작은 주입할 대상이 없으면 오류가 발생하기 때문에 @Autowired(required=false) 와 같은 옵션을 추가해주어야 한다.
setter(수정자) 주입 예시
@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;
}
}
필드에 바로 주입하는 방식이다. 예전에 나도 많이 사용했었지만, 지금은 권장하지 않는다고 한다.
특징
field(필드) 주입 예시
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
일반 method(메소드)를 통해서 주입 받을 수 있다.
특징
method(메소드) 주입 예시
@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;
}
}