스프링 핵심 개념

지윤·2021년 7월 17일
0

Spring

목록 보기
3/7

인프런 스프링 핵심원리 강의를 듣고 중요한 개념 요약 정리

제어의 역전 IoC(Inversion of Control)

프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것

IoC 컨테이너(DI 컨테이너)

메타데이터 AppConfig(xml, java code)를 통해 객체(빈)를 생성하고 관리하면서 의존관계를 연결해 주는 것

스프링 컨테이너

ApplicationContext

  • ApplicationContext를 스프링 컨테이너라 한다.
  • ApplicationContext가 오브젝트의 생성과 관계설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 담당한다. 이를 컨테이너가 코드 대신 오브젝트에 대한 제어권을 갖고 있다고 해서 IoC라고 부른다. 그래서 스프링 컨테이너를 IoC 컨테이너라고도한다.
  • AplicationContext 인터페이스는 BeanFactory 인터페이스를 상속한 서브인터페이스다.

//스프링 컨테이너 생성
//AnnotationConfigApplicationContext는 ApplicationContext의 구현체이다.
ApplicationContext applicationContext =
	new AnnotationConfigApplicationContext(AppConfig.class);

스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다. 이를 통해 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유 해서 효율적으로 재사용할 수 있다.

  • 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
  • 스프링 컨테이너의 이런 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
    - 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
    - DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.
  • 여러 클라이언트가 공유하는 객체이므로 무상태(stateless)로 설계해야 한다.
  • 스프링 설정 정보는 항상 @Configuration을 사용해야 싱글톤이 보장되며, 바이트코드를 조작하는 CGLIB 기술을 사용하여 보장한다.

싱글톤 패턴 단점 - 참고

  • 싱글톤을 구현하는 코드가 많이 들어간다. (thread-safe 고려)
  • 클래스 내부에서 객체를 직접 생성하기 때문에 객체 지향 설계원칙인 OCP, DIP를 위배한다.
  • 테스트가 어렵다.

컴포넌트 스캔

자바 코드의 @Bean이나 XML의 <bean'> 등을 통해서 직접 스프링 빈을 등록할 수 있었다. 그러나 등록할 빈이 수십, 수백개가 된다면?

스프링은 자동으로 빈을 등록하는 기능인 컴포넌트 스캔과 @Autowired라는 편리한 기능을 제공한다.

@ComponentScan

  • 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 기능
  • @Component, @Controlller, @Service, @Repository, @Configuration 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.
  • @ComponentScan이 붙은 설정 정보 클래스의 패키지가 탐색 시작 위치이다.
  • 그래서 스프링 부트의 대표 시작 정보인 @SpringBootApplication를 프로젝트 시작 루트 위치에 두는 것이 관례이다. (이 설정안에 바로 @ComponentScan 이 들어있다.)

@Autowired

  • 의존관계를 자동으로 주입하는 기능

의존관계 자동 주입

크게 4가지 방법

1. 생성자 주입 (추천)

  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용
  • 생성자가 딱 1개만 있으면 @Autowired 생략 가능
  • 이 때 @requiredargsconstructor 함께 사용하면 더 좋음

2. 수정자 주입(setter 주입)

  • 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법
  • 선택, 변경 가능성이 있는 의존관계에 사용
  • 자바빈 프로퍼티 규약의 수정자 메서드 setXXX() 방식을 사용하는 방법
  • 주입할 대상이 없으면 오류가 발생하는데 @Autowired(required = false) 로 지정하면 오류가 발생하지 않는다.

3. 필드 주입

  • 필드에 바로 주입하는 방법으로 외부에서 변경이 불가능하다.
  • DI 프레임워크가 없으면 아무것도 할 수 없다. 즉, 테스트를 하려면 순수 자바코드로 할 수 없으며 스프링을 실행시켜야 한다.
  • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용하기

4. 일반 메서드 주입

  • init()
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.

결론 생성자 주입을 사용해라!

  1. 불변
  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계 할 수 있다.
  1. 누락
  • 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
  • 생성자 주입만 필드에 final 키워드를 사용할 수 있다. 컴파일 오류를 사전에 방지할 수 있다.

자동 빈 등록 vs 수동 빈 등록

  • 기술 지원 로직은 가급적 수동 빈 등록을 사용
    애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.
  • 업무 로직은 자동 빈 등록을 사용
  • 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민하기

