운전자가 자동차를 생산한다.
자동차는 내부적으로 타이어를 생산한다.
new Car();
Car 객체 생성자에서 new Tire();
new를 실행하는 Car와 Tire 사이에서 Car가 Tire에 의존한다.
변수에 값을 할당하는 모든 곳에 의존 관계가 생긴다. 즉, 대입 연산자(=)에 의해 변수에 값이 할당되는 순간에 의존이 생긴다. 변수가 지역 변수이건 속성이건, 할당되는 값이 리터럴이건 객체이건 의존은 발생한다.
그래서 의존 대상은 내부에 있을수도 있고 외부에 있을수도 있다. 그리고 DI는 외부에 있는 의존 대상을 주입하는 것을 말한다. 의존 대상을 구현하고 배치할 때 SOLID와 응집도는 높이고 결합도는 낮추라는 기본 원칙에 충실해야 한다. 그래야 프로젝트 구현과 유지보수가 수월해진다.
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire{
public String getBrand(){
return "코리아 타이어";
}
}
public class AmericaTire implements Tire{
public String getBrand(){
return "미국 타이어";
}
}
public class Car {
Tire tire;
public Car(){
tire = new KoreaTire(); // 의존 간계가 일어난다.
// tire = new AmericaTire();
}
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
new KoreaTire();
부분이 바로 자동차가 타이어를 생산(new)하는 부분, 의존 관계가 일어나고 있는 부분이다.
public class Driver {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.getTireBrand());
}
}
tire
속성에 새로운 타이어를 생성해서 참조할 수 있게 해주었다.운전자가 타이어를 생산한다.
운전자가 자동차를 생산하면서 타이어를 장착한다.
Tire tire = new KoreaTire();
Car car = new Car(tire);
외부에서라는 뜻을 내포한 단어
위 코드에선 자동차 내부에서 타이어를 생산하는 것이 아니라 외부에서 생산된 타이어를 자동차에 장착하는 작업이 주입이다.
public class Car {
Tire tire;
public Car(){
tire = new KoreaTire(); // 의존 간계가 일어난다.
// tire = new AmericaTire();
}
기존 코드는 Car
객체가 Tire
를 직접 생산하는 방식, 즉 Tire
에 대한 의존성을 자체적으로 해결하는 방식이다.
이번에는 외부에서 생산된 tire
객체를 Car
생성자 인자로 주입(장착)하는 형태로 구현하겠다.
public class Car {
Tire tire;
public Car(Tire tire){
this.tire = tire;
}
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
기존 코드와 비교했을 때 생성자 부분이 달라졌다. new
가 사라지고 생성자에 인자가 추가되었다.
public class Driver {
public static void main(String[] args) {
Tire tire = new KoreaTire();
// Tire tire = new America.tire();
Car car = new Car(tire);
System.out.println(car.getTireBrand());
}
}
new
를 사용해 타이어를 생산하는 코드가 Driver.java
로 이동했다.
생산된 tire
객체 참조 변수를 Car
생성자 인자로 전달한다.
기존 코드에서는 Car
가 구체적으로 KoreaTire
를 생산할지 AmericaTire
를 생산할지 결정했다. 그러나 이러한 코드는 유연성이 떨어진다. 기존 코드를 현실세계에 비유하면 "자동차가 생산될 때 어떤 타이어를 생산해서 장착할까"라는 고민을 자동차가 스스로 하는 것이다. 그러나 변경된 Driver.java
코드는 "운전자가 차량을 생산할 때 운전자가 '직접' 어떤 타이어를 장착할까" 고민하는 것이다.
운전자가 타이어를 생산한다.
운전자가 자동차를 생산한다.
운전자가 자동차에 타이어를 장착한다.
Tire tire = new KoreaTire();
Car car = new Car();
car.setTire(tire);
생성자를 통해 의존성을 주입하는 것을 현실 세계에서 보면 자동차를 생산(구입)할 때 한번 타이어를 장착하면 더 이상 타이어를 교체 장착할 방법이 없다. 그래서 더 현실적인 방법인 운전자가 원할 때 마다 Car
의 Tire
를 교체하는 것이다. 이를 구현하려면 속성을 통한 의존성 주입이 필요하다.
운전자가 종합 쇼핑몰에서 타이어를 구매한다.
운전자가 종합 쇼핑몰에서 자동차를 구매한다.
운전자가 자동차에 타이어를 장착한다.
ApplicationContext context = new ClassPathXmlApplicationContext("expert002.xml", Driver.class);
Tire tire = (Tire)context.getBean("tire");
Car car = (Car)context.getBean("tire");
car.setTire(tire);
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("expert002/expert002.xml");
Car car = context.getBean("car", Car.class);
Tire tire = context.getBean("tire", Tire.class);
car.setTire(tire);
System.out.println(car.getTireBrand());
}
}
Driver.java
파일의 코드가 변경되었다. 자세히 살펴보겠다.
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
스프링 프레임워크에 대한 정보를 가지고 있는 패키지 목록이다.
ClassPathXmlApplicationContext("expert002/expert002.xml");
스프링 프레임워크에 필요한 정보이다.
Car car = context.getBean("car", Car.class);
Tire tire = context.getBean("tire", Tire.class);
Car
와 Tire
를 구매하는 코드이다.
<?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/scema/beans/spring-beans.xsd">
<bean id="tire" class="expert002.KoreaTire"></bean>
<bean id="americaTire" class="expert002.AmericaTire"></bean>
<bean id="car" class="expert002.car"></bean>
</beans>
스프링 프레임워크에서 사용할 tire
, americaTire
, car
목록을 등록했다.
이 과정을 현실 세계로 풀어보면 다음과 같다.
**"KoreaTire 상품이 tire
라는 이름으로 진열돼 있고, 구매(getBean)할 수 있다.
위 코드에서 스프링을 도입했을 때 일어나느 효과는 무엇일까?
자동차 타이어 브랜드를 변경할 때 그 무엇도 재컴파일/재배포하지 않아도 XML 파일만 수정하면 프로그램 실행 결과를 바꿀 수 있다.
운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("expert003/expert003.xml");
Car car = context.getBean("car", Car.class);
System.out.println(car.getTireBrand());
}
}
<?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="koreaTire" class="expert003.KoreaTire"></bean>
<bean id="americaTire" class="expert003.AmericaTire"></bean>
<bean id="car" class="expert003.Car">
<property name="tire" ref="koreaTire"></property>
<!--
<property name="tire" ref="americaTire"></property>
-->
</bean>
</beans>
기존 코드에서 아래 코드가 삭제되었다.
Tire tire = context.getBean("tire", Tire.class);
car.setTire(tire);
car.setTire(tire);
이 코드는 property name="tire"
부분으로 Tire tire = context.getBean("tire", Tire.class);
이 코드는 <bean id="koreaTire" class="expert003.KoreaTire"></bean>
그리고 ref="koreaTire"
부분으로 대체되었다.
<bean id="koreaTire" class="expert003.KoreaTire"></bean>
<property name="tire" ref="koreaTire"></property>
얼핏보면 조금 복잡해진 것 같지만 현실 세계에 더 가까워졌으며 유지보수하기 편한 방식으로 변하였다.
운영자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
import org.springframework.beans.factory.annotation.Autowired;
public class Car {
@Autowired
Tire tire;
public Tire getTire(){
return tire;
}
//public void setTire(Tire tire){
// this.tire = tire;
//}
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
<bean id="car" class="expert003.Car">
<property name="tire" ref="koreaTire"></property>
</bean>
<bean id="car" class="expert004.car"></bean>
변경된 XML
코드를 보면 property
태그가 사라졌다. @Autowired
를 통해 car
의 property
를 자동으로 엮어줄 수 있어 (자동 의존성 주입) 생략이 가능해졌다.
운영자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
import javax.annotation.Resource;
public class Car {
@Resource;
Tire tire;
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
@Autowired
어노테이션을 @Resource
로 변경했다. @Autowired
는 스프링 어노테이션이고 @Resource
는 자바 표준 어노테이션이다.
두 어노테이션은 어느 차이점이 있는지 비교해보겠다.
@Autowired | @Resource | |
---|---|---|
출처 | 스프링 프레임워크 | 표준 자바 |
소속 패키지 | org.springframework.beans.factory.annotation.Autowird | javax.annotation.Resource |
빈 검색 방식 | byType 먼저, 못 찾으면 byName(id) | byName(id)먼저, 못 찾으면 byType |
특이사항 | @Qualifier("") 협업 | name 어트리뷰트 |
byName 강제하기 | @Autowired @Qualifier("tire1") | @Resource(name="tire1") |
<?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: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.prg/schema/context/spring-context-3.1.xsd">
<context:annotation-config></context:annotation-config>
<bean class="expert006.KoreaTire"></bean>
<bean id="car" class="expert006.car"></bean>
</beans>
package expert006;
import javax.annotation.Resource;
public class Car {
@Resource
Tire tire;
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
package expert006;
import org.springframework.beans.factory.annotation.Autowired;
public class Car {
@Autowired
Tire tire;
public String getTireBrand(){
return "장착된 타이어: " + tire.getBrand();
}
}
정상적으로 작동한다.
<?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: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.prg/schema/context/spring-context-3.1.xsd">
<context:annotation-config></context:annotation-config>
<bean class="expert006.KoreaTire"></bean>
<bean class="expert006.AmericaTire"></bean>
<bean id="car" class="expert006.car"></bean>
</beans>
No unique bean of type [expert006.Tire] is defined:
expected single matching bean byt found 2:
[expert006.KoreaTire#0, expert006.AmericaTire#0]
<?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: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.prg/schema/context/spring-context-3.1.xsd">
<context:annotation-config></context:annotation-config>
<bean id="tire" class="expert006.KoreaTire"></bean>
<bean id="tire2" class="expert006.AmericaTire"></bean>
<bean id="car" class="expert006.car"></bean>
</beans>
장착된 타이어: 코리아 타이어
<?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: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.prg/schema/context/spring-context-3.1.xsd">
<context:annotation-config></context:annotation-config>
<bean id="tire1" class="expert006.KoreaTire"></bean>
<bean id="tire2" class="expert006.AmericaTire"></bean>
<bean id="car" class="expert006.car"></bean>
</beans>
No unique bean of type [expert006.Tire] is defined:
expected single matching bean byt found 2: [tire1, tire2]
<?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: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.prg/schema/context/spring-context-3.1.xsd">
<context:annotation-config></context:annotation-config>
<bean id="tire" class="expert006.Door"></bean>
<bean id="car" class="expert006.car"></bean>
</beans>
Bean named 'tire' must be of type [expert006.Tire], but was actually of type [expert006.Door]
No matching bean of type [expert006.Tire] found for dependency: expected at least 1 bean ...
이 경우 byName 우선인 @Resource와 byType 우선인 @Autowired 에러 메시지가 서로 다르다.
큰 차이는 없는데 무엇을 써야할까?
@Autowired
가 자동으로 주입되는 장점이 있지만 본 코드에서 사용한 Car
객체는 @Resource
가 더 어울린다. 그런데 @Resource
와 <property>
중에서는 <property>
가 더 낫다. XML
파일만 봐도 DI 관계를 더 쉽게 확인할 수 있고 유지보수성도 좋다. 그리고 프로젝트 규모가 커지면 XML 파일을 용도별로 분리할 수 있어 더욱 좋다.
그러나 개발에서 "무조건" 이라는 말은 없다. 따라서 프로젝트 규모와 팀 성향에 맞게 사용해야 한다.
스프링 입문을 위한 객체 지향의 원리와 이해