Today I Learned_230423

10 분 소요

토비의 스프링 3.1

DAO 개선에 적용된 원칙과 패턴

개방 폐쇄 원칙 Open-Closed Principle

  • 확장에는 열려 있고, 변경에는 닫혀있다.
  • 깔끔한 설계를 위한 객체지향 설계 원칙 중 하나

객체지향 설계 원칙(SOLID)

  • 객체지향의 특징을 잘 살릴 수 있는 설계의 특징
  • 원칙이라는 건 어떤 상황에서든 100% 지켜져야 하는 절대적인 기준이라기보다는, 예외는 있겠지만 대부분의 상황에 잘 들어맞는 가이드라인과 같다.
  • cf) 디자인 패턴 : 특별한 상황에서 발생하는 문제에 대한 구체적인 솔루션
  • 객체지향 설계 원칙 : 일반적인 상황에서 적용 가능한 설계 기준

높은 응집도와 낮은 결합도

  • 높은 응집도
    • 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다.
    • 하나의 공통 관심사는 한 클래스에 모여 있다.
    • 변화가 일어날 때 모듈의 많은 부분이 한꺼번에 바뀐다
  • 낮은 결합도
    • 책임과 관심사가 다른 오브젝트/모듈은 느슨하게 연결된 형태를 유지해야 한다.
    • 느슨한 연결 : 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공
    • 변화 대응 속도가 높아지고 구성이 깔끔해지며 확장에 편리해진다.
    • 하나의 변경이 발생할 때 다른 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태
  • 결합도
    • 하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도
  • 전략 패턴
    • 자신의 기능 context에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스로 분리
    • 알고리즘 클래스를 전략에 따라 바꿔서 사용
    • 알고리즘 : 독립적인 책임으로 분리가 가능한 기능. 대체 가능한 전략
    • 컨텍스트를 사용하는 클라이언트는 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해 제공

오브젝트 팩토리

팩토리

  • 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 오브젝트
  • 클라이언트에서는 객체가 어떻게 만들어지는지, 초기화되었는지 신경 쓰지 않고 팩토리로부터 받아서 사용
  • 컴포넌트의 구조와 관계를 정의한 설계도 역할
  • 어떤 오브젝트가 어떤 오브젝트를 사용하는지 정해놓은 코드

제어의 역전

  • 프로그램의 제어 흐름 구조가 뒤바뀌는 것
  • 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하고 생성하지 않는다.
  • 자신조차 어디서 사용되는지 알 수없다.
  • 모든 제어 권한을 자신이 아닌 다른 대상에게 위임한다.

→ 모든 오브젝트는 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.

  • 프레임워크는 제어의 역전 개념이 적용된 대표적인 기술로, 프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다.
  • 제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계썰정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다.

스프링의 IoC

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

  • 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
  • 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트

빈 팩토리

  • 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트

애플리케이션 컨텍스트

  • IoC 방식을 따라 만들어진 빈 팩토리
  • 애플리케이션 전반에 걸쳐 모든 구성 요소의 제어 작업을 담당하는 IoC 엔진
  • 빈 팩토리의 확장 버전
  • 별도 설정 정보를 참고하여 빈(오브젝트)을 생성하고 관계를 설정한다.

ApplicationContext 사용

@Configuration

  • 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라 지정
  • 이걸 ApplicationContext에서 사용하려면 AnnotationConfigApplicationContext 사용

@Bean

  • 오브젝트를 만들어 주는 메소드에 붙임
package com.example.demo.dao;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DaoFactory {

    @Bean
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }

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

ApplicationContext를 사용하여 설정 정보를 가져옴

package com.example.demo.dao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.sql.SQLException;

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);
    }
}
  • getBean() : ApplicationContext가 관리하는 오브젝트를 요청하는 메소드
    • 기본적으로 Object타입으로 리턴하나, 두번째 인자로 클래스를 주면 됨

ApplicationContext의 동작방식

