JDBC를 이용하는 작업의 일반적인 순서.

  • DB연결을 위한 Connection을 가져온다. 
  • SQL을 담은 Statement를 만든다.
  • 만들어진 Statement를 실행한다.
  • 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다. 
  • 작업 중에 생성된 Connection,Statement,ResultSet같은 리소스는 작업을 마친후 반드시 닫아준다.
  • JDBC API가 만들어 내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다. 

개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다.

 

변경이 일어날 때 필요한 작업을 최소화하고, 그 변경이 문제안일으키게 할 수 있을까? 그것은

분리와 확장을 고려한 설계덕분.

 

UserDao의 관심사항 

  • 첫째, DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심. 더 세분화해서 어떤 DB를 쓰고, 어떤 드라이버를 사용할 것이고, 어떤 로그인 정보를 쓰는데, 그 커넥션을 생성하는 방법은 또 어떤 것이다라는 것까지 구분해서 볼 수 있다. 일단 뭉뚱그려서 DB연결과 관련된 관심이 하나.
  • 둘째, 사용자 등록을 위해 DB에 보낼 SQL문장을 담을 Statement를 만들고 실행하는 것. 여기서 관심은 파라미터로 넘어온 사용자 정보를 Statement에 바인딩시키고, Statement에 담긴 SQL을 DB를 통해 실행시키는 방법. 
  • 셋째, 작업이 끝나면 사용한 리소스를 Statement와 Connection 오브젝트를 닫아줘서 공유 리소스를 시스템에 돌려주기.

중복 코드의 메소드 추출

가장 먼저 할 일은 커넥션을 가져오는 중복된 코드를 분리하는 것. 

1
2
3
4
5
6
7
private Connection getConnection() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.jdbc.Driver");
    Connection c= DriverManager.getConnection(
            "jdbc:mysql://localhost/springbook","spring","book");
    )
    return c;
}
cs

 지금은 UserDao 클래스의 메소드가 두 개지만 나중에 메소드가 2000개쯤 된다고 상상해보면 끔찍.

DB연결과 관련된 부분에 변경이 일어났을 경우, ex DB종류와 접속 방법이 바뀌어서 드라이버 클래스와 URL이 바뀌거나 로그인 정보가 변경돼도 앞으로는 getConnection()이라는 한 메소드의 코드만 수정하면 된다. 관심의 종류에 따라 코드를 구분해놓았기 때문에 한 가지의 관심에 대한 변경이 일어날 경우 그 관심이 집중되는 부분의 코드만 수정하면 된다.

-관심이 다른 코드가 있는 메소드에는 영향X.

-관심내용이 독립적으로 존재하므로 수정 간단해짐.

 

DB커넥션 만들기의 독립

UserDao소스코드를 공개하지않고 고객 소스로 원하는 DB커넥션 생성방식을 적용해가면서 UserDao를 사용하게 할수 있을까?

상속을 통한 확장

UserDao코드를 한단계 더 분리하면 된다. 일단 우리가 만든 UserDao에서 메소드의 구현 코드를 제거하고 getConnection()을 추상 메소드로 만들어놓난다. 추상 메소드라서 메소드 코드는 없지만 메소드 자체는 존재한다.따라서 add(),get() 메소드에서 getConnection()을 호출하는 코드는 그대로 유지 가능.

UserDao구입한 포탈사는 UserDao클래스 상속해서 각각 서브클래스를 만든다. 서브 클래스에서는 UserDao에서 추상메소드로 선언했떤 getConnection() 메소드를 원하는 방식대로 구현가능. 

 

템플릿 메소드 패턴: 이렇게 슈퍼클래스에 기본적인 로직의 흐름(커넥션 가져오기,SQL생성,실행,반환)을 만들고 , 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하는 방법

 

팩토리 메소드 패턴: UserDao()의 getConnection()메서드는 Connection 타입 오브젝트를 생성한다는 기능을 정의해놓은 추상 메서드다. 그리고 UserDao의 서브클래스의 getConnection() 메소드는 어떤 커넥션 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이라 볼 수도 있다. 이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 방법을 팩토리 메소드 패턴이라부른다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Super{
    public void templateMethod(){
        //기본 알고리즘 코드
        hookMethod();
        abstractMethod();
        ...
    }
    protected void hookMethod() {} -> 선택적으로 오버라이드 가능한 훅 메서드
    public abstract void abstractMethod(); ->서브클래스에서 반드시 구현해야 하는 추상 메서드
}
 
public Class Sub1 extends Super{ -> 슈퍼클래스의 메소드를 오버라이드하거나 구현해서 기능확장한다.
    protected void hookMethod(){    다양한 확장 클래스를 만들 수 있다.
        ...
    }
    public void abstractMethod(){
        ...
    }    
}
cs

팩토리 메소드 패턴도 템플릿 메소드 패턴과 마찬가지로 상속을 통해 기능을 확장하게 하는 패턴이다. 

