스프링의 특징중 하나인 Spring IoC에 대해 알아보려고 한다.
들어가기에 앞서 필요한 개념의 정의에 대해 알아보려고 한다.
의존성이란 뭔지, IoC란 뭔지에 대해 알아보자.
- " 의존성이란 클래스 A, B 가 존재한다고 가정할때, 클래스 B변경시
클래스A도 변경해야할때, 클래스 A는 B에 의존한다고 한다. " - -> 의존성이 강한 구조는 각 클래스들이 자유롭지 못하며 서로 영향을 많이 끼치기에 서비스에 문제를 초래한다.
- " IoC 란 제어의 역전이다 즉, 클래스에서 제어가 아닌 서버에서 제어한다."
쉬게 설명하면 아래와 같다
의존성 주입 : 클래스가 현재 사용하고자 하는 클래스를 모르게 한다.
제어의 역전 : 클래스 인스턴스 생성, 종료 등의 생명주기를 스프링에서 한다.
이번엔 예시를 비교해가면서 IoC가 뭔지 의존성이 뭔지 정확하게 알아보고자 한다.
다음 예시는 DB의 회원을 불러오는 Controller - Service - DAO 구조에서 Service - DAO 구조이다.
코드의 변화를 통해 쉽게 이해해보려고 한다.
1. Original
" 기본적으로 환경은 Spring Framework로 가정 (1~4 예시)"
// Service
public class Service{
oracleStore DB = new oracleStore();
Logic(){
DB.create();
DB.delete();
}
}
// oracleStore
public class oracleStore{
public void create() {
}
public void delete() {
}
public void update() {
}
}
다음과 같이 클래스가 정의 되어 있다고 가정하자.
Service에서는 DB객체를 생성하고 그 객체의 메서드를 이용해 DB에 데이터를 CRUD를 한다.
mysql, oracle 클라스를 사용한다고 할때, 둘의 메서드도 다르다고 가정을 할때, 큰 단점이 존재한다.
- 만약 oracle이 아닌 다른 DB로의 변경을 하려고 할때, Service의 모든 코드를 수정해야한다.
- 즉, Service 클래스는 OracleStore 클래스에 의존하고 있다. 라고 볼 수 있다.
2. Interface
그럼 다음과 같이 생각할 수 있다.
" Interface를 통해 메서드 이름을 지정해 놓고 DB 변경시 Service에서는 객체만 바꿔주면 되지 않는가 "
// Service
public class Service{
private DBStore clubStore;
public Service() {
this.DBStore = new oracleStore();
}
public Logic(){
DBStore.create();
DBStore.delete();
}
}
// DBStore
public interface DBStore {
String create();
void delete();
void update();
}
// oracleStore
public class oracleStore implements DBStore{
@Override
public void create() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
}
// mysqlStore
public class mysqlStore implements DBStore{
@Override
public void create() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
}
현재는 DBStore 인터페이스를 생성하고 해당 메서드를 Override한 메서드만을 사용한 특정 DB의 클래스들을 생성해 두었다.
oracle을 사용하던 mysql을 사용하던 메서드 이름은 같게되고
Service에서는 인터페이스 객체 생성 후 그 인터페이스 객체에 특정 DB객체만 연결해 생성 후 사용하면 되니 전보다 자유롭다.
또한, DB를 다른 것을 사용해도 메서드명 변경은 없이 객체만 바꿔주면 된다.
하지만 조금 줄어들었지만 여전히 의존관계에서는 벗어나지 못하고 있다.
전보다는 변화가 적지만 객체이름을 변경하며 사용해야 하기에 아직 의존관계가 남아 있다고 볼 수 있다.
여기서 등장한게 IoC를 통한 의존성 주입(DI)이다.
IoC란 제어의 역전이라고 했다.
이는 Class A - Interface - Class B 가 존재할 때, Class A는 Class B를 몰라도 그냥 Interface만 참조해 Class B를 사용한다는 의미이다. 즉, Class A가 제어하는 것이 아닌 서버에서 Class B를 사용할지 Class C를 사용할지 설정하는 것이다.
이때, 이런 역할을 하는 서버의 부분을 'IoC Container' 라고 한다.
이렇게 IoC Container에 의해 제어의 역전이 일어나면 더이상 Class A는 Class B에 의존하지 않고 Class B가 C로 변해도 Class A는 더이상 아무 변화가 없다.
또한, 제어의 역전에 의한 의존성 주입은 TDD에도 좋은 영향을 끼친다.
TDD는 Test Driven Development의 약자로 클래스간 의존성이 강하다면 TDD는 불가능하다.
예를 들어, 이번 예시와 같이 Service Class, DB Class 가 있다고 가정했을 때, 테스트 시, 설정 변경때마다 DB 클래스를 수정해줘야한다. 이는 TDD라고 볼 수 없으며 두 클래스 간 의존적이지 않을때, TDD가 가능하다.
방법에는 여러가지가 있다.
- IoC : 제어 방법
- 1) xml에 빈들의 관계를 설정하여 제어한다.
- 2) 애너테이션들을 설정하고 xml에 찾을 빈들의 패키지만을 설정하여 제어한다.
- 3) 애너테이션을 이용해 xml작성도 필요없이 제어한다 in SpringBoot.
- DI : 의존성 주입 방법
- 1) Setter 를 통한 주입
- 2) Constructor 를 통한 주입
- 3) Method 를 통한 주입
- 4) @Autowired를 통한 주입
- 5) @RequiredArgsConstructor를 통한 주입 (from Lombok)
DI는 Constructor를 통한 주입만 알아볼 것이고
IoC는 모두 알아볼것이며 밑으로 갈 수록 최신 방식이다.
- 1) xml에 빈 연결관계를 설정하여 의존성을 주입한다.
- 2) 애너테이션을 통해 자체적으로 빈을 생성하고 xml에는 빈을 찾을 코드만 작성해서 의존성을 주입한다.
- 3) 애너테이션을 이용해 xml도 작성할 필요없이 의존성을 주입한다.
- (+) 1~3 과정은 주입 과정은 서버가 실행되면서 주입하도록 설정한다, 또한 의존성 방식은 Constructor로 통일한다.
3. Interface + Contructor + IoC(xml)
1) xml에 빈 연결관계를 설정하여 의존성을 주입한다.
// XML
<beans>
<bean id="oracleDB" class="package.oracle" />
<bean id="mysql" class="package.mysql" />
<bean id="Service" class="package.Service">
<constructor-org ref="oracleDB" />
</bean>
</beans>
// Service
public class Service{
private DBStore dbStore;
public Service(DBStore dbStore){
this.dbStore = dbStore;
}
public Logic() {
dbStore.create();
dbStore.delete();
}
}
// DBStore
public interface DBStore {
String create();
void delete();
void update();
}
// oracleStore
public class oracleStore implements DBStore{
@Override
public void create() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
}
위의 코드를 살펴보면
만약 DB를 oracle을 사용하다 mysql로 변경한다고 가정했을때, 전혀 바꿔야하는 부분이 존재하지 않는다.
즉, 제어의 역전으로 인해 클래스 service와 클래스 oracle 또는 mysql 간 의존성이 사라진 것이다.
xml에 bean을 설정하고 빈을 주입한다. 이 과정이 DI이다.
이후, Service에서는 생성자 호출시 자동으로 mysql Class 또는 oracle Class 가 삽입된다.
4. Interface + Contructor + IoC(xml + annotation)
2) 애너테이션을 통해 자체적으로 빈을 생성하고 xml에는 빈을 찾을 코드만 작성해서 의존성을 주입한다.
// XML
<beans>
<context:component-scan base-package="packageName"></context:component-scan>
</beans>
// Service
@Service
public class Service{
private DBStore dbStore;
public Service(DBStore dbStore){
this.dbStore = dbStore;
}
public Logic() {
dbStore.create();
dbStore.delete();
}
}
// DBStore
public interface DBStore {
String create();
void delete();
void update();
}
// oracleStore
@Repository
public class oracleStore implements DBStore{
@Override
public void create() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
}
앞에 예시에서 제어의 역전을 통해 더 이상 의존성의 관계가 사라졌다. 하지만 좀 더 편한 구현 방식이 있다.
다음과 같이 xml에 작성을 하면 프로그램이 시작하면 해당 패키지 안에 빈들을 알아서 찾겠다는 의미이다.
또한, service 클래스와 oracleDB 클래스 시작에 애너테이션을 설정한다.
즉, xml에 설정된 패키지에 있는 빈들을 찾아서(애너테이션이 설정되어 있는 클래스들을 찾아서) 의존성 주입을 한다는 의미이다.
또한, 주입되는 시각은 클래스가 생성될때 이다. 즉, 생성자 호출시 자동으로 입력이 된다.
애너테이션은 여러가지가 있으며 다른 블로그로 다시 작성하고자 한다.
5. Interface + Contructor + IoC(annotation only) with Spring Boot
3) 애너테이션을 이용해 xml도 작성할 필요없이 의존성을 주입한다.
// Service
@Service
public class Service{
private DBStore dbStore;
public Service(DBstore dbStore) {
this.dbStore = dbStore;
}
public Logic() {
dbStore.create();
dbStore.delete();
}
}
// DBStore
public interface DBStore {
String create();
void delete();
void update();
}
// oracleStore
@Repository
public class oracleStore implements DBStore{
@Override
public void create() {
}
@Override
public void delete() {
}
@Override
public void update() {
}
}
위의 코드를 살펴보면 xml에 따로 bean 관계를 정의할 필요도 없어진다.
-> 스프링 부트에서는 @SpringBootConfiguration 을 통해 IoC를 알아서 진행해줌.
단순히 상속자를 통해 설정을 해두면, @Repository 를 갖는 빈 중 DBStore를 상속받는 클래스를 주입시켜준다.
하지만 문제점이 위의 조건을 만족하는 클래스가 하나여야만 알아서 주입된다.
즉, DBStore를 상속받는 클래스 중 oralce, mysql 등 두개 이상의 클래스 존재시 사용이 불가능하다.
해결 방법
- Class에 설정된 @Repositoy 애너테이션을 한개만 설정한다. 즉, oracle, mysql 둘다 @Respository를 설정하지 말고 쓸 것만 작성해놓고 나머지는 주석처리해 둔다.
- 보통은 Service 클래스든 DAO클래스든 두개 다 빈으로 활용하는 경우는 거의 없으므로 다음과 같이 이용하면 되겠다.
'Server Development > Spring Basic' 카테고리의 다른 글
Spring - AOP (0) | 2023.04.02 |
---|---|
Spring - Exception (0) | 2023.04.01 |
Spring - Log(LogBack) (0) | 2023.04.01 |
Spring - MVC (0) | 2023.03.24 |
Spring - Spring Boot (0) | 2023.03.24 |