Untitled

  • ApplicationContext는 오브젝트 팩토리에 해당
  • IoC 컨테이너, 스프링 컨테이너, 빈 팩토리라 함
  • BeanFactory → ApplicationContext로 상속
  • 오브젝트 팩토리 : 오브젝트를 생성하고 관계를 맺어주는 제한적인 역할
  • ApplicationContext : 애플리케이션에서 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정 담당
  • 직접 오브젝트를 생성하고 관계를 맺어주는 코드가 없고, 별도의 설정 정보를 통해 얻는다.
  • @Configuration : IoC 설정정보
  • 설정정보를 등록하고 @Bean이 붙은 메소드의 이름을 가져와 빈 목록 생성
  • 클라이언트가 getBean 호출 → 빈 목록에서 탐색 → 빈 생성 메소드 호출 → 클라이언트에 돌려줌

ApplicationContext의 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
    • 팩토리가 아무리 많아져도 이를 알아야 하거나 직접 사용할 필요가 없다.
    • 일관된 방식으로 원하는 오브젝트를 가져올 수 있다.
  • 종합 IoC 서비스 제공
    • 오브젝트 생성 방식, 시점, 전략, 자동생성 등등 여러 기능 있다.
  • 빈을 검색하는 다양한 방법 제공
    • 메소드, 타입, 애노테이션 설정 등으로 빈 검색 가능

스프링 IoC 용어 정리

  • 빈 (bean)
    • 스프링이 IoC방식으로 관리하는 오브젝트(managed object)
    • 스프링이 생성과 제어를 담당하는 오브젝트
      • 만들어지는 모든 오브젝트가 빈은 아님. 스프링에 의해 관리되는 오브젝트만 빈.
  • 빈 팩토리 (bean factory)
    • 스프링의 IoC를 담당하는 핵심 컨테이너
    • 빈을 등록, 생성, 조회, 돌려주기, 빈 관리 기능 담당
    • 빈 팩토리를 바로 사용하지 않고 이를 확장한 어플리케이션 컨텍스트 사용
    • 빈의 생성과 제어의 관점에서 얘기할 때 빈 팩토리라고 많이 이야기함
  • 애플리케이션 컨텍스트 (application context)
    • 빈 팩토리를 확장한 IoC 컨테이너
    • 스프링이 제공하는 애플리케이션 지원 기능을 얘기할 때 애플리케이션 컨텍스트라 이야기함
  • 설정정보/설정 메타정보 (configuration metadata)
    • 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보
    • IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용됨
  • 컨테이너/IoC 컨테이너
    • IoC 방식으로 빈을 관리하는 의미에서 애플리케이션 컨텍스트를 컨테이너라고도 함
  • 스프링 프레임워크
    • IoC 컨테이너, 애플리케이션 컨텍스트를 포함한 스프링이 제공하는 모든 기능

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

오브젝트의 동등성과 동일성

Untitled

  • 동일성 (identity)
    • 두 오브젝트가 완전히 동일한지
    • == 연산자로 비교
  • 동등성 (equality)
    • 두 오브젝트가 동일한 정보를 담고 있는지
    • equals() 메소드로 비교

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

  • ApplicationContext는 기본적으로 싱글톤 레지스트리에 해당
  • 싱글톤을 저장하고 관리

싱글톤을 사용하는 이유?

  • 스프링은 대부분 서버환경에서 적용되고 사용됨
  • 클라이언트에서 요청이 올 때마다 오브젝트를 새로 만들면 서버에 부하가 걸린다.
  • 싱글톤 패턴을 사용하면 애플리케이션 내에 한 개의 오브젝트만 만들어서 사용할 수 있다.

싱글톤 패턴

  • 어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수, 주로 하나만 존재하도록 강제하는 패턴
  • 애플리케이션 내에서 전역적으로 접근이 가능
  • 단일 오브젝트만 존재
  • 매우 조심해서 사용해야 하며, 안티패턴의 일종

