U E D R , A S I H C RSS

토비의스프링3/오브젝트와의존관계

1. 초난감 DAO

  • 사용자 정보를 JDBC API를 이용해 DB에 저장하고 조회할 수 있는 간단한 DAO 만들기.
  • DAO(Data Access Object)
    • DB를 사용하여 데이터를 조회, 조작하는 기능을 전담하도록 만든 오브젝트.

1.1. User

  • 사용자 정보를 저장할 때 자바빈 규약을 따르는 오브젝트를 이용하면 편리하다.
    • 자바빈(JavaBean)
      • 간단히 빈이라고도 한다.
      • 원래 의미는 비주얼 툴에서 조작 가능한 컴포넌트.
      • 요즘은
        • 파라미터가 없는 디폴트 생성자를 가지고 있고
        • getter와 setter를 통해 조회, 수정할 수 있는 프로퍼티를 가진 오브젝트를 의미한다.
  • User : 사용자 정보 저장용 자바빈 클래스

package springbook.user.domain;

public class User {
    String id;
    String name;
    String password;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
  • USER 테이블
필드명 타입 설정
UserId VARCHAR(10) Primary Key
Name VARCHAR(20) Not Null
Password VARCHAR(20) Not Null

1.2. UserDao

  • UserDao : JDBC를 이용한 등록, 조회 기능이 있는 DAO 클래스

package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import springbook.user.domain.User;

public class UserDao {
    public void add(User user) throws SQLException, ClassNotFoundException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");

        PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
		
        ps.executeUpdate();
		
        ps.close();
        c.close();		
    }
    public User get(String id) throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");

        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, id);
		
        ResultSet rs = ps.executeQuery();
        rs.next();
		
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));
		
        rs.close();
        ps.close();
        c.close();
		
        return user;
    }
}

1.3. main()을 이용한 테스트

  • 앞서 만든 클래스가 제대로 동작하는지 확인하려면
    • DAO의 기능을 사용하는 웹 어플리케이션을 만들어 서버에 배치한 뒤 웹 브라우저를 통해 사용해보거나
      • UserDao 코드가 동작하는지 확인하기 위해 너무 복잡한 작업이 필요.
    • 오브젝트가 스스로 자신을 검증하도록 만들어준다.
  • main 메소드를 이용한 테스트 코드

public static void main(String[] args) throws SQLException, ClassNotFoundException {
    UserDao dao = new UserDao();

    User user = new User();		
    user.setId("zeropage");
    user.setName("제로페이지");
    user.setPassword("zp");
		
    dao.add(user);
		
    System.out.println(user.getId() + "등록 성공");
		
    User user2 = dao.get(user.getId());		
    System.out.println(user2.getName());
    System.out.println(user2.getPassword());
		
    System.out.println(user2.getId() + "조회 성공");
}
  • 테스트가 성공하면 다음과 같이 출력된다.

zeropage 등록 성공
제로페이지
zp
zeropage 조회 성공

1.4. 초난감 DAO의 문제점?

  • 초난감 DAO?
    • UserDao 클래스는
      • main() 메소드에 작성한 테스트로 확인해보니
      • 기능이 정상적으로 작동한다
    • 그럼에도 불구하고 UserDao 클래스 코드에는 여러가지 문제가 있다.
DeleteMe) 이 부분 아직 덜 씀. - 김수경

2. DAO의 분리

2.1. 관심사의 분리

  • 어플리케이션이 폐기처분될 때까지 오브젝트 설계와 코드는 끊임없이 변한다. 그렇다면 변화에 어떻게 대비할 것인가?
  • 가장 좋은 대책 : 변화의 폭을 최소한으로 줄인다.
    • 동일한 기능 변경을 요청했을때
      • 단 몇 줄의 코드만 수정하고 수정한 뒤에도 문제 없이 작동함을 보여주는데 5분이 걸리는 개발자 > 코드를 수정하는데 5시간이 걸리고 수정한 뒤 문제 없이 작동하는지 확신할 수 없는 개발자
    • 어떻게?
      • 분리와 확장을 고려한 설계
  • 모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다.
    • 관심사의 분리
      • 한 가지 관심이 한 군데에 집중되게 하라.
      • 관심이 같은 것끼리는 하나의 객체 안으로, 관심이 다른 것은 가능한 떨어져 영향을 주지 않도록 분리.

