상세 컨텐츠

본문 제목

[Spring] Spring IoC Container, BeanScan, 생명주기, 스코프

😎 지식/자바_스프링_테스트☕

by :부셔져버린개발자 2024. 11. 20. 17:03

본문

IoC 

- 제어의 역전 : 객체의 생성에서부터 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미 

 

1) DI : Dependency Injection 

생성자, 필드, setter 

동적으로 의존성 주입 -> 응집도 낮춤

디버깅 어려움 + 테스트 코드 짜기 어려움

 

2) DL : Depnedency LookUp 

의존관계가 필요한 객체에서 직접 검색하는 방식

 

"스프링 내"에서

1) 생성자 주입 

- 객체를 생성할 때, 의존성을 강제할 수 있음

private final MasterService; 

MentorController(MasterService masterService ){

   this.masterService = masterService;

}

- 순환참조 예방

 

2)  필드 주입

 

3) setter

- 필요할 때만 외부에서 받아서 사용 가능

 

IoC Container가 왜 필요한가

1) 전역적으로 객체간의 의존관계를 설정

2) 반복적인 객체 생성 x

BeanFactory의 하위 인터페이스가 ApplicationContext가 Spring에서 말하는 IoC Container라고 생각하면 된다

 

IoC Container 를 사용함으로서 얻을 수 있는 장점

객체 의존성을 역전시켜 객체 간의 결합도를 줄이고, 유연한 코드를 작성할 수 있게 하여

가독성 및 코드 중복, 유지 보수를 편하게 할 수 있음

 

어떤 기능을 확장했는가

ApplicationContext의 또다른 기능으로는 AOP와의 쉬운 통합, 메시지 리소스 처리(국제화), 이벤트 게시가 있다 

 

 

ApplicationContext 의 상속 클래스 로 인해 자바코드, XML 등으로 설정 가능

1) AnnotationConfigApplicationContext(AppConfig.class)

2) GenericXmlApplicationContext(appConfig.xml)

 

스프링 빈 이벤트 라이프 사이클

IoC Container 생성 -> 빈 생성 -> 의존 관계 주입 -> 초기화 콜백 메소드 호출@PostConstruct -> 사용 -> 소멸전 콜백 메소드 호출@PreDestroy -> 스프링 종료

 

 

Spring Container : 객체 인스턴스를 싱글톤으로 관리

싱글톤 객체는 상태를 유지하게 설계하면 안된다 

무상태 설계해야 한다

1) 특정 클라이언트가 의존적인 필드가 있으면 안된다

2) 값을 변경할 수 있는 필드가 있으면 안된다

 

public class StatefulService {
	private int price; // 상태를 유지하는 필드
    public void order(String name, int price){
    	System.out.println("name= "+ name + " price = " + price);
    	this.price = price; // 여기가 문제!
    }
    public int getPrice(){
    	return price;
    }
}

 

public class StatefulServiceTest {

	@Test
    void statefulServiceSingleton() {
    	ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService = ac.getBean("statefulService", StatefulService.class);
        StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
        
        statefulService.order("userA", 10000);
        statefulService2.order("userB", 20000);
        
        int price = statefulService.getPrice();
        System.out.println(price); // 20000
        
        Assertions.assertThat(statefulService.getPrice()).isEqualTo(20000);
        
    }
    
    static class TestConfig {
    	@Bean
        public StatefulService statefulService(){
        	return new StatefulService();
        }
    
    }
}

 

 

@Configuration을 적용한 AppConfig클래스는

CGLIB가 바이트코드를 조작하여 싱글톤으로 보장한다

@Bean
public MemberRepository memberRepository() {
	if(memberRepository가 이미 스프링 컨테이너에 등록되어  있음){
    	return 스프링 컨테이너에서 찾아서 반환;
    } else {
    	기존 로직을 호출해서 생성하고 스프링 컨테이너에 등록
        return 반환;
    }
}

 

컴포넌트 스캔

@ComponentScan

@Component 가 붙은 모든 클래스를 스프링 빈으로 등록

basePackages : 필요한 위치부터 탐색하도록 시작 위치 지정

@SpringBootApplication 안에 @ComponentScan이 들어있음

 

@Controller : 스프링 MVC 컨트롤러

@Repository : 스프링 데이터 접근 계층으로 인식, 데이터 계층의 예외를 스프링 예외로 변환

@Configuration : 스프링 설정 정보로 인식, 스프링 빈이 싱글톤을 유지하도록 추가 처리

@Service : 비즈니스 계층 인식 도움 

 

컴포넌트 스캔 제외 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {}

 

@ComponentScan(
  includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class), 
  excludeFilters = @Filter(type= FilterType.ANNOTATION, classess = MyExcludeComponent.class)
)

 

@Autowired

자동으로 타입이 같은 스프링 빈을 찾아서 주입

 

우선순위

1. 필드명 매핑

2. @Qualifier 끼리 매칭 > 빈 이름 매칭

3. @Primary : 우선순위 정하기 

 

 

수동빈 등록 

-> 업무 로직 빈 : 비즈니스 요구사항 

-> 기술 지원 빈 : AOP나 기술적인 문제 처리할 때, 공통 로그 처리, 데이터베이스 연결

 

 

빈 생명주기 콜백

- 인터페이스 (InitializingBean, DisposableBean)

상속받아서 afterPropertiesSet()메서드로 초기화 지원, destroy()메서드로 소멸 지원

 

- 설정 정보에 초기화 메서드, 종료 메서드 지정

@Bean의 destroyMethod 속성에서 close, shutdown 이름의 종료 메서드 사용

@Bean(initMethod="init", destroyMethod="close")

 

- @PostConstruct @PreDestroy 어노테이션 지원

외부 라이브러리 적용 x

 

 

빈 스코프

1) 싱글톤 : 기본 스코프, 시작-종료

@Test
void singletonBeanFind() {
	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicatiohnContext(Singleton.class);
    SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
    SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
    assertThat(singletonBean1).isSameAs(singletonBean2);
    ac.close();
}
@Scope("singleton")
static class SingletonBean {
	@PostConstruct
    public void init(){
    	System.out.println("init");
    }
    @PreDestroy
    public void destroy(){
    	System.out.println("destroy");
    }
}

 

2) 프로토타입 : 빈의 생성과 의존관계 주입까지만 관여, 더는 관리 x, @PreDistory 같은 종료 메서드가 호출되지 않음

주입 받는 시점에 각각 새로운 프로토타입 빈이 생성됨

 

 

프로토타입 스코프 + 싱글톤 빈과 함께 사용시 Provider로 문제 해결

의존관계를 주입받는게 아니라 직접 의존관계 찾는 것을 Dependency Lookup(DL)이라고 한다 

ObjectFactory, ObjectProvider

 

 

3) 웹 관련 스코프 : request(요청들어오고나갈때까지), session(웹세션), application(서블릿컨텍스트와 같은 범위)

스프링 애플리케이션을 실행하는 시점에 request 스코프 빈은 생성되지 않음 -> 실제 고객의 요청이 들어와야 생성됨

그래서, 미리 Proxy 클래스를 다른 빈에 미리 주입함 

 

@Component

@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS) 

public class MyLogger {

  private String uuid,  requestURL;

  public void log(String message){

   System.out.println(uuid+" "+requestURL+" "+message);

  }

  @PostConstruct

  public void init(){

  }

   @PreDestroy

    public void close() {

    }  

}

 

 

프록시 

- CGLIB 라이브러리로 클래스 상속 받은 가짜 프록시 객체

- 프록시 객체는 실제 request scope와는 관계 없음

 

 

728x90

관련글 더보기