싱글톤 패턴의 한계

  • 클래스 밖에서 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
    • 상속이 불가능 → 객체지향적인 설계의 장점을 적용하기 어렵다.
  • 생성된 싱글톤 오브젝트를 저장하기 위한 스태틱 필드 정의
    • 객체지향의 특징이 적용되지 않는 스태틱 필드
  • 스태틱 팩토리 메소드를 만들고, 최초로 호출되는 시점에서 한 번만 오브젝트가 만들어지게 함
    • 객체지향의 특징이 적용되지 않은 스태틱 메소드
  • 한번 오브젝트가 만들어지고 난 다음에는 이미 만들어진 오브젝트 넘겨줌
    • 만들어지는 방식이 제한적이므로 테스트 불가능
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다
    • 클래스 로더의 구성에 따라 하나 이상의 오브젝트가 만들어 질 수 있음
  • 싱글톤의 사용은 전역 상태를 만들 수 있기에 바람직하지 못하다.

싱글톤 레지스트리 (singleton registry)

  • 자바의 기본적인 싱글톤 패턴에는 여러 가지 단점이 있음
  • 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공
  • 스프링 컨테이너는 싱글톤을 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너다.
  • 평범한 자바 클래스를 싱글톤으로 활용하게 도와줌

싱글톤과 오브젝트의 상태

멀티스레드 환경에서는 여러 스레드가 동시에 접근할 수 있으므로 상태 관리에 주의해야 한다.

  • 무상태 방식으로 만들어져야 함
    • 여러 클라이언트가 하나의 변수를 수정하면 위험
    • 읽기 전용의 값이라면 무방 → static이나 static final로 선언
  • DB나 서버의 리소스로부터 생성한 정보는 파라미터, 로컬 변수, 리턴 값 등을 이용
    • 이들은 매번 새로운 값을 저장한 독립적인 공간이 만들어지므로 싱글톤이라고 해도 덮어쓰기가 불가능
    • 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나 파라미터로 주고받으며 사용해야 함

스프링 빈의 스코프

스코프(scope) : 빈이 생성되고, 존재하고, 적용되는 범위

  • 싱글톤(singleton) : 기본 스코프. 컨테이너 내에 한 개의 오브젝트만 만들어져서 강제로 제거되지 않는 한 스프링 컨테이너 존재 동안 계속 유지됨
  • 프로토타입(prototype) : 컨테이너에 빈을 요청할 때 마다 새로운 오브젝트 생성
  • 요청(request) : 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성
  • 세션(session) : 웹의 세션과 스코프가 유사

의존관계 주입

제어의 역전과 의존관계 주입

스프링 IoC 컨테이너 → 객체를 생성하고 관계를 맺어주는 등의 작업을 담당하는 기능을 일반화

의존관계 주입 (Dependency Injection)

  • 스프링이 제공하는 IoC방식을 지칭
  • 오브젝트 레퍼런스를 외부로부터 제공받고 다른 오브젝트와 동적으로 의존관계 생성

런타임 의존관계 설정

Untitled

  • 의존관계는 점선으로 된 화살표로 표현
  • A가 B에 의존한다.
  • 화살표 끝부분에 의존한다.
  • 의존의 뜻 : B가 변하면 A에 영향을 미친다.
  • 사용의 관계에 있는 경우 의존관계가 있다고 한다.
  • 의존관계는 방향성이 있어, 역은 성립하지 않는다.

Untitled

  • 위 그림과 같이 인터페이스에 대해서만 의존관계를 만들면 결합도를 낮출 수 있다 → 변경에서 자유로워진다

런타임 의존관계(오브젝트 의존관계)

  • 런타임 시에 오브젝트 사이에서 만들어지는 의존관계
  • 모델이나 코드 상에서 드러나지 않는다.

의존 오브젝트(dependent object)

  • 프로그램이 시작되고 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상

의존관계 주입

  • 의존 오브젝트와 클라이언트 오브젝트(사용할 주체)를 런타임 시에 연결해주는 작업

제 3의 존재? 관계설정 책임을 가진 코드를 분리해서 만들어진 오브젝트

→ 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등

의존관계 주입을 위한 코드

// 컨테이너가 connectionMaker를 외부에서 따로 만들어서 생성자로 넣어준다.
public UserDao(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker;// 오브젝트의 레퍼런스 전달
}
  • DI 컨테이너는 의존관계를 맺어줄 클래스의 오브젝트를 만들고 생성자의 파라미터로 오브젝트의 레퍼런스를 전달해준다.
  • 전달받은 오브젝트는 인스턴스 변수에 저장

