Goal
- 스프링 삼각형에 대해 알아본다
- 스프링이 구현한 Inversion of Control에 대해 알아본다
- IoC를 사용한 경우와 그렇지 않은 경우 비교
- IoC 컨테이너에 대해 알아보고 이해한다
- Bean에 대해 알아보고 이해한다
스프링 삼각형
스프링을 이해하는 데는 POJO(Plain Old Java Obejct)를 기반으로 스프링 삼각형이라는 애칭을 가진 Ioc/DI, AOP, PSA라고 하는 스프링의 3대 프로그래밍 모델에 대한 이해가 필수다.
이번 포스팅에서는 3대 프로그래밍 중 하나인 IoC에 관해 살펴보자
IoC (Inversion of Control) : 제어의 역전
의존 관계 주입(Dependency Injection)이라고도 하며, 어떤 객체가 사용하는 의존 객체를 직접 만들어 사용하는게 아니라, 주입 받아 사용하는 방법을 말 한다.
무엇에 대한 컨트롤이 뒤바뀐 것인가? => 아무거나 될 수 있으나 주로 의존성에 대한 컨트롤이 뒤바뀜을 말한다
잠깐
의존성이란 무엇인지 알아보자
의사 코드
운전자가 자동차를 생산한다.
자동차는 내부적으로 타이어를 생산한다.
자바로 표현
new Car();
의존성을 단순하게 정의하면 다음과 같다.
의존성은 new다.
new를 실행하는 Car와 Tire사이에서 Car가 Tire에 의존한다
스프링 IoC을 사용하지 않은 경우
의존성에 대한 제어권은 자기 자신이 가지고 있다. 따라서, new로 OwnerRepository()를 생성하여 사용한다
class OwnerController {
private OwnerRepository repository = new OwnerRepository();
}
- OwnerRepository는 OwnerController의 의존성이다
- OwnerRepository가 있어야만 OwnerController를 쓸 수 있다.
멤버 변수를 선언하여, 생성자에 new를 해서 만들어주는 일 = 개발자 영역
이는 그 수가 많아지면 작업을 수동으로 해줘야하는 불편함을 야기한다.
스프링 IoC을 사용한 경우
class OwnerController {
private OwnerRepository repo;
public OwnerController(OwnerRepository repo){
this.repo = repo;
}
}
class OwnerControllerTest {
@Test
public void create(){
OwnerRepository repo = new OwnerRepository();
OwnerController controller = new OwnerController(repo);
}
}
- IoC 의존성을 제어하는 제어권이 뒤바뀜
- 의존성을 내가 관리하지 않고, 나 이외의 누군가가 의존성을 관리 함
- 의존성 주입 (Dependency Injection)
- OwnerRepository를 생성자를 통해 넘겨줄 수 있다
- OwnerRepository라는 의존성을 OwnerController에 주입한다
IoC는 DI의 관계
의존성 주입은 IoC 과정중 하나이며, IoC를 하기 위해 Runtime시에 의존성 주입(DI)를 하는 것이다.
=> IoC는 DI가 아니다 !
IoC (Inversion of Control) 컨테이너
스프링 프레임워크는 inversion of control용 컨테이너를 제공해주는데, 컨테이너의 가장 핵심적인 인터페이스가 바로 applicationcontext (BeanFactory)이다. BeanFactory가 사실상 IoC 컨테이너인데 이유인 즉, applicationContext는 BeanFactory를 상속받고 있기 때문이다. 그 외에 다양한 일을 한다.
IoC 컨테이너
- beanFactory
- 애플리케이션 컴포넌트의 중앙 저장소.
- 빈 설정 소스로 부터 빈 정의를 읽어들이고, 빈을 구성하고 제공한다.
ApplicationContext
- beanFactory를 상속받음
- 메세지 소스 처리 기능(i8n)
- 이벤트 발행 기능
- 리소스 로딩 기능
IoC의 역할
빈(bean)을 만들고 bean들 사이에 의존성을 엮어주고 또 그렇게 만들어져 있는 컨테이너가 가지고 있는 bean들을 제공한다.
// 이 코드를 동작하게 만들어 준다
class OwnerController {
private OwnerRepository repo;
public OwnerController(OwnerRepository repo){
this.repo = repo;
}
}
- OwnerController가 IoC 컨테이너 내부에 객체로 들어오고, IoC 컨테이너 내부에서 OwnerController와 OwnerRepository타입의 객체를 만들어준다
- IoC 컨테이너 내부에 만든 객체들 = bean
- 즉, OwnerController와 OwnerRepository는 applicationcontext에서 만들어주는 bean이다.
- 이러한 bean들의 의존성을 관리해준다
- 그렇기 때문에 이 둘에 대한 의존성은 IoC 컨테이너가 관리를 해준다. 오로지 bean만 관리해줄 수 있다
- bean을 구분하는 방법은 annotation이 붙은지의 여부를 확인
- component scan - @Controller, @Service, @Repository, @Component) 등 자동으로 bean으로 등록이됨
- 어플리케이션 전반적으로 OwnerController라는 인스턴스는 여러개를 생성할 필요 x
- 싱글톤으로 관리하고 싶을 때 IoC 컨테이너를 사용
스프링 bean은 싱글톤?
스프링 ioc 컨테이너에 등록되는 bean들은 기본적으로 싱글톤 스콥으로 빈으로 등록이 된다. 이 관점에서는 싱글톤이 맞다.
하지만 싱글톤을 만들기 위해서는 3가지 규칙을 준수해야 한다.
- 객체 생성을 위한 new에 제약을 걸어야 하고- private 접근제어자를 사용하여 외부로부터 인스턴스 생성 차단
- 만들어진 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
- 유일한 단일 객체를 참조할 정적 참조 변수가 필요하다
여기서, 싱글톤은 유일한 객체임을 유지하기 위해 new에 제약을 걸어 생성을 차단한다. 하지만 bean은 new를 통해 생성할 수 있으며 이는 객체가 또 생성될 수 있음을 의미하므로 (유일성 위배) 이 관점에서는 bean은 싱글톤이라고 할 수 없다.
정리
IoC 컨테이너는 빈(bean)을 만들고 엮어주며 제공해준다. 직접 사용할 일은 많지 않다
Bean 이란?
스프링 IoC 컨테이너가 관리하는 객체
class OwnerControllerTests {
private static final int TEST_OWNER_ID = 1;
ApplicationContext applicationContext;
@Test
public void getBean(){
// 일반적인 객체 - bean x
OwnerController ownerController = new OwnerController();
// bean 객체
// applicationContext에서 가져왔기 때문. applicationContext에서 알고 있음
OwnerController bean = applicationContext.getBean(OwnerController.class);
}
}
스코프
- 싱글톤 : 하나만 만들어서 사용
- bean으로 등록을 할 때 아무런 annotation을 붙히지 않았다면 기본적으로 bean은 싱글톤 스콥으로 생성
- 따라서, 컨테이너로 부터 OwnerController와 OwnerRepository를 받아서 사용한다면 그 인스턴스들은 항상 같은 객체임
- 메모리면에서 효율적 => 컨테이너에 미리 만들어 놓고 하나의 객체를 사용하기 때문
- 런타임시 성능 최적화에도 유리
- 특히, 데이터베이스에 관련된 일을 하는 repository 객체를 만드는 비용이 비싼데, 싱글톤으로 사용하면 장점이 됨
- 프로포토타입 : 매번 만들어 다른 객체를 사용
라이프사이클 인터페이스 지원
컨테이너에 만들어져 있는 bean들에 국한된 이야기로, 빈이 만들어질 때 라이프사이클 콜백에 해당하는 어노테이션에서 호출하여 부가적인 작업을 할 수 있음
Bean을 등록하는 방법
- Component Scan 컴포넌트 스캔 애노테이션을 처리하는 어노테이션 핸들러로, 모든 패키지와 클래스들의 @Component가 붙은 모든 클래스 들을 찾아 그 클래스의 인스턴스를 만들어 bean으로 등록을 해준다 = (우리가 직접 bean으로 등록하지 않더라도 스프링이 알아서 IoC 컨테이너가 만들어질 때 어노테이션을 보고 찾아서 bean으로 등록해준다)
- @Component
- @Repository
- @Service
- @Controller
- Repository - SpringBoot JPA가 제공해주는 기능에 의해 등록이됨. 특정한 어노테이션이 없더라도, 특정한 인터페이스를 상속받은 경우에 그 인터페이스를 상속받고 있는 클래스(인터페이스)를 찾아서 구현체를 내부적으로 만들어 bean을 등록해준다
- @Component
- 직접 등록 xml 이나 자바 설정 파일에 등록
- @Bean 이라는 어노테이션을 사용하여 메서드 정의
- @Bean public String rosie(){ return "rosie"}
- 단, configuration이라는 어노테이션을 가지고 있는 클래스 안에서 정의해야함
- @Bean은 java configuration
- @Bean 이라는 어노테이션을 사용하여 메서드 정의
참고
어노테이션은 자체에는 기능이 없다. 어노테이션을 마커로 사용해서 어노테이션을 처리하는 프로세스를 처리가 있을 뿐
Bean을 어떻게 꺼내쓰는가
1. @Autowired or @Inject
@SpringBootApplication
@ImportRuntimeHints(PetClinicRuntimeHints.class)
public class PetClinicApplication {
@Bean
public String rosie(){
return "rosie";
}
public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args);
}
}
@RestController
public class SampleController {
@Autowired
String rosie;
@GetMapping("/context")
public String context(){
return "hello "+rosie;
}
}
결과
hello rosie
2. ApplicationContext에서 getBean으로 직접 꺼냄
@RestController
public class SampleController {
@Autowired
ApplicationContext applicationContext;
@GetMapping("/context")
public String context(){
return "hello "+applicationContext.getBean(OwnerController.class);
}
}
결과
hello org.springframework.samples.petclinic.owner.OwnerController@561e8bab
의존성 주입 ( Dependency Injection)
필요한 의존성을 어떻게 받아올 것인가?
1. 생성자 주입
private final OwnerRepository owners;
public OwnerController(OwnerRepository clinicService) {
this.owners = clinicService;
}
2. 필드 주입
@Autowired
private OwnerRepository owners;
3. setter 주입
private OwnerRepository owners;
@Autowired
public void setOwners(OwnerRepository owners){
this.owners = owners;
}
생성자 주입을 권장하는 이유
필수적으로 사용해야 하는 레퍼런스 없이는 해당 인스턴스를 만들지 못하도록 강제할 수 있음
=> @Autowired는 에러를 runtime시에 알 수 있는 단점이 있고, 또 순환참조가 발생할 수 있다 A-> B 참조, B->A 참조
생성자 주입은 해당 레퍼런스가 반드시 필요하므로 에러를 바로 알 수 있음
@Autowired가 생략된 경우
스프링 4.3 이상부터, 어떤 Bean에 생성자가 오직 하나만 있고, 또 그 생성자의 파라미터로 받는 타입에 Bean이 존재한다면 @Autowired 없더라도, Bean을 자동으로 주입을 해준다.
참고
멘토님
스프링 입문을 위한 자바 객체지향의 원리와 이해
스프링입문 강의 by 백기선
https://www.inflearn.com/course/lecture?courseSlug=spring&unitId=15538 https://www.inflearn.com/course/spring_revised_edition/dashboard
https://www.inflearn.com/course/spring_revised_edition/dashboard
코드샘플
https://github.com/spring-projects/spring-petclinic
'Spring > about spring' 카테고리의 다른 글
[IoC 컨테이너와 빈] @Autowired (0) | 2023.07.22 |
---|---|
[IoC 컨테이너와 빈] ApplicationContext (0) | 2023.07.19 |
[스프링 삼각형] PSA 잘 만든 인터페이스 (0) | 2023.07.17 |
[스프링 삼각형] AOP 관점 지향 프로그래밍 (0) | 2023.07.11 |
[Transaction] 트랜잭션 전파옵션, 격리수준 (0) | 2023.06.06 |