2.2. 커넥션 만들기의 추출

2.2.1. UserDao의 관심사항

  1. DB 연결을 위한 커넥션을 어떻게 가져올 것인가.
  2. 사용자 등록/조회를 위한 SQL문을 담을 Statement를 만들고 실행하는 것.
  3. 작업이 끝나고 리소스를 close하는 것.

  4. 가장 큰 문제 : DB 연결을 위한 커넥션 오브젝트 가져오기
    • add() 메소드와 get() 메소드에 동일한 코드가 중복되어 있다.
      • 앞으로 수백개의 DAO 메소드를 만든다고 하면
        • DB 커넥션을 가져오는 코드가 수백군데에 중복으로 존재하게 되어
        • DB 커넥션을 가져오는 방식이 바뀌면 수백군데를 하나하나 수정해야한다.

2.2.2. 중복 코드의 메소드 추출

  • 커넥션을 가져오는 중복된 코드를 분리한다.
  • UserDao : getConnection() 메소드를 추출하여 중복을 제거

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

public void get(String id) throws SQLException, ClassNotFoundException {
    Connection c = getConnection();
    …
}
    
private Connection getConnection() throws SQLException, ClassNotFoundException {
    Class.forName("com.mysql.jdbc.Driver");
    Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
}
  • 이제 수백개의 DAO 메소드가 있어도 DB 커넥션을 가져오는 방식이 바뀌면 getConnection() 메소드만 수정해주면 된다.

2.2.3. 변경사항에 대한 검증 : 리팩토링과 테스트

  • 리팩토링
    • 코드 내부 구조를 변경하여 재구성하는 작업
    • 외부 동작 방식은 변화하지 않음.
    • 장점
      • 내부 설계가 개선되어 코드를 이해하기 편해지고 변화에 효율적으로 대응할 수 있다.
    • 리팩토링(마틴 파울러, 켄트 벡 공저)
DeleteMe) 추후 내용 보강 예정 - 김수경

2.3. DB 커넥션 만들기의 독립

3. DAO의 확장

  • 관심사에 따라 오브젝트 나누기
    • 관심사가 다르다
    • 변화의 성격이 다르다
      • 변화의 성격이 다르다는 것은 변화의 이유와 시기, 주기가 다르다는 것을 의미함
    • 추상클래스를 만들어놓고 상속을 통해 변화를 구현하는 방법 -> 불편하다
  • 1.3.1 클래스의 분리
    • 서로 다른 관심사를 독립적인 클래스로 분리해보자
      • 기존의 코드를 수정할 때는 기능의 변화가 아닌 내부 설계를 변경해 더 나은 코드를 만드는 것 이다(리팩토링)
      • 리팩토링 후 기능의 변화가 없는지 검증해야 한다. 테스트 코드가 이를 검증해 준다.
    • 분리된 두 클래스는 의존관계를 가진다.
  • 1.3.2 인터페이스의 도입
    • 클래스 분리에서 가장 중요한 점은 두 클래스가 낮은 결합도를 가져야 한다는 것이다.
    • 추상화
      • 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업
      • interface 이용
      • 상속보다 유연한 관계 설정 가능
  • 1.3.3 관계설정 책임의 분리
    • 코드의 크기가 작고 간단해도 클래스와는 다른 독립적인 관심사를 가지고 있다면 분리해야한다.
      • 클래스의 확장성을 해칠 수 있다.
    • 클래스간의 관계설정
      • 오브젝트와 오브젝트간의 관계설정을 의미한다.
      • 런타임 사용관계 : 오브젝트 사이의 관계란 런타임시에 한 오브젝트가 다른 오브젝트의 레퍼런스를 가지고있는 것.
      • 방법
        • 사용할 오브젝트를 내부에서 생성
        • 인터페이스로 구현된 파라메터를 이용해 외부에서 전달받는다. (다형성)
  • 1.3.4 윈칙과 패턴
    • 개방 폐쇄 원칙(OCP) 객체지향 설계원칙(SOLID)
      • 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다
    • 높은 응집도와 낮은 결합도
      • 개방 폐쇄 원칙의 원리
      • 높은 응집도
        • 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 것을 의미한다.
      • 낮은 결합도
        • 높은 응집도보다 민감한 사항
        • 결합도란 하나의 오브젝트가 의존관계에 있는 다른 오브젝트에게 변화를 요구하는 정도를 의미한다.
        • 변화가 다른 모듈이나 오브젝트에게 전파되지 않는 것이 좋다.
    • 전략 패턴
      • 디자인 패턴의 일부
      • 변경이 필요한 알고리즘을 인테페이스를 통해 외부로 분리시키고, 이를 구체화한 클래스를 필요에 따라 바꿔 사용하게 하는 패턴
  • 결국.. 스프링이란?
    • 객체지향적 설계 원칙과 디자인 패턴에 나타난 장점을 자연스럽게 개발자들이 활용할 수 있게 해주는 프레임워크

