카테고리 없음

[Spring Boot] Dependency Injection DI / Bean 관리

푸라멘 2024. 4. 7. 03:01

Dpendency Injection, DI  (의존성 주입)이란?

외부에서 클라이언트에게 서비스를 제공(주입) 해주는 것을 의미한다.

-> 즉 객체가 필요로 하는 어떤 것을 외부에서 전달 해주는 것이다.

 

* 의존성이란 ?

- 한 객체가 다른 객체를 사용할 때 의존성이 있다고 한다.

 

ex) 아래 MemberService 클래스를 사용하기 위해서는 MySqlMemberRepository가 필요하다.

-> MemberService  는 MySqlMemberRepository에 의존한다.

public class MemberService {

    private MySqlMemberRepository mySqlMemberRepository;

    public MemberService() {
        this.mySqlMemberRepository = new MySqlMemberRepository();
    }

    public void join(Member member) {
        mySqlMemberRepository.save(member);
    }
}

이런 경우에는 두 클래스의 결합도가 커진다는 문제점을 가지고 있다.

  • MemberService가 MySqlMemberRepository를  OracleMemberRepository를 교체할 떄 MemberService의 생성자 코드및  로직 코드도 바꿔야한다.
public class MemberService {

    private OracleMemberRepository oracleMemberRepository;

    public MemberService() {
        this.oracleMemberRepository = new OracleMemberRepository();
    }

    public void join(Member member) {
        oracleMemberRepository.save(member);
    }
    
    // 함수가 여러개 있다면 바꿔야할 코드는 더 많아진다...
}

 

결합도를 낮추기 위해 인터페이스 생성을 통해 MeberService는 오직 DB의 종류가 아닌 DB의 기능에만 관심을 가지도록 Inteface를 만든다.

//MySqlMemberRepository와 OracleMemberRepository가 상속받는다.
public interface MemberRepository {

    Member save(Member member);
}

 

다음과 같이 코드가 변화해 생성자만 수정하게 된다.

public class MemberService {

    private MemberRepository memberRepository;

    public MemberService() {
        this.memberRepository = new OracleMemberRepository();
    }

    public void join(Member member) {
        memberRepository.save(member);
    }

 

그러나 이러한 경우에도 생성자에서는 어떠한 DB를 사용해야 하는지 알아야 하기 때문에 관심사가 완전히 분리되지 못했다.

따라서 DB를 바꾸기 위해서는 여전히 MemberService의 코드를 변경해야한다.

 

public class MemberService {

    private MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public void join(Member member) {
        memberRepository.save(member);
    }
}

// Injection 역할을 하는 클래스라고 가정
public class Injection () {

	public void inejection() {
    	MemberRepository memberRepository = new MemberRepository();
       
       //외부에서 의존관계 객체 생성후 넣어주는 것을 Dependency Injection이라 한다.
        MemberService memberService = new MemberService(memberRepository);
        
    }
}

MemberService를 생성할 때 의존 객체를 외부 주입하도록 코드를 변경하게 되면 Service는  클래스 안에서 어떤 DBRepository를 쓰는지 정확히 몰라도 된다.

 

외부에서 의존하는 객체를 Injection하는 것을 Dependency Inejction이라고 하며 

Spring은 SpringContainer에서 의존관계가 있는 객체들을 생성한후 해당 객체를 사용하는 객체에 Dependency Injection을 수행한다.

MemberRepository를 상속받아 구현한 클래스라면 SpringContainer에서 생성 후 injection 하여 MemberRepository 의존하는 클래스 안에서는 코드수정을 할 필요가 없다.

 

 

 

Bean 등록 방법

Spring Container에서 관리하는 객체를 Bean이라고 한다.

Spring Container에서 생성된 Bean 객체는 Singleton 패턴을 따르는데 요청마다 주입 객체를 계속 생성/소멸 한다면 서버에 부담이 가기 때문이다.

 

* 등록된 bean은 main함수가 들어가있는 package 내에서만 작동한다. (@ComponentScan 을 써서 동작가능)

 

 

XML 이용

src/main 폴더 아래에 resources 폴더를 생성하고 아래와 같이 해당 폴더에 applicationContext.xml 을 생성한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="memberReopository" class="com.example.springExam.MemberReopository"></bean>

</beans>

현재는 잘 쓰지 않는 방법이다.

 

 

Annotaition (@) 이용

@Controller, @Service, @Repository, @Component 등의 annotation을 이용한다.

@Controller
public class MemberController {
	...
}

@Service 
public class MemberService {
	...
}

@Repository
public class MemberRepository {
	...
}

@Component
public class MemberMetaData {
	...
}

class 위에 Annotation을 써놓으면 Spring이 실행될 때 bean으로 등록한다.

* @Component Annotation이 실제로 Bean으로 등록해준다. @Controller, @Service, @Repository의 내부는 @Component Annotaion이 존재한다.

@Controller
@Service
@Repository

직접 명시

@Configuration 과 @Bean을 이용하여 직접 Bean객체를 지정할 수 있다.

@Configuration
public class SpringConfiguration {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

 

 

 

@Autowired

해당 Annotaion이 있으면 Spring Container에서 찾아 객체를 주입한다. Bean으로 등록되지 않으면 Spring Container에 객객체가 존재하지 않으므로 @Autowired를 붙여도 객체를 얻을 수 없다.

public class MemberService {

    private  MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

 

 

 

주입 방법

객체 주입방법 에는 크게 3가지가 존재한다.

  • 필드주입
@Controller
public class MemberController {
    @Autowired
    private MemberService memberService;
    
    ...
}

- 필드 주입을 사용하면 final 제어자를 사용하지 못해 객체가 불변성을 보장 받지 못한다.

- 필드 주입은 정확히 어떤 방식으로 결합되어있는지 자바로 명시되어있지 않다. 따라서 프레임워크 혹은 컨테이너 자체에 생성과 소멸을 의존하게 된다. -> Spring과의 결합도를 증가시킨다.

- 필드 주입은 달랑 Service 선언 위에 @Autowired 애노테이션이 달려있는 구조이기 때문에, 스프링의 도움을 받지 않으면 객체를 생성하기 어렵다. 따라서 테스트를 하려면 프로젝트 전체를 돌려야 한다. 이는 무거운 프레임워크를 피해 자바 코드만으로 가동하려는 테스트의 의미를 무색하게 만든다.

 

 

 

  • Setter 주입
@Controller
public class MemberController {

   private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService;
    }
    ...
}

-  public으로 setter를 설정하므로 실행 도중 바꾸어질 염려가 있다.

 

 

 

  • 생성자 주입(추천)
@Controller
public class MemberController {
    
    private MemberService memberService;
    
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
    ...
}

-  의존 중인 객체가 하나라도 없으면 만들어지지 않아 컴파일할 때 유리하다

- 현재 공식문서의 추천하는 생성자

- final 사용할 수 있어 불변성이 보장된다.

- 생성자가 1개인 경우 @Autowired를 생략할 수 있다.

-  DI 프레임워크에 의존하지 않고, 순수 자바 언어로도 잘 작동하며 자바 언어의 객체 지향이라는 특징을 잘 살릴 수 있다. 

 

 

참고 문헌