토비의 스프링

5 minute read

토비의 스프링

[1장] 오브젝트와 의존관계

스프링이 제공하는 수많은 기술과 접근 방법을 충분히 공부한 후에 나만의 기준을 확립하여 어떤 스타일로 사용할지 상황에 맞게 적용하자.
코드 깔끔, 단순, 객체지향, 생산성, 품질
테스트 코드, 유연하고 쉬운 확장

  • DAO: DB를 사용해 데이터를 조작하는 기능을 전담하는 객체
  • JavaBean: 디폴트 생성자를 통해 오브젝트를 생성하고, 노출하는 이름을 가진 속성인 프로퍼티를 이용해 수정/조회 할 수 있다.
  • 디자인패턴: 소프트웨어 설계 시 자주 만나는 문제를 해결하기 위해 사용하는 재사용 가능 솔루션, 대부분 객체지향 설계 원칙을 지킨다. 문제를 해결하기 위해 적용하는 확장성 추구 방법이 클래스 상속이나 오브젝트 합성으로 대부분 정리되기 때문이다. 패턴은 목적, 의도, 상황, 문제, 해결책, 솔루션 구조와 각 구조 역할의 의도를 기억해서 사용해야 한다.
  • OCP: 확장에는 열려있고 변경에는 닫혀있게 oop 설계하자.
  • solid: srp, ocp, lsp, isp, dip
  • 전략패턴: 클라이언트는 인터페이스와 같은 구조만 제공하고 각 기능이 필요에 따라 변경이 필요한 부분을 구현해서 사용하도록 하는 것

[1.1] 초난감 DAO

  • JDBC를 이용한 작업의 순서
    get connection -> preparedStatement -> execute statement -> make ResultSet -> close resources -> exception
    이 모든 작업을 하나의 객체, 하나의 메서드에서 실행할 때 생길 수 있는 문제를 생각해보자.

[1.2] DAO의 분리

소프트웨어 개발을 하다보면 사용자의 요구사항은 계속해서 변하고 발전해간다. 뿐만 아니라 기술, 운영 환경도 계속해서 변화한다.
이러한 변화를 어떻게 더 빠르고 쉽게 대처해갈 수 있을까? 이게 스프링이, 객체지향 프로그래밍이 시작된 이유이다.

그렇다면 어떻게?
초기의 모든 작업을 하나의 메서드에서 실행했을 때 그것을 담당한 객체가 하는 책임은 connection 연결, statement 만들고 실행, resource close이다.
이 많은 관심사가 100개씩 반복된다고 할 때 생겨날 수많은 중복 코드를 해결해보자.

  1. 중복 코드의 추출
    connection을 연결하는 코드는 어떤 쿼리문을 실행하더라도 같다.
    100개가 중복될 수 있는 것이다.
    중복 코드를 메서드 분리하는 리팩토링을 진행하자.
    그렇지만 사용자가 원하는 db가 달라진다면? 그런 사용자가 100명이 넘는다면?
    추상화를 이용해 해결해보자.

  2. 상속을 통한 확장
    추상 클래스에 connection을 실행하고 닫아주는 코드를 구현하고 추상메서드로 connection을 연결하는 부분을 주어 각 사용자가 이를 상속하여 구현할 때마다 원하는 코드를 구현할 수 있도록 하자.
    이렇게 슈퍼클래스에 공통되는 로직을 구현하고 필요한 일부 메서드를 오버라이딩이 가능하도록 하여 하위 클래스에서 필요에 맞게 구현하는 것이 템플릿 메서드 패턴이다.
    그리고, 특정 객체를 반환하는 데에 있어서 하위클래스에서 어떻게 생성해서 반환할지 구현하도록 하는 것은 팩토리 메서드 패턴이다.
    상속을 통해 변화에 맞춰서 서로에게 영향을 주지 않을 수 있게 되었다.
    하지만 상속을 사용한 이상 부모클래스에 변화가 생긴다면, 불가피하게 모든 하위클래스에 변화가 발생해야한다.
    상속에서 벗어나보자.

