오브젝트의 동일성과 동등성
동일성은 ==, 동등성은 equals()이다.
두 오브젝트가 "동일"하다면, 사실 하나의 오브젝트만 존재하는 것이고,
두 개의 오브젝트 레퍼런스 변수를 갖고 있을 뿐이다.
두 개의 오브젝트가 동일하지는 않지만 동등한 경우에는
두 개의 각기 다른 오브젝트가 메모리상에 존재하는 것인데,
이는 정보가 같다는 것이지, 레퍼런스 변수는 다른다.
@Configuration
public class DaoFactory{
@Bean public UserDao userDao(){
return new UserDao();
}
}
ApplicationContext context = new
AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao3 = context.getBean("userDao",UserDao.class);
UserDao dao4 = context.getBean("userDao",UserDao.class);
//System.out.println()을 할 경우, 동일한 결과값이 나온다.
//(동일하게 참조를 한다.)
( 싱글톤을 저장하고 관리하는 싱글톤 레지스트리 )
스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 "싱글톤"으로 만든다.
( 구현방법은 다름 )
매번 클라이언트 요청이 올 때마다 새로운 객체를 생성하는 것은 JVM의 메모리를 심하게 잡아먹는다.
( 참고로, 단일 오브젝트만 존재해야 하고, 이를 애플리케이션의 여러 곳에서 공유하는 경우에 주로 사용한다. )
싱글톤 패턴의 한계
public class UserDao{
private static UserDao INSTANCE;
private UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
public static synchronized UserDao getInstance(){
if(INSTANCE == null) INSTANCE = new UserDao(???);
return INSTANCE;
}
}
// connectionMaker 어떻게 주입???
private로 바꿘 생성자는 외부에서 호출할 수가 없기 때문에 DaoFactory에서 UserDao를 생성하며 ConncetionMaker 오브젝트를 넣어주는 게 불가능해졌다.
기술적인 서비스만 제공하는 경우라면 상관없지만,
애플리케이션 로직을 담고 있는 일반 오브젝트의 경우 객체지향 설계가 불가능하다.
초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이내믹하게 주입하기가 힘들기 때문에
필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수 밖에 없다.
클래스 로더를 어떻게 구성하고 있느냐에 따라 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다.
아무 객체난 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태는 객체지향 프로그래밍에서 권장되지 않는 모델이다.
(그럴바에 스태틱 필드와 메소드로만 구성된 클래스를 사용하는 편이 낫다.)
스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리한다.
그것이 바로 "싱글톤 레지스트리"이다.
스프링 컨테이너는 싱글톤을 생성,관리,공급하는 싱글톤 관리 컨테이너다.
장점은 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다.
(평범한 자바 클래스라도 IoC방식의 컨테이너를 사용해서 제어권을 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리할 수 있다.)
오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 applicationContext에 있으므로 가능하다.
또한 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고,
테스트를 위한 Mock 오브젝트로 대체하는 것도 가능하다.
생성자 파라미터를 이용해서 사용할 오브젝트를 넣어주게 할 수 있다.
기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는
상태정보를 갖고 있지 않은 무상태(stateless)방식으로 만들어져야 한다.
(다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다.)
메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일은 없다.
public class UserDao{
// 이 변수는 읽기전용이므로, 상관이 없다.
// static final이나 final 권장.
private ConncetionMaker connectionMaker;
// 하위 변수들은 매번 바뀌므로, 1) 메소드의 로컬 변수로 쓰거나,
// 2) 파라미터로 주고받아야 한다.
private Conncetion c;
private User user;
public User get(String id) throws ...{
this.c = connectionMaker.makeConncetion();
this.user = new User();
...
return this.user;
}
}
싱글톤 빈을 만들 때, 개별적으로 바뀌늰 정보는 로컬 변수로 정의하거나
파라미터로 주고받으면서 사용하게 해야한다.
스프링이 관리하는 오브젝트, 즉 빈이 생성되고, 존재하고, 적용되는 범위를
"빈의 스코프"라고 한다.
( 기본 스크로는 싱글톤임. )
싱글톤 스코프는 컨테이너 내에 1개의 오브젝트만 만들어져서,
강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지된다.
싱글톤 외에도 프로토타입 스코프가 있다.
이는 컨테이너 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다.
HTTP 요청이 생길 때마다 생성되는 요청 스코프도 있고,
웹의 세션과 스코프가 유사한 세션 스코프도 있다.
( 10장 참고. )
스프링 Ioc 컨테이너는 객체를 생성하고 관계를 맺어주는 작업을 담당한다.
스프링 IoC의 대표적인 동작원리는 주로 DI(의존관계 주입)이라고 불린다.
의존관계 주입, 의존성 주입, 의존 오브젝트 주입?
DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받고
이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이 핵심이다.
두 클래스가 "의존관계"이면 한 클래스는 "무조건 방향성"을 가진다.