단점은 상속을 사용했다는 점. 만약 이미 UserDao가 다른 목적을 위해 상속을 사용하고 있다면 어쩔 것인가? 

자바는 클래스의 다중상속을 허용하지 않는다. 단지 커넥션 객체를 가져오는 방법을 분리하기 위해 상속구조로 만들어보리면, 후에 다른 목적으로 UserDao에 상속을 적용하기 힘들다. 

 또 다른 문제는 상속을 통한 상하위 클래스의 관계는 생각보다 밀접하다는 점이다. 

상속을 통해 관심이 다른 기능을 분리하고, 필요에 따라 다양한 변신이 가능하도록 확장성도 줬지만 여전히 상속관계는 두 가지 다른 관심사에 대해 긴밀한 결합을 허용한다. 서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있다. 그래서 슈퍼클래스 내부의 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 있다.

반대로 그런 변화에 따른 불편을 주지 않기 위해 슈퍼클래스가 더 이샹 변화하지 않도록 제약을 가해야 할지도..

 

 확장된 기능인 DB커넥션 생성 코드를 다른 DAO 클래스에 적용할 수 없다는 것도 큰 단점이다. 만약 UserDao 외의 Dao클래스들이 계속 만들어진다면 그떄는 상속을 통해서 만들어진 getConnection()의 구현 코드가 매 DAO클래스마다 중복되서 문제발생한다.

 

DAO의 확장

모든 오브젝트는 변한다. 관심사에 따라 분리한 오브젝트들은 제각기 독특한 변화의 특징이 있다.

지금까지 데이터 액세스 로직을 어떻게 만들 것인가와 DB연결을 어떤 방법으로 할 것인가라는 두 개의 관심을 상하위 클래스로 분리시켰다. 이 두개의 관심은 변화의 성격이 다르다.

 

 변화의 성격이 다르다는 건 변화의 이유와 시기, 주기 등이 다르다는 뜻이다. 

USERDao는 JDBC API를 사용할 것인가 DB전용 API를 사용할 것인가, 어떤 테이블 이름과 어떤 필드 이름을 사용해 어떤 SQL을 만들 것인가, 어떤 오브젝트를 통해 DB에 저장할 정보를 전달받고, DB에서 꺼내온 정보를 저장해서 넘겨줄 것인가와 같은 관심을 가진 코드를 모아둔 것이다. 따라서 이런 관심사가 바뀌면 그때 변경이 일어난다.

 

하지만 이때도 DB 연결 방법이 그대로면 DB연결 확장 기능을 담은 다른 Dao코드는 변하지않음. 반대로 사용자 정보를 저장하고 가져오는 방법에 대한 관심은 바뀌지 않지만 DB연결 방식이나 DB커넥션을 가져오는 방법이 바뀌면, 그때는 UserDao 코드는 그대로인 채로 다른 상속 Dao의 코드만 바뀐다.

 

 추상 클래스를 만들고 이를 상속한 서브클래스에서 변화가 필요한 부분을 바꿔서 쓸 수 있게 만든 이유는 이렇게 변화의 성격이 다른 것을 분리해서 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서.

그러나 여전히 상속은 불편..

 

클래스의 분리

이번에는 관심가가 다르고 변화의 성격이 다른 이 두 가지 코드를 좀 더 화끈하게 분리해보자. 

두 개의 관심사를 본격적으로 독립시키면서 동시에 손쉽게 확장할 수 있는 방법이다.

 

 DB커넥션과 관련된 부분을 서브클래스가 아니라, 아예 별도의 클래스에 담기. 그리고 이렇게 만든 클래스를 UserDao가 이용하게 하면 된다. 

그림 1-3 처럼 SimpleConnectionMaker라는 새로운 클래스를 만들고 DB생성 기능을 그 안에 넣는다. 그리고 UserDao는 new 키워드를 사용해 SimpleConnectionMaker 클래스의 오브젝트를 만들어두고 이를 add(),get() 메소드 에서 사용하면 된다. 각 메소드에서 매번 SImpleConnectionMaker의 오브젝트를 만들 수 도 있지만, 그보다는 한번만 SimpleConnectionMaker오브젝트를 만들어서 저장해두고 이를 계속 사용하는게 낫다.

 

 문제는 상속말고 다른 방식으로 DB 커넥션을 제공해야한다. UserDao의 소스코드를 함계 제공하지 않고는 DB연결 방법을 바꿀 수 없다는 처음 문제로 돌아와버렸다. 

상속을 이용했을 떄 처럼 자유로운 확장이 가능하게하려면 두가지 문제를 해결해야 한다.

1. SimpleConnecionMaker의 메소드 문제. 만약 타 사에서 makeNewConnection()을 사용해 DB 커넥션을 가지고 오게했는데 이름을 바꿔버리면 UserDao 내에 있는 add(),get() 메소드의 커넥션 코드를 모두 변경해야한다.

 

2. DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고 있어야 한다는 점이다. 타 사에서 다른 클래스를 구현하면 어쩔 수 없이 UserDao 자체를 수정해야한다. 

 

 이런 문제의 근본적인 원인은 UserDao가 바뀔 수 있는 정보, 즉 DB 커넥션을 가져오는 클래스에 대해 너무 많이 알고 있기 때문이다. 어떤 클래스가 쓰일지, 그 클래스에서 커넥션을 가져오는 메소드는 이름이 뭔지까지 알고 있어야 한다.

따라서 UserDao는 DB커넥션을 가져오는 구체적인 방법에 종속되어 버린다. 

지금은 UserDao가 SImpleConnectionMaker라는 특정 클래스와 그 코드에 종속적이기 때문에 앞으로 납품 후에 고객이 DB커넥션을 가져오는 방법을 자유롭게 확장하기 힘들어짐. 

 

인터페이스의 도입

그렇다면 클래스를 분리하면서 이런 문제를 어떻게 해결? 

가장 좋은 해결책은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어 주는 것이다. 추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다.

자바가 추상화를 위해 제공하는 유용한 도구는 인터페이스다. 

인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춘다. 결국 오브젝트를 만드려면

구체적인 클래스 하나를 선택해야겠지만, 인터페이스로 추상화해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 된다. 인터페이스를 통해 접근하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.

인터페이스는 어떤 일을 하겠다는 기능만 정의해 놓은 것이다. 따라서 인터페이스에는 어떻게 하겠다는 구현방법은 나타나 있지 않다. 그것은 인터페이스를 구현한 클래스들이 알아서 결정.

 

UserDao가 인터페이스를 사용하게 한다면 인터페이스의 메소드를 통해 알 수 있는 기능에만 관심을 가지면되지 구현은 관심X.

인터페이스를 도입했지만 여전히 자바에서 인터페이스를 통해 객체를 생성할때 아래와같이 new DConnectionMaker(); 선언을 해줘야하기떄문에 근본적인 문제가 해결되지않는다

 

public UserDao(){ connectionMaker = new DConnectionMaker(); }

 

UserDao의 다른 모든 곳에서는 인터페이스를 이용하게 만들어서 DB커넥션을 제공하는 클래스에 대한 구체적인 정보는 모두 제거가 가능했지만, 초기에 한 번 어떤 클래스의 오브젝트를 사용할지를 결정하는 생성자 코드는 제거되지 않고 남아 있다. 

 

관계설정 책임의 분리

UserDao와 ConnectionMaker라는 두 개의 관심을 인터페이스 쓰면서 분리해도 왜 UserDao가 구체적인 클래스를 알아햐 하는 문제가 발생하는가? 여전히 UserDao에는 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 코드가 남아 있다. 이 때문에 인터페이스를 이용한 분리에도 불구하고 여전히UserDao 변경 없이는 DB커넥션 기능의 확장이 자유롭지 못하다.

그 이유는 UserDao 안에 분리되지 않은, 또 다른 관심사항이 존재하고 있기 때문.

 

 UserDao에는 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 new DConnectionMaker()라는 코드가 있다. 이 코드는 UserDao가 어떤 ConnectionMaker 구현 클래스의 오브젝트를 이용하게 할지를 결정하는 것이다. 간단히 말하자면 UserDao와 UserDao가 사용할 ConnectionMaker의 특정 구현 클래스 사이의 관계를 설정해주는 것에 관한 관심이다.

 

이 관심사를 담은 코드를 UserDao에서 분리하지 않으면 UserDao는 결코 독립적으로 확장 가능한 클래스 될 수 없다. 

 

-> ConnectionMaker 구현 클래스의 오브젝트 간 관계를 맺는 책임을 담당하는 코드를 UserDao의 클라이언트(사용자)에게 넘겨버리면 다음과 같은 생성자로 변경가능.

1
2
3
public UserDao(ConnectionMaker connectionMaker){
        this.connectionMaker = connectionMaker;  
    }
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
관계설정 책임이 추가된 UserDao 클라이언트인 main() 메소드
 
public class UserDaoTest{
 
    public static void main (String [] args) thorws ClassNotFoundException, SQLException{
        ConnectionMaker connectionMaker= new DConnectionMaker(); //UserDao가 사용할 ConnectionMaker구현 클래스 결정하고 오브젝트만듬.
        UserDao dao = new UserDao(connectionMaker); 
        
        // 1.UserDao ㅐㅇ성
        // 2.사용할 ConnectionMaker 타입의 오브젝트 제공.
        // 결국 두 오브젝트 사이의 의존관계 설정 효과
    }
}
cs

 

 

이렇게 해서 UserDao에는 전혀 손대지 않고도 모든 고객이 만족스럽게 DB연결 기능확장해서 사용가능해짐.

 

요약: 상속<<인터페이스를 도입하고 클라이언트의 도움을 얻는 방법 훨씬유연.

 

