스프링 DI(의존 주입)

스프링의 DI(의존 주입)을 보기 전에 먼저 스프링의 목적을 알아볼 필요가 있다.

2000년대 초반 각종 자바 컨퍼런스에서 자주 논의됐던 주제

‘무엇때문에 자바 엔터프라이즈 프로젝트는 실패하는가?’

그에 대한 답은 바로 시스템 개발이 복잡하다는 것이었다.

왜 자바 엔터프라이즈 프로젝트는 실패하는가?

  • 엔터프라이즈 시스템이란 서버에서 동작하며 기업과 조직의 업무를 처리하는 시스템

이러한 시스템은 비즈니스 로직 구현만이 아닌 보안, 안정성, 확장성, 성능을 모두 고려해서 개발해야한다.

경제위기가 가져온 엔터프라이즈 시스템의 변화

결국 비즈니스 로직, 안정성, 확장성, 성능에 더해 추후 바뀔지 모르는 비즈니스 로직에 대한 유지보수 및 수정까지 고려해야하는 큰 어려움에 부딪혔다

1. 의존이란

  • DI(Dependency Infjection)
    의존 관계 주입이라는 말로 해석 할 수 있다.
    표준국어대사전에 의하면 ‘의존’이란 다른 것에 의지하여 존재하는 즉, 의존관계란 변경에 의해 영향을 받는 관계를 의미한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MemberRegisterService {

// new 연산자를 이용해서 MemeberDao의 인스턴스를 생성한다. 이를 '보통객체'라 부른다.
private MemberDao memberDao = new MemberDao();

public void regist(RegisterRequest req) {
// 이메일로 회원 데이터(Member)조회
Member member = memberDao.selectByEmail(req.getEmail());
if (member != null) {
// 같은 이메일을 가진 회원이 이미 존재하면 익셉션 발생
throw new AlreadyExistingMemberException("dup email : " + req.getEmail());
}
// 같은 이메일을 가진 회원이 존재하지 않으면 DB에 삽입
Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), new Date());
memberDao.insert(newMember);
}
}

위 코드는 전달 받은 회원 객체 내 이메일 주소가 현재 회원 DB 테이블 내에 존재하는 경우
익셉션을 발생시키고 같은 이메일 주소를 가진 회원이 존재하지 않는 경우 DB에 INSERT 시키는 간단한 로직을 가지고 있다.

이와 같이 한 클래스에서 다른 클래스의 메소드를 실행하는 경우를 ‘의존’한다고 말한다.

  • 의존은 변경에 의해 영향을 받는 관계를 의미한다. ex) MembaerDao의 insert()
    메소드의 이름을 insertMemeber()라고 변경하면 이 메소드를 사용하는 타 클래스들도 영향을 받는다.

여기서 알고 가면 좋은 한 가지.
객체지향 프로그래밍의 기초 개념 중 하나는 관심사의 분리 라는 것이다.

관심사의 분리

단순히 DB INSERT하고 SELECT하는 메소드를 만들때 위와 같은 코드를 만들 수 있지만
리소스의 낭비나 환경의 변화에 바로 대응하기 위해선 관심사의 분리가 중요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Connection getConnection() throws ClassNotFoundException, SQLException {   
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection c = DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:orcl", "PPS_DB", "PPS123");
return c;
}
...

public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}

public void get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}

위와 같이 중복 혹은 의존 관계인 코드를 분리하여 의존을 제거할 수 있다.
DB의 종류가 바뀌거나 계정을 바꿔야할 경우 getConnection 메소드만 수정하면 끝!

의존 객체를 구하는 방법에는 DI와 서비스 로케이터가 있다.

  • 서비스 로케이터에 관련하여 별도로 포스트 예정

DI는 의존하는 객체를 직접 생성하지 않고 의존 객체를 전달받는 방식을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Member dao = new MemberDao();
// 의존 객체를 생성자를 통해 주입처리
MemberRegisterService service = new MemberRegisterService(dao);
...

public class MemberRegisterService {
private MemberDao memberDao;
// MemberRegisterServeice가 의존하고 있는 MemberDao 객체를 주입
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao;
}
...
}

위와 같이 의존 객체를 직접 생성하지 않고 생성자를 통해 전달 받으므로 DI(의존 주입) 패턴을 따르고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
MemberAssembler assembler = new MemberAssembler();

Member member = new Member("jh4327", "4327");

RegisterService registerService = assembler.getRegisterService();
registerService.register(member);

member.setPassword("1234");

PwChangeService pwChangeService = assembler.getPwChangeService();
pwChangeService.update(member);
}

위와 같이 사용자를 입력하고 비밀번호를 수정하는 구조를 가진 프로세스가 있을 때

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MemberAssembler {
private MemberDao memberDao;
private RegisterService registerService;
private PwChangeService pwChangeService;

public MemberAssembler() {
memberDao = new MemberDao();
registerService = new RegisterService(memberDao);
pwChangeService = new PwChangeService(memberDao);
}

public MemberDao getMemberDao() {
return memberDao;
}

public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}

public RegisterService getRegisterService() {
return registerService;
}

public void setRegisterService(RegisterService registerService) {
this.registerService = registerService;
}

public PwChangeService getPwChangeService() {
return pwChangeService;
}

public void setPwChangeService(PwChangeService pwChangeService) {
this.pwChangeService = pwChangeService;
}
}

위와 같은 객체 조립기를 통해 DAO 의존 주입이 가능하다.
하나의 동일한 객체를 각 서비스에 주입시켜주는 구조.
최초 생성(로드)할 때 DAO 객체를 다른 객체가 생성될 때 모두 주입하는 것이다.
즉 Service들은 Dao에 의존하고 있으며 의존 주입 관계이다.

이걸 스프링 DI로 변경한다면?

1
2
3
4
5
6
7
8
9
<bean id="memberDao" class="kr.co.jhdev.MemberDao"></bean>

<bean id="registerService" class="kr.co.jhdev.MemberService">
<constructor-arg ref="memberDao"></constructor-arg>
</bean>

<bean id="pwChangeService" class="kr.co.jhdev.PwChangeService">
<constructor-arg ref="memberDao"></constructor-arg>
</bean>

앞서 개발한 객체 조립기를 xml 스프링 컨테이너로 변경한다.
registerService와 pwChangeService는 memberDao를 참조하도록 한다.
이로써 최초 로드시 서비스들과 dao의 의존관계가 설정된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MemberAssembler assembler = new MemberAssembler();
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");

Member member = new Member("jh4327", "4327");

// RegisterService registerService = assembler.getRegisterService();
RegisterService registerService = ctx.getBean("registerService", RegisterService.class);
registerService.register(member);

member.setPassword("1234");

// PwChangeService pwChangeService = assembler.getPwChangeService();
PwChangeService pwChangeService = ctx.getBean("pwChangeService", PwChangeService.class);
pwChangeService.update(member);

스프링 컨테이너에 등록한 bean과 객체들 간 의존 관계를 이용하여 위와 같은 코드로 변경될 수 있다.
직접 주입하지 않고 스프링 컨테이너를 통해 주입하고 getBean을 통해 불러오기만 하면 된다.
지금까진 xml을 이용한 의존 주입을 해왔다. 다음 해당 포스팅에선 어노테이션을 이용한 방법에 대해 설명하도록 한다.

결국 의존 주입은 관심사의 분리를 통해 유지보수와 수정을 용이하게 하기 위함에 목적이 있다.

초보 웹 개발자를 위한 스프링 4 프로그래밍 입문(최범균 지음)
토비의 스프링 3.1(이일민 지음)
Share