Spring Framework DI (Dependency Injection)
먼저 Spring Framework 에서 왜 DI(의존성 주입) 을 사용하는지 알아보자.
우리가 기존의 방식으로 TV를 자바 코드를 이용하여 코딩한다고 치면,
각각의 Bean에 대응하는 클래스를 호출하기 위해 따로
상황별로 맞게끔 클래스를 만들어야한다.
예를 한번 들어보자 우리가 삼성티비와 엘지티비를 만들려고한다.
package polymorphism;
public interface TV {
public void powerOn();
public void powerOff();
public void volumeUp();
public void volumeDown();
}
공통적으로 TV 라는 기본 속성을 가지는 인터페이스를 구현할것이고,
package polymorphism;
public class LgTv implements TV {
public LgTv(){
System.out.println("LG TV Create Constructor..!");
}
@Override
public void powerOn() {
System.out.println("LG TV PowerOn..!");
}
@Override
public void powerOff() {
System.out.println("LG TV PowerOff..!");
}
@Override
public void volumeUp() {
System.out.println("LG TV VolumeUp..!");
}
@Override
public void volumeDown() {
System.out.println("LG TV VolumeDown..!");
}
}
package polymorphism;
public class SamsungTV implements TV {
public SamsungTv(){
System.out.println("Samsung TV Create Constructor..!");
}
@Override
public void powerOn() {
System.out.println("Samsung TV PowerOn..!");
}
@Override
public void powerOff() {
System.out.println("Samsung TV PowerOff..!");
}
@Override
public void volumeUp() {
System.out.println("Samsung TV VolumeUp..!");
}
@Override
public void volumeDown() {
System.out.println("Samsung TV VolumeDown..!");
}
}
TV를 속성으로 한 엘지티비 , 삼성티비를 만들것이다.
package polymorphism;
public class BeanFactory {
public Object getBean(String beanName) {
if(beanName.equals("samsung")){
return new SamsungTV();
}else if (beanName.equals("lg")){
return new LgTv();
}
return null;
}
}
우리는 여기서 삼성티비와 엘지티비를 빈으로 지정한다.
package polymorphism;
public class TVUser {
public static void main(String[] args) throws IOException {
BeanFactory factory = new BeanFactory();
TV tv = (TV)factory.getBean("lg");
tv.powerOn();
tv.volumeUp();
tv.volumeDown();
tv.powerOff();
}
}
그리고 최종적으로 유저 클래스를 만들어서 사용자에게 엘지티비나 삼성티비를 보여줄것이다.
여기서는 간단하게 코딩을 했지만 큰 프로젝트에서는 서로간에 협업이 이루어지게 되는데,
이 코드를 보면 일단 유저에게 보여주는 과정에서 BeanFactory를 거쳐서 진행되며
생성자를 유저클래스에서 호출한다. 만약 우리가 엘지티비 말고 사용자에게
삼성티비를 보여준다고 하면 직접 개발자가 클래스를 수정해야한다.
그리고 만약 애플티비가 새롭게 출시되어 사용자에게 보여주고 싶다면
우리는 또다시 BeanFactory를 수정해야한다.
이런 과정들을 협업하게 된다고 생각하면 상상만해도 끔찍할것이다.
그래서 나온것이 프레임워크이며 프레임워크는
미리 틀을 짜놓아서 개발자가 그 틀안에서 일하는것인데,
그 기능중 하나가 DI(Dependency Injection) 이다.
바로 스프링 프레임워크 기능 중 하나인 DI 를 알아보자.
이제 우리는 BeanFactory 같은 불편한 클래스를 만들 필요가 없다.
우리는 리소스에 xml을 만들어서 의존성을 주입할것이다.
DI 방식도 여러가지가 있는데, 차근차근 알아보자.
첫번째 생성자 인젝션
만약 이번에 나온 엘지티비에 소니 스피커가 탑재되어
출시된다고 생각해보고 코딩을 해보자.
위에서 우리는 xml 을 이용해 의존성을 주입한다고 이야기했다.
의존성 주입을 이용해서 바로 예제를 실행해보자.
package polymorphism;
public interface Speaker {
void volumeUp();
void volumeDown();
}
스피커라면 공통적으로 가지는 속성을 만들고,
package polymorphism;
public class SonySpeaker implements Speaker {
public SonySpeaker(){
System.out.println("SonySpeaker Create Constructor..!");
}
public void volumeDown(){
System.out.println("SonySpeaker volume Down..!");
}
public void volumeUp(){
System.out.println("SonySpeaker volume Up..!");
}
}
소니 스피커 클래스를 따로 만들자 , 속성은 스피커이다.
package polymorphism;
public class LgTv implements TV {
private Speaker speaker;
public LgTv(Speaker speaker){
System.out.println("LG TV Create Constructor..!");
System.out.println("Constructor Injection..!");
this.speaker = speaker;
}
public Speaker getSpeaker() {
return speaker;
}
public void setSpeaker(Speaker speaker) {
this.speaker = speaker;
}
@Override
public void powerOn() {
System.out.println("LG TV PowerOn..!");
}
@Override
public void powerOff() {
System.out.println("LG TV PowerOff..!");
}
@Override
public void volumeUp() {
speaker.volumeUp();
}
@Override
public void volumeDown() {
speaker.volumeDown();
}
}
이 부분에서 나도 처음에 어이가 없었다.
갑자기 멤버변수에 스피커가 들어오고 생성자에 매개변수로 스피커가 들어온다.
사실 여기가 가장 중요하다. 이제 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="tv" class="polymorphism.LgTv">
<constructor-arg ref="sony"></constructor-arg>
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"/>
</beans>
차근차근 해석해보자.
<bean id = "tv" class="polymorpyism.LgTv">
빈의 아이디 값을 tv로 설정하고 그에 상응하는 실제 클래스는 LgTv이다.
<constructor-arg ref="sony">
생성자의 인자로 sony 라는 아이디 값을 가진 클래스를 주입한다.
<bean id="sony" class="polymorphism.SonySpeaker"/>
빈의 아이디값을 sony로 설정하고 그에 상응하는 실제 클래스는 SonySpeaker 이다.
고로 인자로 SonySpeaker 클래스가 주입된다.
그래서 DI 방식중 이런 방식이 생성자 인젝션이라고 한다.
생성자에 공통분모인 매개변수를 잡고, xml에서 인자를 생성자에 주입한다.
package polymorphism;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import java.io.IOException;
public class TVUser {
public static void main(String[] args) throws IOException {
ApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
TV tv = (TV)factory.getBean("tv");
tv.powerOn();
tv.volumeUp();
tv.volumeDown();
tv.powerOff();
}
}
마지막으로 사용자에게 보여주는 클래스인데, 아까와는 다르게 BeanFactory 가 없어졌고,
ApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
라인이 추가되었다. 해석하자면 애플리케이션 문서를 가져오고 다형성을 이용해서 자식 생성자를
호출하여 아까 만든 xml 파일을 가져온다는 내용이다.
추가적으로 생성자에서 매개변수를 여러개 잡았을때
변수를 초기화하는 법을 알아보자.
LgTv 클래스를 아래와 같이 수정한다.
package polymorphism;
public class LgTv implements TV {
private Speaker speaker;
private int price;
private String modelName;
public LgTv(Speaker speaker,int price,String modelName){
System.out.println("LG TV Create Constructor..!");
System.out.println("Constructor Injection..!");
this.speaker = speaker;
this.price = price;
this.modelName = modelName;
}
public Speaker getSpeaker() {
return speaker;
}
public void setSpeaker(Speaker speaker) {
this.speaker = speaker;
}
@Override
public void powerOn() {
System.out.println("LG TV PowerOn..! Price: " + price + " modelName: " + modelName);
}
@Override
public void powerOff() {
System.out.println("LG TV PowerOff..!");
}
@Override
public void volumeUp() {
speaker.volumeUp();
}
@Override
public void volumeDown() {
speaker.volumeDown();
}
}
멤버변수에 price , modelName 가 추가되었다.
생성자에 매개변수로 price 와 modelName 를 추가하여 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="tv" class="polymorphism.LgTv">
<constructor-arg ref="sony"></constructor-arg>
<constructor-arg value="250000"></constructor-arg>
<constructor-arg value="LG-2500MK"></constructor-arg>
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"/>
</beans>
우리가 매개변수로 넣은 순서대로 인자값을 전달한다.
위에선 인자로 ref(reference) 값을 매핑하였지만,
여기서는 value 값을 매핑해주자.
마지막으로 TVUser 을 실행하면 생성자에 잘 주입되어있는 모습을 볼 수 있다.
두번째 Setter Injection
위에서 Constructor Injection 에 대해서 알아봤다.
두번째 방식에 대해서 알아보자.
package polymorphism;
public class LgTv implements TV {
private Speaker speaker;
private int price;
private String modelName;
/* Constructor Injection
public LgTv(Speaker speaker,int price,String modelName){
System.out.println("LG TV Create Constructor..!");
System.out.println("Constructor Injection..!");
this.speaker = speaker;
this.price = price;
this.modelName = modelName;
}
*/
public LgTv(){
System.out.println("LgTv Default Construct Created..");
}
public Speaker getSpeaker() {
return speaker;
}
public void setSpeaker(Speaker speaker) {
System.out.println("setSpeaker() Method Call..!");
this.speaker = speaker;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getModelName() {
return modelName;
}
public void setModelName(String modelName) {
System.out.println("setModelName() Method Call..!");
this.modelName = modelName;
}
@Override
public void powerOn() {
System.out.println("LG TV PowerOn..! Price: " + price + " modelName: " + modelName);
}
@Override
public void powerOff() {
System.out.println("LG TV PowerOff..!");
}
@Override
public void volumeUp() {
speaker.volumeUp();
}
@Override
public void volumeDown() {
speaker.volumeDown();
}
}
위에서 사용했던 생성자 인젝션 방식을 주석처리하고,
기본 생성자를 만들고 , 멤버변수에 대한 getter/setter 메서드를 만든다.
<?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.LgTv">
<property name="speaker" ref="sony"></property>
<property name="price" value="650000"></property>
<property name="modelName" value="LG-2500MK"></property>
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"/>
</beans>
그리고 xml 을 수정하자. setter Injection 은 property(속성)을 설정한다.
우리가 멤버변수로 선언했던 speaker , price , modelName 과 같이 속성 이름을 설정하고,
setter 메소드에 ref , value 로 값을 주입시킨다.