UserDao의 생성자는 ConnectionMaker인터페이스 타입으로 전달받기 때문에 ConnectionMaker 인터페이스를 구현했다면 어떤 클래스로 만든 오브젝트라도 상관없다. 관심도 없다. 

이제 UserDaoTest는 필요한 오브젝트 생성,관계맺어주는 준비 작업 끝났으니 테스트를 위해

UserDao의 add(),get() 메소드를 사용한다. 

 

원칙과 패턴

 개방 폐쇄 원칙: 

  클래스나 모듈은 확장에 열려 있어야 하고 변경에는 닫혀있어야 한다.

  우리가본 완성 예제는 인터페이스를 통해 제공되 확장포인트는 활짝 개방. 반면 인터페이스를 이용하는 클래스는 자신의 변화가 불필요하게 일어나지 않도록 굳게 폐쇄되어있다.

 

높은 응집도와 낮은 결합도

  • 높은 응집도 : 응집도가 높다는 것은 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 것. 모듈의 일부분에만 변경이 일어나도 된다면, 모듈 전체에서 어떤 부분이 바뀌어야 하는지 파악해야 하고 또 그 변경으로 인해 바뀌지 않는 부분에는 다른 영향을 미치지는 않는지 확인해야하는 이중의 부담이 생긴다.  
  • 낮은 결합도: 책임과 관심사가 다른 오브젝트 또는 모듈과는 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다. 느슨한 연결은 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적이고 알 필요도 없게 만들어주는 것이다. 결합도가 낮아지면 변화에 대응하는 속도가 높아지고, 구성이 깔끔해진다. 또한 확장하기도 매우 편리. 여기서 결합도란 '하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도'라고 설명 가능. 낮은 결합도란 결국 하나의 변경이 발생할 때 마치 파문이 이는 것처럼 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태를 말함. ConnectionMaker 인터페이스의 도입으로 인해 DB연결 기능을 구현한 클래스가 바뀌더라도 DAO의 코드는 변경될 필요가 없게 됐다. 

전략 패턴

자신의 기능 맥락에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴.

 UserDao는 전략 패턴의 컨텍스트에 해당한다. 컨텍스트는 자신의 기능을 수행하는데 필요한 기능 중에서 변경 가능한, DB연결 방식이라는 알고리즘을 ConnectionMaker라는 인터페이스로 정의하고, 이를 구현한 클래스, 즉 전략을 바꿔가면서 사용할 수 있게 분리했다.

 전략패턴의 적용 방법을 보면 클라이언트의 역할이 잘 설명 되어 있다. 컨텍스트(UserDao)를 사용하는 클라이언트(UserDaoTest)는 컨텍스트가 사용할 전략(ConnectionMaker를 구현한 클래스)을 컨텍스트의 생성자 등을 통해 제공해주는 게 일반적이다.

  

 스프링이란 바로 지금까지 설명한 객체지향적 설계 원칙과 디자인 패턴에 나타난 장점을 자연스럽게 개발자들이 활용할 수 있게 해주는 프레임워크다. 

 

제어의 역전(Inversion of Control)

IoC라는 약자로 많이 사용되는 제어의 역전이라는 용어가 있다. 이 ioc가 무엇인지 살펴보기 위해 UserDao코드 개선ㄱ

 

오브젝트 팩토리

UserDaoTEst는 엉겁결에 어떤 구현 클래스 사용할지 결정하는 기능을 엉겁결에 떠맡음. 

UserDao가 ConnectionMaker인터페이스를 구현한 특정 클래스로부터 완벽하게 독립할 수 있도록 UserDao의 클라이언트인 UserDaoTest가 그 수고를 담당하게 된 것이다. 

 UserDaoTest는 UserDao의 기능이 잘 동작하는지 테스트하려고 만든거. 성격이 다른 책임이나 관심사를 분리해야됨!

분리될 기능은 UserDao와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 것과, 그렇게 만들어진 두 개 오브젝트가 연결되서 사용 가능하게 관계맺어주는 것.

 

팩토리

분리시킬 기능을 담당할 클래스를 하나 만들어보겠다. 이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것인데, 이런 일을 하는 오브젝트를 팩토리라고 부른다. 

이는 디자인 패턴에서 말하는 특별한 문제를 해결하기 위해 사용되는 추상팩토리 패턴이나 팩토리 메소드패턴과는 다름.

단지 오브젝트 생성하는 쪽과 생성된 오브젝트 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용하는 것이다. 어떻게 만들지와 어떻게 사용할지는 다른 관심.

 

 팩토리 역할을 맡을 클래스는 DaoFactory 그리고 UserDaoTest에 담겨 있던 UserDao,ConnectionMaker 관련 생성 작업을 DaoFactory로 옮기고 , UserDaoTest에서는 DaoFactory에 요청해서 미리 만들어진 UserDao 오브젝트를 가져와 사용하게 만듬.

 

