토비의스프링vol.1

[토비의 스프링vol.1 1장] 오브젝트와 의존관계

ohyujeong 2022. 10. 3. 21:01

1장 오브젝트와 의존관계

 

스프링은 객체지향 프로그래밍을 추구한다. 그렇다면, 스프링이 집중하는 오브젝트를 먼저 이해해야 한다.

1장에서는 UserDao 클래스를 만들고, 코드를 객체지향에 충실한 코드로 리팩토링 하며 오브젝트와 의존관계에 대해 이해한다.

1.1 초난감 DAD

  • DAO(Data Access Object) : DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트
  • 자바빈 : 다음 두 가지 관례를 따라 만들어진 오브젝트
    • 디폴트 생성자: 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
    • 프로퍼티: 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)을 이용해 수정 또는 조회할 수 있다.

1.2 DAO의 분리

  • 관심사의 분리 : 관심이 같은 것끼리는 하나의 객체 or 친한 객체, 관심이 다른 것은 가능한 서로 영향을 주지 않도록 분리

 

UserDao의 크게 분류한 최소 3가지 관심사항 :

  1. DB Connection (어떤 DB, 어떤 드라이버, 어떤 로그인 정보, 어떻게 커넥션 생성)
  2. SQL 문장을 담을 Statement를 만들어 정보를 바인딩 시키기고, SQL을 DB를 통해 실행
  3. 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫고 공유 리소스 반환

→ UserDao 에서는 각기 다른 관심사들이 하나의 메소드에 담겨있고, 코드가 중복되어 있음 ( db 연결 코드가 add(), get() 메소드에 모두 나타나고 있음)

→ 관심의 대상이 모두 얽혀 있어 확장 및 변경이 힘듦

 

가장 먼저 할 일 : 중복 코드 제거

UserDao 의 중복 코드 : DB Connection

getConnection 메소드 추출을 통해 독립적으로 관심사를 분리한다.

getConnection 메소드 코드를 공개하고 싶지 않다. 이런 경우, 고객이 스스로 원하는 db 커넥션 방식을 적용해가며 어떻게 사용할 수 있을까?

→ 코드를 한 단계 더 분리

→ UserDao에서 메소드의 구현 코드 제거 후, 추상메소드로 만듦. 추상 메소드라서 메소드 코드는 없지만 메소드 자체는 존재. 따라서 UserDao 클래스를 상속해서, 고객에 만든 서브클래스에서 getConnecntion을 호출하여 자유롭게 커넥선 방식 적용

→ 상속을 통한 확장

public abstract Connecntion getConnection() throws 예외처리 {
    ...
}

 

  • 템플릿 메소드 패턴 : 기능의 일부를 초상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브 클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하는 디자인패턴 (스프링에서 애용)
    • 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 대표적인 방법
    • 변하지 않는 기능을 슈퍼클래스에 만들고, 자주 변경되며 확장할 기능은 서브클래스에
    • 슈퍼클래스에서 미리 기본 알고리즘을 담은 템플릿 메소드를 만듦
    • 디폴트 기능을 정의해두거나, 비워뒀다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드를 훅(hook) 메소드라고 한다.
    • 서브클래스에서는 추상 메소드를 구현하거나, 훅 메소드를 오버라이드 하는 방법을 이용해 기능을 확장
  • 팩토리 메소드 패턴 : 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것

1.3 DAO의 확장

상속을 이용한 확장 방식 대신, DB 커넥션을 아예 별도의 클래스로 분리

→ 문제점 : Dao마다 커넥션 코드가 중복되지 않을 수 있는 코드가 됐지만, 고객사에 커넥션 코드를 노출해야 한다. UserDao의 코드가 DB 커넥션을 생성하는 특정 클래스에 종속되어 있기 때문이다. 커넥션 생성 기능을 변경하기 위해서는 UserDao 코드를 수정해야 한다.

→ UserDao가 바뀔 수 있는 정보, 즉 db 커넥션을 가져오는 클래스에 대해 너무 많이 알고 있다.

 

해결책: 인터페이스 도입

: 두 클래스를 연결하되, 중간에 추상적인 느슨한 연결고리를 만듦

 

