본문 바로가기
Programming/Spring

Spring DI

by TinKerBellBass 2017. 8. 18.
728x90
반응형

참고도서

초보 웹 개발자를 위한 스프링4 프로그래밍 입문
국내도서
저자 : 최범균
출판 : 가메출판사 2015.03.02
상세보기




1. 의존이란?

의존은 변경에 의해 영향을 받는 관계를 의미한다.
간단히 말해 어떤 클래스에서 new 연산자로 다른 클래스의 객체를 생성하면 의존관계가 발생한다.
new 연산자로 생성된 객체의 클래스에 변경이 발생하면,
의존하고 있는 클래스에도 변경이 일어난다.
유지보수 관점에서 문제가 발생한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MemberDao{
 
    void insert(){
    }
}
 
class MemberRegisterService{
// new 연산자로 MemberDao 객체 직접 생성
    // MemberRegisterService 는 MemberDao에 의존하게 되었다.
// 만약 MemberDao를 교체해야 할 때에도 MemberRegisterService에는 변경이 일어난다.
    MemberDao memberDao = new MemberDao();
    
    void register(){
        // MemberDao의 insert() 메소드명이 insertMember()로 변경되면,
        // MemberRegisterService 에서도 변경이 일어난다. 
        // memberDao.insertMember()로 수정해야 한다.
        memberDao.insert();    
    }
}
cs

2. DI를 통한 의존 처리

DI(Dependency Injection, 의존주입)는 의존하는 객체를 new 연산자를 사용해서 직접 생성하지 않고,
의존 객체를 주입(전달) 받는 방식을 뜻한다.

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
class MemberDao{
 
    void insert(){
    }
}
 
class MemberRegisterService{
    
    MemberDao memberDao;
 
    // MemberRegisterService 객체를 생성할 때 MemberDao 객체를 주입 받는다
    public MemberRegisterService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void register(){        
        memberDao.insert();    
    }
}
 
class Client{
 
    MemberDao memberDao = new MemberDao();
    // MemberRegisterService 객체를 생성할 때 MemberDao 객체를 주입 받는다.
    MemberRegisterService svc = new MemberRegisterService(memberDao);
}
cs

이렇게 의존 객체를 직접 생성하지 않고 주입하는 방식을 사용하는 이유는
객체를 변경할 때 유연하게 대처할 수 있기 때문이다.

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
// new 연산자로 직접 객체를 생성하는 경우
class MemberDao{
    void insert(){
    }    
    void delete(){
    }
}
 
class MemberRegisterService{
    
    MemberDao memberDao = new MemberDao();
    
    void register(){        
        memberDao.insert();    
    }    
}
 
class MemberDeleteService{
 
MemberDao memberDao;
    
    MemberDao memberDao = new MemberDao();
    
    void delete(){        
        memberDao.delete();
    }
}
 
class Client{
 
    MemberDao memberDao = new MemberDao();
    MemberRegisterService svci = new MemberRegisterService();
    MemberDeleteService svcd = new MemberDeleteService();
}
cs

MemberDao 객체를 사용하는 MemberRegisterService, MemberDeleteService 클래스가 있고,

MemberRegisterService, MemberDeleteService 두 객체를 사용하는 Client 클래스가 있다.

만약, MemberDao 클래스를 확장하여 상속하는 CashedMemberDao 클래스의 객체를

MemberRegisterService, MemberDeleteRegister 클래스가 사용해야 한다면 

두 클래스와 Client 클래스를 변경해야 한다. 


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
36
37
38
39
40
41
42
43
44
45
46
47
// MemberDao 를 CashedMemberDao 로 교체
class MemberDao{
    void insert(){
    }    
    void delete(){
    }
}
 
class CashedMemberDao extends MemberDao{
    void cashedInsert(){
    }
    void cashedDelete(){
    }
}
 
class MemberRegisterService{
    // 변경 발생
    MemberDao memberDao = new CashedMemberDao();
    
