Today I Learned_220813

3 분 소요

Spring Framework

인프런 스프링 강의 수강

Provider

싱글톤 빈 안에 의존관계 주입을 통해 프로토타입 빈을 사용할 경우

  1. 싱글톤 빈은 주입 시점에 스프링 컨테이너에 프로토타입 빈 요청
  2. 스프링 컨테이너는 프로토타입 빈을 생성해 싱글톤 빈에게 넘김
  3. 다른 클라이언트가 싱글톤 빈 호출 - 동일한 싱글톤 빈 호출이 됨
  4. 싱글톤 빈이 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈으로, 새로이 생성되지 않음

-> 이 문제를 해결하기 위해 Provider 사용해야 함

(또한 의존관계 순환 참조일 때도 Provider 사용하기도 함)

Dependency Lookup(DL) : 의존관계를 외부에서 주입받는게 아니라 직접 필요한 의존관계를 찾는 것. Provider가 DL 기능을 제공하기도 한다.

ObjectFactory, ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스 제공.

ObjectProvider가 ObjectFactory를 상속했으며, 편의 기능이 추가되었다.

Provider는 스프링 빈으로 등록하지 않아도 스프링이 자동으로 만들어서 주입해 준다.

또한, Provider는 프로토타입 빈에 한정되어 사용되는 것이 아니라, 스프링 컨테이너를 통해 대신 조회해주는 대리자이다.

-> Provider를 사용하면 필요할 때 마다 스프링 컨테이너에 요청해서 사용하는 프로토타입 빈을 의도대로 사용할 수 있다.

특징

  • ObjectFactory : 기능이 단순, 별도의 라이브러리가 필요 없음, 스프링에 의존적
  • ObjectProvider : ObjectFactory를 상속했으며, 편의 기능이 많고 별도의 라이브러리가 필요 없으며, 스프링에 의존적

JSR-330 Provider

자바 표준 프로바이더. 외부 라이브러리를 사용하기에 라이브러리를 gradle에 추가해야 한다.

특징

  • get() 메서드 하나로 기능이 단순하다.
  • 별도의 라이브러리 추가가 필요하다.
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용 가능하다.

스프링 제공 기능 vs. 자바 표준 제공 기능

스프링이 더 다양하고 편리한 기능을 제공해주므로, 기능상에 이점이 있다면 스프링 제공 기능 사용. (@Autowired)

기능이 둘이 서로 비슷하다면 자바 표준을 주로 사용 (@PostConstruct, @PreDestroy)

사실상 최근에는 스프링 자체가 표준이 되어가고 있을지도..?

웹 스코프

  • 싱글톤 : 스프링 컨테이너의 시작과 끝까지 동일한 객체 사용
  • 프로토타입 : 스프링 컨테이너가 스프링 빈을 생성하고 의존관계 주입하고 초기화 진행하고 관리 끝
  • 웹 스코프 : 스프링이 해당 스코프의 종료시점까지 관리한다 -> 종료 메서드(@PreDestroy)가 호출이 된다.

웹 스코프의 종류

  • request : HTTP 요청이 들어오고 나갈 때 까지 유지되는 스코프. 각각의 요청 하나당 별도의 인스턴스가 생성된다. -> 가장 자주 사용
  • session : HTTP session과 동일한 생명주기를 가지는 스코프
  • application : 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
  • websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프

Request Scope

HTTP request 요청 당 각각 할당됨

클라이언트 A가 요청을 하면 A 전용 빈이 만들어지고 A 응답이 나가면 destroy 된다.

서비스/컨트롤러 상관 없이 같은 요청에서 빈을 달라고 하면 동일한 객체를 준다.

request가 같으면 같은 객체 인스턴스를 바라보게 된다.

스프링 웹 환경

스프링 웹을 사용하려면 build.gradle에 web 라이브러리를 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-web'

이 라이브러리를 추가하면 스프링 부트는 내장 tomcat 서버를 활용해 웹 서버와 스프링을 함께 실행시킨다.

스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext 을 기반으로 애플리케이션을 구동한다.

웹 라이브러리가 추가되면 AnnotationConfigServletWebServerApplicationContext 를 기반으로 애플리케이션을 구동한다.

-> @Autowired로 받아서 찍어보면 AnnotationConfigServletWebServerApplicationContext 가 나온다.

웹과 관련된 부분은 컨트롤러까지만 사용하되, 서비스 계층은 웹 기술에 종속되지 않고 가급적이면 순수하게 유지하는 편이 좋다.

Request Scope와 Provider

Provider 없이 request scope를 추가하고 스프링 컨테이너를 실행시키면 아래와 같은 오류가 발생

Error creating bean with name 'myLogger': Scope 'request' is not active for the
current thread; consider defining a scoped proxy for this bean if you intend to
refer to it from a singleton;

Scope ‘request’ is not active -> 스프링 컨테이너가 뜰 때는 Request 요청이 오지 않는다. 실제로 고객의 request가 와야 하는데 어찌 와야 하나? -> Provider 사용!! 요청마다 따로 객체를 관리해 주자!

provider(ObjectProvider)를 추가하여 ObjectProvider.getObject()를 호출하는 시점까지 request scope 빈을 스프링 컨테이너에 요청해서 생성하는 것을 지연시킬 수 있다.

ObjectProvider.getObject() -> 호출 시점에는 http 요청이 진행중이므로 request scope 탈 수 있다.

Request Scope와 Proxy

빈 클래스에 아래와 같이 추가

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
  • 적용 대상이 클래스이면 TARGET_CLASS
  • 적용 대상이 인터페이스이면 INTERFACES

클래스의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입 가능

동작 원리 (ScopedProxyMode.TARGET_CLASS로 설정)

  • 스프링 컨테이너는 CGLIB(바이트코드를 조작하는 라이브러리)를 사용해 원본 클래스를 상속받은 가짜 프록시 개체 생성
  • 컨테이너에 진짜 객체 대신 가짜 프록시 개체가 대신 등록
  • getBean을 통해 조회해보면 프록시 개체가 조회
  • 의존관계 주입도 프록시 객체가 주입됨 -> 프록시 개체 자체는 싱글톤처럼 동작
  • 프록시 객체 내부에 진짜 객체를 찾는 위임 로직이 있음 - 클라이언트가 메서드를 호출하면 가짜 프록시 객체가 진짜 메소드 호출 -> 사용자는 원본인지 아닌지 모르게 사용할 수 있다.

프록시 특징

  • 진짜 객체 조회를 꼭 필요한 시점까지 지연처리한다.
  • 웹 스코프가 아니어도 프록시 사용 가능
  • 싱글톤을 사용하는 것 같지만, 결과적으로는 내부에서 요청별로 다르게 생성되므로 주의해서 사용해야 한다.
  • 특별한 scope는 유지보수 및 테스트가 어려우므로 무분별하게 사용하지 말고 꼭 필요한 곳에서만 사용하자.

IntelliJ

before

위와 같은 상황에서 윗 라인에 커서를 두고 command + option + N을 하면 한 라인으로 합쳐진다. (inline variable)

after

카테고리:

업데이트: