Spring - Spring Bean


1. 스프링 빈

  • 스프링 컨테이너에 등록되어, 스프링에게 관리받는 클래스 ( 의존관계 주입 등을 위해 )



2. 스프링 빈의 순서와 콜백

  • 스프링 빈은 [ 객체 생성 -> 의존관계 주입 ] 의 순서를 갖는다. ( setter주입, 필드주입 )
  • 그러나 생성자 주입은 예외이다. 생성자를 만들 때 스프링빈이 같이 들어와야 하기 때문이다.
    • 스프링빈은 객체를 생성하고, 의존관계 주입이 다 끝난다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다. 따라서 초기화 작업은 의존관계가 주입이 모두 완료되고 난 다음에 호출해야 한다.
    • 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공하고, 스프링 컨테이너가 종료되기 직전에 소멸 콜백도 준다. 그래서 그걸 활용하면 안전하게 작업할 수 있다.
    • 스프링은 3가지 방법으로 빈 생명주기 콜백을 지원한다.

2.1. 인터페이스 ( InitializingBean, DisposableBean )

  • 스프링 초창기에 사용했지만, 지금은 잘 사용하지 않는다. 그냥 현업에서 개발을 할 때 이런코드가 있을 경우 콜백과 관련된 코드구나 정도를 알기만 하면 된다.
  • InitializingBean는 의존관계 주입 완료 후 콜백
  • DisposableBean는 스프링 컨테이너 종료전에 콜백
  • 스프링 전용 인터페이스이다.
  • 초기화 소멸 메서드의 이름을 변경할 수 없다. ( 오버라이딩을 해서 만들어야하므로 메서드명이 자유롭지 못하다.)
  • 외부 라이브러리에 적용할 수 없다.

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

  • 메이저하게 사용하지는 않는다. 그러나 외부 라이브러리를 @Bean으로 등록할 때는 사용한다.
  • 빈 등록시에 @Bean(initMethod = “초기화 메서드”, destroyMethod = “종료 메서드”) 이렇게 등록하면 된다.
  • 메서드 이름을 자유롭게 줄 수 있다.
  • 스프링빈이 스프링코드에 의존하지 않는다.
  • 외부 라이브러리에도 사용할 수 있다.
  • 특히 destoryMethod에는 Default가 있는데, 외부 라이브러리의 close, shutdown 이라는 이름의 메서드를 자동으로 호출해준다. ( 추론을 해서 알아보는 거임 ) destoryMethod 만 선언할 경우 그렇다. 이러한 추론을 사용하기 싫으면 destoryMethod = “” 을 하면 된다.

2.3. @PostConstruct, @PreDestory 어노테이션 지원

  • 메이저하게 사용하는 방법이다.
  • Post는 초기화시, Pre는 종료전 실행하는 메서드에 붙여주면 된다.
  • 스프링에 종속적인 기술이 아닌, Javax에서 지원하는 어노테이션이다.
  • 컴포넌트 스캔과 잘 어울린다. ( @Bean 뿐 아니라, @Component에 사용해도 잘 어울린다는 이야기이다. )
  • 유일한 단점이 있다면 외부 라이브러리에는 적용하지 못한다.
  • 외부 라이브러리에 사용하고 싶으면 위에 설명한 2번 방법을 사용하자.


스프링 빈 콜백을 사용하는 이유

  • 어데이터베이스와 어플리케이션을 스프링 빈 등록 후에 바로 미리 연결한다거나 하는 이유에 있다.
  • 왜냐하면 나중에 사용할 때 그제서야 연결하면 연결 과정에서 일어나는 작업들에 시간이 소요된다. 그래서 미리 연결해두는 것이다.



3. 스코프 빈의 라이프 사이클

  • 스프링 빈의 이벤트 라이프사이클 ( 싱글턴 스코프 빈의 라이프사이클임 )
    • 스프링 컨테이너 생성 –> 스프링 빈 생성 –> 의존관계 주입 –> 초기화 콜백 –> 사용 –> 소멸전 콜백 –> 스프링 종료


  • 객체의 생성과 초기화는 분리하는게 중요하다.
    • 생성자는 필수 정보만을 받고 객체를 생성하는데에 집중해야된다.
    • 그 안의 필드를 초기화 하는 작업은 생성자에서 하지않는게 좋다.
    • 단, 초기화 작업이 내부 값들만 약간 변경하는 정도의 단순한 경우는 생성자에서 한 번에 처리하는게 좋을 수도 있다.