cf) 추상화란? 어떤 것들의 공통적인 성격을 뽑아내 이를 따로 분리해내는 작업. 자바가 추상화를 위해 제공하는 가장 유용한 도구가 인터페이스
. 인터페이스에서는 어떤 일을 하겠다는 기능만 정의, 구현 방법은 나타나지 X

 

ConnectionMaker 인터페이스를 사용하도록 개선한 UserDao

public class UserDao {
    private ConnectionMaker connectionMaker; // 인터페이스를 통해 오브젝트에 접근하므로 구체적은 클래스 정보를 알 필요가 없다.

    public UserDao{
        connectionMaker = new DConnectionMaker(); -> 문제점 발견, 클래스 이름 나옴
    }

    public void add(User user) throws 예외처리{
        Connectnion c = connectionMaker.makeConnection(); //인터페이스에 정의된 메소드를 사용하므라 클래스가 바뀐다고 해도 메소드 이름이 변경될 걱정이 없다. 
    }
}

→ 클래스 이름을 넣어서 오브젝트를 만들지 않으려면 어떻게 해야 되는가? 여전히 고객이 직접 UserDao의 생성자 메소드를 직접 수정해야 한다.

→ UserDao 안에 또 다른 관심사항이 존재하고 있기 때문에 문제 발생.

→ UserDao와 UserDao가 사용할 커넥션메이커의 특정 구현 클래스 사이의 관계를 설정해주는 것에 관한 관심

 

관계설정 책임을 통해 해결: Dao를 사용하는 Client Object에서 관계설정을 하도록 분리한다. Dao에게 파라미터 등으로 전달하여 관계를 연결함

 

  • 개방 폐쇄 원칙(OCP, Open-Closed Principle)
    • 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
  • 객체지향 설계 5원칙(SOLID)
    • SRP(Single Respnsibility Principle) : 단일 책임의 원칙
    • OCP(Open CLosed Principle) : 개방 폐쇄 원칙
    • LSP(Liskov Subsitution Principle) : 리스코프 치환 원칙
    • ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
    • DIP(Dependency Inversion Principle) : 의존관계 역전 원칙
  • 높은 응집도와 낮은 결합도
    • 높은 응집도
      • 변화가 일어날 때, 해당 모듈에서 변하는 부분이 크다. 즉 변경이 일어날 때 모듈의 많은 부분이 함께 바뀐다면 응집도가 높다. (하나의 관심사에 집중되어 있다.)
    • 낮은 결합도
      • 변화가 일어날 때, 다른 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태(관심사가 분리되어 있다.)
  • 전략패턴 : 자신의 기능 맥락에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴
    • UserDao는 전략패턴의 컨텍스트에 해당한다.

 

리팩토링 과정

  1. 메소드 추출을 통해 중복 코드 제거하는 리팩토링
  2. 팩토리 메소드 패턴을 적용하여 추상화해서 상속을 통한 확장
  3. 상속을 통해 만들어진 DB 커넥션 코드가 Dao 클래스마다 중복되는 문제 발생
  4. DB 커넥션을 아예 클래스 분리하는 방향으로 리팩토링 했더니 다시 고객에서 디비 커넥션 코드 노출해야 하는 문제 발생
  5. 인터페이스 도입을 통해, DB 커넥션 제공하는 클래스에 대한 구체적인 정보 모두 지움, 하지만 클래스 이름을 넣어서 오브젝트를 만들어야 함.
  6. 관계설정 책임을 통해 UserDao를 사용하는 클라이언트 코드에게 책임 전가해서 객체 간의 관계 설정

 

1.4 제어의 역전(IoC)

  • 팩토리
    • 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 일을 하는 오브젝트
    • 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용
  • 제어의 역전
    • 작업을 사용하는 쪽이 제어하는 구조, 제어의 역전은 이러한 제어의 흐름을 거꾸로 뒤집음
    • 객체가 사용할 객체를 스스로 선택하거나 생성하지 않는다.
    • 객체 자신도 어떻게 만들어지고 어디서 사용되는 지 알 수 없다.
    • 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.

 