1
2
3
4
5
6
7
8
9
public class DaoFactory{
  public UserDao userDao(){
    //팩토리메소는 UserDao 오브젝트를 어떻게 만들고 어떻게 준비시킬지 결정한다.
    ConnectionMaker connectionMaker = new DConnectionMaker();
    UserDao userDao = new UserDao(connectionMaker);
 
    return userDao;
  }
}
cs

UserDaoTest는 이제 UserDao가 어떻게 만들어지는지 어떻게 초기화되어 있는지에 신경 쓰지 않고 팩토리로부터 UserDao 오브젝트를 받아다가, 자신의 관심사인 테스트를 위해 활용하기만 하면 된다.

 

1
2
3
4
5
6
public class UserDaoTest{
  public static void main(String[] args) throws ClassNotFoundException,SQLException{
    UserDao dao = new DaoFactory.userDao();
    ...
  }
}
cs

 

설계도로서의 팩토리

이제 타 사에 UserDao를 공급할 때 UserDao,ConnectionMaker와 함께 DaoFactory도 제공한다. UserDao와 달리 DaoFactory는 소스를 제공한다. 새로운 ConnectionMaker 구현 클래스로 변경이 필요하면 DaoFactory를 수정해서 변경된 클래스를 생성해 설정해주도록 코드를 수정해주면 된다. 

 DaoFactory를 분리했을 때 얻을 수 있는 장점은 매우 다양. 앱의 컴포넌트 역할을 하는 오브젝트와 app의 구조를 결정하는 오브젝트를 분리했다는 데 가장 의미가 있다.

 

오브젝트 팩토리의 활용

DaoFactory에 UserDao가 아닌 다른 DAO의 생성 기능을 넣으면?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DaoFactory{
 
 
  public UserDao userDao(){
    return new UserDao(new DConnectionMaker();)
  }
 
  public AccountDao userDao(){
    return new UserDao(new DConnectionMaker();)
  }
 
  ...
 
}
cs

 

이렇게 오브젝트 생성 코드가 중복되면 ConnectionMaker의 구현클래스를 바꿀때마다 모든 메소드를 일일이 수정해야하므로 나쁜 현상.

 

 중복 문제를 해결하려면 역시 분리해내는 게 가장 좋은 방법이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DaoFactory{
 
 
  public UserDao userDao(){
    return new UserDao(new DConnectionMaker();)
  }
 
  public AccountDao userDao(){
    return new UserDao(new DConnectionMaker();)
  }
 
  ...
 
  public ConnectionMaker connectionMaker(){
    return new DConnectionMaker();
  }
 
}
cs

 

제어권의 이전을 통한 제어관계 역전

제어의 역전: 프로그램의 제어 흐름 구조가 뒤바뀌는 것.

 일반적으로 프로그램 흐름은 main() 메소드와 같이 프로그램이 시작되는 지점에 다음에 사용할 오브젝트를 결정하고, 결정한 오브젝트를 생성하고, 만들어진 오브젝트에 있는 메소드를 호출하고, 그 오브젝트 메소드 안에서 다음에 사용할 것을 결정하고 호출하는 식의 작업이 반복. 

 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택X.

당연히 생성하지도 않음. 또 자신도 어떻게 만들어지고 어디서 사용되는지 모름. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문. 프로그램을 시작 담당하는 main()과 같은 엔트리 포인트를 제외하면 모든 오브젝트는 이렇게 위임받은 제어 권한을 갖는 특별한 오브젝트에 위해 결정되고 만들어진다.

 

서블릿이란?https://mangkyu.tistory.com/14

 

 서블릿. 일반적인 자바프로그램은 main()메소드에서 시작해 개발자가 미리 정한 순서를 따라 오브젝트가 생성되고 실행된다. 그런데 서블릿을 개발해서 서버에 배포할 수는 있지만, 그 실행을 개발자가 직접 제어할 수 있는 방법은 없다. 서블릿안에 main()메소드가 있어 직접 실행할수 있는것도아님. 대신 서블릿에 대한 제어 권한을 가진 컨테이너가 적절한 시점에 서블릿 클래스의 오브젝트를 만들고 그 안의 메소드를 호출한다.

이렇게 서블릿이나 JSP,EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용 되어 있다.

 

제어의 역전 개념이 적용된 예를 디자인 패턴에서도 여럿 찾아볼 수 있다. 

 초난감 초기 추상 구현 시, 추상UserDao를 상속한 서브클래스는 getConnection()을 구현. 하지만 이 메소드가 언제 어떻게 사용될지 자신은 모른다. 서브클래스에서 결정되는 것이 아니다. 단지 이런 방식으로 DB커넥션을 만든다는 기능만 구현해놓으면, 슈퍼클래스인 UserDao의 템플릿 메소드인 add(),get() 등에서 필요할 때 호출되어 사용한다. 즉 제어권을 상위 템플릿메소드에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다는, 제어의 역전 개념을 발견 가능. 템플릿 메소드는 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴.

 

 프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 프레임워크는 라이브러리의 다른 이름이 아니다. 프레임워크는 단지 미리 만들어둔 반제품이나, 확장해서 사용할 수 있도록 준비된 추상 라이브러리 집합이 아니다. 프레임워크가 어떤 것인지 이해하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다. 라이브러리를 사용하는 앱코드는 앱 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다. 반면 프레임워크는 거꾸로 앱 코드가 프레임에 의해 사용된다.