빈 스코프란

  • 일단 빈은 스프링 컨테이너에서 생성, 관리되는 객체이다.
  • 스코프는 그런 빈의 생명주기를의 종류를 의미한다.
  • 위에서 이야기한 라이프 사이클이 일반적인 이유는 빈이 기본적으로 싱글턴 스코프로 생성되기 때문이다.
  • 스프링에는 3가지 스코프를 지원한다.
    • 싱글턴 스코프
      • 기본 스코프. 스프링 컨테이너의 시작 ~ 종료까지 유지되는 가장 넓은 범위의 스코프
      • 스프링 컨테이너 생성 시점에 의존성 주입하고, 초기화가 진행됨
    • 프로토타입 스코프
      • 클라이언트가 요청시에 생성되고 의존성 주입하고, 초기화 진행.
      • 클라이언트가 요청을 하면 그 때 프로토타입 빈의 생성과 의존관계 주입까지만 관리하고 리턴해준다음 이후는 관리하지 않는 짧은 범위의 스코프 ( @PreDestroy 같은 종료 메서드 호출이 안 됨 )
    • 웹 관련 스코프
      • request : 하나의 웹 요청이 들어오고 나갈때까지만 유지되는 스코프
      • session : 웹 세션이 생성되고 종료될 때까지만 유지되는 스코프
      • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
      • websocket : 웹 소켓과 동일한 생명주기


싱글턴 빈과 프로토타입 빈을 함께 사용할 경우 문제점이 발생할 수 있음

  • 싱글턴 빈 안에서 생성자주입 방식으로 Autowired로 프로토타입 빈을 받으면, 그 프로토타입 빈이 싱글턴 빈 안에서 계속 살아있으므로 여러 클라이언트가 접근해도 그 안에 계속 값이 남아서 공유된다.
  • 그러고 싶지 않으면 좀 극단적인 방법이지만 싱글턴 빈 안에서, 어떤 메서드를 동작할 때마다 applicationContext.getBean으로 해당 프로토타입 빈을 계속 새롭게 생성해주면 된다. ( 근데 이렇게는 잘 안한다. –> 스프링에 너무 종속적이고 단위테스트가 힘듦. )
  • ObjectProvider 나 ,Provider 라는 걸 사용해서 문제를 해결할 수 있다.


ObjectProvider

  • private final ObjectProvider prototypeBeanProvider; 이렇게 한다음 생성자 방식으로 받거나,
  • 애초에 필드주입 방식으로 어쨌든 의존성 주입을 한다.
  • 그 다음 필요한 곳에서 PrototypeBeanTest bean = prototypeBeanProvider.getObject( );
  • 이렇게 가져오면, 매번 다른 프로토타입 빈이 생성된다.
  • ObjectProvider « 이걸 우린 스프링빈에 등록한 적이 없다. 그러나 이건 스프링빈에 자동으로 주입이 된다.
  • 근데 ObjectProvider는 스프링에서 제공해주는 것이므로 너무 스프링 의존적이다. 그래서 스프링만 무조건 사용할 거라면 이걸 사용해도 되지만 그게 아니라면 Provider를 사용해야 한다.


Provider

  • Gradle에 implementation ‘javax.inject:javax.inject:1’ 을 추가하면 inject 관련된 Provider를 사용할 수 있다.
    • private final Provider prototypeBeanProvider;
    • PrototypeBeanTest bean = prototypeBeanProvider.get( );
    • 이렇게 사용하면 된다.
  • Gradle이나 Maven에 별도의 라이브러리를 등록해야 하는 단점이 있지만, 스프링 의존적이지 않기 때문에 다른 컨테이너에서도 사용할 수 있는 장점이 있다.
  • get( ) 하나만 알면 되기 때문에 단순하고 유용하다.
  • ObjectProvider 때랑 마찬가지로 get( )으로 호출 할 때 DL(Dependenct Lookup)이 일어나서, 그 때 해당 프로토타입 빈을 새롭게 가져오게 된다.


Web 관련 빈스코프

  • 웹 관련 빈의 스코프 중 request를 예시로 들어보면 HTTP 요청을 할 때 생성되고, 요청이 끝날 때 삭제된다. 이 빈을 사용할 때는UUID 를 만들어서 저장하여 다른 HTTP 요청과 구분할 수 있다.
    • 웹 관련 스코프는 생명주기만 다르지 request와 비슷하게 다 동작한다.
  • 사용할 때 주의할 점은, 생명주기가 request이면 HTTP호출을 할 때 생성이 되는데, 그걸 controller나 service에서 @Autowired로 의존성 주입을 받으면.. 아직 생성도 안된 빈을 의존성으로 주입하려 하는거니 오류가 난다.
  • 이 때 위에서 말한 Provider를 사용해주면 된다.
  • 단, 이 때 우리가 알아야하는 것이 있는데 Controller나 Service에서 각각 get으로 호출하여도 HTTP 요청이 진행중이므로 get을 각각해도 같은 스프링 빈이 반환된다.
    • 일반적으로는 get 마다 다른 스프링 빈이 반환되야 정상이다.


프록시

  • 자, 그런데 개발자들은 이걸 더 단순화 하기 위해 프록시라는걸 만들었다.
  • @Scope(value = “request”, proxyMode = ScopedProxyMode.TARGET_CLASS)
  • 이런식으로 넣으면 해당 빈이 CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 컨테이너에 등록하고 주입한다. 이게 Provider 역할을 대신해준다.
  • 이 가짜 프록시 객체는 요청이 오면 그 때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있을 뿐 싱글턴처럼 동작한다.
  • Provider 든 프록시든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 것이다. 단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다.
  • 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다.






results matching ""

    No results matching ""