    void register(){        
        memberDao.insert();    
    }
    void cahsedRegister(){        
        memberDao.insert();    
    }    
}
 
class MemberDeleteService{
 
MemberDao memberDao;
    // 변경 발생
    MemberDao memberDao = new CashedMemberDao();
    
    void delete(){        
        memberDao.delete();
    }
    void cashedDelete(){        
        memberDao.delete();
    }
}
 
class Client{
    // 변경 발생
    MemberDao memberDao = new CashedMemberDao();
    MemberRegisterService svci = new MemberRegisterService();
    MemberDeleteService svcd = new MemberDeleteService();
}
cs


지금은 세 곳이지만 MemberDao 객체를 사용하는 클래스가 많으면 변경할 곳은 더 늘어난다.


DI를 통해서 처리를 하면 Client 클래스만 변경해 주면 해결된다.


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 생성자를 통해 객체를 주입 받고 있을 때,
// MemberDao 를 CashedMemberDao 로 교체하는 경우
class MemberDao{
    void insert(){
    }    
    void delete(){
    }
}
 
class CashedMemberDao extends MemberDao{
    void cashedInsert(){
    }
    void cashedDelete(){
    }
}
 
// 변경 없음
class MemberRegisterService{
    
    MemberDao memberDao;
 
    public MemberRegisterService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void register(){        
        memberDao.insert();    
    }
    void cahsedRegister(){        
        memberDao.insert();    
    }    
}
 
// 변경 없음
class MemberDeleteService{
 
    MemberDao memberDao;
 
    public MemberDeleteService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void delete(){        
        memberDao.delete();
    }
    void cashedDelete(){        
        memberDao.delete();
    }
}
 
class Client{
    // 의존받는 객체를 실제 생성하는 한 곳만 변경하면 OK
    MemberDao memberDao = new CashedMemberDao();
    MemberRegisterService svci = new MemberRegisterService(memberDao);
    MemberDeleteService svcd = new MemberDeleteService(memberDao);
}
cs


3. 객체 조립기

Client 에서 의존받는 객체를 생성해서 사용했는데,
의존받는 객체를 생성해서 MemberRegisterService, MemberDeleteService 에 주입해서
Client 에 넘기는 클래스를 사용할 수도 있다. 이 클래스를 객체 조립기라고 한다.
Client 는  객체조립기 객체를 생성하고 객체조립기 객체를 이용해서 
MemberRegisterService, MemberDeleteService 객체를 넘겨 받아서 사용할 수 있다.
이 경우 MemberDao를 CashedMemberDao로 변경해야 한다면 객체 조립기의 코드만 수정하면 된다.

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 생성자를 통해 객체를 주입 받고 있을 때,
// MemberDao 를 CashedMemberDao 로 교체하는 경우
class MemberDao{
    void insert(){
    }    
    void delete(){
    }
}
 
class CashedMemberDao extends MemberDao{
    void cashedInsert(){
    }
    void cashedDelete(){
    }
}
 
// 변경 없음
class MemberRegisterService{
    
    MemberDao memberDao;
 
    public MemberRegisterService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void register(){        
        memberDao.insert();    
    }
    void cahsedRegister(){        
        memberDao.insert();    
    }    
}
 
// 변경 없음
class MemberDeleteService{
 
    MemberDao memberDao;
 
    public MemberDeleteService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void delete(){        
        memberDao.delete();
    }
    void cashedDelete(){        
        memberDao.delete();
    }
}
 
// 의존받는 객체를 실제 생성하여 MemberRegisterService, MemberDeleteService 에 주입하는 클래스(객체조립기)
class Assembler{    
    MemberDao memberDao;
    MemberRegisterService svci;
    MemberDeleteService svcd;
 