[1.3] DAO의 확장

  1. 클래스의 분리
    책임이 다르고 다른 클래스와 연관이 없는 connection을 만드는 클래스를 나누었다.
    그렇지만 이 분리한 클래스를 지정해두었다보니 여기서 변화가 필요하면 add, get 모두 변화가 필요하다. 그리고 UserDao 객체에 ConnectionMaker가 의존한다는 문제가 있다.
    의존성이 추가된 이상 ConnectionMaker가 쉽게 바뀌기 어렵다.

  2. 인터페이스의 도입
    이를 해결하기 위해 인터페이스가 등장했다.
    의존성을 끊기 위해 그 둘 사이에 추상화를 추가하는 인터페이스를 만들어주었다.
    인터페이스는 자신을 구현한 구현체에 대해서 알 수 없게 한다. 따라서 변화가 필요하면 구현체만 수정하면 되고, UserDao 객체는 그 결과애서 보여주는 것만 사용하면 되는 것이다.
    하지만 여기서 connectionMaker = new DConnectionMaker();를 사용한다는 것은 어떤 클래스를 사용하는지 알려주는 꼴이 된다.
    이것조차 없애보자.

  3. 관계설정 책임의 분리
    UserDao와 ConnectionMaker의 특정 구현 클래스 사이의 관계를 설정해주는거에 대한 관심을 분리하자.
    생성자의 파라미터로 DConnectionMaker를 전달해주면 UserDao는 어떤 클래스로 구현되었는지 알지 못한다.

이렇게 리팩토링함으로써 우리는 모듈의 응집도는 높이고 모듈 간의 결합도는 낮출 수 있었다.
결합도가 높아지거나 응집도가 낮아지면 여러 관심사와 책임이 얽혀 변경이 필요한 부분을 찾기도 어렵고, dao의 다른 기능에 영향을 줘 오류가 나지는 않을지 일일이 확인이 필요해진다.
변화에 대응하기 쉬워졌고, 확장하기도 편리해졌다.

[1.4] 제어의 역전

처음의 코드였다면 userDao가 담당했던 기능을 ConnectionMaker가 넘겨받고, UserDaoTest가 new DConnectionMaker를 선언해주며 완전히 독립된 클래스로 만들어주었다.
UserDaoTest는 검사용인데 일을 맡아버렸다.

  1. 오브젝트 팩토리
    특정한 객체를 특정하게 만들어서 돌려주는 팩토리라는 객체를 만들어주자.
    이렇게 함으로서 test도 그냥 만들어진 userDao를 사용할 수 있고, 책임이 없어졌다.
    하지만 이 오브젝트 팩토리도 각 메서드마다 중복된 코드를 통해서 만들어주어야 한다는 단점이 있다.
    메서드 분리를 통해 중복 코드를 없애주자.

처음의 코드였다면, 오브젝트가 자신이 필요한 객체를 스스로 고르고 구현하는 방식이었다.
제어의 역전이란 자신이 어디서 어떻게 만들어지고 사용되는지 알지 못하고 내가 사용할 객체를 만들지도 않는 것이다.
또 다른 객체가 사이에서 만들어주기 때문이다.
제어권을 상위 템플릿 메서드에 넘기고 자신은 호출되는 것이다.

ex) servlet: 우리는 jar 파일을 통해 서버에 배포를 진행한다. 언제 어떤 객체를 관리할 지 제어할 수 없다. 한 번 배포를 하면 서블릿이 알아서 적절한 때에 객체를 만들고 컨테이너를 만들고 넣는다.
ex) framework: 라이브러리는 어플리케이션의 흐름을 직접 제어한다. 동작 중 필요한 때에 능동적으로 라이브러리를 사용한다.
프레임워크는 어플리케이션 코드가 프레임워크에 의해서 사용된다. 프레임워크가 흐름을 제어하고 그 중에 사용자의 어플리케이션 코드가 있는 것이다.

