들어가기 앞서..
추상적이다 vs 구체적이다의 차이점이 뭘까?
추상적이다는 구현에 대한 세부 사항을 감추고 기능에 대한 명시를 통해서 이해하는 것이고
구체적이다는 위에 대한 디테일한 구현에 대한 세부적인 로직을 말하는 것이다.
예를 들어, 커피 머신을 생각해보자.
추상적 = 커피 뽑기 버튼(이 버튼을 누르면 믹스커피가 뽑힌다.)
구체적 = 뒷단인 기계 안에서 일어나는 모든 연산들(무슨 커피인지, 설탕은 얼만큼인지, 물의 온도는 몇도인지,결제는 어떻게 되는지..)
API 문서도 서버에서 제공하는 기능을 추상화를 하는 것과 같습니다.
추상화를 왜 하는 걸까요? 그걸 갖다 쓰는 사람이 쉽게 쓸 수 있게 복잡성을 낮추려고요.
커피를 뽑아 먹는 사람이 기계 내부 구현을 알 필요가 있을까요..?
/api/user/update?no=3
위와 같은 추상적인 개념을 보고 아? 이건 no가 3인 유저를 업데이트하는 api 구나!
이 api를 갖다 쓰는 사람은 서버에 클래스 객체가 어쩌고 dao가 어쩌고 sql이 어쩌고 알 필요가 없습니다.
위와 같이 스프링 빈도 일종의 인스턴스를 생성하는 과정을 추상화한 것입니다
getBean("빈 이름") 버튼 하나로 한번으로 필요로 하는 세팅을 한번에 가져오는 것이지요.
조금 더 비유를 해보자면..
기존에 new를 해주고 set 메소드로 값을 일일히 하나씩 설정해주는 것은
집에서 재료를 전부 사서 그때 그때(기존 방식) 직접 요리를 하는 것과 같습니다.
반면, Bean에 뭐뭐가 필요하다고 명시를 해주는 것은 밀키트(스프링 빈)와 같습니다.
필요한 재료들이 한번에 필요한 만큼 적절히 딸려오니까요.
만약에 인스턴스 생성자, setter 세팅을 다르게 한 버전이 필요하다면, 빈에 추가만 해주고
getBean으로 이름에 맞게 갖다 쓰면 되지요?
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ여기부터는 의존성 개념에 대해서 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
이때, A 클래스의 인스턴스만 딱 가져오면 정말 좋겠지만,,
public class SungJukImpl implements SungJuk {
private SungJukDTO sungJukDTO = null;
....
.... getter setter 등등
}
위와 같이 A 클래스 안에 멤버 변수로 B 클래스 인스턴스가 있는 경우에는
A를 만들 때 B까지 같이 만들어줘야 해요.
이것을 A클래스가 B클래스를 의존 한다고 합니다.
필요로 한다는 것이지요.
그래서 이거를 일일히 매번
SungJuk sungJuk = new SungJukImpl (); // Impl 인스턴스 생성
SungJukDTO dto = new SungJukDTO(); // DTO 인스턴스 생성
dto.setName("건우"); // setter 함수로 디폴트 값 설정
dto.setKor(90); // setter 함수로 디폴트 값 설정
dto.setEng(80); // setter 함수로 디폴트 값 설정
dto.setMath(85); // setter 함수로 디폴트 값 설정
sungJuk.setSungJukDTO(dto) // dto 인스턴스를 Impl 에 설정
이렇게 해주니 너무너무 귀찮고 변경이 생기면 코드가 있는 모든 곳을 다 고쳐야 하니깐
A클래스가 만들어질 때 B클래스도 필요하니깐! 같이 만들어라!
A가 만들어질 때 B 클래스도 같이 만들어서 꽂아줘 !
만들 때 생성자 + setter로 디폴트 값까지 넣어서!
이게 DI(Dependency Injection)의 개념입니다.
우리가 프로젝트에서 Controller에서 Service 인스턴스를 만들고 그 안에서 DAO를 getInstance로 가져오는 것을
위와 같이 누가 누가를 필요로 하는지 의존 관계만 명시해주면
스프링 빈에 등록만 해주면 한번에 쫘르르 세팅이 됩니다.
마치 낚시대를 던졌는데 물고기가 10마리가 줄줄이 잡히는 느낌..!
아니면 밀키트 같은 느낌..!
그러면 실제로 스프링에서 구현은 어떻게 해야할까?
---------------------------------추상적인 부분 ↓--------------------------------
ApplicationContext context = new FileSystemXmlApplicationContext("src/applicationContext.xml");
// 전원을 켜서 커피 머신 정보를 불러온다.
MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
// 커피를 뽑는다.
getBean으로 쓸 때는 세부 구현을 알 필요가 있나요?
알아서 잘 구현 됐겠거니 생각하는 겁니다.
---------------------------------추상적인 부분↑--------------------------------
---------------------------------구체화한 부분(@어노테이션 기반 방법)--------------------------------
1. ApplicationContext context 에 빈 명세서에서
<context:component-scan base-package="sample05" />
컴포넌트 스캔이 보이죠?
@Component(얘도 걍 bean이랑 같은거임 클래스 위에 쓸때는 이렇게 써줘야할 뿐)
@Component가 지정된 빈 정보를 싹 읽습니다. (스프링 컨테이너에 빈이 등록된다고 하죠)
스프링 컨테이너라는 박스에 빈 정보들이 싹싹 다 들어간다!!!
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
2. getBean으로 인스턴스가 필요한 시점에서
MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
스프링 컨테이너에 등록된 빈 정보에서 id가 messageBeanImpl인 빈을 찾습니다.
즉, @Component가 달려있는 MessageBeanImpl 클래스를 먼저 찾아가는 거죠.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
3. MessageBeanImpl 클래스에 @Autowired가 있다면?
1) 가장 먼저, @Autowired이 생성자 이면 디폴트 값에 알맞게 세팅됩니다.(생성자 혹은 setter 의존성 주입)
@Autowired
public MessageBeanImpl (@Value("홍길동") String name) {
this.name = name;
}
2) 만약, @Autowired이 Setter 메소드 이면 디폴트 값에 알맞게 세팅됩니다.(생성자 혹은 setter 의존성 주입)
@Autowired
public void setName(@Value("홍길동") String name) {
this.name = name;
}
3) 만약, @Autowired 특정 클래스라면
@Autowired 적힌 SungJukDTO 클래스도 당연히 빈에 등록이 되어 있어야 합니다.
@Autowired
private SungJukDTO sungJukDTO = null;
↓ ↓ ↓ ↓ ↓ ↓ 의존성 찾기
@Component
SungJukDTO {
..
}
↓ ↓ ↓ ↓ ↓ ↓ 의존성 주입
@Autowired
private SungJukDTO sungJukDTO = 인스턴스가 생성됨!
↓↓ ↓ ↓ ↓ ↓ 빈 생성이 끝났으면 이제 그것을 갖다 쓰는 위치로 복귀
MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
위와 같이 필요로 하는 모든 세팅이 연쇄 반응 처럼 일어나지요.
나중에 Controller ---> Service ----> DAO로 흘러가는 인스턴스 목록들을 연쇄적으로 주입받아 한번에 얻을 수 있습니다.
----------------------------Bean의 인스턴스 생성시점------------------
이때, 스프링 컨테이너에는 빈을 명세하는 정보만 올라가는 것이지
1) 싱글톤은 서버가 시작될 때 미리 만들어두고 getBean을 호출하기 전에도 인스턴스 1개가 이미 만들어져있음
싱글톤 = 시작과 동시에 1개만 만들어주고 인스턴스 1개를 공유하면서 재사용
2) 프로토타입은 getBean을 호출한 시점에 그때 그때 인스턴스가 만들어짐
프로토타입 = 흔히 말하는 new A()로 각각의 인스턴스는 따로따로 독립적으로 생성됨!
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ이제부터는 XML로 설정 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
<bean id="fileOutputter" class="sample05.FileOutter">
<property name = "filePath" value = "D:/Spring/"></property>
<property name = "fileName" value = "result.txt"></property>
</bean>
<bean id = "messageBeanImpl" class = "sample05.MessageBeanImpl">
<constructor-arg value="홍길동"></constructor-arg>
<property name="phone" value="010-1234-1234"></property>
<property name="outputter" ref="fileOutputter"></property>
</bean>
MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
1. getBean으로 빈의 id를 먼저 찾습니다.
2. </constructor-arg value="홍길동"> 생성자를 만들면서 "홍길동" 값을 주입합니다.
3.
</property name="phone" value="010-1234-1234">
</property name="outputter" ref="fileoutputter">
setter 주입으로 값을 setPhone, setOutputter 해줍니다...
4. 이때, outputter는 클래스 인스턴스를 필요로 하므로, ref에 적힌 Bean id인 fileoutputter를 찾습니다.
</bean id="fileoutputter" class="sample05.fileoutter">
</property name = "filepath" value = "d:>
</property name = "filename" value = "result.txt">
여기서 bean id="fileoutputter에 알맞는 클래스 인스턴스를 만들고, property로 setter로 값을 주입을 합니다.
이것으로 만들어진 인스턴스를 </property name="outputter" ref="fileoutputter">에 꽂아 넣습니다.
★ ★ ★ ★ ★ ★ ★ ★ ref = ~~~ 이랑 @AutoWired 랑 방법만 다르지 똑같음!!! ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
5. 최종적으로 만들어진 인스턴스가 MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
messagebean에 만들어지는 것이죠.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ마지막 예시ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
<bean id="fileOutputter" class="sample05.FileOutter">
<property name = "filePath" value = "D:/Spring/"></property>
<property name = "fileName" value = "result.txt"></property>
</bean>
<bean id = "messageBeanImpl" class = "sample05.MessageBeanImpl">
<constructor-arg value="홍길동"></constructor-arg>
<property name="phone" value="010-1234-1234"></property>
<property name="outputter" ref="fileOutputter"></property>
</bean>
1. 여기서 Bean id 가 messageBeanImpl를 찾는다.
MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
2. 생성자로 인스턴스를 만들면서 홍길동 값을 세팅해준다.
3. </property name="outputter" ref="fileoutputter"> 여기서 ref에 적힌 Bean id를 찾는다.
4. <bean id="fileOutputter"> 에서 인스턴스를 만든다.
5. </property name="outputter" ref="fileoutputter"> 만든거를 여따 주입한다.
6. MessageBean messagebean = (MessageBean) context.getBean("messageBeanImpl");
여기에 최종적으로 세팅한다...
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
결론적으로..
스프링이 빈을 관리한다는 것은
쓰는 사람은 getBean("빈 이름") 추상적인 버튼 하나로
스프링이 빈 명세서를 기반으로 구체적인 설정 을
XML혹은 @어노테이션 명세서에 적힌 내용 그대로 연쇄적으로 자동으로 값과 인스턴스를 세팅 해주면서
특정 인스턴스가 필요로 하는 의존 관계와 디폴트 값을 다 세팅해주고
그 결과로 만들어진 인스턴스를 반환 해준다고 보면 됩니다.
그 결과로 만들어진 인스턴스에 나중에 값을 set하거나 하는건 getBean으로 호출한 사람 자유예요
밀키트에 재료를 한번에 가져오는 거지 밀키트에 소금을 추가하거나 고추장을 넣는 것은 먹는 사람 마음이죠
결국, xml이든 @어노테이션이든 구현에 대한 방법만 다를 뿐,
빈 정보를 찾고,
그 빈이 @Autowired로 필요로 생성자, setter 혹은 클래스 빈을 찾고,
값이나 인스턴스를 만들어서 꽂아 넣고
getBean 호출한 곳에 인스턴스를 넣어주는 것입니다.