-
안녕하세요 현우입니다. 이번 포스팅은 [ IOC의 개념과 스프링 부트에서의 IOC 이해하기 ] 입니다.
SpringBoot학습에 도움을 주신 백기선 개발자님께 감사드립니다 :)
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/lecture/13520
yes24.com/Product/Goods/6229706
일반적인 의존성에 대한 제어권 : "내가 사용할 의존성은 내가 만든다"
1번)
class OwnerController{ private OwnerRepository = new OwnerRepository(); }
IOC: 내가 사용할 의존성을 외부에서 가져온다
아래의 OwnerController 클래스는 OwnerRepository를 사용은 하지만 만들지는 않는다. 누군가가 밖에서 Owner코드 밖에서 줄 수 있게 생성자를 통해 받아온다. 즉 제어권이 역전되어 있다고 볼 수 있다. 이렇게 의존성을 주입해주는 일을 DI(Depengency Injection : 종속 객체 주입) 이라고 부르고 의존성을 주입하고 관리하는 일이 자기 자신이 아닌 외부의 누군가로 바뀌었으니 DI 또한 일정의 IOC라고 볼 수 있다.
2번)
class OwnerController{ private OwnerRepository repo; public OwnerController(OwnerRepository repo){ this.repo = repo; } // repo를 사용 } class OwnerControllerTest{ @Test public void create(){ OwnerRepository repo = new OwnerRepository(); OwnerController controller = new OwnerController(repo); } }
위의 두 코드를 비교해보자.
보다시피 1번의 OwnerController클래스는 생성자 안에 레포지토리를 생성하여 강하게 결합 시켰다. 레포지토리의 기능을 인스턴스가 생성된 시점에 종속되고 이는 레포지리를 유연성을 저하 시키게 된다. 결합도가 높은 코드는 테스트와 재활용이 어렵고 이해하기도 어려우며, 오류를 하나 수정하면 다른 오류가 발생하는 경향이 있다.
반면 2번은 생성자의 인자에 레포지토리가 부여되며 이와 같은 종속객체 주입을 생성자 주입 이라 한다. 해당 클래스의 함수가 특정 구현체에 결합 되지 않는 다는 사실이고 레포지토리를 구현하기만 하면 어떤 종류의 방법도구현이 가능하다. 이를 DI의 주요 이점인 'loose coupling' 이라고 한다. 이는 DI를 인터페이스를 통해서만 알고있다면, 사용 하는 객체의 코드를 변경하지 않더라도 DI를 다른 구현체로 바꿀 수 있다.
1. IOC(Inversion of Control) 컨테이너
ApplicationContext (BeanFactory) 중 하나를 사용하게 된다.
beanfactory 가 사실상 IOC컨테이너이며 ApplicationContext는 BeanFactory를 상속받고 있으므로 같은 일을 하고 있다. IOC컨테이너가 해주는 일은 bean을 만들고 bean들 사이에 의존성을 엮어주며 컨테이너가 가지고 있는 빈들을 제공해주는 역할을 한다.
모든 클래스들이 bean으로 등록되어 있는 건 아니다
클래스 옆에 녹색 홈으로 등록되어 있는 것이 bean으로 등록되었다는 뜻이다.
bean으로 등록되기 위한 조건
- @Controller, @Serverice, @Repository 특정 어노테이션
- 특정 인터페이스 Repository상속
- 직접 bean으로 등록
의존성 주입은 bean끼리만 가능하다. 즉 스프링 ioc컨테이너 안에 들어있는 객체들끼리만 의존성 주입을 해준다. ioc밖에 있는 컨테이너에다 주입을 해주지는 않는다. (엔티티클래스에서 빈주입 불가)
2. Bean
일반적인 객체인데 IOC컨테이너가 관리하는 객체를 말한다.
ApplicationContext가 만들어서 그 안에 담고 있는 객체를 Bean이라 한다.
ApplicationContext applicationContext; @Test public void getBean() { OwnerController ownerController = new OwnerController(); //Bean이 아니다 OwnerController bean = applicationContext.getBean(OwnerController.class); //Bean 이다 }
어떻게 컨테이너 안에다가 특정 인스턴스를 Bean으로 만들어줄까?
- ComponentScan
- Bean으로 직접 등록
1. ComponentScan
에노 테이션 프로세스 중 스프링 IOC컨테이너가 사용하는 여러 가지 인터페이스들을 LifeCycle, CallBack이라 부르며 @Component 어노테이션이 붙어있는 모든 클래스들을 찾아서 해당 클래스의 인스턴스들을 Bean으로 등록한다.
/** * * SpringBootApplication 어노테이션 안의 내용 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )}
즉 @ComponentScan이라는 어노테이션이 붙여져 있는 위치에서부터 모든 하위 패키지에 있는 모든 클래스를 전부 훑어보며 @Controller, @Repository, @Configuration... 어노테이션이 붙어있는 클래스들을 전부 빈으로 등록한다.
Repositrory의 경우 스프링 데이터 JPA에 의해 bean으로 등록되며 특정 어노테이션이 없더라도 특정인 터페이스를 상속하고 있는 인터페이스를 찾고 해당 구현체를 bean으로 등록해준다.
2. Bean직접 등록
@Configuration으로 등록하고 @Bean으로 빈을 직접 정의할 수 있다. 리턴하는 객체 자체가 ioc컨테이너 안에 bean으로 등록된다.
아래 사진처럼 ApplicationContext에서 직접 꺼내와서 사용할 수 도 있지만,
- 대부분 Spring DI @Autowired를 사용한다
3. DI 의존성 주입
주입방식 3가지
- Autowired 필드 주입
- 생성자 주입
- setter 주입
class OwnerController{ //Autowired 주입 @Autowired private OwnerRepository owners; // 생성자를 통한주입 private final OwnerRepository owners; public OwnerController(OwnerRepository clinicService){ this.owners = clinicService } //setter 주입 @Autowired public void setOwners(OwnerRepository owners){ ` dataBinder.setDisallowedFields("id"); } }
스프링 프레임워크 레퍼런스에서 권장하는 방법은 생성자 이다.
private final OwnerRepository owners; public OwnerController(OwnerRepository clinicService){ this.owners = clinicService; }
필수적으로 사용해야하는 레퍼런스 없이는 해당 클래스의 인스턴스를 만들지 못하도록 강제 할 수 있다.
4. SpringBoot에서 IOC 깊게 파헤쳐보자
- Spring 에서의 pom.xml
-springBoot 에서의 pom.xml
첫번째 이미지의는 Spring를 사용하기 위해 수많은 의존성을 추가하였고 일일이 버전을 명시해 주었지만, 두번째 이미지
에서의 SpringBoot는 의존성을 두개만 정의하였고, 버전을 명시하지 않았다. 이것이 바로 스프링 부트가 관리해주는 의존성 관리 기능 덕분이다.
어디서 어떻게 의존성 관리가 되는걸까?
pom.xml -> parent -> dependencies 이동
최상위 디펜전시 파일에 버전이 명시되어있고,
그 아래 Managment영역이 정의 되어 있다. 위의 Management영역에 하나라도 정의 되어있는 것을 pom.xml에서 사용하게되면 버전을 명시해주지 않아도 된다.
우측의 Gradle에서 명시된 버전을 계층화하여 확인이 가능하다.
결론
스프링 부트 스타터에 수많은 의존성들이 관리되어있다. 우리가 직접 관리해야할 의존성의 수가 줄어든다 즉 우리의 일이 줄어든다는 말이다. 우리가 스프링버전을 올리거나 서트파티 라이브러리 버전을 올릴 경우 다른버전과의 호환을 확인하기 위해서는 실제로 프로젝트를 돌려보고 문제점을 찾아 일일이 버전을 맞춰서 바꿔보는 방법 밖에 없다. Hibernate와 스프링간의 버전충돌 문제가 많았는데 스프링부트는 이러한 문제를 해결하였다.
'웹개발 > SpringBoot' 카테고리의 다른 글
3. 스프링 PSA (0) 2020.04.28 2. AOP(Aspect Oriented Programming) (0) 2020.04.10 내장 웹 서버 응용 ( 톰켓 말고 다른 서버 사용하기) (0) 2020.04.03 내장 웹 서버 이해 (0) 2020.04.03