1.5 스프링의 IoC

    • 스프링이 제어권을 가지고 직접 생성하고 관계를 부여하고, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트
    • 모든 오브젝트가 다 빈이 아님, 스프링이 직접 생성과 제어를 담당하는 오브젝트만
  • 빈 팩토리
    • 빈의 생성/관계설정 등의 제어를 담당하는 IoC 오브젝트
    • 빈의 등록/생성/조회/반환/관리 하는 기능을 담당하며, 보통은 빈 팩토리를 확장한 애플리케이션 컨텍스트를 사용한다.
    • 스프링의 IoC를 담당하는 핵심 컨테이너
    • getBean()과 같은 메소드 정의되어 있음
  • 애플리케이션 컨텍스트
    • 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IOC 엔진
    • 별도의 정보를 참고해서 빈(오브젝트)의 생성, 관계 설정 등의 제어 작업을 총괄한다.
    • 빈 팩토리를 상속한다.
  • 설정정보
    • 애플리케이션 컨텍스트 또는 빈 팩토리 IoC를 적용하기 위해 사용하는 메타정보
  • 컨테이너 또는 IoC 컨테이너
    • IoC 방식으로 빈을 관리한다는 의미에서, 애플리케이션 컨텍스트나 빈 팩토리를 일컫는다.
  • 스프링 프레임워크
    • IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말한다.

 

스프링 빈 팩토리가 사용할 설정정보를 담은 DaoFactory 클래스

@Configuration : 애플리케이션 컨텍스트가 사용할 설정 정보라는 것을 알림

@Bean : 오브젝트 생성을 담당하는 IoC용 메소드라는 표시

 

@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao() {
        UserDao dao = new UserDao(connectionMaker());
        return dao;
    }

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

DaoFactory를 설정정보로 사용하는 애플리케이션 컨텍스트

public class UserDaoTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        AnnotationConfigApplicationContext context 
      = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = context.getBean("userDao", UserDao.class); // userDao가 애플리케이션컨텍스트에 등록된 빈의 이름
    // ....
  }
}

오브젝트 팩토리 : 제한적으로 객체 생성/관계설정

애플리케이션 컨텍스트 : 애플리케이션에서 IoC를 적용해 관리할 모든 객체에 대한 생성/관계 설정

 

애플리케이션 컨텍스트는 객체를 직접 생성하고 할당하는 코드가 없는 대신에 그것에 대한 정보를 별도의 설정 정보를 통해 얻는다. 때로는 외부의 오브젝트 팩토리에 그 작업을 위임하고 결과를 사용하기도 한다.

 

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

  1. @Configuration 붙여 설정정보 클래스를 등록한다.
  2. @Bean이 붙은 메소드들의 이름을 가져와 빈 목록을 만들어둔다.
  3. 클라이언트로부터 getBean() 메소드가 호출되면 빈 목록에서 요청한 이름이 있는지 찾고, 있다면 빈을 생성하는 메소드를 호출해서 오브젝트를 생성시킨 후 클라이언트에게 반환한다.

 

애플리케이션 컨텍스트 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
  • 종합 IoC 서비스를 제공해준다.
  • 빈을 검색하는 다양한 방법을 제공한다

 

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

  • 오브젝트 팩토리와 스프링 애플리케이션 컨텍스트 동작방식의 차이
    • 오브젝트 팩토리는 getBean() 실행마다 매번 new에 의해 새로운 객체 만들어짐
    • 스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려줌

→ 스프링은 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만들기 때문이다.

→ 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리

 

싱글톤으로 만드는 이유

초당 수많은 요청을 처리할 때마다, 새로운 객체를 생성하는 것은 낭비이기 때문이다.

 

일반적인 싱글톤 패턴을 적용했을 때 문제점

  • private 생성자를 갖고 있기 때문에 상속할 수 없다.
    • 싱글톤 패턴은 생성자를 pirvate으로 제한한다. 오직 싱글톤 클래스 자신만이 자기 오브젝트를 만들도록 제한하는 것.
    • 객체지향의 장점인 상속과 이를 이용한 다형성 적용 불가능
  • 싱글톤은 테스트하기 힘들다
    • 생성자를 통해 객체에 동적으로 정보 주입이 불가능하기 때문이다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어진다.
  • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
    • 사용하는 클라이언트가 정해져 있지 않다. 따라서 스태틱 메소드를 이용해 언제든지 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 사용될 수 있고, 그러다 보면 자연스럽게 전역 상태로 사용되기 쉽다.

 

