실습 코드 참조
moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)
4.1절 의존성 관리
1. 스프링의 의존성 관리 방법
- 스프링 프레임워크의 특징 : 객체의 생성과 의존관계를 컨테이너가 자동으로 관리 (IoC의 핵심 원리)
- 스프링은 Dependency Lookup과 Dependency Injection의 2가지 형태로 IoC를 지원함
- Dependency Lookup : 컨테이너가 애플리케이션 운용에 필요한 객체를 생성하고 클라이언트는 컨테이너가 생성한 객체를 검색(Lookup )하여 사용
- 지금까지 해온 방식이 Dependency Lookup 방식임 (실제 애플리케이션 개발 과정에서 잘 사용하지 않음)
- Dependency Injection은 객체 사이의 의존 관계를 스프링 설정 파일에 등록된 정보에 따라 컨테이너가 자동으로 처리
- 의존성 설정을 바꾸고 싶을 때 코드를 수정하지 않고 스프링 설정 파일만 수정하면 됨 (유지보수성 향상)
- Dependency Injection : 컨테이너가 직접 객체들 사이에 의존관계를 처리함을 의미
- Dependency Injection는 또다시 Setter 인젝션과 Constructor 인젝션으로 나뉨
2. 의존성 관계 ( _010_BoardWeb_DI )
1) 의존성(Dependency ) 관계란?
- 객체와 객체의 결합 관계, 즉 하나의 객체에서 다른 객체를 이용할 때 이용하려는 객체에 대한 정보가 필요
- SamsungTV는 SonySpeaker의 메소드를 이용해서 기능을 수행
- 따라서 SamsungTV는 SonySpeaker타입의 speaker 변수를 가지고 있어야함
- 또한 speaker 변수는 SonySpeaker 객체를 참조하고 있어야함
2) SonySpeaker.java 생성
package polymorphism;
public class SonySpeaker {
public SonySpeaker() {
System.out.println("====> 소니 스피커 객체 생성");
}
public void volumeUp() {
System.out.println("소니 스피커---소리 울린다.");
}
public void volumeDown() {
System.out.println("소니 스피커---소리 내린다.");
}
}
3) SamsungTV 클래스
- SonySpeaker객체 생성 및 볼륨 조절 기능을 SonySpeaker가 사용할 수 있도록 수정
public class SamsungTV implements TV {
//소니스피커 객체 생성
SonySpeaker speaker;
public void volumeUp() {
speaker = new SonySpeaker();
speaker.volumeUp();
}
public void volumeDown() {
speaker = new SonySpeaker();
speaker.volumeDown();
}
}
4) 실행 결과
- 단점 1 : volumeUp down 두개에 소니스피커 객체를 두번이나 만들어줘야하는 단점
- 단점 2 : 애플스피커로 변경하려면 volumeUp()과 volumeDown() 두 개의 메소드를 모두 수정해주어야하는 단점
- 다음 절에서 의존성 주입을 사용해서 변경해볼 예정
4.2절 생성자 인젝션 이용하기
1. 생성자 인젝션으로 의존성 주입 테스트 ( _011_BoardWeb_DI_Constructor )
1) 생성자 인젝션 특징
- 컨테이너가 기본 생성자 말고 매개변수를 가지는 다른 생성자를 호출하도록 설정이 가능
- 생성자의 매개변수로 의존관계에 있는 객체의 주소 정보를 전달할 수 있음
2) SamsungTV 클래스에 생성자 추가
package polymorphism;
public class SamsungTV implements TV {
//소니스피커 객체 생성
SonySpeaker speaker;
//생성자 만들기
public SamsungTV() {
System.out.println("====> SamsungTV 객체(1) 생성");
}
public SamsungTV(SonySpeaker speaker) {
System.out.println("====> SamsungTV 객체(2) 생성");
this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
}
public void powerOn() {
System.out.println("SamsungTV---전원 켠다.");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다.");
}
public void volumeUp() {
speaker.volumeUp();
}
public void volumeDown() {
speaker.volumeDown();
}
}
3) applicationContext.xml에 <bean> 객체 추가
- 생성자 매개변수로 순서 자동 변경 (삼성티비보다 소니객체를 먼저! 생성하도록)
<bean> 객체에 등록된 순서가 아닌, 필요에 따라 객체 생성 순서를 지정해줌
<bean id="tv" class="polymorphism.SamsungTV">
<constructor-arg ref="sony"></constructor-arg>
<!-- 생성자 매개변수로 전달할 때 사용하는 엘리먼트임 / 자동으로 전달해줌! -->
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"></bean>
4) 실행결과
- 스프링 컨테이너는 기본적으로 bean 등록된 순서대로 객체를 생성함
- 모든 객체는 기본 생성자 호출을 원칙으로 함
- 하지만 생성자 인젝션으로 의존성 주입될 SonySpeaker가 먼저 객체 생성됨
- SonySpeaker 객체를 매개변수로 받아들이는 생성자를 호출하여 객체를 생성함
- SamsungTV는 SonySpeaker 객체를 참조할 수 있으므로 문제없이 볼륨 조절 기능 처리 가능함
2. 다중 변수 매핑 ( _012_BoardWeb_DI_Constructor_MultiVariables )
1) SamsungTV.java
- 생성자 인젝션에서 초기화해야 할 멤버변수가 여러 개면 여러 개의 값을 한꺼번에 전달해야함
package polymorphism;
public class SamsungTV implements TV {
//소니스피커 객체 생성
private SonySpeaker speaker;
private int price;
//생성자 만들기
public SamsungTV() {
System.out.println("====> SamsungTV 객체(1) 생성");
}
public SamsungTV(SonySpeaker speaker) {
System.out.println("====> SamsungTV 객체(2) 생성");
this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
}
public SamsungTV(SonySpeaker speaker, int price){
System.out.println("====> SamsungTV 객체(3) 생성");
this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
this.price = price; //소니스피커가 0번째, 가격이 1번째 인덱스가 됨
}
...
}
2) applicationContext.xml
- index 속성을 이용하여 어떤 값이 몇 번째 매개변수로 매핑되는지 지정할 수 있음
- 이렇게 생성자가 여러 개 오버로딩 된 경우 어떤 생성자를 호출해야할지 알려주는 역할
<?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="tv" class="polymorphism.SamsungTV">
<!-- 생성자 매개변수로 전달할 때 사용하는 엘리먼트임 / 자동으로 전달해줌! -->
<constructor-arg ref="sony"></constructor-arg>
<!-- bean으로 생성된 객체를 참조할 때는 ref속성을 사용하지만 고정된 값을 주입할 때는 value 사용해서 의존성 주입 -->
<!-- index : 생성자함수의 몇 번째 인자값인지 명시할 수 있음 -->
<constructor-arg index="1" value="2700000"></constructor-arg>
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"></bean>
</beans>
3) 실행결과
3. 의존관계 변경 ( _013_BoardWeb_DI_Interface )
1) 인터페이스 생성하는법
- 단축키 : alt + shift + T
2) Speaker 인터페이스 생성하기
- 모든 스피커의 최상위 부모로 사용할 인터페이스임
package polymorphism;
public interface Speaker {
void volumeUp();
void volumeDown();
}
3) AppleSpeaker 생성
- Speaker 인터페이스를 구현한 또 다른 스피커 클래스
- SonySpeaker 클래스도 Speaker 인터페이스를 implements 하도록 수정
package polymorphism;
public class AppleSpeaker implements Speaker {
public AppleSpeaker() {
System.out.println("====> 애플 스피커 객체 생성");
}
public void volumeUp() {
System.out.println("애플 스피커---소리 울린다.");
}
public void volumeDown() {
System.out.println("애플 스피커---소리 내린다.");
}
}
4) SamsungTV 클래스 수정
- 멤버변수와 매개변수 타입을 모두 SonySpeaker에서 Speaker로 수정하기
package polymorphism;
public class SamsungTV implements TV {
private Speaker speaker;
private int price;
//생성자 만들기
public SamsungTV() {
System.out.println("====> SamsungTV 객체(1) 생성");
}
public SamsungTV(Speaker speaker) {
System.out.println("====> SamsungTV 객체(2) 생성");
this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
}
public SamsungTV(Speaker speaker, int price){
System.out.println("====> SamsungTV 객체(3) 생성");
this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
this.price = price; //소니스피커가 0번째, 가격이 1번째 인덱스가 됨
}
//initMethod() 초기화 작업 진행
public void initMethod() {
System.out.println("객체 초기화 작업 처리...");
}
//destroyMethod() 객체 삭제전 처리할 로직
public void destroyMethod() {
System.out.println("객체 삭제전 처리할 로직...");
}
public void powerOn() {
System.out.println("SamsungTV---전원 켠다. (가격 : " + price + ")");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다.");
}
public void volumeUp() {
speaker.volumeUp();
}
public void volumeDown() {
speaker.volumeDown();
}
}
5) applicationContext.xml
- AppleSpeake도 <bean> 등록해주기
- constructor-arg> 엘리먼트의 속성값 지정
- 속성값을 apple로 지정하면 SamsungTV가 AppleSpeaker를 이용하여 볼륨을 조절함
<bean id="tv" class="polymorphism.SamsungTV">
<constructor-arg ref="apple"></constructor-arg>
<constructor-arg index="1" value="2700000"></constructor-arg>
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"></bean>
<bean id="apple" class="polymorphism.AppleSpeaker"></bean>
6) ref만 sony로 변경하면 SonySpeaker가 실행됨
- 빈 객체로 apple을 지정해주고 ref로 의존성을 주입해서 바로바로 사용가능하다는게 큰 장점!!
<constructor-arg ref="apple"></constructor-arg>
7) 실행 결과
- <bean>에 추가한 객체는 일단 모두 생성됨
- 요청할때만 생성해주는건 lazy 방식
4.3절 Setter 인젝션 이용하기
1. Setter 인젝션 기본
1) Setter 인젝션 특징
- 생성자인젝션은 생성자를 이용하여 의존성을 처리했지만 Setter 인젝션은 Setter메소드를 호출하여 의존성주입을 처리
- Setter 인젝션이나 Constructor 인젝션 중에 아무거나 사용해도 상관없지만 프로젝트진행시 하나의 방식으로 통일하기
2) Setter 인젝션 테스트 ( _014_BoardWeb_DI_Setter )
① Setter 메소드 추가
- Setter 메소드는 스프링 컨테이너가 자동으로 호출
- <bean> 객체 생성 직'후'에 호출하게됨
- 따라서 Setter 인젝션이 동작하려면 기본 생성자도 당연히 필요함
package polymorphism;
public class SamsungTV implements TV {
private Speaker speaker;
private int price;
public SamsungTV() {
System.out.println("====> SamsungTV 객체(1) 생성");
}
public void setSpeaker(Speaker speaker) {
System.out.println("====> setSpeaker() 호출");
this.speaker = speaker;
}
public void setPrice(int price) {
System.out.println("====> setPrice() 호출");
this.price = price;
}
public void initMethod() {
System.out.println("객체 초기화 작업 처리...");
}
public void destroyMethod() {
System.out.println("객체 삭제전 처리할 로직...");
}
public void powerOn() {
System.out.println("SamsungTV---전원 켠다. (가격 : " + price + ")");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다.");
}
public void volumeUp() {
/* speaker = new SonySpeaker(); */
speaker.volumeUp();
// System.out.println("SamsungTV---소리 올린다.");
}
public void volumeDown() {
/* speaker = new SonySpeaker(); */
/* speaker = new AppleSpeaker(); */
speaker.volumeDown();
// System.out.println("SamsungTV---소리 내린다.");
}
}
② 스프링 설정 파일
- <property> 엘리먼트 사용
- name 속성값에 써있는 speaker에서 첫 글자를 대문자로 바꾸고 앞에 set을 붙인 후 해당하는 메소드 이름을 호출
<bean id="tv" class="polymorphism.SamsungTV">
<!-- property의 name속성으로 각각에 맞는 setter 메소드를 찾아서 의존성 주입 -->
<property name="speaker" ref="apple"></property>
<property name="price" value="2700000"></property>
</bean>
③ name 속성값과 Setter 메소드 이름의 관계
④ 실행 결과
2. p 네임스페이스 사용하기 ( _015_BoardWeb_DI_p_namespace )
1) STS 기능으로 p 네임스페이스 추가
- applicationContext.xml > nemespaces > p 태그 체크 > 저장
- p 네임스페이스를 이용하면 좀 더 효율적으로 의존성 주입 처리가 가능
2) applicationContext.xml
- 참조형 변수에 참조할 객체를 할당할 수 있게됨
- p:변수명-ref="참조할 객체의 이름이나 아이디"
- p:변수명="설정할 값"
<!-- p:변수명-ref = 참조할 객체 -->
<!-- p:변수명 = 설정할 값 -->
<bean id="tv" class="polymorphism.SamsungTV" p:speaker-ref="sony" p:price="2700000">
</bean>
3) 실행 결과
4.4절 컬렉션(Collection) 객체 설정
0. 컬렉션 매핑 엘리먼트 종류 ( _016_BoardWeb_DI_Collection )
1. List 타입 매핑
1) CollectionBean 클래스
- List 컬렉션을 멤버변수로 추가
- getter, setter 메소드 추가
package com.springbook.ioc.injection;
import java.util.List;
public class CollectionBean {
private List<String> addressList;
public List<String> getAddressList() {
return addressList;
}
public void setAddressList(List<String> addressList) {
this.addressList = addressList;
}
public Set<String> getAddressList2() {
return addressList2;
}
}
2) applicationContext.xml
- 위에서 만든 CollectionBean 클래스를 스프링 설정 파일에 <bean> 등록하기
- 세 개의 문자열 주소가 저장된 List 객체를 setAddressList() 메소드를 호출할 때 인자로 전달
- addressList 멤버변수를 초기화하는 설정임
<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">
<!-- Collection(List, Set, Map, Properties) 의존성 주입 -->
<property name="addressList">
<list>
<value>서울시 강남구 역삼동</value>
<value>서울시 성동구 행당동</value>
<value>서울시 성동구 행당동</value>
</list>
</property>
</bean>
3) 클라이언트 클래스 생성
- List 컬렉션이 정상적으로 의존성 주입되었는지 확인
package com.springbook.ioc.injection;
import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class CollectionBeanClient {
public static void main(String[] args) {
AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
CollectionBean bean = (CollectionBean)factory.getBean("collectionBean");
List<String> addressList = bean.getAddressList();
for(String address : addressList) {
System.out.println(address.toString());
}
factory.close();
}
}
4) 실행 결과
- 중복된 값이라도 둘다 출력됨
2. Set 타입 매핑
1) CollectionBean 클래스
- Set 컬렉션을 멤버변수로 추가 (중복 값을 허용하지 않는 집합 개념)
- getter, setter 메소드 추가
package com.springbook.ioc.injection;
import java.util.List;
import java.util.Map;
public class CollectionBean {
private List<String> addressList;
private Set<String> addressList2;
public void setAddressList2(Set<String> addressList2) {
this.addressList2 = addressList2;
}
public Map<String, String> getAddressList3() {
return addressList3;
}
}
2) applicationContext.xml
<!-- <bean id="sony" class="polymorphism.SonySpeaker"></bean>
<bean id="apple" class="polymorphism.AppleSpeaker"></bean> -->
<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">
<property name="addressList2">
<set value-type="java.lang.String">
<value>서울시 강남구 역삼동</value>
<value>서울시 성동구 행당동</value>
<value>서울시 성동구 행당동</value>
</set>
</property>
</bean>
3) 실행 결과
- 같은 주소가 두 번 등록되었지만 저장할 때는 중복된 값을 하나만 저장하므로 실제 실행해보면 중복된 값 중 하나만 출력됨을 알 수 있음
3. Map 타입 매핑
1) CollectionBean 클래스
- Map컬렉션을 멤버변수로 추가 (특정 key로 데이터를 등록하고 사용)
- getter, setter 메소드 추가
package com.springbook.ioc.injection;
import java.util.Map;
public class CollectionBean {
private Map<String, String> addressList3;
public Map<String, String> getAddressList3() {
return addressList3;
}
public void setAddressList3(Map<String, String> addressList3) {
this.addressList3 = addressList3;
}
}
2) applicationContext.xml
- setAddressList() 메소드가 호출될 때 Map 타입의 객체를 인자로 전달하는 설정
- <key> 엘리먼트는 Map 객체의 key 값을 설정할 때 사용
- <value> 엘리먼트는 Map 객체의 value를 설정할 때 사용
<property name="addressList3">
<map>
<entry>
<key><value>고길동</value></key>
<value>서울시 강남구 역삼동</value>
</entry>
<entry>
<key><value>마이콜</value></key>
<value>서울시 강서구 화곡동</value>
</entry>
</map>
</property>
3) 실행 결과
2. Properties 타입 매핑
1) CollectionBean 클래스
- Properties 컬렉션을 멤버변수로 추가 (key=value 형태의 데이터를 등록할 때 사용)
- getter, setter 메소드 추가
package com.springbook.ioc.injection;
import java.util.Properties;
public class CollectionBean {
private Properties addressList4;
public Properties getAddressList4() {
return addressList4;
}
public void setAddressList4(Properties addressList4) {
this.addressList4 = addressList4;
}
}
2) applicationContext.xml
- key=value 형태의 데이터를 등록할 때 사용
- <props> 엘리먼트 사용해서 설정
- name 속성의 addressList4를 통해 setAddressList4() 메소드를 호출함
- 호출 시 Properties 타입의 객체를 인자로 전달하는 설정
<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">
<property name="addressList4">
<props>
<prop key="고길동">서울시 강남구 역삼동</prop>
<prop key="마이콜">서울시 강서구 화곡동</prop>
</props>
</property>
</bean>
3) 실행 결과
댓글