보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 앱 코드를 사용하도록 만드는 방식이다.  프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다. 앱 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 한다.

 

 우리가 만든 코드도 제어의 역전이 적용되어 있다. 원래 ConnectionMaker의 구현 클래스르 ㄹ결정하고 오브젝트를 만드는 제어권은 UserDao에게 있었다. 그런데 지금은 DaoFactory에게 있다. 자신이 어떤 ConnectionMaker 구현 클래스를 만들고 사용할지를 결정할 권한을 DaoFactory에 넘겼으니 UserDao는 이제 능동적이 아니라 수동적인 존재가 됨. UserDao 자신도 팩토리에 의해 수동적으로 만들어지고 사용할 오브젝트도 DaoFactory가 공급해주는 것을 수동적으로 사용함.  UserDaoTest는 DaoFactory가 만들고 초기화해서 자신에게 사용하도록 공급해주는 ConnectionMaker를 사용할 수 밖에 없다. 더욱이 UserDao와 COnnectionMaker의 구현체를 생성하는 책임도 DaoFactory가 맡고있따. 이게 바로 제어의 역전이 일어난 상황.

 

 IoC를 적용함으로써 설계가 깔끔해지고 유연성이 증가하며 확장성이 좋아지기 때문에 필요할때면 IoC스타일설계와 코드를 만들어 사용하면 된다. 

 

 IoC에서는 프레임워크 또는 컨테이너와 같이 앱 컴포넌트의 생성과 관계설정, 사용 ,생명주기 관리 등을 관장하는 존재가 필요하다.

DaoFactory는 오브젝트 수준의 가장 단순한 IoC컨테이너 내지는 IoC프레임워크로 볼수 있다.

 

스프링의 IoC

 오브젝트 팩토리를 이용한 스프링 IoC

애플리케이션 컨텍스트와 설정정보

빈 :스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트

자바빈 또는 엔터프라이즈 자바빈에서 말하는 빈과 비슷한 오브젝트 단위의 앱 컴포넌트를 말한다. 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다.

 

 스프링에서 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리라고 부른다. 보통 빈 팩토리보다는 이를 좀더 확장한 애플리케이션 컨텍스를 주로 사용한다. 애플리케이션 컨텍스트는 IoC방식을 따라 만들어진 일종의 빈 팩토리이다.  앞으로 두용어를 함께사용할텐데 동일하다고 생각해라.

빈팩토리라 말할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점.

애플리케이션 컨텍스트라고 말할 때는 앱 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC엔진이라는 의미가 좀 더 부각된다고 보면 된다.

 

ac==애플리케이션 컨텍스트 bf==빈팩토리

 ac는 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄한다. 기존 DaoFactory코드에는 설정정보, 예를 들어 어떤 클래스의 오브젝트를 생성하고 어디에서 사용하도록 연결해줄 것인가 등에 관한 정보가 자바코드로 만들어져 있다. ac는 직접 이런 정보를 담고 있진 않다. 대신 별도로 설정정보를 담고 있는 무엇인가를 가져와 이를 활용하는 범용적인 IoC 엔진 같은것이다.

 

 bf또는 ac가 사용하는 설정정보를 만드는 방법은 여러가지가 있는데, 우리가 앞에서 만든 오브젝트 팩토리도 조금만 손을 보면 설정정보로 사용가능. 

 

DaoFactory를 사용하는 ac

스프링의 빈 팩토리가 사용할 수 있는 설정 정보로 만들어보자.

먼저 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식할 수 있도록 @Configuration을 붙임. 그리고 오브젝트를 만들어주는 메소드에는 @Bean을 붙인다. 

 이 두가지 어노테이션으로 스프링 프레임워크 빈 팩토리 또는 ac가 IoC방식 기능 제공할 때 사용할 완벽한 설정정보가 되었다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class DaoFactory{
 
  @Bean
  public UserDao userDao(){
    return new UserDao(connectionMaker());
  }
 
  @Bean
  public ConnectionMaker connectionMaker(){
    return new DConnectionMaker();
  }
}
cs

 이제 DaoFactory를 설정정보로 사용하는 ac를 만들어보자. 