    public Assembler(){
        // MemberDao를 CashedMemberDao로 교체하려면 이곳만 변경하면 OK
        memberDao = new CashedMemberDao();
        svci = new MemberRegisterService(memberDao);
        svcd = new MemberDeleteService(memberDao);
    }
 
    public MemberRegisterService getMemberRegisterService(){
        return svci;
    }
    public MemberDeleteService getDeleteService(){
        return svcd;
    }
}
 
// 변경 없음
class Client{
    // Assembler 객체를 만들고 get 메소드를 통해 MemberRegisterService, MemberDeleteService 객체를 넘겨 받
    Assembler assembler = new Assembler();
    MemberRegisterService svci = assembler.getMemberRegisterService();
    MemberDeleteService svcd = assembler.getMemberDeleteService();    
}
cs

그리고 MemberRegisterService, MemberDeleteService 객체가 필요한 곳은

Assembler 객체를 생성하고 MemberRegisterService, MemberDeleteService 객체를 get 을 통해 넘겨 받아 사용하면 된다.


4. Spring 의 DI 설정

스프링은 객체 조립기와 같은 기능을 제공한다. 
즉 스프링은 Assembler 클래스의 생성자처럼 필요한 MemberRegisterService, MemberDeleteService 객체를 생성하고 
두 객체에 MemberDao 객체(의존)를 주입해준다.
그리고 get 메소드 처럼 객체를 제공하는 기능도 가지고 있다.
스프링이 객체 조립기 기능을 하게 되면 MemberDao 를 교체해야 할 때,
자바 클래스 어디에도 변경은 필요가 없게 된다. 
단지 스프링 설정을 변경해 주기만 하면 되는 것이다.

필요한 객체를 생성시키는 스프링 XML 설정 파일

1
2
3
4
5
6
7
8
9
10
11
12
<!-- MemberDao 를 변경하려면 이 곳만 변경하면 OK -->
<bean id="memberDao" class="spring.MemberDao"/>
 
<bean id="memberRegSvc" class="spring.MemberRegisterService">
    <!-- 생성자를 통해 객체를 주입할 때는 constructor-arg 엘리먼트를 사용 -->
    <!-- ref 값으로 주입할 객체를 생성하는 bean 엘리먼트의 id를 설정 -->
    <constructor-arg ref="memberDao"/>
</bean>
 
<bean id="memberDelSvc" class="spring.MemberDeleteService">
    <constructor-arg ref="memberDao"/>
</bean>
cs


스프링을 사용한 후의 클래스들

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package spring;

class MemberDao{
    void insert(){
    }    
    void delete(){
    }
}
 
class CashedMemberDao extends MemberDao{
    void cashedInsert(){
    }
    void cashedDelete(){
    }
}
 
class MemberRegisterService{
    
    MemberDao memberDao;
 
    public MemberRegisterService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void register(){        
        memberDao.insert();    
    }
    void cahsedRegister(){        
        memberDao.insert();    
    }    
}
 
class MemberDeleteService{
 
    MemberDao memberDao;
 
    public MemberDeleteService(MemberDao memberDao){
        this.memberDao = memberDao
    }
    
    void delete(){        
        memberDao.delete();
    }
    void cashedDelete(){        
        memberDao.delete();
    }
}
 
class Client{
    // 객체 조립기로 스프링을 사용, 스프링 컨테이너는 스프링 설정파일을 통해 MemberDao를 주입받은 
    // MemberRegisterService, MemberDeleteService 객체를 생성한다.
    ApplicationContext ctx = new GenericXmlApplicationContext("classpath:appCtx.xml");
    MemberRegisterService svci = ctx.getBean("memberRegSvc", MemberRegisterService.class);
    MemberDeleteService svcd = ctx.getBean("memberDelSvc", MemberDeleteService.class);
}
cs


만약, 두 개의 객체를 주입받아야 한다면 constructor-arg 를 두 개 사용하면 된다.


1
2
3
4
5
6
7
<bean id="memberDao" class="spring.MemberDao"/>
<bean id="memberPrinter" class="spring.MemberPrinter"/>
 
