토비의 스프링 vol.1 1장 오브젝트와 의존관계 내용으로
자바 JDBC부터 스프링까지 변화과정을 객체지향으로 설명하며, 이를 정리한다.
예제를 따라하는 실행하는 것보단 예제코드를 보면서 객체지향에 대해 생각해본다.
자세한 내용이 궁금하면 읽는 걸 추천한다.
1장을 시작하며
스프링은 자바 기반 기술이며, 객체지향 프로그래밍 가치를 중요하게 생각한다.
스프링이 관심을 두는 대상은 오브젝트다. 이 오브젝트를 이해해야 스프링을 이해할 수 있다.
오브젝트 관심을 오브젝트 설계로 발전한다. 객체지향 설계의 기초와 원칙을 비롯해서, 디자인패턴, 리팩토링, 단위 테스트와 같은 오브젝트 설계와 구현에 여러 가지 응용 기술과 지식이 요구된다.
이번 장에서는 오브젝트의 설계와 구현, 동작원리를 알아보자.
초난감DAO
JDBC API를 사용하여 저장하고 조회하는 간단한 DAO를 작성한다.
DAO(Data Access Object)는 DB를 사용해 데이터를 조회, 조작하는 기능을 전담하는 오브젝트다.
사용자 정보를 저장할 자바 빈 User 클래스
public class User {
String id;
String name;
String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
...
}
DB에 넣고 관리하는 DAO 클래스
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) value(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
c.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
main() 메서드를 이용해서 테스트를 시도한다.
public class UserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao();
User user = new User();
user.setPassword("study");
user.setId("loop");
user.setName("반복");
userDao.add(user);
System.out.println(user.getId() + " 등록 성공");
User user2 = userDao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + " 조회 성공");
}
}
테스트 실행 결과
loop 등록 성공
반복
study
loop 조회 성공
초난감DAO 문제점
저장, 조회 기능이 제대로 작동하고 결과까지 문제없다. 하지만 다른 개발자가 보면 당황하는 초난감 코드다.
기능이 정상적으로 동작하는데 무엇이 문제일까? 개선해야 하는 이유는? 개선하는 장점은?
스프링을 공부한다는 건 바로 이런 문제 제기와 의문에 대한 답을 찾아나가는 과정이다.
관심사의 분리
프로그래밍의 기초 개념 중에 관심사의 분리가 있다. 관심이 같은 것끼리 하나의 객체나 친한 객체로 모으고, 관심이 다른 것은 서로 떨어뜨려 영향을 주지 않도록 분리하는 거다. UserDao 코드로 관심사항을 파악해보자.
UserDao의 관심사항
1. DB와 연결을 위한 커넥션에 대한 관심
2. DB에 보낼 SQL 문장을 담은 Stetement 만들고 실행에 대한 관심
3. 작업 종료 후 공유 리소스를 시스템에 반환에 대한 관심
중복 코드의 메소드 추출
UserDao를 보면 관심사항인 DB 커넥션이 메소드마다 중복되고 있다.
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
모두 DB에 연결하는 코드로 메서드마다 필수적으로 있어야 한다. 메서드 개수만큼 생기는 중복 코드다.
문제는 변경이 생기면 개수만큼 수정해야 하니 이를 방지하기 위해 하나의 메소드로 추출하자.
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
return c;
}
공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 메소드 추출이라고 한다.
main()을 실행하여 테스트가 잘 되는지 확인해보자.
DB 커넥션의 독립
위의 코드가 잘 되어 여러 회사에 납부가 된다면 DB 연결 로직이 회사에 맞게 변경되어야 한다. 이를 위해 getConnection을 추상 메서드로 만들고 납부하자.
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
이제 고객들은 추상 클래스를 상속받아 원하는 DB로 연결할 것이다.
class NUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// N 사 DB 커넥션 코드
}
}
class DUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// D 사 DB 커넥션 코드
}
}
변경된 코드를 보면 SQL 문장을 실행하는 UserDao와 DB 연결을 담당하는 NUserDao, DUserDao가 클래스 레벨로 구분되고 관심도 분리되었다. 이제 UserDao는 코드 변경 없이 상속을 통해 DB 연결을 바꾸기만 하면 된다.
이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고 추상메서드로 서브클래스에 맞게 구현시키는 것을 템플릿 메소드 패턴이라 한다.
여기서 getConnection은 어떤 Connection 오브젝트 생성을 결정하는데 이런 구체적인 오브젝트 생성 방법을 결정하는 것을 팩토리 메소드 패턴이라 한다.
DAO의 확장
위처럼 분리한 오브젝트는 관심사에 따라 변화의 특징이 있고, 변화의 성격이 다르다. 변화의 성격이 다른 것은 변화의 이유와 시기, 주기 등이 다르다는 의미다.
추상 클래스 덕분에 관심사를 분리했지만, 상속이 아닌 다른 방법으로 해보자.
클래스의 분리
DB 연결을 담당하는 클래스를 독립적으로 만들어보자.
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
this.simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.getConnection();
...
}
}
public class SimpleConnectionMaker {
public Connection getConnection() throws ClassNotFoundException, SQLException {
...
}
}
이전에 비해 코드가 많이 변했지만 기능에 변화는 없다. 기능에 변화가 없는 것이 리팩토링에 대한 전제다.
하지만 DB 연결을 확장하던 방식이 사라졌다. DB 연결이 SimpleConnectionMaker에 종속되어 버렸기 때문이다.
또한 UserDao는 DB 커넥션을 제공하는 SimpleConnectionMaker를 구체적으로 알아야한다.
인터페이스의 도입
이런 문제를 해결하기 위해서는 중간에 추상적인 느슨한 연결고리를 만드는 것이다.
추상화란 공통적인 성격을 뽑아내 따로 분리해내는 작업이다. 자바에서는 추상화를 제공하는 도구는 인터페이스다.
인터페이스는 구현에 대한 구체적인 정보를 모두 감춘다. 이를 통해 접근하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class DConnectionMaker implements ConnectionMaker{
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
// D사 Connection 로직
}
}
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
this.connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
...
}
}
인터페이스를 이용해서 DB 커넥션 클래스에 대한 구체적인 정보는 제거되었다. 하지만 생성자를 보면 new DConnectionMaker() 남아있다. 결국은 원점이다.
관계설정 책임의 분리
문제는 new DConnectionMaker()라는 구현클래스 코드가 남아있어서 DB 연결에 대한 관심이 제대로 분리되지 않았다고 봐야한다.
UserDao를 사용하는 클라이언트가 여럿이 있다면 클라이언트가 사용하는 DB에 관계없이 강제로 DConnectionMaker에 고정된 DB를 사용하게 된다.
지금까지 UserDao 내부에서 직접 오브젝트를 만들었지만 외부에서 오브젝트를 가져오는 방법도 존재한다.
외부에서 오브젝트를 받으려면 생성자 파라미터나 메서드 파라미터를 이용하면 된다.
// UserDao 생성자 수정
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
UserDao 내부에서 구현 클래스 관계 맺는 책임을 외부로 던졌다. 이를 사용하는 클라이언트는 다음과 같이 수정하면 된다.
// 클라이언트
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao(new DConnectionMaker());
...
}
UserDao는 이제 SQL을 생성하고 실행하는 작업에만 집중할 수 있게 되었다.
DB 연결에 대한 책임은 더 이상 없다. 클라이언트에게 떠넘겼기 때문이다.
이렇게 인터페이스를 이용해서 클라이언트의 도움을 받는 것은 상속에 비해 훨씬 유연하다.
main()을 실행하여 테스트가 잘 되는지 확인해보자.
원칙과 패턴
개방 폐쇄 원칙(Open Close Principle)은 깔끔한 설계를 위한 객체지향 설계 원칙 중 하나다. '클래스나 모듈은 확장에 열려있어야 하고 변경에는 닫혀있어야 한다'의 뜻을 가진다.
초난감DAO 코드는 이 원칙을 지키지 못한 구조를 보여준다. 잘 설계된 객체지향의 구조를 보면 OCP를 잘 지키고 있다.
객체지향 설계 원칙(SOLID)
SRP(Single Responsibility Principle) : 단일 책임 원칙
OCP(Open Close Principle) : 개방 폐쇄 원칙
LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
ISP(Inteface Segregation Principle) : 인터페이스 분리 원칙
DIP(Dependency Inversion Principle) : 의존관계 역전 원칙
높은 응집도와 낮은 결합도
높은 응집도 : 응집도가 높다는 건 하나의 모듈, 클래스가 하나의 책임 또는 관심사에 집중되어 있다는 뜻이다. 응집도가 높은 것은 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다고 볼 수 있다. 즉, 변경이 일어날 때 모듈의 많은 부분이 바뀌면 응집도가 높다.
낮은 결합도 : 결합도란 '하나의 오브젝트가 변경이 일어날 때 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도'라고 설명할 수 있다. 즉, 낮은 결합도란 변경이 생겼을 때 다른 모듈과 객체로 변경의 영향이 전파되지 않는 상태를 뜻한다.
전략패턴
현재 개선한 상태를 디자인 패턴 시각으로 보면 전략 패턴(Strategy Pattern)에 해당된다. 전략 패턴은 다양하게 자주 사용되는 디자인 패턴의 꽃이다.
제어의 역전(IoC)
팩토리
객체의 생성 방법을 결정하고 오브젝트를 반환하는 것을 팩토리라 부른다. 흔히 디자인 패턴의 추상 메소드 패턴이나 팩토리 메소드 패턴과는 다르니 유의하자.
UserDao를 팩토리로 생성하도록 변경해보자.
public class DaoFactory {
public UserDao userDao() {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
}
main() 메소드에서 코드를 다음과 같이 수정된다.
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new DaoFactory().userDao();
...
}
main()을 실행하여 테스트가 잘 되는지 확인해보자.
제어권의 이전을 통한 제어 관계 역전
일반적인 프로그램의 흐름을 보면 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 오브젝트를 생성하고 사용한다.
제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는다. 오브젝트는 본인이 사용할 오브젝트를 외부에서 받아서 사용해야 한다. 이전과 다르게 본인이 직접 제어 권한을 가지지 않고 다른 대상에게 위임하기 때문이다.
이런 제어의 역전은 다양한 곳에서 사용되고 있다. 초난감DAO에서 봤던 템플릿 메소드 패턴도 제어의 역전에 속한다. 슈퍼클래스인 UserDao에게 add(), get()을 받아 사용하기 때문이다. 프레임워크도 제어의 역전의 대표 기술이다. 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.
스프링의 IoC
애플리케이션 컨텍스트와 설정 정보
스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부르며, 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계 설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리킨다.
- 빈 팩토리 : 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트다.
- 애플리케이션 컨텍스트 : 빈 팩토리를 좀 더 확장한 것이다.
보통 빈 팩토리와 애플리케이션 컨텍스트를 같은 의미로 사용한다. 빈 팩토리라 말하면 IoC 기본 기능에 초점을 맞춘거고, 애플리케이션 컨텍스트라고 말할 때는 애플리케이션의 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 좀 더 부각된다.
@Configuration를 이용하면 애플리케이션 컨텍스트 설정 정보로 이용할 수 있다.
@Configuration // 애플리케이션 컨텍스트 또는 빈 패곹리가 사용할 설정정보라는 표시
public class DaoFactory {
@Bean // 오브젝트 생성을 담당하는 IoC용 메소드라는 표시
public UserDao userDao() {
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
main()을 실행하여 테스트가 잘 되는지 확인해보자.
애플리케이션 컨텍스트의 동작방식
이전에 사용했던 오브젝트 팩토리에 대응되는 것이 스프링의 애플리케이션 컨텍스트다. 스프링에서는 이 애플리케이션 컨텍스트를 IoC 컨테이너 혹은 스프링 컨테이너라고 부른다. 또는 빈 팩토리라고도 한다. ApplicationContext는 BeanFactory 인터페이스를 상속해서 일종의 빈 팩토리다.
오브젝트 팩토리와 비교했을 때 장점은 다음과 같다.
- 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
- 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
- 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
스프링 IoC 용어 정리
빈(bean) : 스프링이 IoC 방식으로 관리하는 오브젝트 중에서 스프링이 직접 생성과 제어를 담당하는 오브젝트만을 빈으로 본다.
빈 팩토리(bean factory) : IoC를 담당하는 핵심 컨테이너로 빈을 등록하고 생성하고 조회하는 등 관리하는 기능을 담당한다.
애플리케이션 컨테스트(application context) : 빈 팩토리를 확장한 IoC 컨테이너다. 빈 팩토리가 빈의 생성과 제어의 관점이라면 애플리케이션 컨텍스트는 스프링이 제공하는 지원 기능을 포함해서 이야기한다.
설정정보/설정 메타정보(configuration metadata) : IoC 컨테이너에 관리되는 애플리케이션 오브젝트의 형상 정보다
컨테이너(container) 또는 IoC 컨테이너 : IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고 한다.
스프링 프레임워크 : IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말한다.
싱글톤 레지스트리와 오브젝트 스코프
애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리다. 스프링은 기본적으로 내부에서 생성하는 빈 오브젝트를 싱글톤으로 만든다.
서버 애플리케이션과 싱글톤
싱글톤으로 만든 이유는 스프링은 대부분 서버 환경에서 사용된다. 초당 수십에서 수백 번의 요청이 들어오는 환경에서 요청마다 오브젝트를 만들면 서버가 감당하기 힘들어진다. 그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍 사용해왔다. 서블릿은 가장 기본적인 서비스 오브젝트라고 할 수 있으며, 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들고 공유해 동시에 사용한다. 이렇게 한 개의 오브젝트를 사용하는 것이 싱글톤 패턴의 원리다. 하지만 문제점이 많아 안티 패턴이라 부르는 사람도 있다.
public class UserDao {
private static UserDao INSTANCE;
private ConnectionMaker connectionMaker;
private UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public static synchronized UserDao getInstance() {
if (INSTANCE == null) {
INSTANCE = new UerDao(??);
}
return INSTANCE;
}
...
}
싱글톤 패턴의 한계
- private 생성자를 갖고 있기 때문에 상속할 수 없다.
- 싱글톤은 테스트하기가 힘들다
- 서버 환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다
- 싱글톤의 사용은 전역 상태를 만들기 때문에 바람직하지 못하다
싱글톤 주의점
- 멀티스레드 환경에서 상태 정보를 갖지 않는 무상태를 유지해야 한다.
- 만약 상태가 읽기 전용으로 사용된다면 문제가 없다.
싱글톤 레지스트리
자바의 싱글톤 방식은 문제가 있어, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는데 이게 바로 싱글톤 레지스트리(singleton registry)다. 싱글톤 레지스트리의 장점은 자바의 static이나 private 같은 클래스가 아니라 평범한 클래스도 싱글톤으로 활용하게 해 준다.
의존관계 주입(DI)
Dependency Injection은 여러 가지 번역이 있지만, 흔히 사용되는 단어가 의존성 주입이다.
DI는 오브젝트 레퍼런스를 외부에서 주입받고 여타 오브젝트와 동적인 의존관계가 만들어지는 것이 핵심이다.
의존관계
의존한다는 건 A라는 클라이언트가 기능 구현함에 있어 B를 사용해야 된다는 의미다.
기능 구현에 B를 사용해야 하는데 B에 변경이 일어나면, 이를 가져다 사용하는 A도 변경의 영향을 받게 된다. 이렇게 사용의 관계에 있는 경우 A와 B는 의존관계가 있다 말할 수 있다. 반대로 보면 B는 A에 의존하지 않고, A의 변화에 영향을 안 받는다는 뜻이다.
의존관계 주입의 조건
- 인터페이스에만 의존하고 있어야 한다
- 런타임 시점의 의존관계는 컨테이너 or 팩토리 같은 제3의 존재가 결정한다.
- 의존관계에 사용될 오브젝트를 외부에서 주입해줌으로써 정해진다.
// 의존관계 주입을 위한 코드
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
의존관계 검색과 주입
의존관계를 맺는 방법 중에 외부 주입이 아닌 스스로 검색하는 의존관계 검색 방법도 있다.
public UserDao() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}
위의 코드처럼 팩토리 클래스나 스프링 API를 사용하는데 코드가 섞여있는 게 어색해서 대부분 의존 주입을 사용한다.
의존검색과 의존주입의 차이점
검색 방식에는 스프링의 Bean이 아니어도 가능하다.
주입 방식은 스프링이 제어 권한을 가지기에 스프링 Bean으로 등록되어야 한다.
*참고 : 스프링이 제공하는 다양한 기능의 99%는 DI 혜택을 이용한다.
의존주입 방법
1. 생성자 주입 (DI 방식에 가장 적합)
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
2. 수정자 메소드 주입
public void setConnectionMaker(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
3. 일반 메소드 주입
public void add(User user, ConnectionMaker connectionMaker) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
...
}
XML 이용한 의존관계
의존정보를 자바 코드로 정해주고 수정될 때마다 다시 컴파일하는 것이 번거롭다. 다른 방법으로 DI 의존관계 설정 정보를 만들 수 있는데, 바로 XML이다. XML은 단순한 텍스트 파일이라 다루기 쉽고 빌드 작업이 없는 게 장점이다.
XML 설정
스프링의 애플리케이션 컨텍스트는 XML에 담긴 DI 정보를 사용할 수 있다.
자바 코드 설정 정보와 대응되는 XML 설정 정보다
자바 코드 설정 정보 | XML 설정 정보 | |||
빈 설정파일 | @Configuration | <beans> | ||
빈의 이름 | @Bean methodName() | <bean id="methodName" | ||
빈의 클래스 | return new BeanClass() | class="a.b.c... BeanClass"> |
XML설정 정보에서 DI 주입하는 방법은 <bean> 태그 내부에서 <property> 태그를 사용하면 된다. 이 태그는 name, ref 두 개의 속성을 가진다. name은 프로퍼티의 이름이고 ref는 수정자 메소드를 통해 주입할 오브젝트의 빈 이름이다.
// 자바 코드 설정 정보
userDao.setConnectionMaker(connectionMaker())
// XML 설정 정보
<bean id="userDao" class="spring.book.dao.userDao">
<property name="connectionMaker" ref="connectionMaker" />
</bean>
XML을 이용하는 애플리케이션 컨텍스트
애플리케이션 컨텍스트가 사용하는 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="springbook.user.dao.DConnectionMaker" />
<bean id="userDao" class="springbook.user.dao.UserDao">
<property name="connectionMaker" ref="connectionMaker" />
</bean>
</beans>
파일 위치는 리소스 폴더 바로 아래다.
XML 설정 정보를 가져오는 애플리케이션 코드를 main()에 선언해보자.
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean("userDao", UserDao.class);
...
}
* 클래스패스 파일 경로에 / 가 없으면 루트에서 시작한다는 점을 알아두자
그 외에도 지정된 오브젝트 기준으로 xml 설정 파일을 찾는 애플리케이션 컨텍스트도 존재한다.
new ClassPathXmlApplicationContext("daoContext.xml", UserDao.class);
UserDao 위치에서 daoContext.xml을 찾는다는 의미다.
XML 설정정보로 DataSource 주입
DB 연결을 자바 코드와 XML 설정 정보로 알아보자
@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;
}
자바 코드에 대응하는 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="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="dirverClass" 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="toby.springtoby01.UserDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
value 값의 자동 변환
dataSource 태그안에 property를 보면 이전과 다르게 ref 대신 value를 사용하고 있다. 해당 프로퍼티마다 문자열이 아니라 클래스타입도 섞여있는데 문제없이 사용이 가능하다. 스프링이 프로퍼티의 값을 수정자 메소드의 파라미터를 참고해서 자동으로 변환해주기 때문이다.
정리
초난감DAO 코드를 시작으로 다양한 방법으로 패턴, 원칙, IoC/DI 프레임워크까지 여러 과정을 지나 개선했다.
과정을 정리하면 다음과 같다.
- 관심사를 다른 클래스로 분리한다.
- 변경이 자주 일어날 클래스를 인터페이스로 구현하도록 하고, 다른 쪽은 인터페이스로 접근하게 바꾼다.
- 개방 폐쇄 원칙이 적용되고, 높은 응집도와 낮은 결합도를 가진 설계가 되었다.
- 의존관계에 대한 제어 책임을 오브젝트 팩토리 혹은 IoC 컨테이너로 위임하여 기존 오브젝트의 생성과 선택의 책임을 벗어났다.
- 싱글톤 패턴을 알아보고 이를 극복한 스프링 싱글톤 레지스트리를 알게 되었다.
- 클래스와 인터페이스의 느슨한 관계로 런타임 시에 의존 오브젝트를 제삼자의 도움으로 주입받아 동적인 의존관계를 갖게 해주는 의존관계 주입을 알아봤다.
- 의존관계 주입에 대한 여러 가지 방법(생성자 주입, 수정자 주입 등)을 알아봤다.
1장 마지막에 xml을 이용한 설정 방법도 여러 서술되었지만 현재는 애노테이션 방법을 선호하기에 간단 요약한다.
궁금하다면 직접 보는 것을 권해드린다.
'서적 > 토비의 스프링' 카테고리의 다른 글
토비의스프링 vol.1 - 2장, 테스트 (0) | 2021.06.29 |
---|---|
토비의 스프링 포스팅을 시작하며 (0) | 2021.06.19 |