🙇
- 지식재산권을 고려하여 해당 시리즈 게시글이 비공개로 전환될 수 있습니다
[토비의 스프링 Vol.1 ] - 스프링의 이해와 원리
스프링은 자바를 기반으로 한 기술입니다.
스프링이 자바에서 가장 중요하게 가치를 두는 것은 바로 객체지향 프로그래밍이 가능한 언어라는 점이다.
DAO(Data Access Object) : DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말합니다.
public class User {
String id;
String name;
String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
USERS 테이블 필드 구성
필드명 | 타입 | 설정 |
---|---|---|
Id | VARCHAR(10) | Primary Key |
Name | VARCHAR(20) | Not Null |
Password | VARCHAR(20) | Not Null |
JDBC를 이용하는 작업의 일반적인 순서
개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로 "미래의 변화를 어떻게 대비할 것인가"입니다.
객체 지향 기술은 흔히 실세계를 최대한 가깝게 모델링해낼 수 있기 때문에 의미가 있다고 여겨진다고 합니다. 하지만, 그보다 객체지향 기술이 만들어내는 가상의 추상세계 자체를 효과적으로 구성할 수 있고, 이를 자유롭고 편리하게 변경, 발전, 확장시킬 수 있다는 것!!
이것에 더 의미가 있습니다.
변화는 대체로 집중된 한 가지 관심에 대해 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다고 합니다.
변화가 한 번에 한 가지 관심에 집중돼서 일어난다면, 우리가 준비해야 할 일은 한 가지 관심이 한 군데에 집중되게 하는 것입니다. 즉 관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있도록 하는 것입니다.
결합도 DOWN, 응집도 UP
프로그래밍의 기초 개념 중에 관심사의 분리라는 게 있습니다.
관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고,
관심이 다른 것은 되도록 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것입니다.
중복된 DB 연결 코드를 getConnection() 이라는 이름의 독립적인 메소드로 만들어둡니다.
각 DAO 메소드에서는 이렇게 분리한 getConnection() 메소드를 호출해서 DB 커넥션을 가져오도록 합니다.
여러 메소드에 중복돼서 등장하는 특정 관심사항이 담긴 코드를 별도의 메소드로 분리해냈습니다.
기능이 추가되거나 바뀐 것은 없지만 UserDao는 이전보다 훨씬 깔끔해졌고 미래의 변화에 좀 더 손쉽게 대응할 수 있는 코드가 되었습니다!
이런 작업을 리팩토링이라고 합니다. 또한 getConnection() 이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는
메소드 추출기법이라고 부릅니다.
다른 종류의 DB 사용, DB 커넥션을 가져오는데 있어 독자적으로 만든 방법을 적용하고 싶다면?
DAO의 핵심 기능인 "어떻게 데이터를 등록하고 가져올 것인가"라는 관심을 담당하는 UserDao와, "DB 연결 방법은 어떻게 할 것인가"라는 관심을 담고있는 서브클래스가
클래스 레벨로 구분이 되고 있습니다.
클래스 계층구조를 통해 두 개의 관심이 독립적으로 분리되면서 변경 작업은 한층 용이해졌습니다.
슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를
필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴이라고 한다고 합니다.
UserDao의 서브클래스의 getConnection() 메소드는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이라고도 볼 수 있습니다.
이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴이라고 부릅니다.
UserDao는 Connection 오브젝트가 만들어지는 방법과 내부 동작 방식에는 상관없이 자신이 필요한 기능을 Connection 인터페이스를 통해 사용하기만 할 뿐입니다.
매우 깔끔한 방식으로 관심사항을 분리해 상하위 클래스에 나눠 담도록 만든 것입니다.
디자인 패턴이란?
이렇게 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고,
이 방식을 통해 오브젝트 생성 방법과 나머리 로직, 즉 슈퍼클래스의 기본 코드에서 독립시키는 방법을 팩토리 메소드 패턴이라고 한다.
- 이 방법은 상속을 사용했다는 단점이 있다. 자바는 클래스의 다중상속을 허용하지 않기 때문이다.
- 또 다른 문제는 상속을 통한 상하위 클래스의 관계는 생각보다 밀접하다는 점이다. 서브 클래스는 슈퍼 클래스의 기능을 직접 사용할 수 있기 때문에
슈퍼클래스 내부의 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 있다.- 확장된 기능은 DB 커넥션을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없다는 것도 큰 단점이다.
DB 커넥션과 관련된 부분을 서브클래스가 아니라, 아예 별도의 클래스에 담습니다.
그리고 이렇게 만든 클래스를 UserDao가 이용하게 하면 됩니다.
UserDao 클래스만 공급하고 상속을 통해 DB 커넥션 기능을 확장해서 사용하게 했던게 다시 불가능해졌습니다. UserDao의 코드가 특정 Connection클래스에 종속되어 있기 때문입니다.
connectionMaker = new DConnectionMaker();
하지만 위의 코드처럼 초기에 한 번 어떤 클래스의 오브젝트를 사용할지를 결정하는 생성자의 코드는 제거되지 않고 남아 있습니다. (다시 원점)
new DConnectionMaker();
오브젝트 사이의 관계를 설정해주면 됩니다.
클라이언트와 같은 제 3의 오브젝트인 UserDaoTest라는 이름의 클래스를 하나 만들고 UserDao 오브젝트가 사용할 ConnectionMaker 오브젝트를 전달해봅니다.
UserDaoTest는 UserDao와 ConnectionMaker 구현 클래스와의 런타임 오브젝트 의존 관계를 설정하는 책임을 담당해야 합니다.
클라이언트인 UserDaoTest가 이렇게 수고해준 덕분에 이제는 UserDao의 변경 없이도 자유롭게 각각의 자신들을 위한 DB 접속 클래스를 만들어서 UserDao가 사용하게 할 수 있습니다.
'클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.'
UserDao는 DB 연결 방법이라는 기능을 확장하는 데는 열려 있다. UserDao에 전혀 영향을 주지 않고도 얼마든지 기능을 확장할 수 있게 되어 있다. 동시에 UserDao 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않고 유지할 수 있으므로 변경에는 닫혀 있다.
개방 폐쇄 원칙은 높은 응집도와 낮은 결합도라는 소프트웨어 개발의 고전적인 원리로도 설명이 가능하다. 응집도가 높다는 건 하나의 모듈, 클래스가 하나의 책임 또는 괌심사에만 집중되어 있다는 뜻이다. 불필요하거나 직접 관련이 없는 외부의 관심과 책임이 얽혀 있지 않으며, 하나의 공통 관심사는 한 클래스에 모여 있다.
전략 패턴은 자신의 기능 맥락에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다. UserDao는 전략 패턴의 컨텍스트에 해당한다. 컨텍스트는 자신의 기능을 수행하는데 필요한 기능 중에서 변경 가능한, DB 연결 방식이라는 알고리즘을 ConnectionMaker라는 인터페이스로 정의하고, 이를 구현한 클래스, 즉 전략을 바꿔가면서 사용할 수 있게 분리했다.
전략 패턴은 UserDaoTest와 같은 클라이언트의 필요성에 대해서도 잘 설명하고 있다. 전략 패턴의 적용 방법을 보면 클라이언트의 역할이 잘 설명되어 있다. 컨텍스트를 사용하는 클라이언트는 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해 제공해주는 게 일반적이다.
UserDaoTest는 UserDao의 기능이 잘 동작하는지를 테스트하려고 만든 것인데, 지금은 또 다른 책임까지 떠맡고 있으니 이것도 분리하자. 분리될 기능은 UserDao와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 것과, 그렇게 만들어진 두 개의 오브젝트가 연결돼서 사용될 수 있도록 관계를 맺어주는 것이다.
분리시킬 기능을 담당할 클래스를 하나 만들면, 이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것인데, 이런 일을 하는 오브젝트를 흔히 팩토리라고 부른다. 단지 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용하는 것이다. 어떻게 만들지와 어떻게 사용할지는 분명 다른 관심이다.
UserDao가 아닌 다른 DAO의 생성 기능이 추가 되면 팩토리에 new DConnectionMaker()의 코드가 중복 발생한다.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(new DConnectionMaker());
}
public AccountDao accounDao() {
return new AccountDao(new DConnectionMaker());
}
public MessageDao messageDao() {
return new MessageDao(new DConnectionMaker());
}
}
중복된 코드는 분리해 내자.
ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 코드를 별도의 메소드로 뽑아내자.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaker());
}
public AccountDao accounDao() {
return new AccountDao(connectionMaker());
}
public MessageDao messageDao() {
return new MessageDao(connectionMaker());
}
public ConnectonMaker connectionMaker() {
return new DConnctionMaker();
}
}
각 오브젝트는 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다. 모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지를 스스로 관장한다. 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조다.
제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이다. 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
원래 ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 제어권은 UserDao에게 있었다. 그런데 지금은 DaoFacroty에게 있다. 자신이 어떤 ConnectionMaker 구현 클래스를 만들고 사용할지를 결정할 권한을 DaoFactory에 넘겼으니 UserDao는 이제 능동적이 아니라 수동적인 존재가 됐다. UserDao 자신도 팩토리에 의해 수동적으로 만들어지고 자신이 사용할 오브젝트도 DaoFactory가 만들고 초기화해서 자신에게 사용하도록 공급해주는 ConnectionMaker를 사용할 수 밖에 없다. 더욱이 UserDao와 ConnectionMaker의 구현체를 생성하는 책임도 DaoFactory가 맡고 있다. 바로 이것이 제어의 역전이 일어난 상황이다.
스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다. 오브젝트 단위의 애플리케이션 컴포넌트이다. 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다.
스프링에서는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리라고 부른다. 보통 빈 팩토리보다는 이를 좀 더 확장한 애플리케이션 컨텍스트를 주로 사용한다. 애플리케이션 컨텍스트는 IoC 방식을 따라 만들어진 일종의 빈 팩토리라고 생각하면 된다. 빈 팩토리와 애플리케이션 컨텍스트라는 용어를 동일하다고 생각하면 된다. 빈 팩토리라고 말할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것이고, 애플리케이션 컨텍스트라고 말할 때는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 좀 더 부각된다고 보면 된다.
스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식할 수 있도록 @Configuration이라는 애노테이션을 추가한다. 그리고 오브젝트를 만들어주는 메소드에는 @Bean이라는 애노테이션을 붙여준다. 또한 ConnectionMaker 타입의 오브젝트를 생성해주는 connectionMaker() 메소드에도 @Bean을 붙여준다.
이 두 가지 애노테이션만으로 스프링 프레임워크의 빈 팩토리 또는 애플리케이션 컨텍스트가 IoC 방식의 기능을 제공할 때 사용할 완벽한 설정정보가 된 것이다.
애플리케이션 컨텍스트는 ApplicationContext 타입의 오브젝트다. ApplicationContext를 구현한 클래스는 여러 가지가 있는데 DaoFatory처럼 @Configuration이 붙은 자바 코드를 설정정보로 사용하려면 AnnotationConfigApplicationContext를 이용하면 된다. 애플리케이션 컨텍스트를 만들 때 생성자 파라미터로 DaoFactory 클래스를 넣어준다. 이제 이렇게 준비된 ApplicationContext의 getBean()이라는 메소드를 이용해 UserDao의 오브젝트를 가져올 수 있다.
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가 관리하는 오브젝트를 요청하는 메소드다. getBean()의 파라미터인 "userDao"는 ApplicationContext에 등록된 빈의 이름이다. DaoFactory에서 @Bean이라는 애노테이션을 userDao라는 이름의 메소드에 붙였는데, 이 메소드 이름이 바로 빈의 이름이 된다.
오브젝트 팩토리에 대응되는 것이 스프링의 애플리케이션 컨텍스트다. 스프링에서는 이 애플리케이션 컨텍스트를 IoC 컨테이너라 하기도 하고, 간단히 스프링 컨테이너라고 부르기도 한다. 또는 빈 팩토리라고 부를 수도 있다.
애플리케이션 컨텍스트는 DaoFactory 클래스를 설정정보로 등록해두고 @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만들어둔다. 클라이언트가 애플리케이션 컨텍스트의 getBean() 메소드를 호출하면 자신의 빈 목록에서 요청한 이름이 있는지 찾고, 있다면 빈을 생성하는 메소드를 호출해서 오브젝트를 생성시킨 후 클라이언트에 돌려준다. DaoFactory를 오브젝트 팩토리로 직접 사용했을 때와 비교해서 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점
스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.
애플리케이션 컨텍스트는 우리가 만들었던 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너다. 그러면서 동시에 이 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.
매번 클라이언트에서 요청이 올 때 마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용하면 부하가 심하여 서버가 감당하기 힘들다. 따라서 스프링은 싱글톤으로 빈을 만든다.
자바에서 싱글톤을 구현하는 방법
일반적으로 싱글토 패턴 구현 방식에는 다음과 같은 문제가 있다.
스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리다. 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다. IoC 방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다. 오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문이다.
싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다. 따라서 상태 관리에 주의를 기울여야 한다. 기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태 정보를 내부에 갖고 있지 않은 무상태방식으로 만들어져야 한다. 그리고 각 요청에 대한 정보나, DB나 서버의 리소스로부터 생성한 정보는 파라미터와 로컬 변수, 리턴 값 등을 이용하면 된다. 메소드 파라미터나, 메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일은 없다.
스프링이 관리하는 오브젝트, 즉 빈이 생성되고, 존재하고, 적용되는 범위를 빈의 스코프라고 한다. 스프링 빈의 기본 스코프틑 싱글톤이지만, 경우에 따라서는 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어주는 프로토타입, 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성되는 요청(request) 스코프, 웹의 세션과 스코프가 유사한 세션(session)스코프가 있다.
B가 변하면 그것이 A에 영향을 미친다. B의 기능이 추가되거나 변경되거나, 형식이 바뀌거나 하면 그 영향이 A로 전달된다.
UserDao가 ConnectionMaker 인터페이스를 사용한다. UserDao가 ConnectionMaker 인터페이스에만 의존하고 있다. 따라서 ConnectionMaker 인터페이스가 변한다면 그 영향을 UserDao가 직접적으로 받게 된다. 하지만 ConnectionMaker 인터페이스를 구현한 클래스가 다른 것으로 바뀌거나 그 내부에서 사용하는 메소드에 변화가 생겨도 UserDao에 영향을 주지 않는다. 이렇게 인터페이스에 대해서만 의존관계를 만들어두면 인터페이스 구현 클래스와의 관계는 느슨해지면서 변화에 영향을 덜 받는 상태가 된다. 결합도가 낮다고 설명할 수 있다.
프로그램이 시작되고 UserDao 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상, 즉 실제 사용대상인 오브젝트를 의존 오브젝트라고 말한다. 의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.
의존관계 주입이란 다음과 같은 세 가지 조건을 충족하는 작업을 말한다.
의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다는 것이다.
의존관계 검색은 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. 물론 자신이 어떤 클래스의 오브젝트를 이용할지 결정하지는 않는다. 의존관계 검색은 런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성작업은 외부 컨테이너에게 IoC로 맡기지만, 이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용한다. 미리 정해놓은 이름을 전달해서 그 이름에 해당하는 오브젝트를 찾게 된다. 따라서 이를 일종의 검색이라고 볼 수 있으며, 그 대상이 런타임 의존관계를 가질 오브젝트이므로 의존관계 검색이라고 부르는 것이다.
스프링의 IoC 컨테이너인 애플리케이션 컨텍스트는 getBean()이라는 메소드를 제공한다. 바로 이 메소드가 의존관계 검색에 사용되는 것이다.
의존관계 검색과 의존관계 주입 방법 중 주입 쪽이 훨씬 단순하고 깔끔하다.
스프링은 전통적으로 메소드를 이용한 DI 방법 중에서 수정자 메소드를 가장 많이 사용해왔다.
스프링은 DaoFactory와 같은 자바 클래스를 이용하는 것 외에도, 다양한 방법을 통해 DI 의존관계 설정정보를 만들 수 있다. 가장 대표적인 것이 바로 XML이다.
XML은 단순한 텍스트 파일이기 때문에 다루기 쉽다. 또, 쉽게 이해할 수 있으며 컴파일과 같은 별도의 빌드 작업이 없다는 것도 장점이다.
하나의 @Bean 메소드를 통해 얻을 수 있는 빈의 DI 정보는 다음 세 가지다.
자바에서는 DB 커넥션을 가져오는 오브젝트의 기능을 추상화해서 비슷한 용도로 사용할 수 있게 만들어진 DataSouce라는 인터페이스가 존재한다.
ref 대신에 value를 사용하여 주입: 일종의 DI
스프링이 프로퍼티의 값을, 수정자 메소드의 파라미터 타입을 참고로 해서 적절한 형태로 변환해준다. setDriverClass() 메소드의 파라미터 타입이 Class임을 확인하고 "com.mysql.jdbc.Driver"라는 텍스트 값을 com.mysql.Driver.class 오브젝트로 자동 변경해주는 것이다.
<?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-3.0.xsd">
<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>
<bean id="userDao" class="springbook.user.dao.UserDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
스프링이란 '어떻게 오브젝트가 설계되고, 만들어지고, 어떻게 관계를 맺고 사용되는지에 관심을 갖는 프레임워크'
스프링의 관심은 오브젝트와 그 관계다. 하지만 오브젝트를 어떻게 설계하고, 분리하고, 개선하고, 어떤 의존관계를 가질지 결정하는 일은 스프링이 아니라 개발자의 역할이며 책임이다. 스프링은 단지 원칙을 잘 따르는 설계를 적용하려고 할 때 필연적으로 등장하는 번거로운 작업을 편하게 할 수 있도록 도와주는 도구일 뿐이다.