
1장 오브젝트와 의존관계
스프링은 객체지향 프로그래밍을 추구한다. 그렇다면, 스프링이 집중하는 오브젝트를 먼저 이해해야 한다.
1장에서는 UserDao 클래스를 만들고, 코드를 객체지향에 충실한 코드로 리팩토링 하며 오브젝트와 의존관계에 대해 이해한다.
1.1 초난감 DAD
- DAO(Data Access Object) : DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트
- 자바빈 : 다음 두 가지 관례를 따라 만들어진 오브젝트
- 디폴트 생성자: 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
- 프로퍼티: 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)을 이용해 수정 또는 조회할 수 있다.
1.2 DAO의 분리
- 관심사의 분리 : 관심이 같은 것끼리는 하나의 객체 or 친한 객체, 관심이 다른 것은 가능한 서로 영향을 주지 않도록 분리
UserDao의 크게 분류한 최소 3가지 관심사항 :
- DB Connection (어떤 DB, 어떤 드라이버, 어떤 로그인 정보, 어떻게 커넥션 생성)
- SQL 문장을 담을 Statement를 만들어 정보를 바인딩 시키기고, SQL을 DB를 통해 실행
- 작업이 끝나면 사용한 리소스인 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는 전략패턴의 컨텍스트에 해당한다.
리팩토링 과정
- 메소드 추출을 통해 중복 코드 제거하는 리팩토링
- 팩토리 메소드 패턴을 적용하여 추상화해서 상속을 통한 확장
- 상속을 통해 만들어진 DB 커넥션 코드가 Dao 클래스마다 중복되는 문제 발생
- DB 커넥션을 아예 클래스 분리하는 방향으로 리팩토링 했더니 다시 고객에서 디비 커넥션 코드 노출해야 하는 문제 발생
- 인터페이스 도입을 통해, DB 커넥션 제공하는 클래스에 대한 구체적인 정보 모두 지움, 하지만 클래스 이름을 넣어서 오브젝트를 만들어야 함.
- 관계설정 책임을 통해 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를 적용해 관리할 모든 객체에 대한 생성/관계 설정
애플리케이션 컨텍스트는 객체를 직접 생성하고 할당하는 코드가 없는 대신에 그것에 대한 정보를 별도의 설정 정보를 통해 얻는다. 때로는 외부의 오브젝트 팩토리에 그 작업을 위임하고 결과를 사용하기도 한다.
애플리케이션 컨텍스트 동작 방식
@Configuration
붙여 설정정보 클래스를 등록한다.@Bean
이 붙은 메소드들의 이름을 가져와 빈 목록을 만들어둔다.- 클라이언트로부터
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다.
의존관계 주입
- Static한 클래스 모델, 코드에는 드러나지 않다가 런타임에 의존관계가 드러나야한다. 이를 위해 인터페이스에만 의존해야 한다.
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 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”> |
'토비의스프링vol.1' 카테고리의 다른 글
[토비의 스프링vol.1 6장] AOP - 1 (0) | 2023.04.03 |
---|---|
[토비의 스프링vol.1 5장] 서비스 추상화 (0) | 2023.04.03 |
[토비의 스프링vol.1 4장] 예외처리 (0) | 2023.04.03 |
[토비의 스프링vol.1 3장] 템플릿 (1) | 2022.10.31 |
[토비의 스프링vol.1 2장] 테스트 (0) | 2022.10.07 |