4. 제어의 역전(IoC)

  • 1.4.1 오브젝트 팩토리
    • 팩토리
      • 객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려준다.
      • 관심사의 분리 - 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽을 분리하는 역할을 한다.
      • DaoFactory 장점 : 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 구조를 결정하는 오브젝트를 분리.
  • 1.4.2 오브젝트 팩토리의 활용
    • 오브젝트 생성 코드 반복을 분리한다

public class DaoFactory{
	//  ConnectionMaker를 설정하고 생성하는 코드가 반복됨
	public UserDao userDao( ){
		return new UserDao(new DConnectionMaker());
	}

	public AccountDao accountDao( ){
		return new AccountDao(new DConnectionMaker());
	}

	public MessageDao messageDao( ){
		return new MessageDao(new DConnectionMaker());
	}
}
분리 전
public class DaoFactory{
	public UserDao userDao( ){
		return new UserDao(connectionMaker());
	}

	public AccountDao accountDao( ){
		return new AccountDao(connectionMaker());
	}

	public MessageDao messageDao( ){
		return new MessageDao(connectionMaker());
	}

	// 중복 코드 분리
	public ConnectionMaker connectionMaker(){
		return new DConnectionMaker();
	}
}
분리 후

  • 1.4.3 제어권의 이전을 통한 제어관계 역전
    • 제어의 역전이란?
      • 프로그램의 제어 흐름 구조가 뒤바뀌는 것
      • 제어 권한을 다른 대상에게 위임한다.
      • 프레임워크는 제어의 역전이 적용된 대표적인 기술이다.
        • 라이브러리는 애플리케이션이 능동적으로 사용한다.
        • 반면 프레임워크는 애플리케이션 코드가 프레임워크에 사용된다.

  • 스프링은 IoC를 극한까지 적용한 프레임워크다.
    • 프레임워크는 분명한 제어의 역전 개념이 적용되어 있어야 한다.

5. 스프링의 IoC

5.1 오브젝트 팩토리를 이용한 스프링 IoC
  • 5.1.1
    • 빈(bean) : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트. 자바빈에서 말하는 빈과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트. 스프링이 직접 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.
    • 빈 팩토리(bean factory) : 빈의 생성과 관계설정 등의 제어를 담당하는 IoC오브젝트. 스프링의 IoC를 담당하는 핵심 컨테이너. 일반적으로 직접 사용하지 않고 이를 확장한 애플리케이션 컨텍스트를 사용한다.
    • 애플리케이션 컨텍스트(application context) : IoC방식을 따라 만들어진 일종의 빈팩토리. 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄한다. 설정 정보를 따로 받아와서 이를 활용하는 IoC엔진이라고 볼 수 있다. 주로 설정에는 xml을 사용한다.
    • 설정정보 : 애플리케이션 컨텍스트나 빈 팩토리가 IoC를 적용하기 위해 사용하는 정보. 주로 IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용된다.
    • 컨테이너/IoC컨테이너 : IoC방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너/IoC컨테이너라고도 한다.
  • 5.1.2
    • 빈 팩토리 또는 애플리케이션 컨텍스트의 설정정보를 만드는 방법 ->
      • 1. 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식할 수 있도록 @Configuration이라는 애노테이션을 추가한다.

