평소와 달리 블로그를 쓰면서 공부를 하니 2~3배 더오래걸린다. 장점은 2~3배 더 자세히 하게되는 거 같은데, 똥멍청이라 평소라면 아몰랑 필요없어! 하던 것들을 좀더 자세히 들여다 볼 수 있다는 것 정도인것 같다.
1.5 스프링의 IOC
우린 저번 포스트에서 자바빈에 관해서 이야기 했었는데 스프링에서의 빈은 또 다르다.
스프링에서의 빈은 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트로 정의된다. 하지만 뭐, 자바빈 또는 엔터프라이즈 자바빈에서 말하는 빈과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트를 말한다.(라고 써있다. 둘다 자세히 모르는 것들...)
빈 팩토리는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것
애플리케이션 컨텍스트는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC엔진이라는 것에 초점을 맞춘 것
원래 우리가 사용하고 있던 DaoFactory를 스프링의 애플리케이션 컨텍스트가 사용할 수 있도록 변경 할 수 있는데 @Configuration과 @Bean을 이용해서 구현 할 수 있다.
@Configuration : 애플리케이션 컨테스트 또는 빈 팩토리가 사용할 설정정보라는 표시
@Bean : 오브젝트 생성을 담당하는 IoC용 메소드라는 표시 @Bean을 사용할 경우 해당 메소드의 이름으로 ApplicationContext에 등록된다.
| @Configuration public class DaoFactory { @Bean public UserDao userDao(){ UserDao userDao = new UserDao(connectionMaker()); return userDao; } @Bean public ConnectionMaker connectionMaker(){ return new NConnectionMaker(); } } | cs |
UserDao.class는 일종의 제네릭 함수로 원래 getBean함수는 return을 Object 클래스로 준다. 아래와 같이 클래스를 넘겨주게 되면 불필요하게 케스팅하는 과정을 하지 않아도 되게 만들어준다.(java 5버전 이상에서 제공한다고 한다. )
| 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); // ... } } | cs |
어플리케이션 컨텍스트의 동작 방식
@Configuration을 붙인 Factory가 ApplicationContext에 등록되고 이 펙토리 안에 이는 모든 @Bean목록이 applicationContext안의 빈 목록에 들어간다. getBean요청이 오면 해당 펙토리의 Bean을 생성하는 방식을 취하고 있다.
재밌는 방식인듯...
장점
1) 클라이언트가 구체적인 팩토리 클래스를 알 필요가 없다.
2) ApplicationContext는 종합 IoC 서비스를 제공한다. (이건 나중 돼봐야 알 수 있을 것 같다)
3) 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다. ( 빈 검색이 이름, 특정한 애노테이션 등으로 검색가능)
1.5을 정리하자면
빈 : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
빈 팩토리 : 빈의 생성과 관계설정 같은 제어를 담당하는 IoC오브젝트. BeanFactory는 인터페이스로 getBean과 같은 메소드들이 정의되어 있다.
애플리케이션 컨텍스트 : 빈 팩토리와 개념은 같다고 보면 된다. BeanFactory를 상속해서 구현한 것이다. BeanFactory가 제공하는 기능에 더해 스프링이 제공하는 추가적인 기능등을 포함한다.
설정정보/설정 메타정보 : IoC를 적용하기 위해 사용하는 메타정보다.
IoC 컨테이너 : 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC컨테이너라고한다.
1.6 싱글톤 레지스트리와 오브젝트 스코프
스프링은 빈 오브젝트를 특별한 설정을 하지 않는한 싱글톤으로 생성한다. 설명의 흐름상 싱글톤을 설명해야할 것 같지만 내가 알고있는 개념이므로 설명하지않는다. (ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ ㅈㅅ)
싱글톤의 한계
private생성자를 갖고 있기 때문에 상속할 수 없다.
테스트하기가 힘들고, 때에 따라서는 테스트 자체가 불가능 할 수도 있다.
하나의 오브젝트임을 보장하지 못한다. 즉, 서버환경에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다. 심지어 여러개의 JVM에 분산설치가 되는 경우에는 각각 독립적으로 오브젝트가 생겨 싱글톤으로서의 가치가 떨어진다.
싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
이처럼 순수한 싱글톤 패턴은 문제가 많아서 스프링은 이에 대한 해결책으로 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 이것이 싱글톤 레지스트리다.
어찌 됐든 싱글톤으로 빈이 관리되기 때문에 몇가지 주의점이 생기는데
기본적으로 stateless방식으로 관리되어야 한다는 것이다. 즉, 상태정보를 내부에 저장하지 않아야한다. 만약 빈이 상태 정보를 가지고 있을 경우 멀티 쓰레드환경에서 싱글톤 오브젝트의 인스턴스 변수를 수정하게 되는데, 이는 매우 위험한 행위다. => 로컬 변수와 리턴값, 파라미터 등을 이용하자.
현재 구현되어 있는 UserDao는 stateless하다.
빈의 생성, 존재 하는 범위인 스코프는 10장에서 다룬데요. <- 따라서 지금은 다루지 않는다.
1.7 의존관계 주입 (DI: Dependency Injection)
의존관계란? A가 B에 의존하고 있다는 의미는 B가 변하면 A가 영향을 받는다는 의미다. 일반적으로 의존관계는 방향성이 있는데 A가 B에 의존적이라고 하더라도 일반적으로 B는 A에 의존적이지 않다. 대표적인 예로 A의 내부에서 B를 사용할 경우를 들 수 있다.
의존 오브젝트란? 클래스상에서의 의존관계가 아니라 런타임시에 의존관계를 맺는 대상
의존관계 주입의 세 가지 조건
1) 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. (인터페이스에만 의존하고 있어야한다.)
2) 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
3) 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로서 만들어진다.
| public UserDao(ConnectionMaker connectionMaker){ this.connectionMaker = connectionMaker; } | cs |
UserDao의 생성자가 의존관계를 주입받기 위한 코드이다.
이 때, DaoFactory가 의존관계(제 3의 존재)를 주입하게 된다.
의존관계 주입 말고 검색이라는 친구도 있는데 아래 코드처럼 getBean을 통해서 검색을 통해 가져오는 것을 말하는데 아래의 코드는 의존관계 검색을 통해 구현한 UserDao의 생성자이다.
| public UserDao(ConnectionMaker connectionMaker){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class); this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class); } | cs |
그다지 이쁜 모습은 아닌데, main에서 context를 가져와서 테스트한 UserDaoTest의 경우가 의존관계 검색의 예라고 볼 수 있다.
의존관계 검색과 의존관계 주입의 가장 큰 차이점은 검색은 검색하는 오브젝트가 스프링의 빈일 필요가 없다는 점이고, 주입의 경우 반드시 컨테이너를 만드는 빈 오브젝트여야 한다는 것이다. (UserDao와 ConnectionMaker모두 bean이다.) 왜냐하면 주입하기 위해서는 UserDao가 생성과 초기화 권한을 위임해야하기 때문이다.
의존관계를 주입하기위해 우리는 생성자를 이용했는데 사실 뭐 수정자 메소드를 이용해도 상관없다. 사실, 이게 더 자주 사용된다.(setter메소드를 말한다.)
1.8 XML을 이용한 설정
사실 DaoFactory를 작성하는 일은 반복적이고 틀에 박힌 것으로 굳이 자바로 관리할 필요는 없다. 따라서 이것을 XML로 관리할 수 있는 편의를 스프링은 제공해주는데
@Configuration : <beans>
@Bean : <bean>
에 대응한다
여기에 추가로 property라는 친구가 있는데 이 property라는 친구에는 name과 ref를 속성으로 가지는데 name은 DI에 사용할 수정자 메소드의 프로퍼티 이름이며(setter메소드) ref는주입할 오브젝트를 정의할 빈의 ID다.
DaoFactory를 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="user.NConnectionMaker"></bean> <bean id="userDao" class="user.UserDao"> <property name="connectionMaker" ref="connectionMaker"/> </bean> </beans> | cs |
XML은 텍스트이기 때문에 찾아 바꾸기를 해야한다. 따라서 실수할 가능성이 높으므로 이름을 잘 정하도록 하자
ConnectionMaker 대신에 DataSource로 바꾸기
토비에 나와있는 예제는 connectionMaker 대신에 DataSource로 바꾸는데 그대로 따라하다보면 몇가지 에러를 발생시킨다.
| <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> | cs |
여기서 에러가 발생하는데 class를 찾을 수 없다고 나온다.
이 때, pom.xml에 dependency를 추가해주면 해결된다( auto import 를 사용하면 자동으로 잡아준다 )
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework-version}</version> </dependency> | cs |
여기서 $(~) 는 버전명이다. 나의 경우에는 4.3.9.RELEASE 를 사용했다.
여기까지의 소스코드 : https://github.com/choiking10/spring_test/tree/vol1_1.5_1.9/lifeignite