싱글톤 레지스트리

스프링은 평범한 public 생성자를 가진 자바 클래스를 싱글톤으로 활용할 수 있게 만들어준다.

오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문이다.

 

싱글톤 주의점

  • 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야한다.
  • 요청에 따라 달라지는 값에 대한 정보는 파라미터, 지역 변수, 반환값 등을 이용한다.

 

스프링 빈의 스코프

스코프 : 스프링이 관리하는 오브젝트, 즉 빈이 생성/존재/적용되는 범위

  • Default Scope : 싱글톤, 강제로 제거하지 않는 한 계속 유지
  • Prototype Scope : 컨테이너에 빈을 요청할 때마다 새로운 오브젝트 생성해준다.
  • Request Scope : 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성
  • Session Scope : 웹의 세션과 스코프가 유사

 

1.7 의존관계 주입(DI)

의존관계란?

[A] → [B] : A가 B에 의존하고 있다.

A의 변화에 B는 영향 받지 X

A는 B의 변화에 영향 받음

 

UserDao의 의존관게

[UserDao] → ConnectionMaker(인터페이스)

[DConnectionMaker] → ConnectionMaker(인터페이스)

 

관게설정 책임 분리 전의 생성자

public Userdao() {
    connectionMaker = new DConnecntionMaker();
}

인터페이스 관계 뿐만이 아니라, 런타임 의존관계, 즉 DConnectionMaker 오브젝트를 사용하겠다는 것까지 UserDao가 결정하고 관리하는 코드.

→ IoC 방식을 써서 UserDao로부터 런타임 의존관계를 드러내는 코드를 제거하고, 제 3의 존재에 런타임 의존관계 결정 권한을 위임한다. 그래서 최종적으로 만들어진 것이 DaoFactory다.

 

의존관계 주입

  1. Static한 클래스 모델, 코드에는 드러나지 않다가 런타임에 의존관계가 드러나야한다. 이를 위해 인터페이스에만 의존해야 한다.
  2. 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3자가 결정한다.
  3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 메소드를 실행하면서 파라미터로 전달(외부에서 주입) 함으로써 만들어짐
  • DI 컨테이너
    • 두 객체 사이의 런타임 의존관계를 설정해주는 의존 관계 주입 작업을 주도하는 존재
    • 오브젝트 생성/초기화/제공 등의 작업을 수행
    • 예제에서는 DaoFactory

 

의존관계 검색

  • 외부로부터의 주입이 아니라, 객체 스스로 검색을 이용해서 의존 관계 결정
  • 런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성 작업은 외부 컨테이너에게 IoC로 맡기지만, 이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법 사용

DaoFactory를 이용하는 생성자

public UserDao() {
  DaoFactory daoFactory = new DaoFactory();
    this.connectionMaker = daoFactory.connectionMaker();
}

의존관계 검색을 이용하는 UserDao 생성자

public UserDao() {
  AnnotationConfigApplicationContext context =
    new AnnotationConfigApplicationContext(DaoFactory.class);
  this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}

 

  • 검색과 주입의 차이점
    • 검색 : 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다.
    • 주입 : 자기 자신도 컨테이너가 관리하는 오브젝트 빈이어야 한다.
  • 의존관계 주입 방법
    • 생성자
    • Setter(수정자) 메소드
    • 일반 메소드

1.8 XML을 이용한 설정

XML을 통해 DI를 위한 오브젝트 의존관계 정보 설정이 가능하다.

XML 이용시 정보 수정할 때, 재빌드 할 필요가 없다.

  자바 코드 설정 정보 XML 설정정보
빈 설정파일 @Configuration <beans
빈의 이름 @Bean methodName() <bean id=”methodName”
빈의 클래스 return new BeanClass(); class=”a.b.c…. BeanClass”>