@Configuration
public class DaoFactory{}
  • 2. 오브젝트를 만들어주는 메소드에는 @Bean이라는 애노테이션을 붙여준다.

@Configuration
public class DaoFactory{
@Bean
public UserDao userDao(){ ...}
}
  • 만들어진 설정정보를 사용하는 애플리케이션 컨텍스트의 생성 및 빈 생성 ->
    • 1. 애플리케이션 컨텍스트는 ApplicationContext타입의 오브젝트다. 사용시 @Configuration이 붙은 자바코드를 설정정보로 사용하려면 AnnotationConfigApplicationContext에 생성자 파라미터로 @Configuration이 붙은 클래스를 넣어준다.

public static void main(Strings[] args) throws ClassNotFoundException, SQLException{
ApplicatioContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
}
  • 2. 준비된 ApplicationContext의 getBean()메소드를 이용해 등록된 빈의 오브젝트를 가져올 수 있다.

public static void main(Strings[] args) throws ClassNotFoundException, SQLException{
ApplicatioContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
}
getBean()메소드 : ApplicationContext가 관리하는 오브젝트를 요청하는 메소드. ""안에 들어가는 것은 ApplicationContext에 등록된 빈의 이름. 빈을 가져온다는 것은 메소드를 호출해서 결과를 가져온다고 생각하면 된다. 위에서는 userDao()라는 메소드에 붙였기 때문에 ""안에 userDao가 들어갔다. 메소드의 이름이 myUserDao()라면 "myUserDao"가 된다. 기본적으로 Object타입으로 리턴하게 되어있어서 다시 캐스팅을 해줘야 하지만 자바 5 이상의 제네릭 메소드 방식을 사용해 두 번째 파라미터에 리턴 타입을 주면 캐스팅을 하지 않아도 된다.
5.2 애플리케이션 컨텍스트의 동작방식
  • 5.2.1
    • @Configuration이 붙은 클래스는 애플리케이션 컨텍스트가 활용하는 IoC 설정정보가 된다. 내부적으로는 애플리케이션 컨텍스트가 @Configuration클래스의 @Bean메소드를 호출해서 오브젝트를 가져온 것을 클라이언트가 getBean() 메소드로 요청할 때 전달해준다.
  • 5.2.2
    • 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점
      1. 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
      2. 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
      3. 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

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

6.1 싱글톤 레지스트리로서의 애플리케이션 컨텍스트
  • 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다. 스프링은 기본적으로 별다른 설정이 없으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.
  • 스프링은 주로 서버환경에 적용되기 때문에 서버에 걸리는 부하를 줄이기 위해 빈을 싱글톤으로 만들게 되어있다. 스프링 컨테이너는 싱글톤을 생성, 관리하는 싱글톤 관리 컨테이너이기도 하다.
  • 일반적인 싱글톤 패턴의 구현의 한계
    • 1. private 생성자를 가지고 있기 때문에 상속할 수 없다.
    • 2. 싱글톤은 테스트하기가 힘들다.
    • 3. 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 4. 싱글톤의 사용은 전역상태를 만들 수 있기 때문에 바람직하지 못하다.
6.2 싱글톤과 오브젝트의 상태
  • 싱글톤 오브젝트의 인스턴스 변수를 수정 시 서로 값을 덮어쓰고 읽어올 수 있기 때문에 위험하다. 읽기전용의 속성을 가진 정보라면 인스턴스 변수로 사용해도 좋다.