의존하고 있다는 뜻은 타이틀,본문,댓글이 바뀌면 게시글이 바뀌는 것을 의미한다.
(한 클래스는 다른 클래스에 종속되어 있다.)
인터페이스에 대해서만 의존관계를 만들어두면
인터페이스 구현 클래스와의 관계는 느슨해지면서 변화에 영향을 덜 받는 상태가 된다
(결합도가 낮다.)

아무리 DConncetionMaker를 바꾸더라도 UserDao는 영향을 받지 않는다.
(어차피 구체적인 클래스는 알 필요 없고, 인터페이스로 가져다가 쓰면 그만이다.)
(실제 사용대상인 오브젝트를 의존 오브젝트라고 한다.)
클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다.
그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 주입해줌으로써 만들어진다.
ApplicationContext, BeanFactory, IoC container 등 모두 외부에서 오브젝트 사이의 런타임 관계를 맺어주는 제 3의 존재이다.
의존관계가 느슨할지라도 UserDao가 사용할 구체적인 클래스를 알고 있어야 한다.
public UserDao(){
connectionMaker = new DConnectionMaker();
}
위 코드는 이미 런타임 시의 의존관계가 코드 속에 다 미리 결정되어 있다는 점이다.
그래서 Ioc 방식을 써서, 런타임 의존관계를 드러내는 코드를 제거하고
제 3의 존재에 런타임 의존관계 결정 권한을 위임한다.
그래서 아래와 같이 수정을 한다.
public class UserDao(){
private ConncetionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
}
UserDao는 DConnectionMaker 오브젝트의 레퍼런스를 생성자 파라미터를 통해서
주입을 받는다.
DI 컨테이너는 자신이 결정한 의존관계를 맺어줄 클래스의 오브젝트를 만들고 이 생성장의 파라미터로 오브젝트의 레퍼런스를 전달해준다.
의존관계 검색이라는 방법도 있음
( 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. )
런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과
오브젝트의 생성 작업은 외부 컨테이너에게 IoC로 맡기지만,
이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용한다.
이 방법은 스스로 IoC 컨테이너에 요청을 하는 것이다.
public UserDao(){
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionMaker = context.getBean("connectionMaker",ConnectionMaker.class);
}
결론적으로 그냥 의존관계 주입 방식을 사용하는 편이 낫다.
그런데 의존관계 검색방식을 사용해야 할 때가 있다.
의존관계 검색 방식에서는 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다는 점이다.
구체적으로
UserDao는 굳이 스프링이 만들고 관리하는 빈일 필요가 없고,
그냥 new UserDao()해서 만들어서 사용해도 된다.
이때는 ConnectionMaker만 스프링의 빈이기만 하면 된다.
반면에 의존관계 주입에서는 UserDao와 ConnectionMaker 사이에는 DI가 적용되려면 반드시 UserDao도 빈 오브젝트여야 한다.
DI 받는다.
DI에서 말하는 주입은 다이내믹하게 구현 클래스를 결정해서 제공받을 수 있도록 인터페이스 타입의 파라미터를 통해 이뤄줘야 한다.
(특정 클래스 타입으로 고정되어 있다면 DI가 일어날 수 없다.)
런타임 클래스에 대한 의존관계가 나타나지 않고,
인터페이스를 통해 결합도가 낮은 코드를 만들므로
다른 책임을 가진 사용 의존관계에 있는 대상이 바뀌거나 변경되더라도 자신은 영향을 받지 않으며,
변경을 통한 다양한 확장 방법에 자유롭다.
UserDao가 ConnectionMaker라는 인터페이스에만 의존하고 있다는 것은
ConnectionMaker를 구현하기만 하고 있다면 어떤 오브젝트든지 사용할 수 있음을 의미한다.
DI는 스프링의 핵심이다.
지금까지는 UserDao의 의존관계 주입을 위해 생성자를 사용했다.
그런데 반드시 꼭 생성자가 있어야 하는 것은 아니다.
일반메소드로도 가능하다.
수정자(setter) 메소드를 이용한 주입
파라미터로 전달된 값을 보통 내부의 인스턴스 변수에 저장하는 것이다.
일반 메소드를 이용한 주입
수정자 메소드처럼 set으로 시작해야 하고 한 번에 한 개의 파라미터만 가질 수 있다는 제약이 싫다면
여러 개의 파라미터를 갖는 일반 메소드를 DI용으로 사용할 수 있다.
아래는 수정자 메소드 DI를 적용한 UserDao이다.
public class UserDao{
private ConnectionMaker connectionMaker;
public void setConnectionMaker(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
}
@Bean
public UserDao userDao(){
UserDao userDao = new UserDao();
userDao.setConncetionMaker(connectionMaker());
return userDao;
}
XML은 단순한 텍스트 파일이고, 컴파일과 같은 별도의 빌드 작업이 없다.
환경이 달라져서 오브젝트의 관계가 바뀌는 경우에도 변경사항을 반영할 수 있다.
applicationContext는 XML에 담긴 DI 정보를 활용할 수 있다.
DI정보가 담긴 XML 파일은 beans 를 루트 엘리먼트로 사용한다.
<beans>
<bean>...</bean>
<bean>...</bean>
...
</beans>
이는 @Configuration -> , @Bean -> 으로 치환하는 것과 동일하다.
하나의 @Bean 메소드를 통해 얻을 수 있는 빈의 DI정보는 다음 3가지이다.

@Bean //---><bean
public ConncetionMaker{
connectionMaker(){ //---> id="connectionMaker"
return new DConnectionMaker(); //--->class="springboot...DConnectionMaker"/>
}
DI 컨테이너는 이 태그의 정보를 읽어서 connectionMaker()와 같은 작업을 진행한다.
수정자 메소드를 선호하는 이유 중에는 XML로 의존관계 정보를 만들 때 편리하다는 점이 있다.
자바빈의 관례에 따라서 수정자 메소드(setter)는 프로퍼티가 된다.
프로퍼티 이름은 메소드 이름에서 set을 제외한 나머지 부분을 사용한다.
seConnectionMaker() -> conncetionMaker라는 프로퍼티를 갖는다고 할 수 있다.
태그를 사용해 의존 오브젝트와의 관계를 정의한다.
property는 2개의 attribute를 갖는다.
userDao.setConnectionMaker(connectionMaker());
<property name="connectionMaker" ref="connectionMaker"/>
// 수정자를 이용해서 connectionMaker를 UserDao에 주입하겠다.
<bean id="userDao" class="springboot.dao.UserDao">
<property name="connectionMaker" ref="connectionMaker"/>
</bean>
@Bean
public UserDao userDao{
connectionMaker(){
UserDao userDao =
userDao.setConnectionMaker(connectionMaker());
return userDao;
}
XML의 의존관계 주입 정보
<beans>
<bean id="connectionMaker" class="springboot.user.dao.DConnectionMaker"/>
<bean id ="userDao" class="springboot.user.dao.UserDao">
<property name ="connectionMaker" ref="connectionMaker"/>
</bean>
</beans>
@Configuration
public class DaoFactory{
@Bean //---><bean
public ConncetionMaker{
connectionMaker(){ //---> id="connectionMaker"
return new DConnectionMaker(); //--->class="springboot...DConnectionMaker"/>
}
@Bean
public UserDao userDao connectionMaker(){// id=userDao
UserDao userDao =
userDao.setConnectionMaker(connectionMaker());
// property name="connectionMaker"
return userDao;
}
}
의 name은 수정자 메소드의 프로퍼티 이름이고, ref는 주입할 오브젝트를 정의한 빈의 ID다.
보통 프로퍼티 이름과 DI되는 빈의 이름이 같은 경우가 많다.
(보통 name도 인터페이스를 따르고, ref도 인터페이스를 따른다.)
때로는 같은 인터페이스를 구현한 의존 오브젝트를 여러개 정의해두고 그중에서 원하는 걸 골라서 DI하는 경우도 있다.
<beans>
// id=메소드명, class=리턴타입
<bean id="localDB..." class="...LocalDB..."/>
<bean id="testDB..." class="...TestDB..."/>
<bean id="productionDB..." class="...ProductionDB..."/>
// setter = connectionMaker이고, 주입 객체는 localDB ...
<bean id="userDao" class="...UserDao">
<property name="connectionMaker" ref="localDBConncetionMaker"/>
</bean>
</beans>
GenericXmlApplicationContext로 XML에서 빈의 의존 관계 정보를 이용해서 IoC/DI 작업을 수행한다.
DB 커넥션을 가져오는 오브젝트의 기능을 추상화해서 비슷하게 쓸 수 있다.
핵심은 getConnection 메소드 하나뿐이다.
이를 통해서 DB 커넥션을 가져오면 된다.
@Bean
public DataSource dataSource(){
SimpleDriverDataSource dataSource = new Simple...();
dataSource.setDriverClass(...);
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
return dataSource;
}
<bean id="dataSource" class="...">
<property name ="driverClass" value="..."/>
<property name ="url" value=".."/>
...
<property name ="password" value="..."/>
</bean>