1
2
3
4
5
6
7
8
public class UserDaoTest{
  public static void main(String[] args) throws ClassNotFoundException,SQLException{
    ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
    UserDao dao = context.getBean("userDao",UserDao.class);
    ...
  }
 
}
cs

 getBeab() 메소드는 ac가 관리하는 오브젝트를 요청하는 메소드다. 파라미터인 userDao는 Ac에 등록된 빈의 이름이다. DaoFactory에서 @Bean이라는 어노테이션을 userDao라는 이름의 메소드에 붙였는데 이 메소드 이름이 바로 빈의 이름이 된다. userDao라는 이름의 빈을 가져온다는 것은 DaoFactory의 userDao() 메서드를 호출해서 그 결과를 가져온다고 생각하면 된다. 

 

애플리케이션 컨텍스트의 동작방식

기존 오브젝트 팩토리를 이용했떤 방식과 스프링 ac를 사용한 방식을 비교해보자. 

 오브젝트 팩토리에 대응되는 것이 스프링의 ac다. 스프렝이서는 이 ac를 IoC컨테이너라 하기도 하고 간단히 스프링 컨테이너라고 부르기도 한다. 또는 빈팩토리. ac는 ApplicationContext 인터페이스를 구현하는데 , AC는 빈팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로 ac는 일종의 빈 팩토리인 셈이다. ac가 스프링의 가장 대표적인 오브젝트이므로, ac를 그냥 스프링이라 부르는 개발자도 있음.

 

 DaoFactory가 UserDao를 비롯한 DAO 오브젝트를 생성하고 DB생성 오브젝트와 관계를 맺어주는 제한적인 역할을 하는데 반해, ac는 앱에서 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정을 담당한다. 

대신 AC에는 DaoFactory와 달리 직접 오브젝트를 생서하고 관계 맺는 코드가 없고, 그런 생성정보와 연관관계 정보를 별도의 설정정보를 통해 얻는다. 때로는 외부의 오브젝트 팩토리에 그 작업을 위임하고 그 결과를 가져다가 사용하기도 함.

 

 @Configuration이 붙은 DaoFactory는 이 ac가 활용하는 IoC설정정보다. 내부적으로는 ac가 DaoFactory의 userDao() 메소드를 호출해서 오브젝트를 가져온 것을 클라이언트가 getBean()으로 요청할 때 전달해준다.

 

ac는 DaoFactory 클래스를 설정정보로 등록해두고 @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만들어 둔다.

클라이언트가 ac의 getBean() 메서드를 호출하면 자신의 빈 목록에서 요청한 이름이 있는지 찾고, 있다면 빈을 생성하는 메서드를 호출해서 오브젝트를 생성시킨 후 클라이언트에 돌려준다. 

 

 DaoFactory와 같은 오브젝트 팩토리에서 사용했던 IoC 원리를 그대로 적용하는데 ac를 사용하는 이유는 범용적이고 유연한 방법으로 IoC기능을 확장하기 위해서이다. 

 

ac를 사용했을떄 얻을 수 있는 장점은 다음과 같다.

 

  •  클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.

 앱이 발전하면 DaoFactory처럼 IoC를 적용한 오브젝트도 계속 추가된다. 클라이언트가 필요한 오브젝트를 가져오려면 어떤 팩토리 클래스를 사용해야 할 지 알아야 하고, 필요할 때마다 팩토리 오브젝트를 생성해야 하는 번거로움이 있다. ac를 사용하면 오브젝트 팩토리가 많아져도 이를 알아야 하거나 직접 사용할 필요X. ac를 이용하면 일관된 방식으로 원하는 오브젝트를 가져올 수 있다. 또 DaoFactory처럼 자바 코드를 작성하는 대신 XMl처럼 단순한 방법을 사용해 ac가 사용할 IoC설정 정보를 만들 수 있다.

  • 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.

ac의 역할은 단지 오브젝트 생성과 다른 오브젝트 관계설정만이 전부가 아님. 오브젝트가 만들어지는 방식, 시점과 전략을 다르게 가져갈 수도 있고, 이에 부가적으로 자동생성, 오브젝트에 대한 후처리, 정보조합, 설정방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 활용할 수 있는 다양한 기능을 제공. 또, 빈이 사용할 수 있는 기반기술 서비스나 외부 시스템과의 연동 등을 컨테이너 차원에서 제공.

  • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

ac의 getBean() 메서드는 빈의 이름을 이용해 빈을 찾아준다. 타입만으로도 빈을 검색하거나 특별한 어노테이션 설정되어있는 빈을 찾을 수도 있다.

 

용어정리

 

빈 또는 빈 오브젝트는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다. 관리되는 오브젝트라고 부른다. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어진 모든 오브젝트가 빈은 아니라는 사실. 그중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.

 

빈 팩토리

스프링의 IoC를 담당하는 핵심 컨테이너. 빈을 등록,생성,조회하고 돌려주고 그 외 부가적인 빈을 관리하는 기능담당.

보통은 이 빈 팩토리를 바로 사용하지 않고 이를 확장한 ac를 이용. BeanFactory라고 붙여쓰면 빈 팩토리가 구현하고 있는 가장 기본적인 인터페이스의 이름이 된다. 이 인터페이스에 getBean()과 같은 메서드가 정의 되어있다.

 