<bean id="memberRegSvc" class="spring.MemberRegisterService">        
    <constructor-arg ref="memberDao"/>
    <constructor-arg ref="memberPrinter"/>
</bean>
cs


5. setter 메소드를 사용한 DI 설정

생성자를 통해서 의존 객체를 주입받을 수도 있지만, setter 메소드를 통해서 의존 객체를 주입 받을 수도 있다.
이 때는 constructor-arg 엘리먼트 대신에 property 엘리먼트를 사용하면 된다.

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
<bean id="memberDao" class="spring.MemberDao"/>
<bean id="memberPrinter" class="spring.MemberPrinter"/>
 
<bean id="memberRegSvc" class="spring.MemberRegisterService">        
    <property name="memberDao" ref="memberDao"/>
    <property name="printer" ref="memberPrinter"/>
</bean>
 
<!--
MemberRegisterService 클래스
class MemberRegisterService{
    private MemberDao memDao;
    private MemberPrinter printer;
 
    public void setMemberDao(MemberDao memberDao){
        this.memDao = memberDao;
    }
 
    public void setPrinter(MemberPrinter printer){
        this.printer = Printer;
    }
}
 
스프링은 property 의 name 값의 앞 첫자를 대문자로 바꾸고 set을 붙인 이름의 메소드를 찾는다.
name="memberDao" -> setMemberDao()
name="printer" -> setPrinter()
-->
cs


6. 주입하는 값이 객체가 아닌 기본 데이터 값 타입일 경우

ref 속성 대신에 value 속성을 사용하면 된다.
그리고 value 속성의 값은 모두 문자열이지만 스프링이 알아서 타입의 형변환을 해준다.

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
36
37
38
39
40
<bean id="memberDao" class="spring.MemberDao"/>
<bean id="memberPrinter" class="spring.MemberPrinter"/>
 
<bean id="memberRegSvc" class="spring.MemberRegisterService">        
    <property name="memberDao" ref="memberDao"/>
    <property name="printer" ref="memberPrinter"/>
    <property name="version" value="4"/>
    <property name="name" value="NyangNyang"/>
</bean>
 
<!--
MemberRegisterService 클래스
class MemberRegisterService{
    private MemberDao memDao;
    private MemberPrinter printer;
    private int version;
    private String name;
 
    public void setMemberDao(MemberDao memberDao){
        this.memDao = memberDao;
    }
 
    public void setPrinter(MemberPrinter printer){
        this.printer = Printer;
    }
 
    public void setVersion(int version){
        this.version = version;
    }
 
    public void setName(String name){
        this.name = name;
    }
}
 
<property name="version" value="4"/> 은 value 엘리먼트를 사용해서 바꿀 수도 있다.
<property name="version">
    <value>4</value>
</property>
-->
cs


7. 두 개 이상의 설정 파일 사용

1
2
3
4
5
6
7
8
9
// conf1.xml, conf2.xml 두 개의 설정파일을 사용해야 할 때
 
// 1. 스프링 컨테이너를 생성할 때 둘 다 설정
ApplicationContext ctx = new GenericXmlApplicationContext("classpath:conf1.xml","classpath:conf2.xml");
 
// 2. <import> 엘리먼트 사용
// conf1.xml 에 conf2.xml을 import
// conf1.xml에 <import resource="classpath:conf2.xml"> 추가
ApplicationContext ctx = new GenericXmlApplicationContext("classpath:conf1.xml");
cs


728x90
반응형

'Programming > Spring' 카테고리의 다른 글

자바 코드를 이용한 설정  (0) 2017.08.19
의존 자동 주입  (0) 2017.08.18
Maven / Spring 기본  (0) 2017.08.18
JPA 스프링 연동  (0) 2017.08.14
JPA (Java Persistence API) 기본 개념  (2) 2017.08.13

댓글