Today I Learned_230423
토비의 스프링 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의 동작방식
- 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 컨테이너, 애플리케이션 컨텍스트를 포함한 스프링이 제공하는 모든 기능
싱글톤 레지스트리와 오브젝트 스코프
오브젝트의 동등성과 동일성
- 동일성 (identity)
- 두 오브젝트가 완전히 동일한지
- == 연산자로 비교
- 동등성 (equality)
- 두 오브젝트가 동일한 정보를 담고 있는지
- equals() 메소드로 비교
싱글톤 레지스트리로서의 애플리케이션 컨텍스트
- ApplicationContext는 기본적으로 싱글톤 레지스트리에 해당
- 싱글톤을 저장하고 관리
싱글톤을 사용하는 이유?
- 스프링은 대부분 서버환경에서 적용되고 사용됨
- 클라이언트에서 요청이 올 때마다 오브젝트를 새로 만들면 서버에 부하가 걸린다.
- 싱글톤 패턴을 사용하면 애플리케이션 내에 한 개의 오브젝트만 만들어서 사용할 수 있다.
싱글톤 패턴
- 어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수, 주로 하나만 존재하도록 강제하는 패턴
- 애플리케이션 내에서 전역적으로 접근이 가능
- 단일 오브젝트만 존재
- 매우 조심해서 사용해야 하며, 안티패턴의 일종
싱글톤 패턴의 한계
- 클래스 밖에서 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
- 상속이 불가능 → 객체지향적인 설계의 장점을 적용하기 어렵다.
- 생성된 싱글톤 오브젝트를 저장하기 위한 스태틱 필드 정의
- 객체지향의 특징이 적용되지 않는 스태틱 필드
- 스태틱 팩토리 메소드를 만들고, 최초로 호출되는 시점에서 한 번만 오브젝트가 만들어지게 함
- 객체지향의 특징이 적용되지 않은 스태틱 메소드
- 한번 오브젝트가 만들어지고 난 다음에는 이미 만들어진 오브젝트 넘겨줌
- 만들어지는 방식이 제한적이므로 테스트 불가능
- 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다
- 클래스 로더의 구성에 따라 하나 이상의 오브젝트가 만들어 질 수 있음
- 싱글톤의 사용은 전역 상태를 만들 수 있기에 바람직하지 못하다.
싱글톤 레지스트리 (singleton registry)
- 자바의 기본적인 싱글톤 패턴에는 여러 가지 단점이 있음
- 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공
- 스프링 컨테이너는 싱글톤을 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너다.
- 평범한 자바 클래스를 싱글톤으로 활용하게 도와줌
싱글톤과 오브젝트의 상태
멀티스레드 환경에서는 여러 스레드가 동시에 접근할 수 있으므로 상태 관리에 주의해야 한다.
- 무상태 방식으로 만들어져야 함
- 여러 클라이언트가 하나의 변수를 수정하면 위험
- 읽기 전용의 값이라면 무방 → static이나 static final로 선언
- DB나 서버의 리소스로부터 생성한 정보는 파라미터, 로컬 변수, 리턴 값 등을 이용
- 이들은 매번 새로운 값을 저장한 독립적인 공간이 만들어지므로 싱글톤이라고 해도 덮어쓰기가 불가능
- 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나 파라미터로 주고받으며 사용해야 함
스프링 빈의 스코프
스코프(scope) : 빈이 생성되고, 존재하고, 적용되는 범위
- 싱글톤(singleton) : 기본 스코프. 컨테이너 내에 한 개의 오브젝트만 만들어져서 강제로 제거되지 않는 한 스프링 컨테이너 존재 동안 계속 유지됨
- 프로토타입(prototype) : 컨테이너에 빈을 요청할 때 마다 새로운 오브젝트 생성
- 요청(request) : 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성
- 세션(session) : 웹의 세션과 스코프가 유사
의존관계 주입
제어의 역전과 의존관계 주입
스프링 IoC 컨테이너 → 객체를 생성하고 관계를 맺어주는 등의 작업을 담당하는 기능을 일반화
의존관계 주입 (Dependency Injection)
- 스프링이 제공하는 IoC방식을 지칭
- 오브젝트 레퍼런스를 외부로부터 제공받고 다른 오브젝트와 동적으로 의존관계 생성
런타임 의존관계 설정
- 의존관계는 점선으로 된 화살표로 표현
- A가 B에 의존한다.
- 화살표 끝부분에 의존한다.
- 의존의 뜻 : B가 변하면 A에 영향을 미친다.
- 사용의 관계에 있는 경우 의존관계가 있다고 한다.
- 의존관계는 방향성이 있어, 역은 성립하지 않는다.
- 위 그림과 같이 인터페이스에 대해서만 의존관계를 만들면 결합도를 낮출 수 있다 → 변경에서 자유로워진다
런타임 의존관계(오브젝트 의존관계)
- 런타임 시에 오브젝트 사이에서 만들어지는 의존관계
- 모델이나 코드 상에서 드러나지 않는다.
의존 오브젝트(dependent object)
- 프로그램이 시작되고 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상
의존관계 주입
- 의존 오브젝트와 클라이언트 오브젝트(사용할 주체)를 런타임 시에 연결해주는 작업
제 3의 존재? 관계설정 책임을 가진 코드를 분리해서 만들어진 오브젝트
→ 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등
의존관계 주입을 위한 코드
// 컨테이너가 connectionMaker를 외부에서 따로 만들어서 생성자로 넣어준다.
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;// 오브젝트의 레퍼런스 전달
}
- DI 컨테이너는 의존관계를 맺어줄 클래스의 오브젝트를 만들고 생성자의 파라미터로 오브젝트의 레퍼런스를 전달해준다.
- 전달받은 오브젝트는 인스턴스 변수에 저장
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 |
- class 애트리뷰트에는 메소드의 리턴 타입이 아닌 오브젝트를 만들때 사용하는 클래스 이름이 들어가야 함.
자바 코드 ↔ XML 대응 코드
@Bean // <bean
public ConnectionMaker connectionMaker() { // id = "connectionMaker"
return new DConnectionMaker(); // class = "com.example.demo.dao.DconnectionMaker" />
}
XML을 이용한 의존관계 주입 정보 기술
- 수정자 메소드를 사용해 의존관계 주입을 한다고 가정
- 수정자 메소드는 자바빈 관례에 따라 프로퍼티가 된다.
-
태그를 사용해 의존관계 정의 - name : DI에 사용할 수정자 메소드의 프로퍼티 이름
- ref : 수정자 메소드를 통해 주입해줄 오브젝트의 빈 이름
- 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 이걸 클래스 이름으로 변환해서 사용한다.