제어의 역전에서 컴포넌트의 생성과 관계 설정, 사용, 생명주기 등을 관장하는 존재가 컨테이너나 프레임워크인 것이다.

mysql 사용

implementation 'mysql:mysql-connector-java:8.0.16'로 라이브러리 추가

[1.5] 스프링의 IoC

  • 빈: 관리되는 오브젝트
  • 빈 팩토리: 빈 생성, 관계 설정 등 IoC를 담당하는 핵심 컨테이너
  • 애플리케이션 컨텍스트: IoC를 따른 빈 팩토리의 확장으로 제어 작업 총괄
  • 설정정보/설정 메타정보: configuration
  • 컨테이너: 빈 팩토리, application context
  • 스프링 프레임워크

@Configuration 어노테이션으로 application context / bean factory가 사용할 설정정보라고 알려줌
@Bean 오브젝트 생성 담당하는 IoC용 메서드임을 알려줌
getBean("user", UserDao.class)로 오브젝트를 가져올 수 있는데 이름을 달아주는 이유는 UserDao를 생성하는 방식이나 구성을 다르게 할 수 있기 때문이다.

  • application context를 사용할 때 장점
    클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
    애플리케이션 컨텍스트는 종합 IoC 서비스를 제공한다.
    애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

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

  • 동등성: 동일한 정보를 가진 객체 ==으로 비교
  • 동일성: 완전히 동일한 객체 equals()로 비교
    서블릿 클래스당 하나의 오브젝트를 만들고, 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용
    서버에서는 권장되지만 디자인 패턴으로는 최 악
  • java의 singleton
    private 생성자, static field, getInstance()로 호출
    상속이 어렵다, 테스트가 힘들다, 서버환경에서 하나 이상의 객체 만들어질 수 있다. 전역 상태 객체지향 싫어함
  • spring의 singleton
    스프링 컨테이너가 평범한 java 클래스를 싱글톤으로 생성, 관리, 공급
    스프링 빈은 기본적으로 싱글톤으로 스프링 컨테이너가 존재하는 동안 유지된다. 하지만 프로토타입 스코프의 경우 빈에 요청할 때마다 새로운 객체를 만들어준다. 세션 스코프, 웹 스코프도 존재한다.

  • 주의할 점
    여러 스레드가 접근하게 되므로 상태 관리에 유의
    값을 바꾸지 말고 인수, 로컬 변수, 리턴 값으로 전달(connection과 같이 읽기 전용인 경우에만 인스턴스 변수로 사용)

[1.7] 의존관계 주입

스프링의 IoC -> DI로 직결
a가 b에 의존한다는 것은? a에서 b가 사용된다는 것. b가 바뀌면 a도 영향을 미치는 것. b는 a에 영향을 받지 않는 것

  • 런타임 의존관계(오브젝트 의존관계)
    객체가 만들어지고 나서(런타임에) 의존관계가 생기는 것
    설계와 코드에서 의존관계가 드러나지 않는 것
    이때에 의존관계는 컨테이너가 팩토리가 외부에서 주입해줌으로써 생성
  • 의존관계 검색
    스스로 검색을 통해 자신이 필요한, 의존할 객체를 찾는 것
    외부에서 의존할 대상, 생성을 하고 그걸 주입받는 것만 요청할 수 있다.
    this.connectionMaker = daoFactory.connectionMaker();와 같이 만드는게 아니라 요청만 하는 것
    AnnotationConfigApplicationContextgetBean()를 이용해서도 검색을 하도록 할 수 있다.
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
    this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
    

    일반적으로는 주입을 받는게 낫고, test와 같이 주입 방법이 없는 경우에 검색을 사용하는게 좋다. 서블릿에서도 쓰는데 이건 스프링이 해준다.

  • 메서드를 이용한 의존관계 주입
    setter, 일반 메서드(set으로 시작해야하고, 하나의 파라미터): xml 사용시 편리

질문

p.108 서버환경에서 여러개?
프로토타입 스코프? 서블릿?