1.1 초난감 DAO
: 대충 메소드도 분리되어있지 않고 DB 커넥션도 없는 DAO를 스프링에 맞게 업그레이드 시켜보자는 Intro
1.2 DAO의 분리
1.2.3 DB커넥션 만들기의 독립
- 만약 UserDao라는 클래스의 getConnection이라는 메소드를 분리하고 싶을 때, '상속'과 추상메소드를 통해 분리할 수 있다.
public abstract class UserDao{
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c = getConnection();
}
public abstract getConnection() throws ClassNotFoundException, SQLException;
// make the method abstract // code will be written by subClass
}
//Subclass 1
public class NUserDao extends UserDao{
public Connection getConnection() throws ClassNotFoundExpction, SQLException{
//~~~~~~~
}
}
//Subclass 2
public class DUserDao extends UserDao{
public Connection getConnection() throws ClassNotFoundExpction, SQLException{
//~~~~~~~
}
}
이런식으로 슈퍼클래스에 추상메소드나 protected 메소드(서브클래스가 오버라이딩 가능)로 로직의 흐름을 구현,
서브클래스에서 필요에 맞게 구현한 디자인 패턴을 '템플릿 메소드 패턴' 이라고 한다.
또한, 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는것을 '팩토리 메소드 패턴' 이라고 한다.
1.3 DAO의 확장
Q : 그렇다면, DB 커넥션을 얻는 메소드를 추상화하고 각각 다른 Dao 클래스에서 구현하는 것이 아니라, 아예 클래스를 분리하면 안될까? Make SimpleConnectionMaker() in UserDao generator!
A : 그럼 그 UserDato가 SimpleConnectionMaker객체에 종속되기 때문에 커넥션 정보를 수정할 방법이 없어. 인터페이스를 사용하자!
// 1. Create Interface named ConnectionMaker
public interface ConnectionMaker {
public Connection makeConnection() throws SQLException;
}
// 2. implements Interface in Subclass
public class DConnectionMaker implements Connetion Maker {
public Connection makeConnection(){
// ~~ code specific code to get connection
}
}
// 3.Generate Interface in UserDao Class
public class UserDao{
public ConnectionMaker connectionMaker;
pubic UserDao(){
connectionMaker = new DConnectionMaker(); /****note****/
}
}
Note***
ConnectionMaker를 인터페이스로 분리시키고 DConnectionMaker라는 객체를 분리도 시켰는데, 여전히 UserDao에서는 Connection 구현 방식에 따라 connectionMaker에 DConnection 객체를 넣어줘야 한다.
(DConnection 이던, EConnection이던 UserDao 밖에서 정해주고 싶단 말이다!)
여기서 나온 개념이 바로 '관계설정 책임의 분리'이다.
1.3.3 관계설정 책임의 분리
UserDao 객체의 관심사는 UserDao 오브젝트들이 구체적으로 어떤 Connection 전략을 취하는지가 아니다! 따라서 UserDao와 ConnectionMaker 구현 클래스의 관계를 결정해주는 기능을 구현해 둔 UserDao의 클라이언트 오브젝트와 ConnectionMaker 오브젝트 사이에 관계를 설정해주면 된다.
// 1. UserDaoTest's main 함수에게 UserDao와 ConnectionMaker의 런타임 오브젝트 의존관계 설정의 역할을 맡긴다.
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ConnectionMaker connectionMaker = new DConnectionMaker();
//UserDao가 사용할 구현 클래스를 결정(DConnectionMaker)
UserDao dao = new UserDao(connectionMaker);
//dao라는 오브젝트와 connectionMaker라는 오브젝트의 의존성 설정
}
}
// 2. 제어의 역전(IoC) - 오브젝트 팩토리(오브젝트 생성 및 사용을 관리하는 목적)를 사용해 구현 전략 설정
// DaoFactory의 도입은 1.4 제어의 역전에서 설명
public class DaoFactory{
public UserDao userDao(){
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
}
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
UserDao dao = DaoFactory.userDao();
}
}
* 전략패턴
: 위와같이 UserDaoTest - UserDao -ConnectionMaker 구조를 디자인 패턴의 시각으로 보면 '전략패턴'에 해당한다고 볼 수 있다.
전략패턴이란, 자신의 기능맥락(context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 사용할 수 있게하는 디자인 패턴이다.
- UserDao : 기능맥락(Context)
- ConnectionMaker : UserDao가 필요에 따라 변경이 필요한 알고리즘을 인터페이스로 빼낸 객체
- UserDaoTest : 컨텍스트가 사용할 전략을 제공해주는 클라이언트
1.4 제어의 역전(IoC)
UserDaoTest파일에 Connection 정보를 집어넣는 코드를 보자. 무슨 테스트 코드가 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 역할을 맡냐! 이건 좋은 코드라 할 수 없으니 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 분리하는 객체를 하나 더 만들자. 이런 코드를 보통 팩토리라고 부른다. 이러한 팩토리 객체를 분리함으로써 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리할 수 있게 되었다!!
1.4.3 제어권의 이전을 통한 제어 관계 역전
위와 같은 예시를 볼 때, 제어의 역전에서는 프로그램의 시작을 담당하는 main()과 같은 엔트리 포인트를 제외하면 모든 오브젝트는 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다. 오브젝트들은 자신이 사용할 오브젝트를 스스로 선택하지도, 생성하지도 않고 어디서 사용되는지도 알 수가 없다. 제어 권한을 자기 자신이 아닌 다른 대상에게 위임하기 때문이다.
프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 애플리케이션의 흐름은 프레임워크가 주도하고, 개발자가 등록한 클래스는 이 흐름에 의해 실행 될 뿐!
1.5 스프링 IoC
1.5.1 오브젝트 팩토리를 이용한 스프링 IoC
- 애플리케이션 컨텍스트와 설정정보
* 애플리케이션 컨텍스트 : IoC방식을 따라 만들어진 일종의 빈팩토리이다. 빈을 생성하고 관계를 설정하며 모든 구성요소의 제어 작업을 담당하는 IoC엔진.
- DaoFactory를 사용하는 애플리케이션 컨텍스트
* @Configuration : 오브젝트 설정을 담당한다는 애노테이션. 이 애노테이션이 붙은 자바 코드를 설정정보로 사용하려면
AnnotationConfigApplicationContext를 사용하면 된다.
* @Bean : 오브젝트를 만들어주는 메소드에 붙는 애노테이션
public class UserDaoTest{
public static void main(String[] args) throws ClassNotFoundException{
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
//@Configuration이 붙은 DaoFactory 코드를 설정정보로 사용하겠다.
UserDao dao = context.getBean("userDao",UserDao.class);
}
}
1.5.2 애플리케이션 컨텍스트의 동작방식
- @Configuration 애노테이션이 붙은 DaoFactory 오브젝트가 @Bean들이 등록되어있는 ApplicationContext를 불러온다.
- Client가 userDao객체를 사용하고 싶다고 getBean()함수를 불러오면 ,
- DaoFactory가 UserDao객체를 리턴해준다.
1.5.3 스프링 IoC의 용어 정리
- 빈 : 스프링이 직접 생성과 제어를 담당하는 오브젝트. 모든 오브젝트가 빈인것은 아니다.
- 빈팩토리 : 스프링IoC를 담당하는 핵심 컨테이너. getBean()과 같은 메소드가 정의되어 있다.
- 애플리케이션 컨텍스트 : 빈팩토리의 개념 + 스프링의 각종 부가서비스 추가
- 설정정보/설정 메타정보 : 애플리케이션 컨텍스트가 IoC를 적용하기 위해 사용하는 메타정보
1.6 싱글톤 레지스트리와 오브젝트 스코프
스프링이 관리하는 오브젝트들이 생성되고 존재하는 범위를 스코프라고 한다. 이 스코프의 범위는 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어주는 '프로토타입스코프' , 웹을 통해 새로운 HTTP 요청이 생길 대마다 생성되는 요청(request)스코프 , 웹의 세션과 스코프가 유사한 세션스코프가 있지만..
스프링의 오브젝트는 기본적으로 싱글톤스코프이다!!
애플리케이션 컨텍스트는 이 싱글톤스코프의 오브젝트를 저장하고 관리하는 싱글톤레지스트리이다.
* 싱글톤스코프란? 예시를 통해 알아보자
//직접 생성한 DaoFactory 오브젝트 출력 코드
DaoFactory factory = new DaoFactory();
UserDao dao1 = new UserDao();
UserDao dao2 = new UserDao();
System.out.println(dao1); //springbook.dao.UserDao@118f375
System.out.println(dao2); //springbook.dao.UserDao@117a8bd
//스프링 컨텍스트로부터 가져온 오브젝트 출력 코드
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao3 = context.getBean("userDao",UserDao.class);
UserDao dao4 = context.getBean("userDao",UserDao.class);
System.out.println(dao3); //springbook.dao.UserDao@ee22f7
System.out.println(dao4); //springbook.dao.UserDao@ee22f7
위의 예시에서 볼 수 있듯이, 스프링 컨텍스트로부터 오브젝트를 가져올 경우 매번 동일한 오브젝트를 돌려준다. 이렇듯 싱글톤패턴이란 어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수, 이름처럼 주로 하나만 강제하도록 하는 패턴이다. 이렇게 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다.
* 자바에서 싱글톤 구현 방법과 그 한계
스텝1. 클래스 밖에서 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
> 한계1. 다른 생성자가 없다면 상속이 불가능하다.
스텝2. 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
> 한계2. 한계1과 마찬가지로 상속과 다형성같은 객체지향의 특징이 적용되지 않음.
스텝3. getInstance()함수를 만들고 최초 생성일때는 인스턴스 생성, 이후 client에게 기존 인스턴스를 리턴해준다.
> 한계3. 사용하는 클라이언트가 정해져있지 않기 때문에 전역상태(global state)로 사용되기 쉽다.
> 한계4. 서버환경에서는 여러개의 JVM에 분산되어 설치가 되는 경우에 싱글톤이 보장되지 않는다.
> 한계5. 다이나믹하게 객체를 생성할 수 없어 테스트가 어렵다.
* 스프링의 싱글톤 레지스트리
자바에서 싱글톤을 구현한 것의 한계때문에 스프링은 자신이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 싱글톤 레지스트리 기능을 제공한다. IoC방식의 컨테이너를 사용해 제어권을 컨텍스트에게 넘기면 public을 사용한 평범한 클래스, 테스트 환경에서 자유롭게 생성되는 목 오브젝트들도 싱글톤으로 사용될 수 있다는 말이다!!
* 스프링에서 싱글톤 상태관리시 유의해야 할 점
: 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태 정보를 내부에 갖고있지 않은 무상태(stateless)방식으로 만들어져야한다. 변경이 필요한 정보는 파라미터와 로컬변수, 리턴값 등 매번 새로운 값을 저장할 독립적인 공간이 만들어지는 변수들을 사용하여 여러개의 스레드가 동일한 변수의 값을 덮어 쓸 일이 없도록 하자. 단, 읽기전용의 정보들은 인스턴스 변수들을 사용해도 상관없다. 예시를 통해 알아보자!!
public class UserDao{
private ConnectionMaker connectionMaker; // 중간에 바뀌지않는 읽기 전용 인스턴스 변수
private Connection c; // client마다 변경되는 정보 > 인스턴스 변수로 관리하면 심각한 문제 발생. 변경필요
private User user; // client마다 변경되는 정보 > 인스턴스 변수로 관리하면 심각한 문제 발생. 변경필요
public User get(String id){
this.c = connectionMaker.makeConnection();
this.user = new User();
this.user.setId(rs.getString("id"));
...
return this.user;
}
}
1.7 의존관계 주입(DI)
DI란? 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이나믹하게 의존관계를 구성하는 것. 이러한 DI를 구성하기 위해 세가지 조건이 필요한데
1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
2. 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제 3의 존재가 결정한다.
3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.
UML에서 드러나지 않아 설계 시점에는 알지 못하지만, 제 3의 존재로 인하여 런타임시에 오브젝트간에 의존관계가 실체화 되는 것. 이것을 의존관계 주입이라고 한다.
public class UserDao{ //의존관계 주입을 위한 코드
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
//userDao는 ConnectionMaker를 사용함으로써 DcoonectionMaker를 주입받게된다.
//이는 설계시점에는 드러나지 않지만 런타임시점에 실현됨.
}
}
의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용하는 '의존관계 검색'이라는 개념도 있다. 자신이 필요로 하는 의존 오브젝트를 능동적으로 사용하는 것이다.
public UserDao(){ // 의존관계 검색을 통한 ConnectionMaker 가져오기
AnnotationConfigApplicationcontext context
= new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
//스프링이 제공하는 getBean : 의존관계 검색에 사용
}
단, 의존관계 주입에 사용되는 빈들은 모두 컨테이너가 만드는 빈 오브젝트여야 한다. 반면 의존관계 검색에 사용되는 빈들은 스프링의 빈일 필요는 없다.
1.7.4 의존관계 주입의 응용
- 기능 구현의 교환
ex. 운영계와 개발계의 DB 서버가 다른 경우
DI를 사용한다면 모든 DAO가 아래의 ConnectionMaker타입의 오브젝트를 컨테이너로부터 제공받기 때문에 ConnectionMaker만 수정해주면 된다. (LocalDBConnectionMaker 클래스를 DAO에서 매번 구현해서 사용했다면 배포 전 LocalDBConnectionMaker를 ProductionDBConnectionMaker로 바꿔주고, 개발 시 다시 원복을해줘야되니 끔찎하다)
//개발용 ConnectionMaker 생성 코드
@Bean
public ConnectionMaker connectionMaker(){
return new LocalDBConnectionMaker();
}
//운영용 ConnectionMaker 생성 코드
@Bean
public ConnectionMaker connectionMaker(){
return new ProductionDBConnectionMaker();
}
- 부가 기능의 추가
만약 커넥션을 연결할 때마다 연결 횟수를 카운팅하고 싶다고 하자. 그럼 기존의 코드는 그대로 두고 ConnectionMaker 인터페이스를 구현한 CountingConnectionMaker를 하나 만들자. UserDao는 ConnectionMaker의 인터페이스에만 의존하고 있기 때문에, ConnectionMaker 인터페이스를 구현하고 있다면 어떤 것이든 DI가 가능하기 때문이다. 기존에 UserDao > DConnectionMaker 의존관계를 UserDao > CountingConnectionMaker > DConnectionMaker로 변경해주자. 그리고 @Configuration이 붙은 설정용 클래스인 CountingDaoFactory를 만들어주고 그 안에서 UserDao에게 CountingConnectionMaker를 DI해준다. 그리고 이 CountingDaoFactory를 설정정보로 사용해주면 되는거야!
즉 기존에 UserDao부분은 그냥 두고, 설정정보와 의존관계만 변경하면 된다는거지. 예시간다!
//UserDao가 호출하게될 새로운 오브젝트
public class ContingConnectionMaker implements ConnectionMaker {
int counter = 0 ;
private ConnectionMaker realConnectionMaker;
//기존에 사용하던 ConnectionMaker를 인스턴스로 가지는 오브젝트를 생성하고
public CountingConnectionMaker(ConnectionMaker realConnectionMaker){
this.realConnectionMaker = realConnectionMaker;
}
public Connection makeConnection(){
this.counter++;
return realConnectionMaker.makeConnection();
//UserDao가 makeConnection()을 호출할때마다 카운트를 증가시키고 ConnectionMaker 호출해줌
}
public int getCounter(){
return this.counter;
}
}
//설정파일로써의 CountingDaoFactory
@Configuration
public class CountingDaoFactory{
@Bean
public UserDao(){
return new UserDao(connectionMaker());
//여전히 모든 UserDao는 connectionMaker 오브젝트를 주입받기 때문에 한번 더 메소드를 만든다
// 기존의 코드를 수정하기 싫기 때문이다.
}
@Bean
public ConnectionMaker connectionMaker(){
return new CountingConnectionMaker(realConnectionMaker());
}
@Bean
public ConnectionMaker realConnectionMaker(){
return new DConnectionMaker();
}
}
//테스트 클래스
public class UserDaoConnectionCountingTest{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
CountingConnectionMaker ccm = context.getBean("connectionMaker", CountingConnectionMaker.class);
//의존관계 검색을 사용하면 이름을 이용해 어떤 빈이든 가져올 수 있다.
System.out.println("Connection counter : " + ccm.getCounter());
}
}
1.7.5
기존의 예시는 계속 생성자를 통한 의존관계 주입을 보여줬는데, 수정자 혹은 메소드를 이용한 주입도 가능하다. 여러개의 파라미터를 갖는 장점이 있으니 잘 활용하도록. 예시간다.
// 수정자를 사용한 UserDao 오브젝트
public class UserDao{
private ConnectionMaker connectionMaker;
public void setConnectionMaker(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
}
//수정자를 사용한 팩토리 메소드
@Bean
public UserDao userDao(){
UserDao userDao = new UserDao();
userDao.setConnectionMaker(connectionMaker());
return userDao;
}
1.8 XML을 이용한 설정
위 포스팅은 토비의 스프링 3.1을 읽고 정리한 내용임을 알려드립니다.
'Study > Spring' 카테고리의 다른 글
스프링 핵심기술 05. 추상화 (0) | 2022.02.13 |
---|---|
스프링 핵심기술 04. ApplicationContext가 상속받는 인터페이스들 (0) | 2022.02.06 |
스프링 핵심기술 03. 스프링 IoC 컨테이너 - Bean의 Scope (0) | 2022.02.03 |
스프링 핵심기술 02. 스프링 IoC 컨테이너 - @Autowire (0) | 2022.02.02 |
스프링 핵심기술 01. 스프링 IoC 컨테이너 - ApplicationContext와 Bean 설정 방법 (0) | 2022.02.02 |