6.3 스프링 빈의 스코프
  • 스코프(scope) : 스프링이 관리하는 빈이 생성되고 존재하고 적용되는 범위. 빈의 기본 스코프는 싱글톤으로 컨테이너 내에 한 개의 오브젝트만 만들어져서 강제로 제거하지 않는 한 계속 유지된다. 경우에 따라서 싱글톤 외의 프로토타입(prototype), 요청(request), 세션(session)스코프 등을 가질 수 있다.

7. 의존관계 주입(DI)

  • 의존관계 : 두 클래스 또는 모듈이 있을 때 한 쪽의 변화가 다른 쪽에 영향을 미치는 상황.
  • 의존관계 주입(DI)? : 의존관계 주입(Dependency Injection)이란 스프링에 사용된 제어의 역전(IoC) 방식을 조금 더 명확하게 나타내기 위해서 사용한 용어이다.
    • 의존관계 주입의 세 가지 조건
      1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
      2. 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
      3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.
  • DI의 장점 : DI를 받았을 경우 주입된 오브젝트를 인터페이스로 받는데 이렇게 하면 코드에 런타임 클래스와의 관계가 직접 드러나지 않기 때문에 주입시 주입하는 오브젝트를 바꿔주는 것으로 코드의 변경, 확장에 쉽게 대응할 수 있다.
  • 스프링에서의 DI
    • Bean : 스프링에서는 DI를 쉽게 하기 위해서 Bean을 이용하여 오브젝트를 관리한다.
    • Bean Factory : 런타임 시점에서 의존관계를 결정하기 위해 Bean Factory에서 Bean을 관리하고 오브젝트간의 관계를 맺어준다.
    • 의존관계 검색(Dependency Lookup) : 스프링의 DI방식을 이용하기 위해서는 DI를 받는 오브젝트가 반드시 Bean이어야 한다. 하지만 DL을 이용하면 Bean이 아닌 오브젝트에서도 의존관계를 설정할 수 있다.

8. XML을 이용한 설정

  • XML 설정
  • 스프링에서는 DI의존관계를 만들 때 직접적으로 코드를 수정하는 것 외에도 다양한 방식을 제공하고 있다. XML은 단순한 텍스트 파일이기 때문에 다루기 쉽고 추가적인 빌드작업이 필요하지 않다.
    • <beans> : @Configuration에 대응한다. 여러 개의 <bean>이 들어간다.
      • <bean> : @Bean이 붙은 자바 메소드에 대응한다.
        • <id> : @Bean 메소드의 이름. getBean()에서 사용한다.
        • <class> : @Bean 메소드가 return하는 값. 패키지까지 모두 써 줘야 한다.
        • <property> : @Bean 메소드에 DI를 할 때 사용한다. 수정자 메소드이다.
          • <name> : 수정자 메소드에서 set부분을 제외한 나머지 부분의 이름이다.
          • <ref> : 수정자 메소드를 이용해서 주입할 오브젝트의 Bean의 id이다.
          • <value> : 다른 Bean 오브젝트가 아니라 단순 값을 주입할 때 <ref> 대신 사용한다. 스프링에서 프로퍼티의 값을 적절하게 변환하기 때문에 스트링, 오브젝트 등 다양한 값을 사용할 수 있다.
  • 예시 (Java 코드)

@Configuration
class DaoFactory {
 ...

 @Bean
 public ConnectionMaker connectionMaker() {
  return new DConnectionMaker();
 }

 @Bean
 userDao setConnectionMaker(connectionMaker()) {
 ... };
}
  • 예시 (XML)

<beans>
 <bean id="myConnectionMaker" class="springbook.user.dao.DConnectionMaker" />

 <bean id="userDao" class="springbook.dao.UserDao">
  <property name="connectionMaker ref="myConnectionMaker" />
 </bean>
</beans>
  • XML을 이용하는 애플리케이션 컨텍스트
    • 애플리케이션 컨텍스트 생성시 GenericXmlApplicationContext("xml 경로")를 이용해서 컨텍스트를 생성한다.

ApplicationContext context = new GenericXmlApplicationContext("springbook/user/dao/daoContext.xml")

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2011-03-19 08:49:12
Processing time 0.3056 sec