Untitled

DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부로 넘기고, 자신은 수동적으로 주입받은 오브젝트를 사용한다.

의존관계 검색과 주입

의존관계 검색(dependency lookup)

  • 의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색하는 것
  • 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다.
  • 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성 작업은 외부 컨테이너에게 맡긴다.
  • 이를 가져올 때는 스스로 컨테이너에게 요청한다.
  • 스프링의 애플리케이션 컨텍스트 - 미리 정해놓은 이름을 전달해서 이름에 해당하는 오브젝트를 찾는다
  • getBean() 메소드를 통해 의존관계 검색 방식으로 오브젝트를 가져온다.
public UserDao(ConnectionMaker connectionMaker) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
    }

DI vs. DL

  • DI (Dependency Injection)
    • 객체가 필요로 하는 의존성을 직접 주입
    • 스프링이 객체 간의 의존성을 관리하고, 객체 생성 및 관리를 수행
    • 객체가 자신이 사용할 객체를 직접 결정하지 않고 외부에서 주입받는다.
    • A와 B 사이에 DI가 적용되려면 A도 반드시 빈 오브젝트여야 함
    • DI를 원하는 오브젝트는 자기 자신이 빈이 되어야 함!
    • 객체 간의 결합도를 낮추어 유지보수성을 높이는데 도움을 준다.
    • 다이내믹하게 구현 클래스를 결정해서 제공받을 수 있도록 인터페이스 타입의 파라미터를 통해 이루어져야 한다.
  • DL (Dependency Lookup)
    • 객체가 필요로 하는 의존성을 검색
    • ApplicationContext 사용 필요
    • 검색하는 오브젝트는 자신이 빈일 필요가 없다.
      • A가 B를 필요로 하는 경우, A는 빈일 필요가 없다.
    • 객체 간의 결합도를 높여서 코드의 유연성을 높이는데 사용된다.

의존관계 주입의 응용

스프링은 DI에 의해 99% 돌아가고 있다고 해도 과언이 아니다 → 그만큼 DI는 중요하다.

  • 개발 시와 운영 시에 각각 다른 런타임 오브젝트에 의존관계를 갖게 하여 문제 해결
  • DI의 장점 : 관심사의 분리를 통해 높은 응집도를 얻어낼 수 있다.

메소드를 이용한 의존관계 주입

수정자 메소드를 이용한 주입

  • 외부에서 오브젝트 내부의 속성값을 변경하려는 용도로 사용
  • 파라미터로 전달된 값을 인스턴스 변수에 저장
  • 스프링에서 많이 사용
  • 이름 잘 짓자 - DI받을 오브젝트 타입 이름을 따르는 것이 관례
