•
스프링의 핵심 철학은 자바 엔터프라이즈 기술의 혼란 속에서 잃어버렸던 객체지향 기술의 진정한 가치를 회복하고, 그로부터 객체지향 프로그래밍이 제공하는 폭넓은 혜택을 다시금 누릴 수 있도록 기본으로 돌아가자는 것이다.
•
그렇기 때문에 자연스럽게 스프링은 가장 많은 관심을 오브젝트, 즉 객체에 둔다.
◦
애플리케이션 상에서 오브젝트가 생성되고 다른 오브젝트와 관계를 맺고, 사용되고, 소멸되기까지의 전 과정을 주의깊게 살펴볼 필요가 있다.
◦
더 나아가서는 어떻게 설계되어야 하는지, 어떤 단위로 만들어지며 어떤 과정을 통해 자신의 존재를 노출하는지에 대해서도 살펴봐야 한다.
◦
즉, 관심은 오브젝트의 기술적인 특징에서부터 설계까지 발전하게 된다.
•
1장에서는 스프링이 어떤 것이고 무엇을 제공하는지보다 스프링이 관심을 갖는 대상인 오브젝트의 설계와 구현, 동작원리에 더 집중해서 살펴보는 것을 추천한다.
1. 초난감 DAO
•
DAO는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
1. User
@Getter
@Setter
public class User {
String id;
String name;
String password;
}
Java
복사
2. UserDao
•
사용자 정보를 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?characterEncoding=UTF-8", "spring",
"book");
PreparedStatement ps = c.prepareStatement(
"insert into users(id, name, password) values(?,?,?)");
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?characterEncoding=UTF-8", "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;
}
}
Java
복사
3. main()을 이용한 테스트 코드
•
실제 UserDao가 DB 저장, 조회를 수행하는지 확인하기 위해 서버를 띄우고 사용해보는 것은 품이 많이 든다.
•
만들어진 코드의 기능을 검증하고자 할 때 사용할 수 있는 가장 간단한 방법은 오브젝트 스스로 자신을 검증하도록 만들어주는 것이다.
2. DAO의 분리
1. 관심사의 분리
•
세상에는 변하는 것과 변하지 않는 것이 있지만 객체지향의 세계는 모든 것이 변한다.
•
그렇기 때문에 객체를 설계할 때, 개발자가 가장 염두에 두어야할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다.
•
그렇다면 어떻게 변경이 일어날 때 필요한 작업을 최소화하고 그 변경이 다른 곳에 문제를 일으키지 않도록 격리할 수 있을까? 이는 분리와 확장을 고려한 설계가 있었기 때문이다.
◦
분리에 대해 생각해보자.
▪
보통 변경 사항은 다음과 같이 발생하지 않는다.
•
DB를 오라클에서 MySQL로 바꾸고, 웹 화면의 레이아웃을 다중 프레임에서 단일 프레임으로 바꾸고 매출 알림 시스템을 만들고 그 시스템의 API 중 한 API의 필드의 타입을 바꿔라
▪
모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다.
▪
여기서 우리가 직면하는 문제는 즉, 변화는 대체로 집중된 한 가지 관심에 대해서만 일어나지만 그에 따른 작업은 한 곳에서만 수행되지 않는다는 점이다.
▪
변화가 한 가지 관심에 집중되게 일어난다면 우리는 응당 그 한 가지 관심이 하나의 장소에 모여들어 자기들끼리 있도록 집중시켜야 한다.
▪
프로그래밍의 기초 개념 중 관심사의 분리(Segmentation Of Concerns)를 객체지향에 적용시킨다면 관심사가 같은 것끼리 모으고 다른 것은 떨어뜨리는 것이다.
◦
그렇다면 왜 분리를 해야할까? (개인견해)
▪
집중을 위해서? 인간의 뇌가 한 번에 생각할 수 있는 용량에는 제한이 있기 때문에 관심사를 제한하여 효과적으로 개발을 할 수 있도록
▪
관심사의 분리가 일어나지 않은 슈퍼 메소드는 변경 발생이 잦고 그때마다 문제가 생길 가능성이 있기 때문에?
▪
내 관심사가 아닌(DB 이슈로 쿼리를 수정했으나 Controller쪽 로직에 영향이 갈 수 있어서?) 로직에 영향을 받을 수 있기 때문에?
2. 커넥션 만들기의 추출
•
UserDao의 메소드들을 잘 살펴보자. 먼저 add()에선 적어도 세 가지 관심 사항을 발견할 수 있다.
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring", "book");
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
c.close();
}
JavaScript
복사
◦
잘 생각해보자.
UserDao의 관심사항
1.
DB 연결을 위한 커넥션을 어떻게 획득할것인가에 대한 관심
2.
DB에 보내 처리할 SQL을 만들고 실행하는 관심
3.
작업이 종료된 후, 획득한 커넥션과 PreparedStatement를 어떻게 돌려줄 것인가에 대한 관심
중복 코드의 메소드 추출
•
get 메소드를 보면 앞서 1번 관심사인 커넥션 획득에 대한 관심사가 중복되는 것을 확인할 수 있다.
•
이 메소드를 getConnection()과 같은 형태로 추출하여 관심사를 분리할 수 있다.
•
이후, main()이나 테스트 코드를 활용해 해당 리팩토링이 성공적으로 문제없이 수행되었는지 확인하는 절차는 필수다.
3. DB 커넥션 만들기의 독립
•
갑자기 독립이 왜 나오지? 싶을 수 있다. 무슨 의도일까? 생각해보면
•
단순히, 중복 코드의 메소드 추출에서 그치지 않고, 그 객체 자체의 책임의 범위가 커지면서 별도 책임으로 분리될 수도 있다.는 내용인 것 같다.
•
그 과정에서 상속을 통한 확장, 템플릿 메소드 패턴이라던가, 팩토리 메소드 패턴 등이 사용되어 그 쓰임새를 보여준다.
•
다만 그 과정에서 상속이라는 방법이 가지는 단점을 명시하는데, 상속을 통한 상, 하위 클래스 간의 강한 결합도라던가, Java의 다중상속 제한 등이 그 예다.
3. DAO의 확장
•
UserDao를 추상 클래스로 만들고 이를 상속한 서브 클래스로 하여금 필요한 부분을 저마다 바꿔쓸 수 있게 하는 이유는 변화의 성격이 다른 것을 분리하여 서로 영향을 주지 않은채로 독립시키기 위해서다.
•
그러나 여러 단점을 가지는 상속을 사용했다는 점이 걸린다.
1. 클래스의 분리
•
메소드 수준이나 클래스 수준에서 나뉘더라도 여전히 상속이라는 관계가 맺혀있는 것이 아닌 아예 남으로 취급되는 클래스 수준의 관심사 분리가 발생한다.
•
SimpleConnectionMaker와 같은 클래스를 선언하고 해당 객체를 UserDao가 참조하도록 해볼 수 있다.
•
이렇게 클래스를 분리하더라도 상속을 이용했을 때와 마찬가지로 자유로운 확장이 가능하게 하려면 두 문제를 해결해야 한다.
1.
특정 클래스의 메소드 시그니처에 종속되어 해당 클래스의 변경 시, 영향을 받을 수 있다.
2.
UserDao가 자신이 필요로 하는 커넥션을 제공해주는 클래스를 직접 의존하게 된다는 것이다.
•
이는 상속으로 인한 서브 클래스의 확장을 방해하는 행위가 된다. Connection이 최상위 부모 클래스 단에서 정의가 되어있기 때문에 ㅇㅇ
•
이 문제들의 근본적인 원인은 DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 알고 있어야한다는 점에서 출발한다.
•
자신이 어떤 것을 써야할지 알고 있기 때문에 그 구체적인 내용에 본인도 모르게 종속되어버린다.
•
Java는 이런 문제를 해결하기 위해 가장 좋은 방법인 인터페이스를 제공해주고 있다.
2. 인터페이스의 도입
•
두 클래스 간의 결합을 느슨하게 만들어주기 위해 만드는 추상화 계층이다.
•
추상화란 어떤 것들의 공통적인 성격을 뽑아내거 이를 따로 분리해내는 작업으로 Java에서 이를 가장 잘 수행해낼 수 있는 도구가 인터페이스다.
•
이렇게 각 메소드, add, get의 본문에 인터페이스를 성공적으로 도입함으로써 모든 것이 해결된 것처럼 보였으나 아직 하나가 남아있다. 바로 생성 과정에서 발생하는 구체 의존이다.
3. 관계설정 책임의 분리
•
UserDao에는 여전히 어떤 ConnectionMaker 구현 클래스를 사용해야되는 지에 대한 내용이 남아 있다.
•
즉, 직접 생성에 대한 책임이 문제다, 따라서 이를 UserDao가 지는 것이 아니라 생성되어 있는 구체 클래스를 갖다 쓰기만 하면 된다.
•
이때, UserDao의 클라이언트로 하여금 런타임 시점에 ConnectionMaker의 구현체를 선택하도록 하여 UserDao의 책임을 덜어줄 수 있다.
4. 원칙과 패턴
•
개방 폐쇄 원칙, 높은 응집도와 낮은 결합도, 전략 패턴 등, 지금까지의 UserDao 개선 방안을 구체적인 용어를 통해 설명한다.
4. 제어의 역전
1. 오브젝트 팩토리
•
자, UserDao의 관계설정 책임을 클라이언트인 UserDaoTest가 가져갔다.
•
근데 이 객체의 본래 목표는 UserDao가 잘 동작하는지 테스트하는 것뿐인데, 이런 책임을 부여하는 것이 합당할까?
•
이것도 분리할 수 있는 관심사다. 이 장에서는 객체의 생성 방법을 결정하고 그렇게 생성한 객체를 돌려주는 생성 패턴에 대해서 알아본다.
•
분리시킬 기능, 즉 생성에 관한 책임을 가지는 클래스를 설계한다. 이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 반환하는 것이다.
◦
이렇게 분리하는 이유는 오브젝트를 생성하는 쪽과 사용하는 쪽의 역할과 책임을 깔끔하게 분리하기 위함이다.
•
이때, 가장 좋은 점은 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리했다는 점이다.
2. 오브젝트 팩토리의 활용
•
만약 DaoFactory가 UserDao 외에도 MessageDao, AccountDao 등의 생성 책임을 맡게되었다고 생각해보자.
•
이때 우리는 자연스럽게 메소드 시그니처에만 다르고 구현 내용은 같은 메소드들을 찍어낼 것이다. 이는 중복에 해당하므로 해결해야할 문제에 해당한다.
•
기존 생성자의 경우, 매개변수로 구체적인 생성자를 받고 있으므로 중복을 피하기가 어려우나 이를 내부 메소드로 감싸면 영향이 덜 가게끔 할 수 있다.
3. 제어권의 이전을 통한 제어관계 역전
•
제어의 역전이라는 개념에 대해서 알아보자. 이는 간단하게 프로그램의 제어 흐름 구조가 뒤바뀌는 것이라고 설명할 수 있다.
•
일반적으로 프로그램의 흐름은 main() 메소드와 같이 시작 지점에서 다음에 사용할 오브젝트를 결정, 생성, 호출하고 다시 그 오브젝트 안에서 결정, 생성, 호출하는 구조가 반복된다.
•
이런 프로그램 구조에서 각 오브젝트는 프로그램의 흐름을 결정하거나 사용할 오브젝트를 구성하는 과정에 능동적으로 참여한다.
•
제어의 역전이란 이런 흐름의 개념을 거꾸로 뒤집는 것이다. 즉, 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 결정하지 않는다.
5. 스프링의 IoC
1. 오브젝트 팩토리를 이용한 스프링 IoC
애플리케이션 컨텍스트와 설정정보
•
스프링에서는 스프링이 제어권을 가지면서, 직접 만들고 관계를 부여하는 오브젝트를 빈이라고 부른다.
•
즉, 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트다.
•
빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리라고 부르는데, 보통은 이를 좀더 확장한 애플리케이션 컨텍스트를 주로 사용한다.
◦
빈 팩토리라고 말하는 경우는 IoC의 기본에 좀 더 초점을 맞춘 것이고 애플리케이션 컨텍스트라고 부를 때는 애플리케이션 전반에 걸쳐 영향력을 행사하며 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 강하다.
•
애플리케이션 컨텍스트는 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄한다.
DaoFactory를 사용하는 애플리케이션 컨텍스트
•
설정정보를 만들어주기 위해 우리는 @Configuration 어노테이션을 사용해서 설정을 담당하는 클래스임을 명시할 수 있고, @Bean 어노테이션을 통해 오브젝트를 생성해주는 메소드임을 명시할 수 있다.
•
이렇게 만들어진 메소드와 클래스를 이용해 다음과 같이 애플리케이션 컨텍스트를 사용해 빈을 생성할 수 있다.
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);
...
}
}
JavaScript
복사
◦
getBean() 메소드의 경우, 일반적으로 Obejct 타입을 반환하나, 두 번째 매개변수로 해당 클래스의 정보를 넘겨주면 제네릭을 이용해 해당 클래스로 반환한다.
2. 애플리케이션 컨텍스트의 동작방식
•
기존의 오브젝트 팩토리와 애플리케이션 컨텍스트의 동작방식을 비교했을때, 다음과 같은 부분에서 차이가 생긴다.
클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
•
애플리케이션이 확장될수록 DaoFactory처럼 IoC를 적용한 오브젝트도 추가될 것인데, 이때마다 필요한 오브젝트를 찾기 위해 어떤 팩토리 클래스를 사용해야하는지 알아야 하는 번거로움이 있다.
•
반면 애플리케이션 컨텍스트의 동작방식은 팩토리가 아무리 많아져도 이를 알아야 하거나 직접 사용할 필요가 없다. 또한 이를 사용하면 일관된 방식으로 원하는 오브젝트를 획득할 수 있다.
애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
•
애플리케이션 컨텍스트는 오브젝트의 생성과 관계설정 외에도 오브젝트가 만들어지는 방식, 시점과 전략을 다르게 가져갈 수 있게 해준다.
•
이외에도 자동생성, 오브젝트에 대한 후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 활용할 수 있는 다양한 기능을 제공한다
애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
•
일반적으로 getBean() 메소드는 빈의 이름을 이용해 빈을 찾아주며 이외에도 타입만으로 빈을 검색하거나 특별한 어노테이션 설정이 붙은 빈을 찾을 수도 있다.
3. 스프링 IoC의 용어 정리
빈
•
스프링이 IoC 방식으로 관리하는 오브젝트다. 혹은 관리되는 오브젝트라고도 한다.
•
주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 빈은 아니라는 것이다. 그중에서도 스프링이 직접 생성과 제어를 담당하는 오브젝트만이 빈이 될 수 있다.
빈 팩토리
•
IoC를 담당하는 핵심 컨테이터를 가리킨다. 빈의 등록, 생성, 조회, 반환 등 관리 기능을 담당하며 보통은 이를 확장한 애플리케이션 컨텍스트가 사용된다.
애플리케이션 컨텍스트
•
빈 팩토리를 확장한 IoC 컨테이너로 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 유사하나 여기에 스프링이 제공하는 부가 서비스를 추가 제공한다.
설정정보/설정 메타정보
•
애플리케이션 컨텍스트 혹은 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다.
컨테이너 또는 IoC 컨테이너
•
IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 혹은 IoC 컨테이너라고 한다.
◦
후자는 빈 팩토리의 관점으로 보는 경향이 강하고 전자는 애플리케이션 컨텍스트를 가리키는 것이라고 보면 된다.
스프링 프레임워크
•
스프링 프레임워크는 IoC 컨테이너, 애플리케이션 컨텍스트를 포함해 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용한다.
6. 싱글톤 레지스트리와 오브젝트 스코프
•
오브젝트 팩토리를 쓰나 빈 팩토리를 쓰나 결과는 크게 달라지지 않아 보이는데, 어떤 차이점들이 있는 것일까?
1. 싱글톤 레지스트리로서의 애플리케이션 컨텍스트
•
DaoFactory의 userDao()를 두 번 호출한다면 각각의 결과에 대한 두 인스턴스는 서로 동일한 오브젝트일까?
•
아니다. 즉 userDao() 메소드는 매 요청마다 새로운 오브젝트를 만들어 반환한다. 반면 빈 팩토리는 몇 번의 요청을 수행하건 동일한 오브젝트를 반환한다.
•
싱글톤을 저장하고 관리하기 때문에 이를 싱글톤 레지스트리라고 부른다. 스프링은 기본적으로 싱글톤으로 빈 오브젝트를 생성한다.
서버 애플리케이션과 싱글톤
•
왜 스프링은 빈을 싱글톤으로 생성할까? 이는 스프링이 주로 적용되는 부문이 자바 엔터프라이즈 기술을 사용하는 서버 환경이기 때문이다.
•
엔터프라이즈 환경에서 각 로직을 담당하는 오브젝트들이 매 요청마다 생성되는 것은 메모리 이슈를 발생시킬 수 있기 때문에, 싱글톤이 사용되었다.
싱글톤 패턴의 한계
•
자바에서 싱글톤을 구현하는 방법은 보통 다음과 같다.
◦
클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private으로 만들고, 생성된 싱글톤 오브젝트를 저장할 수 있도록 자신과 같은 타입의 스태틱 필드를 내부에 정의한다.
◦
스태틱 팩토리 메소드인 getInstance()를 만들고, 이 메소드가 최초로 호출되는 시점에 딱 한번 오브젝트를 생성하도록 한 후, 이를 스태틱 필드에 저장한다. 또는 호출 전의 초기화 시점에 미리 만들어둘 수도 있다.
◦
한번 오브젝트가 만들어지고 난 후에는 getInstance() 메소드를 통해 이미 만들어져 있는 스태틱 필드의 오브젝트를 넘겨준다.
•
일반적으로 싱글톤 패턴 방식에는 다음과 같은 문제가 있다.
◦
private 생성자를 갖고 있기 때문에 상속할 수 없다.
◦
싱글톤은 만들어지는 방법이 제한적이기 때문에 테스트하기가 힘들다.
◦
서버에서 클래스 로더를 어떻게 구성하느냐에 따라 싱글톤임에도 하나 이상의 오브젝트가 만들어질 수 있다. 즉, 자바 언어를 이용한 싱글톤 기법이 서버 환경에서 꼭 보장된다고 할 수 없다.
◦
싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
싱글톤 레지스트리
•
자바에서 싱글톤을 구현할 때의 단점들 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는데, 그것이 싱글톤 레지스트리다.
•
즉, 스프링 컨테이너는 싱글톤을 생성하고 관리하고 공급하는 싱글톤 관리 컨테이너이기도 하다. 이의 장점은 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아닌, 평범한 자바 클래스를 싱글톤으로 활용할 수 있게 해준다는 점이다.
2. 싱글톤과 오브젝트의 상태
•
싱글톤 오브젝트는 멀티스레드 환경에서 여러 스레드가 동시에 접근할 수 있는데, 때문에 상태 관리에 주의를 기울여야 한다.
◦
기본적으로 싱글톤이 멀티스레드 환경에서 사용되는 경우, 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야 한다.
3. 스프링 빈의 스코프
•
스프링이 관리하는 오브젝트인 빈이 생성, 존재, 적용되는 범위를 빈의 스코프라고 한다.
•
스프링 빈의 기본 스코프는 싱글톤으로 싱글톤 스코프는 컨테이너 내에 하나의 오브젝트가 생성된 후, 강제로 제거하지 않고 컨테이너가 존재하는 한 계속 유지된다.
•
이외에도 프로토타입, 세션 등의 스코프가 존재한다.