빈 생명주기 콜백

스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.

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

  1. 스프링 컨테이너 생성
  2. 스프링 빈 생성
  3. 의존관계 주입
  4. 초기화 콜백
  5. 사용
  6. 소멸전 콜백
  7. 스프링 종료

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원
1. 인터페이스(InitializingBean, DisposableBean)
2. 설정 정보에 초기화 메서드, 종료 메서드 지정
3. @PostConstruct, @PreDestory 애노테이션 지원(이 방법 추천)


빈 스코프

빈이 존재할 수 있는 범위

@Scope("prototype")
@Component
public class bean {
	...
}

스프링 빈 스코프는 싱글톤, 프로토타입 두 가지가 있다.

1. 싱글톤(기본)

  • 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
  • 싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다.

2. 프로토타입

  • 스프링 컨테이너가 프로토타입 빈의 생성과 의존관계 주입, 초기화까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
  • 그래서 초기화 메서드는 실행되지만, @PreDestory 같은 종료 메서드가 호출되지 않는다.
  • 클라이언트가 관리해야 한다.
  • 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.

프로토타입 빈을 싱글톤 빈과 함께 사용하면 나타나는 문제점

스프링에서는 일반적으로 싱글톤 빈을 사용!
그래서 싱글톤 빈이 프로토타입 빈을 사용하게 된다.

싱글톤빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.

프로토타입 빈을 주입 시점에만 새로 생성하는 것이 아니라 사용할 때 마다 새로 생성하려면?

1. Provider

싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;

public int logic() {
	//getObject() 를 호출하면 내부에서는 스프링 컨테이너를 통해 
    	//해당 빈을 찾아서 반환한다. (Dependency Lookup)
	PrototypeBean prototypeBean = 
    			prototypeBeanProvider.getObject();
	prototypeBean.addCount();
	int count = prototypeBean.getCount();
	return count;
}

2. JSR-330 Provider

'javax.inject:javax.inject:1' gradle 추가 필수
자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.


웹 스코프

웹 환경에서만 동작하는 스코프로 스프링이 종료시점까지 관리한다.

종류

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

사용 방법

@Scope(value = "request") 사용
request 스코프 빈을 생성하면 requestURL과 같은 웹과 관련된 정보를 웹과 관련없는 서비스 계층에도 깔끔하게 넘겨줄 수 있다. 참고로 웹과 관련된 부분은 컨트롤러까지만 사용해야 한다. 서비스 계층은 웹 기술에 종속되어서는 안된다.

request 스코프 빈은 애플리케이션이 실행될 때 생성해서 주입하는 싱글톤 빈과 달리 실제 고객의 요청이 와야 생성할 수 있다. 따라서 위에서 언급한 Provider 또는 프록시모드 속성을 사용하면 된다.

ObjectProvider 사용

ObjectProvider.getObject()를 호출하는 시점까지 request 스코프 빈의 생성을 지연할 수 있다. 호출 시점에는 당연히 HTTP 요청이 진행중으로 정상 작동한다. 또한 여러 곳에서 빈을 호출해도 동일한 빈이 반환된다.

proxyMode 속성 사용

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

request 스코프 빈의 가짜 프록시 클래스를 만들어서 HTTP 요청과 상관없이 가짜 프록시 객체를 미리 주입해 둘 수 있다.

동작 원리
1. 해당 속성이 정의되어 있으면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서 request 스코프 빈을 상속받은 가짜 프록시 객체를 생성한다.
2. 스프링컨테이너에 진짜 객체 대신 가짜 프록시 객체를 등록하고 의존관계 주입도 이 가짜 프록시 객체를 주입한다. 싱글톤처럼 동작한다.
3. 가짜 프록시 객체에는 실제 요청이 오면 진짜 객체를 요청하는 위임로직이 들어있어서 진짜 객체를 호출한다.(다형성)

따라서 프록시 객체를 사용하면 싱글톤 빈을 사용하듯이 request 스코프 빈을 사용할 수 있다.

Provider와 프록시 객체 모두 진짜 객체를 실제 사용하는 시점까지 지연처리 하는 것이 핵심 아이디어이다.

profile
헬로🙋‍♀️

0개의 댓글