public void setConnectionMaker(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

팩토리 메소드 변경

@Bean
    public UserDao userDao() {
        UserDao userDao = new UserDao();
        userDao.setConnectionMaker(connectionMaker());
        return userDao;
    }

일반 메소드를 이용한 주입

  • 여러 개의 파라미터를 갖는 일반 메소드 사용

XML을 이용한 설정

기존에 만들었던 DaoFactory → 자바 코드로 만든 오브젝트 의존관계 설정 정보

→ 스프링은 다양한 방법을 통해 DI 의존관계 설정정보를 만들 수 있다 → XML이 대표적

XML 설정

  • 를 루트 엘리먼트로 사용
  • 안에는 여러 개의 정의 가능

클래스 설정과 XML 설정의 대응항목

| | 자바 코드 설정정보 | XML 설정정보 | | — | — | — | | 빈 설정파일 | @Configuration | | | 빈의 이름 | @Bean methodName() | <bean id = “methodName” | | 빈의 클래스 | return new BeanClass(); | class = “com.example.BeanClass”> |

  • class 애트리뷰트에는 메소드의 리턴 타입이 아닌 오브젝트를 만들때 사용하는 클래스 이름이 들어가야 함.

자바 코드 ↔ XML 대응 코드

@Bean       // <bean
public ConnectionMaker connectionMaker() {  // id = "connectionMaker"
    return new DConnectionMaker();          // class = "com.example.demo.dao.DconnectionMaker" />
}

XML을 이용한 의존관계 주입 정보 기술

  • 수정자 메소드를 사용해 의존관계 주입을 한다고 가정
  • 수정자 메소드는 자바빈 관례에 따라 프로퍼티가 된다.
  • 태그를 사용해 의존관계 정의
    • name : DI에 사용할 수정자 메소드의 프로퍼티 이름
    • ref : 수정자 메소드를 통해 주입해줄 오브젝트의 빈 이름

Untitled

  • name과 ref는 들어가는 값이 같아도 의미는 완전 다르므로 유의할 것!
  • 스프링 XML 설정파일은 DTD와 스키마 모두 지원 → 스키마가 대세인 듯

XML을 이용하는 애플리케이션 컨텍스트

  • GenericXmlApplicationContext 사용
  • 생성자 파라미터로 XML 파일의 클래스패스 지정
  • /가 없어도 루트부터 시작 → src/main/resource에 넣어야 한다.
  • 클래스패스 뿐만 아니라 다양한 소스로부터 설정파일을 읽어올 수 있다.
  • ClassPathXmlApplicationContext도 사용 가능 → XML파일을 클래스패스에서 가져올 때 사용
  • 일반적으로는 GenericXmlApplicationContext를 더 많이 쓴다.

XML 설정정보를 담은 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="connectionMaker" class="com.example.demo.dao.DConnectionMaker"/>
    <bean id="userDao" class="com.example.demo.dao.UserDao">
        <property name="connectionMaker" ref="connectionMaker"/>
     </bean>
</beans>

applicationContext.xml을 사용하도록 UserDaoTest 수정

package com.example.demo.dao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import java.sql.SQLException;

public class UserDaoTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
        UserDao dao = context.getBean("userDao", UserDao.class);
        System.out.println("dao = " + dao);
    }
}

DataSource 인터페이스

  • DB 커넥션을 가져와서 연결해주는 일련의 기능들이 포함된 인터페이스
  • getConnection()을 통해 연결을 가져옴
  • 구현 클래스를 통해 세팅
  • SimpleDriverDataSource : 스프링이 제공해주는 DataSource 구현 클래스
    • DB 연결에 필요한 필수 정보를 받을 수 있도록 수정자 메소드를 가지고 있다.
public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

public void add(User user) throws  ClassNotFoundException, SQLException { //예외는 메소드 밖으로 던지기
    Connection c = dataSource.getConnection();
    // ...

자바 코드 설정방식

@Bean
public UserDao userDao() {
    UserDao userDao = new UserDao();
    userDao.setDataSource(dataSource());
    return userDao;
}

@Bean
public DataSource dataSource() {
    SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

    dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
    dataSource.setUrl("jdbc:mysql://localhost/springbook");
    dataSource.setUsername("spring");
    dataSource.setPassword("book");

    return dataSource;
}

gradle에 dependency 추가 필요

dependencies {
	implementation 'org.springframework:spring-jdbc:5.3.9'
	implementation 'org.springframework:spring-jdbc:5.3.9'
	implementation 'mysql:mysql-connector-java:8.0.26'
}

값 주입을 통한 XML 설정 방식

빈 오브젝트의 레퍼런스 뿐만 아니라 단순 정보도 전달 가능

→ value 애트리뷰트 사용

자바 코드를 통한 연결정보

dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
dataSource.setUrl("jdbc:mysql://localhost/springbook");
dataSource.setUsername("spring");
dataSource.setPassword("book");

XML을 통한 연결정보

<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost/springbook"/>
    <property name="username" value="spring"/>
    <property name="password" value="book"/>
</bean>
  • 스프링은 프로퍼티의 값을 수정자 메소드의 파라미터 타입을 참고로 하여 적절한 형태로 변환해 준다.
    • com.mysql.jdbc.Driver 이걸 클래스 이름으로 변환해서 사용한다.

정리

카테고리:

업데이트: