스프링이 자바에서 가장 중요하게 가치를 두는 것 → 객체지향 프로그래밍이 가능한 언어라는 점 스프링이 가장 관심을 많이 두는 대상 → 오브젝트
- 오브젝트를 어떻게 효과적으로 설계하고 구현하고, 사용하고, 개선해 나갈 것인가에 대한 기준을 마련해줌
- 객체지향 기술과 설계, 구현에 관한 실용적인 전략과 검증된 베스트 프랙티스를 평범한 개발자도 자연스럽고 손쉽게 적용할 수 있도록 프레임워크 형태로 제공
1.1 DAO
DAO
DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
자바빈
- 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
- 프로퍼티 : 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)를 이용해 수정 또는 조회할 수 있다.
1.2 DAO의 분리
1.2.1 관심사의 분리
변경이 일어날 때 필요한 작업을 최소화하고 그 변경이 문제를 일으키지 않게 할 수 있는 방법
= 분리와 확장
을 고려한 설계
변화는 대체로 한 가지 관심에 대해 일어나지만, 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많음 ⇒ 한 가지 관심이 한 군데에 집중되게 해야 함
관심사의 분리
: 관심이 같은 것끼리는 하나의 객체 안으로 모이게 하고 관심이 다른 것은 가능한 따로 떨어져서 영향을 주지 않도로고 분리하는 것
리팩토링
기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다.
1.2.3 DB 커넥션 만들기의 독립
상속을 통한 확장
public abstract class UserDao {
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
public Class NUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// N사 DB connection 생성코드
}
}
public Class DUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// D사 DB connection 생성코드
}
}
}
- 클래스 계층 구조를 통해 두 개의 관심이 독립적으로 분리되고, DB 연결 기능을 새롭게 정의한 클래스를 만들 수 있음
- 변경이 용이하다 + 손쉽게 확장된다
슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected메소드등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하는 방법을 디자인 패턴에서 템플릿 메소드 패턴
이라고 한다.
서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴
이라고 부르기도 한다.
상속구조를 통해 성격이 다른 관심사항을 분리한 코드를 만들어내고, 서로 영향을 덜 주도록 했는지를 이해하는 것이 중요하다.
디자인 패턴
소프트웨어 설계시 특정상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션
템플릿 메소드 패턴
상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스에서 만들도록 한다.
팩토리 메소드 패턴
상속을 통해 기능을 확장하게 하는 패턴 슈퍼클래스 코드에서 서브클래스에서 구현할 메소드를 호출하여 필요한 타입의 오브젝트를 가져와 사용 서브클래스에서 오브젝트 생성방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고 이 방식을 통해 오브젝트 생성 방법을 나머지 로직에서 독립시키는 방법
문제점
- 자바는 클래스의 다중상속을 허용하지 않기 때문에 다른 목적으로 상속을 적용하기 힘들다.
- 상속을 통해 관심이 다른 기능을 분리하고, 필요에 따라 다양하게 변신이 가능하도록 확장성도 줬지만 여전히 상속관계는 두 가지 관심사에 대해 긴밀한 결합을 허용함. 서브 클래스는 슈퍼클래스의 기능을 직접 사용할 수 있다. 슈퍼 클래스 내부의 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 잇다. 반대로 그런 변화에 따른 불편을 주지 않기 위해 변화하지 않도록 제약을 가해야 할 수도 있다.
- 확장된 기능인 DB 커넥션을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없다.
1.3 DAO의 확장
추상 클래스를 만들고 이를 상속한 서브클래스에서 변화가 필요한 부분을 바꿔서 쓸 수 있게 만든 이유는 변화의 성격이 다른 것을 분리해서 , 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서다.
1.3.1 클래스의 분리
SimpleConnectionMaker
라는 새로운 클래스를 만들고 DB 생성 기능을 그 안에 넣는다.
UserDao
는 new
키워드를 사용해 SimpleConnectionMaker
클래스의 오브젝트를 만들어두고, add(), get() 메소드에서 사용한다.
문제점 1) SimpleConnectionMaker 메소드 문제
- makeNewConnection()을 이용해 DB 커넥션을 가져오게 했는데 다른 이름의 메소드(openConnection)를 사용했다면 커넥션을 가져오는 코드를 일일이 변경해야 함
Connection c = simpleconnectionMaker.opinConnection()
문제점 2) DB 커넥션을 제공하는 클래스가 어떤 것인지 UserDao가 구체적으로 알고 있어야 함
1.3.2 인터페이스의 도입
클래스를 분리하면서도 문제를 해결하는 방법 = 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것
추상화 : 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업
자바가 추상화를 위해 제공하는 유용한 도구 = 인터페이스 인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다.
인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.
UserDao는 자신이 사용할 클래스가 어떤 것인지 몰라도 되고 인터페이스를 통해 원하는 기능을 사용하기만 하면 된다.
인터페이스는 어떤 일을 하겠다는 기능만 정의해놓은것이다 = 구현 방법은 나타나있지 않음
인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다.
1.3.3 관계설정 책임의 분리
userDao에는 어떤 ConnectionMaker 구현 클래스를 사용할 지 결정하는것 = UserDao와 UserDao가 사용할 ConnectionMaker의 특정 구현 클래스사이의 관계를 설정해주는것에 관한 관심
관심사를 담은 코드를 분리하지 않으면 독립적으로 확장 가능한 클래스가 될 수 없다.
클래스 사이에 관계가 만들어진다는 것은 한 클래스가 인터페이스 없이 다른 클래스를 직접 사용한다는 뜻 = 오브젝트와 오브젝트 사이의 관계를 설정해줘야 함
ConnectionMaker = new DConnectionMaker();
오브젝트 사이의 관계는 런타임 시에 한쪽이 다른 오브젝트의 레퍼런스를 갖고있는 방식으로 생성
오브젝트는 얼마든지 메소드 파라미터 등을 이용해 전달할 수 있으니 외부에서 만든걸 가져올수도 있음
클래스 사이의 관계는 코드에 다른 클래스 이름이 나타나기 때문에 만들어짐 오브젝트 사이의 관계는 X, 특정 클래스를 전혀 알지 못하더라도 해당 클래스가 구현한 인터페이스를 사용했다면, 그 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용할 수 있음 ⇒ 객체지향 프로그램의 다형성 덕분
UserDaoTest
: UserDao와 ConnectionMaker 구현 클래스와의 런타임 오브젝트 의존 관계를 설정하는 책임 담당, 특정 Connection 구현 클래스의 오브젝트를 만들고, UserDao 생성자 파라미터에 넣어 두 개의 오브젝트를 연결해준다
1.3.4 원칙과 패턴
개방 폐쇄 원칙(OCP, Open-Closed Principle)
: 클래스나 모듈은 확장에는 열려있어야 하고 변경에는 닫혀 있어야 한다.
인터페이스를 통해 제공되는 확장 포인트는 확장을 위해 개방되어 있다 인터페이스를 이용하는 클래스는 변화가 불필요하게 일어나지 않도록 폐쇄되어 있다
인터페이스를 사용해 확장 기능을은 정의한 대부분의 API는 이 개방 폐쇄 원칙을 따른다고 볼 수 있다.
객체지향 설계 원칙(SOLID)
- SRP : 단일 책임 원칙
- OCP : 개방 폐쇄 원칙
- LSP : 리스코프 치환 원칙
- ISP : 인터페이스 분리 원칙
- DIP : 의존관계 역전 원칙
높은 응집도와 낮은 결합도
응집도가 높다 = 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다.
높은 응집도
응집도가 높다 = 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다 변경이 일어날 때 모듈의 많은 부분이 함께 바뀐다면 응집도가 높다
낮은 결합도
느슨한 연결 = 관계를 유지하는데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적이고 알 필요도 없게 만들어주는 것 결합도가 낮다 = 변화에 대응하는 속도가 높아지고 구성이 깔끔해진다, 확장하기에 편리하다
전략패턴
자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인패턴
1.4 제어의 역전(IoC)
1.4.1 오브젝트 팩토리
UserDao와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 것 그렇게 만들어진 두 오브젝트가 연결돼서 사용될 수 있도록 관계를 맺어주는 것
으로 기능 분리
팩토리
팩토리 : 분리시킬 기능을 담당한 클래스, 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것
설계도로서의 팩토리
UserDao, ConnectionMaker : 애플리케이션의 핵심적인 데이터 로직과 기술 로직 담당 - 실질적인 로직 DaoFactory : 애플리케이션의 오브젝트들을 구성하고 관계를 정의하는 책임 - 컴포넌트의 구조와 관계를 정의
1.4.2 오브젝트 팩토리의 활용
public class DaoFactory {
public UserDao userDao() {
return new UserDao(new DConnectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(new DConnectionMaker());
}
}
DAO의 생성 기능을 추가하면 오브젝트 생성 코드가 중복되어 나타남 중복문제를 해결하려면? > 분리
public class DaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(connectionMaker());
}
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
1.4.3 제어권의 이전을 통한 제어관계 역전
제어의 역전(IoC) : 프로그램의 제어 흐름 구조가 뒤바뀌는 것
일반적인 흐름 - 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정하고 메소드를 호출하는 작업을 반복
IoC - 일반적인 흐름을 거꾸로 뒤집는 것
IoC에서는 오브젝트가 자신이 사용할 오브젝트를 선택하지도, 생성하지도 않는다. 모든 제어 권한을 자신이 아닌 다른 대상하게 위임한다. 모든 오브젝트는 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.
IoC를 적용함으로써 설계가 깔끔해지고 유연성이 증가하며 확장성이 좋아짐
제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계 설정, 사용, 생명주기 관리등을 관장하는 존재가 필요
1.5 스프링의 IoC
1.5.1 오브젝트 팩토리를 이용한 스프링 IoC
애플리케이션 컨텍스트와 설정정보
빈 : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트 = 오브젝트 단위의 애플리케이션 컴포넌트 = 스프링 컨테이너가 생성과 관계 설정, 사용등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말
빈 팩토리 : 빈의 생성과 관계설정같은 제어를 담당하는 IoC 오브젝트 애플리케이션 컨텍스트 : 빈 팩토리를 확장한 것, IoC 방식을 따라 만들어진 일종의 빈 팩토리
애플리케이션 컨텍스트는 별도의 정보를 참고해서 빈의 생성, 관계 설정 등의 제어 작업을 총괄한다.
DaoFactory를 사용하는 애플리케이션 컨텍스트
@Configuration
: 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식할 수 있도록 하는 애노테이션
@Bean
: 오브젝트를 만들어주는 메소드에 붙여주는 애노테이션
두가지 애노테이션만으로 스프링 프레임워크의 빈 팩토리 또는 애플리케이션 컨텍스트가 IoC 방식의 기능을 제공할 때 사용할 완벽한 설정정보가 되었다.
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);
}
@Configuration
이 붙은 자바코드를 설정 정보로 사용하려면 AnnotationConfigApplicationContext
를 이용한다
getBean()
: ApplicationContext가 관리하는 오브젝트를 요청하는 메소드
1.5.2 애플리케이션 컨텍스트의 동작방식
ApplicationContext
- 빈 팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로 일종의 빈 팩토리
- 애플리케이션에서 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정 담당
- 직접 오브젝트를 생성하고 관계를 맺어주는 코드가 없고 생성정보와 연관관계 정보를 별도의 설정정보를 통해 얻는다.
IoC 원리를 그대로 적용하는 데 애플리케이션 컨텍스트를 사용하는 이유 = 범용적이고 유연한 방법으로 IoC 기능을 확장하기 위해서
애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점
- 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
- 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
- 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
1.5.3 스프링 IoC 용어정리
빈
- 스프링이 IoC 방식으로 관리하는 오브젝트, 관리되는 오브젝트
- 스프링이 직접 생성과 제어를 담당하는 오브젝트만을 빈 이라고 부른다.
빈 팩토리
- 스프링의 IoC를 담당하는 핵심 컨테이너
- 빈을 등록하고, 생성하고, 조회하고, 돌려주고, 그 외 부가적인 빈을 관리하는 기능 담당
애플리케이션 컨텍스트
- 빈 팩토리를 확장한 IoC 컨테이너
- 빈을 등록하고 관리하는 기본적인 빈 팩토리 기능 + 스프링이 제공하는 부가 서비스 추가 제공
설정정보/설정 메타정보
- 애플리케이션 컨텍스트/빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보
컨테이너/IoC 컨테이너
- IoC 방식으로 빈을 관리한다는 의미
1.6 싱글톤 레지스트리와 오브젝트 스코프
1.6.1 싱글톤 레지스트리로서의 애플리케이션 컨텍스트
애플리케이션 컨텍스트 = IoC 컨테이너 = 싱글톤을 저장하고 관리하는 싱글톤 레지스트리
서버 애플리케이션과 싱글톤
스프링은 엔터프라이즈 시스템을 위해 고안된 기술이기 때문에 서버환경에서 사용될 때 가치가 있다.
서블릿 : 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트
- 대부분 멀티스레드 환경에서 싱글톤으로 동작
- 서블릿 클래스 당 하나의 오브젝트만 만들어두고 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.
애플리케이션 안에 제한된 수, 한개의 오브젝트만 만들어서 사용하는 것 = 싱글톤 패턴의 원리
싱글톤 패턴의 한계
- private 생성자를 갖고있기 때문에 상속할 수 없다
- 싱글톤은 테스트하기가 힘들다
- 서버환경에서는 싱글톤이 하나만 만들어지는것을 보장하지 못한다
- 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다
싱글톤 레지스트리
싱글톤 레지스터리 : 스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는 것
- 싱글톤을 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너
- 평범한 자바 클래스를 싱글톤으로 활용하게 해준다
싱글톤 방식으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있어 간단히 오브젝트를 생성해서 사용할 수 있다. 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고 테스트를 위한 목 오브젝트로 대체하는것도 간단하다.
싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴등을 적용하는 데 아무런 제약이 없다
1.6.2 싱글톤과 오브젝트의 상태
싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다.
싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우 → 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 생성해야 함
상태가 없는 방식으로 클래스를 만드는 경우 → 각 요청에 대한 정보나 DB나 서버의 리소스로부터 생성한 정보는 파라미터와 로컬 변수, 리턴값을 이용해서 다룸
스프링 싱글톤 빈으로 사용되는 클래스를 만들 때 기존의 UserDao처럼 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으면서 사용하게 함
connectionMaker는 읽기 전용 정보이기 때문에 인스턴스 변수로 사용 가능 ⇒ 자신이 사용하는 다른 싱글톤 빈을 저장하려는 용도라면 인스턴스 변수 사용 가능
1.6.3 스프링 빈의 스코프
스코프 : 빈이 생성되고 존재하고 적용되는 범위
기본 스코프 = 싱글톤
- 컨테이너 내 한개의 오브젝트만 만들어져서 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지됨
1.7 의존관계 주입(DI)
1.7.1. 제어의 역전과 의존관계 주입
스프링이 제공하는 IoC 방식을 의존 관계 주입이라는 이름을 사용함
1.7.2 런타임 의존관계 설정
의존관계
- A가 B에 의존하고 있다
- B가 변하면 A에 영향을 미친다.
의존관계에는 방향성이 있다.
- B는 A에 의존하지 않는다
- B는 A의 변화에 영향을 받지 않는다
의존관계 주입이란 다음 조건을 충족하는 작업을 말한다.
- 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
- 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제 3의 존재가 있다는 것이다.
DI 컨테이너 : 의존관계 주입을 담당하는 컨테이너
- 자신이 결정한 의존관계를 맺어줄 클래스의 오브젝트를 만들고 이 생성자의 파라미터로 오브젝트의 레퍼런스를 전달해준다.
1.7.3 의존관계 검색과 주입
의존관계 검색
- 외부로부터의 주입이 아니라 스스로 검색을 이용함
- 의존관계 검색 방식에서는 검색하는 오브젝트는 스프링 빈일 필요가 없음
의존관계 주입을 원하는 오브젝트는 자기 자신이 컨테이너가 관리하는 빈이 되어야 함
1.7.4 의존관계 주입의 응용
의존관계 주입의 장점
- 모든 객체지향 설계 와 프로그래밍의 원칙을 따랐을 때 얻을 수 있는 장점이 그대로 적용됨
- 코드에는 런타임 클래스에 대한 의존관계가 나타나지 않음
- 다른 책임을 가진 사용 의존 관계에 있는 대상이 바뀌거나 변경되더라도 자신은 영향을 받지 않음
- 변경을 통한 다양한 확장 방법에는 자유로움
기능 구현의 교환
개발 환경과 운영 환경에서 DI 설정정보를 다르게 만들어두면 나머지 코드에는 손대지 않고 개발시와 운영시에 각각 다른 런타임 오브젝트 의존관계를 갖게 해줘서 문제를 해결할 수 있다.
부가 기능 추가
기존 코드의 수정없이 컨테이너가 사용하는 설정 정보만 수정해서 런타임 의존관계만 새롭게 정의하면 됨
1.7.5 메소드를 이용한 의존관계 주입
생성자가 아닌 메소드를 이용해 의존 오브젝트와 관계를 주입해주는 방법
- 수정자 메소드를 이용한 주입
- 오브젝트 내부의 애트리뷰트 값을 변경하려는 용도로 주로 사용
- 파라미터로 전달된 값을 보통 내부의 인스턴스 변수에 저장하는 것
- 일반 메소드를 이용한 주입
- 한 번에 여러개의 파라미터를 받을 수 있음
1.8 XML을 이용한 설정
XML을 이용해 DI 의존관계 설정정보를 만들 수 있다.
이일민, [토비의 스프링 3.1], 에이콘 출판사(2012)