포스팅의 목차는 다음과 같다.
1. Dependency Injection(의존성 주입 : DI)
1) 생성자 주입
2) setter주입
3) p name space
4) 예제
2. 컬랙션의 사용
3. 어노테이션(@)
1) @Component
2) @Autowired
3) @Qualifier
4) @과 xml의 융합
1. Dependency Injection(의존성 주입 : DI)
Dependency Injection 의존성 주입은 총 세가지 방법으로 진행할 수 있다. 내용 설명 전 실습에 대해 다시 한 번 정리하자면 4파트로 설명할 수 있다.
- tv객체와 remote는 의존관계에 있다.
- tv의 메서드를 수행하기 위해서 remote객체가 필요하다.
- remote객체를 주입받아야 tv객체를 생성할 수 있다.(remote가 null이면 전원이나 키겠지만, 볼륨조절:내가 원하는 바를 수행하지 못한다.)
- 객체주입은 1)생성자 주입 2) setter주입으로 DI를 수행한다.
1) 생성자를 통한 주입
저번 포스팅의 맨 마지막 목차 확인을 통해 내용을 파악할 수 있다.
[IoC] 설치와 Spring Framwork의 이해
해당 포스팅의 목차는 다음과 같다. 1. 설치 및 초기설정 2. Spring Framwork란 3. 결합도와 스프링 프레임워크 4. Bean 속성 5. Dependency Injection(의존성 주입) 1. 설치 및 초기설정 1) Spring 설치 아래의..
gkawjdgml.tistory.com
2) setter를 활용한 주입
사용할 객체(TV)에 의존관계를 주입할 객체(Remote)를 변수화하여 setter를 생성한다.
package test;
public class SamsungTV implements TV{
//멤버변수
private Remote remote;
private int tvid;
//생성자
public SamsungTV() {
System.out.println("Samsung 티비 객체 생성");
}
//getter & setter
public Remote getRemote() {
return remote;
}
public void setRemote(Remote remote) {
this.remote = remote;
}
public int getTvid() {
return tvid;
}
public void setTvid(int tvid) {
this.tvid = tvid;
}
//객체 생성의 수행과 사용 종료시 할 작업 메서드화
public void initMethod() {
System.out.println("객체의 멤버변수들 초기화 작업");
// 초기화 : 초기값 생성
// 생성자, setter가 원래 하던 행동
}
public void destoryMethod(){
System.out.println("객체 메모리 해제 작업");
}
//현재 사용 메서드
@Override
public void powerOn() {
// TODO Auto-generated method stub
}
@Override
public void powerOff() {
// TODO Auto-generated method stub
}
@Override
public void volumeUp() {
// TODO Auto-generated method stub
}
@Override
public void volumeDown() {
// TODO Auto-generated method stub
}
//수정 전 메서드
/* public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 니다.");
}
public void soundUp() {
System.out.println("소리를 올립니다.");
}
public void soundDown() {
System.out.println("소리를 립니다.");
} */
}
위의 setter를 주입하는 작업은 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"
xmlns:p="http://www.springframework.org/schema/p"
<!-- new를 대신하는 역할 -->
<bean class="test.SamsungTV" id="tv">
<property name="remote" ref="rb"/>
<property name="tvid" value="1002"/>
</bean>
<bean class="test.RemoteB" id="rb"/>
</beans>
생성자 주입 방식과 다른 태그를 사용하는 것을 확인할 수 있다.
*생성자 주입때는 리모컨 객체가 먼저 생성되고 티비였는데, setter로 주입하니까 티비먼저, 리모컨 나중이네?*
순서에 대해 설명하자면 내용은 아래와 같다.
생성자 주입 : New RemoteB(); -> new TV(RemoteB);
setter주입 : New TV();//티비 기본생성자 -> new RemoteB(); -> tv.setRemote(RemoteB);//setter()메서드들을 호출
3) p네임 스페이스
사용도가 높은 편은 아니나, 공공연히 사용되는 코드기에 다른 코더의 코드분석시 용이할 수 있으므로 개념을 짚어나간다. 이 개념은 setter주입을 조금 더 편리하게 할 수 있게 한다.
하단의 source탭이 선택되어 있는 상태에서 Namespaces탭으로 이동하게 되면 다음과 같은 화면을 마주할 수 있다.
기본으로 선택되어있는 beans외에 p라는 태그를 클릭하고 다시 source탭으로 이동한다.
설정에 대한 정보에 아래와같이 한 줄이 추가된 것을 확인할 수 있다.
위의 작업을 통해 p태그를 사용할 수 있게 되며, 코드는 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="test.SamsungTV" id="tv" p:remote-ref="rb" p:tvid="1002"/>
<bean class="test.RemoteB" id="rb"/>
</beans>
* 이때 만일 설치가 완벽히 진행되지 않은 이유로 P네임 스페이스를 사용할 수 없을 수 있는데, 이때는 spring tool을 재설치 하거나 beans 태그 내부에 아래의 문장을 추가하면 정상적으로 사용 가능하다. *
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
4) 예제
현재까지의 내용으로 예제 풀이를 진행한다. iPhone / galaxy phone, Apple Watch / galaxy watch의 조합으로 생성하고, 각각 의존관계를 형성하여 구동될 수 있도록 구현할 예정이다. 이외 추가 조건은 다음과 같다.
갤럭시 + 스마트워치 -> 생성자 주입
아이폰 + 애플워치 -> setter주입
① 인터페이스 생성
각 phone과 watch류의 메서드명 통일을 위해 인터페이스를 생성한다.
phone.interface
package exam;
public interface Phone {
void powerOn();
void powerOff();
void soundUp();
void soundDown();
}
watch.interface
package exam;
public interface Watch {
void soundUp();
void soundDown();
}
② Galaxy Phone
package exam;
public class GalaxyPhone implements Phone{
//생성자 주입 방식을 활용
private Watch w;
public GalaxyPhone(Watch w){
this.w=w;
}
@Override
public void powerOn() {
System.out.println("갤럭시 전원 on");
}
@Override
public void powerOff() {
System.out.println("갤럭시 전원 off");
}
//각 볼륨 조절에 watch의 메서드를 수행할 수 있도록 함
@Override
public void soundUp() {
w.soundUp();
}
@Override
public void soundDown() {
w.soundDown();
}
}
③ IPhone
package exam;
public class IPhone implements Phone{
//setter 주입을 사용할 예정
private Watch w;
public Watch getW() {
return w;
}
public void setW(Watch w) {
this.w = w;
}
@Override
public void powerOn() {
System.out.println("아이폰 전원 on");
}
@Override
public void powerOff() {
System.out.println("아이폰 전원 off");
}
//각 볼륨 조절에 watch의 메서드를 수행할 수 있도록 함
@Override
public void soundUp() {
w.soundUp();
}
@Override
public void soundDown() {
w.soundDown();
}
}
④ 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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 갤럭시폰 + 스마트워치 -->
<bean class="exam.GalaxyPhone" id="gp">
<constructor-arg ref="sw"/>
</bean>
<bean class="exam.SmartWatch" id="sw"/>
<!-- 아이폰 + 애플워치 -->
<bean class="exam.IPhone" id="ip">
<property name="w" ref="aw"/>
</bean>
<bean class="exam.AppleWatch" id="aw"/>
</beans>
⑤ Client 및 실행 결과
컨테이너에 객체를 요청하고, 각 phone을 동작시켜보았다.
package exam;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class Client {
public static void main(String[] args) {
//설정파일을 참조
AbstractApplicationContext factory=new GenericXmlApplicationContext("applicationContext.xml");
//컨테이너에 객체를 요청!
Phone ip=(Phone)factory.getBean("ip");
Phone gp=(Phone)factory.getBean("gp");
ip.powerOn();
ip.soundUp();
ip.powerOff();
gp.powerOn();
gp.soundDown();
gp.soundDown();
gp.powerOff();
factory.close();
}
}
결과는 아래와 같았다.
2. 컬랙션의 사용
3. 어노테이션(@)
설정파일(xml)의 단점은 과도한 설정이 있다는 것이다. 이 단점을 극복하기 위해 어노테이션(애너테이션)이 발생하였다. 어노테이션으로 인해 컨테이너가 객체의 생성과 호출 시기를 파악할 수 있게 된다. 즉 @이 설정파일의 역할을 수행할 수 있다.
1) @Component
어노테이션을 활용하기 위해서는 초기 설정이 필요하다. name spaces탭에서 context를 선택해주면 된다.
다시 source탭으로 돌아오게 되면 beans태그에 다른 설정이 추가된 것을 확인할 수 있다. 하단에 <context:component-scan>태그를 활용하여 패키지를 참조할 수 있게 된다. base-package는 필수 속성으로, 참조해야할 패키지명을 작성해준다. 이때 태그 바디는 닫아주어도 무관하다.
초기화(new)를 진행하고 싶은 클래스 메서드의 상단에 @Component를 선언해주면 <bean class="test.LgTV"/>(new LgTV();)의 역할을 대신할 수 있다.
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("tv") // <bean class="test.LgTV" id="tv"/> 혹은 LgTV tv = new LgTV();와 같다.
public class LgTV implements TV{
private Remote remote;
private int tvid;
public LgTV() {
System.out.println("LG티비 객체 생성");
}
//TV살때부터 리모컨과 연결(초기화)
// 생성자를 통해 의존관계에 있는 객체를 주입하고 있다. -> 생성자주입, 생성자 인젝션
public LgTV(Remote remote) {
System.out.println("lg티비 객체생성 2");
this.remote=remote;
}
public LgTV(Remote remote, int tvid) {
System.out.println("lg티비 객체생성 3");
this.remote=remote;
this.tvid=tvid;
}
public void powerOn() {
System.out.println("전원 ON");
}
public void powerOff() {
System.out.println("전원 Off");
}
public void volumeUp() {
//System.out.println("소리 UP");
remote.volumeUp();
}
public void volumeDown() {
//System.out.println("소리 DOWN");
remote.volumeDown();
}
}
이때 어노테이션을 사용하려면 해당 클래스에 기본생성자가 꼭 있어야하만 한다! 서블릿의 기본생성자 존재 이유에 대해 상기해보면 이해할 수 있다.
2) @Autowired
인젝션(DI 혹은 의존성 주입)이 필요할때 사용되는 어노테이션이다. 위의 코드를 살펴보면, 볼륨조절 메서드를 수행하기 위해 Remote라는 객체의 초기화(new)가 필요한 것을 알 수 있다. 어노테이션은 설정하고자 하는 대상 위에 위치하기 때문에 그 상단에 @Autowired을 선언함으로써 의존성을 주입할 수 있게 된다. (autowired는 멤버변수와 메서드 모두에 사용된다.)
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("tv") // <bean class="test.LgTV" id="tv"/> 혹은 LgTV tv = new LgTV();와 같다.
public class LgTV implements TV{
@Autowired // 멤버변수, 메서드에 모두 사용 가능하다.(의존성 주입의 목적)
private Remote remote;
private int tvid;
public LgTV() {
System.out.println("LG티비 객체 생성");
}
//TV살때부터 리모컨과 연결(초기화)
// 생성자를 통해 의존관계에 있는 객체를 주입하고 있다. -> 생성자주입, 생성자 인젝션
public LgTV(Remote remote) {
System.out.println("lg티비 객체생성 2");
this.remote=remote;
}
public LgTV(Remote remote, int tvid) {
System.out.println("lg티비 객체생성 3");
this.remote=remote;
this.tvid=tvid;
}
public void powerOn() {
System.out.println("전원 ON");
}
public void powerOff() {
System.out.println("전원 Off");
}
public void volumeUp() {
//System.out.println("소리 UP");
remote.volumeUp();
}
public void volumeDown() {
//System.out.println("소리 DOWN");
remote.volumeDown();
}
}
@Autowired는 본인이 설정된 대상의 타입(나 Remote구나?)을 우선으로 확인하고, 다음 순서로 스프링 컨테이너가 가지고 있는 Remote를 확인한다. 즉, 해당 타입이 메모리에 존재하는지(new가 되었는지)를 확인한다. 메모리에 존재하지 않은 상태로 프로그램을 동작하게 되면 다음과 같은 내용의 오류 문구를 마주하게 된다. - 자주 마주하게 되는 오류이므로 내용 숙지
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tv': Injection of autowired dependencies failed;
interface는 객체화되지 않으니 초기화 하고자 하는 클래스로 이동하여 클래스 메서드 상단에 @Component를 달아주면 된다. 이때 @Autowired는 해당 멤버변수의 타입만으로 DI가 가능하기 때문에 id설정을 별도로 해주지 않아도 된다!
package test;
import org.springframework.stereotype.Component;
@Component
public class RemoteA {
public RemoteA() {
System.out.println("A리모컨 생성완료");
}
public void volumeUp() {
System.out.println("A: 소리 UP");
}
public void volumeDown() {
System.out.println("A: 소리 DOWN");
}
}
3) @Qualifier
위 같은 경우에 Remote라는 타입에 해당하는 두개 이상의 객체를 초기화하고 싶다면 이때 모호성 이슈가 발생하게 된다.
즉, 리모컨이 A리모컨과 B리모컨으로 두개가 존재할때 둘 중 누구를 참조해야할지 모르는 상황이 발생하는 것이다.
이 이슈를 해결하기 위한 개념으로 @Qualifier을 사용하면 된다. DI될 객체의 이름으로 의존성 주입이 가능해진다.
각 리모컨 클래스 상단에 @Component("id설정")을 진행해줌으로써 객체에 이름을 부여한다.
package test;
import org.springframework.stereotype.Component;
@Component("ra")
public class RemoteA {
public RemoteA() {
System.out.println("A리모컨 생성완료");
}
public void volumeUp() {
System.out.println("A: 소리 UP");
}
public void volumeDown() {
System.out.println("A: 소리 DOWN");
}
}
package test;
import org.springframework.stereotype.Component;
@Component("rb")
public class RemoteB implements Remote{
public RemoteB() {
System.out.println("B리모컨 객체생성");
}
@Override
public void volumeUp() {
System.out.println("b: 소리 ++");
}
@Override
public void volumeDown() {
System.out.println("b: 소리 --");
}
}
이후 다시 TV 객체 클래스로 돌아와서 @Qualifier("참조할 객체명")을 통해 프로그램을 정상 수행시킬 수 있다.
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("tv") // <bean class="test.LgTV" id="tv"/> 혹은 LgTV tv = new LgTV();와 같다.
public class LgTV implements TV{
@Autowired // 멤버변수, 메서드에 모두 사용 가능하다.(의존성 주입의 목적)
@Qualifier("ra")//참조할 클래스객체의 이름을 지정
private Remote remote;
private int tvid;
public LgTV() {
System.out.println("LG티비 객체 생성");
}
//TV살때부터 리모컨과 연결(초기화)
// 생성자를 통해 의존관계에 있는 객체를 주입하고 있다. -> 생성자주입, 생성자 인젝션
public LgTV(Remote remote) {
System.out.println("lg티비 객체생성 2");
this.remote=remote;
}
public LgTV(Remote remote, int tvid) {
System.out.println("lg티비 객체생성 3");
this.remote=remote;
this.tvid=tvid;
}
public void powerOn() {
System.out.println("전원 ON");
}
public void powerOff() {
System.out.println("전원 Off");
}
public void volumeUp() {
//System.out.println("소리 UP");
remote.volumeUp();
}
public void volumeDown() {
//System.out.println("소리 DOWN");
remote.volumeDown();
}
}
4) @와 xml의 융합
과도한 설정이 문제가 되는 xml의 단점을 보완하고자 어노테이션을 사용했는데, 어노테이션은 모호성 이슈가 문제가 된다. 그래서 두 개념을 융합해서 사용한다.
@Qualifier내용을 삭제하고, xml파일에서 DI될 객체를 지정해주면 된다.
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("tv") // <bean class="test.LgTV" id="tv"/> 혹은 LgTV tv = new LgTV();와 같다.
public class LgTV implements TV{
@Autowired // 멤버변수, 메서드에 모두 사용 가능하다.(의존성 주입의 목적)
private Remote remote;
private int tvid;
public LgTV() {
System.out.println("LG티비 객체 생성");
}
//TV살때부터 리모컨과 연결(초기화)
// 생성자를 통해 의존관계에 있는 객체를 주입하고 있다. -> 생성자주입, 생성자 인젝션
public LgTV(Remote remote) {
System.out.println("lg티비 객체생성 2");
this.remote=remote;
}
public LgTV(Remote remote, int tvid) {
System.out.println("lg티비 객체생성 3");
this.remote=remote;
this.tvid=tvid;
}
public void powerOn() {
System.out.println("전원 ON");
}
public void powerOff() {
System.out.println("전원 Off");
}
public void volumeUp() {
//System.out.println("소리 UP");
remote.volumeUp();
}
public void volumeDown() {
//System.out.println("소리 DOWN");
remote.volumeDown();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="test"/><!-- 어노테이션 사용 가능해짐 -->
<bean class="test.RemoteB"/>
</beans>
결과적으로 @Autowired가 LgTV에 선언된 Remote가 스프링 컨테이너에 있는지 조회하게 될텐데, 이때 <bean>으로 RemoteB에 있는 객체가 초기화되기 때문에 각 Remote 클래스 객체에는 어노테이션이 추가되지 않을 것이다.
'Spring' 카테고리의 다른 글
[IoC] XML파일 @로 바꾸기 : Controller_2 (0) | 2022.04.01 |
---|---|
[IoC] MVC패턴 이해 실습_2 : Spring제공 클래스 사용 (0) | 2022.03.31 |
[IoC] MVC패턴 이해 실습_1 (0) | 2022.03.30 |
[IoC] 스프링, DB연동_service (0) | 2022.03.29 |
[개요] 설치와 Spring Framwork의 이해 (0) | 2022.03.28 |