애플리케이션 컨텍스트

빈 팩토리를 확장한 IoC컨테이너다. 빈을 등록하고 관리하는 기본적인 기능은 동일. 여기에 스프링이 제공하는 각종 부가 서비스를 추가로 제공. 빈 팩토리라고 부를 때는 주로 빈의 생성과 제어 관점에서 이야기하는 것이고, 애플리케이션 컨텍스트라고 할 때는 스프링이 제공하는 애플리케이션 지원 기능을 모두 포함해서 이야기하는 것이라고 보면 됨.

스프링에서는 ac라는 용어를 bf보다 많이사용. ApllicationContext라고 적으면 ac가 구현해야하는 기본인터페이스를 가리키는 것이기도 함. AC는 BeanFactory를 상속한다. 

 

설정정보/ 설정 메타정보(configuration metadata)

스프링의 설정정보란 ac또는 bf가 IoC를 적용하기 위해 사용하는 메타정보를 말한다. 영어로 configuration이라 하는데 이는 구성정보 내지 형상정보라는 의미다. 실제로 스프링의 설정정보는 컨테이너에 어떤 기능을 세팅하거나 조정하는 경우에도 사용하지만, 그보다는 IoC컨테이너에 의해 관리되는 앱 오브젝트를 사용하고 구성할 떄 사용된다. 앱의 형상정보라 부르기도 함. 또는 앱의 전체 그림이 그려진 청사진이라고도 함.

 

컨테이너 또는 IoC 컨테이너

IoC방식으로 빈을 관리한다는 의미에서 ac나 bf를 컨테이너 또는 IoC컨테이너라고도 한다. 후자는 bf관점, 전자는 ac관점. 컨테이너라는 말 자체가 IoC개념을 담고 있어서 이름이 긴 ac대신에 스프링 컨테이너라고 부르는 걸 선호하는 사람들도 있다. 

 

스프링 프레임워크

스프링 프레임워크는 IoC 컨테이너, ac를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할때 주로사용. 줄여서 스프링.

 

싱글톤 레지스트리와 오브젝트 스코프

DaoFactory를 직접사용하는 것과 @Configuration 어노테이션 추가해서 스프링 ac통해 사용하는 것은 테스트 결과만 보자면 동일한것 같다. 그저 ac에 userDao라는 이름의 빈을 요청하면 DaoFactory의 userDao() 메서드를 호출해서 그 결과 돌려주는 것이라 생각할수도있다. 하지만 스프링의 ac는 기존에 직접 만들었던 of와 중요한 차이점이있따.

 

 먼저 DaoFactory의 userDao()메서드를 두번 호출해서 리턴되는 UserDao 오브젝트를 비교해보자. 이 두개는 같은 오브젝트일까? 

 알아보고 싶은 것은 DaoFactory의 userDao()를 여러 번 호출했을 때 동일한 오브젝트가 돌아오는가 아닌가이다. 

코드를 보면 매번 userDao 메서드를 호출할 때마다 new연산자에 의해 새로운 오브젝트가 만들어지게 되어있다.

당연히 매번 다른 오브젝트가 만들어져서 돌아올 것이라고 예상할 수 있다. 

 

그러나 getBean()메서드를 이용해 userDao라는 이름으로 등록된 오브젝트를 가져오면 오브젝트가 동일하다는 사실을 알 수 있다. 동일성은 == , 동등성은 equals.

 

 스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다. 단순하게 getBean()을 실행할 때마다 userDao()메서드를 호출하고, 매번 new에 의해 새로운 UserDao가 만들어지지 않는다는 뜻.

 

싱글톤 레지스트리로서의 애플리케이션 컨텍스트

ac는 우리가 만든 of와 비슷한 방식으로 동작하는 IoC 컨테이너다. 그러면서 동시에 이 ac는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리다. 

 스프링은 기본적으로 별다른 설정하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다. 디자인패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 구현방법은 다름.

 

서버 애플리케이션과 싱글톤

왜 스프링은 싱글톤으로 빈을만들까? 이는 스프링이 주로 적용되는 대상이 자바엔터프라이즈 기술을 사용하는 서버환경이기 떄문. 태생적으로 스프링은 엔터프라이즈 시스템을 위해 고안된 기술이기 떄문에 서버환경에서 사용될때 그 가치가 있다. 실제로 스프링은 대부분 서버환경에서 사용됨.

 

 스프링이 처음 설계됐던 대규모 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백 번씩 브라우저나 여타 시스템으로부터 요청을 받아 처리할 수있는 높은 성능이 요구되는 환경이었다. 또 하나의 요청을 처리하기 위해 데이터 액세스 로직, 서비스로직, 비즈니스 로직, 프레젠테이션 로직 등의 다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이뤄진 경우가 대부분이다. 비즈니스 로직도 복잡한 경우가 많다. 

 

 

 

 

+ Recent posts