1. 스프링 컨테이너와 빈
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
System.out.println("beanDefinition = " + beanDefinition);
}
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByParentTypeBeanName(){
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType(){
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value: " + beansOfType.get(key));
}
}
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String s : beansOfType.keySet()) {
System.out.println("key = " + s + " value: " + beansOfType.get(s));
}
}
@Test
@DisplayName("조회한 빈이 모두 필요할 때")
void findAllBean(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "userA", Grade.VIP);
int discountPolicy = discountService.discount(member, 10000, "fixDiscountPolicy");
}
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;
}
public int discount(Member member, int price, String discountCode){
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
2). ComponentScan
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters= @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
@Service
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired(required = false)
public void setNoBean(Member member){
setNoBean = member
@Autowired
public void setNoBean3(@Nullable<Member> member){
setNoBean3 = member
@Autowired
public void setNoBean3(Optional<Member> member){
setNoBean3 = member
}
3) 여러빈이 있을 경우
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
@Primary
public class FixDiscountPolicy implements DiscountPolicy
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy
@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("fixDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
4. 빈 생명주기 콜백
- 스프링 빈 라이프 사이클: 객체 생성 -> 의존관계 주입
- 스프링 빈의 이벤트 라이프사이클: 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 사용전 콜백 -> 스프링 종료
- 3가지 방법으로 빈 생명주기 콜백 지원
public class NetworkClient implements InitializingBean, DisposableBean
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
public void init(){
System.out.println("init");
}
public void close(){
System.out.println("NetworkClient.close");
}ㅋ
@PostConstruct
public void connect(){
System.out.println("connect: " + url);
}
@PreDestroy
public void disconnect(){
System.out.println("close " + url);
}
5. 빈 스코프
- 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입: 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 웹 관련 스코프
- request: 웹 요청이 들어오고 나갈때까지 유지되는 스코프
- session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
- application: 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
1). 싱글톤, 프로토타입
@Test
void singletonClientUsePrototype(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean bean = ac.getBean(ClientBean.class);
int count1 = bean.logic();
assertThat(count1).isEqualTo(1);
}
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init" + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
2. 웹 스코프
- 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출
- 종류: request, session, application, websocket
- requestURL은 빈이 생성되는 시점에는 알 수 없으므로 setter로 입력받는다
- Scope가 고객이 들어와서 나갈때까지 인데 그냥 스프링 컨테이너가 띄워질때 주입이 될수없다
스프링에게 빈을 달라고 하는 시점은 스프링 애플리케이션 실행할때가 아니라 고객의 요청이 올때
이럴때 Provider를 쓴다
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("/log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller Test");
logDemoService.logic("testId");
return "ok";
}
}
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Data
public class MyLogger {
private String uuid;
private String requestURL;
public void log(String message) {
System.out.println("["+uuid+"] " + " ["+ requestURL+"] "+message);
}
@PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
System.out.println("["+uuid+"] request scope bean create: " + this);
}
@PreDestroy
public void close(){
System.out.println("["+uuid+"] request scope bean close: " + this);
}
}