더 열심히 해!!
class A {
B b = new b();
}
A 클래스가 B 클래스에 의존하고있다.
클래스 A는 B라는 클래스의 new
키워드를 통해 인스턴스를 생성하고 있습니다.
B 타입 변수 b는 B 클래스의 인스턴스를 가리키고 있습니다. 즉, 의존 객체는 A 클래스 내부의 b 변수가 참조하는 객체인 new B()
입니다.
전체는 A, 부분은 b로 A는 b에 의존한다.
즉, A클래스는 의존 객체인 new B()
를 알고있고 의존한다.
이때, DIP를 만족하려면 구체적인 것이 추상화된 것에 의존해야한다.
따라서 B가 A보다 덜 변경되어야하고, 클래스가 아니라 추상화된 것이어야한다.
package expert001_01;
public interface Tire {
String getBrand();
}
package expert001_01;
public class KoreaTire implements Tire{
@Override
public String getBrand() {
return "코리안 타이어";
}
}
package expert001_01;
public class AmericaTire implements Tire{
@Override
public String getBrand() {
return "미국 타이어";
}
}
전략객체생성을 컨텍스트의 생성자내에서 실제 구현 객체에 new
를 사용해 의존하고 있음
package expert001_01;
public class Car {
Tire tire;
public Car(){
tire = new KoreaTire(); # 의존관계
// tire = new AmericaTire();
}
public String getTierBrand(){
return "장착된 타이어:" + tire.getBrand();
}
}
package expert001_01;
public class Driver {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.getTierBrand()); #장착된 타이어:코리안 타이어
}
}
실행화면
장착된 타이어:코리안 타이어
테스트를 하는 방법은 따로 포스팅 했다. 보러가기
package expert001_01;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.*;
class CarTest {
@Test
public void 자동차_장착_타이어브랜드_테스트(){
Car car = new Car();
assertEquals("장착된 타이어:코리안 타이어",car.getTierBrand());
}
}
Driver
가 Car
에 의존한다.Car
이 실제 구현 객체 KoreaTier
에 의존한다.Car
이 자주 변경될 수 있는 구현 객체 부분, 즉 구체적인 것에 의존하고 있어서 DIP를 따르지 않는다.
또한 전략객체를 변경할 떄마다 Car
의 내부코드를 변경해야해 OCP를 위반한다.
🤔 주입이란?
주입은 외부에서라는 뜻을 내포하고 있는 단어이다.
즉, 외부에서 객체를 장착하는 작업을 말한다.
package expert001_02;
public class Car {
Tire tire;
Car(Tire tire){
this.tire = tire; //셍성자를 통해서 전략객체를 주입받는다.
}
public String getTierBrand(){
return "장착된 타이어:" + tire.getBrand();
}
}
package expert001_02;
public class Drive {
public static void main(String[] args) {
Tire koreaTire = new KoreaTire();
Car koreaCar = new Car(koreaTire);
System.out.println(koreaCar.getTierBrand());
Tire americaTier = new AmericaTire();
Car americaCar = new Car(americaTier);
System.out.println(americaCar.getTierBrand());
}
}
실행화면
장착된 타이어:코리안 타이어
장착된 타이어:미국 타이어
package expert001_02;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.*;
class CarTest {
@Test
public void 생성자주입_코리아타이어_장착_자동차_테스트() {
Tire tire = new KoreaTire();
Car car = new Car(tire);
assertEquals("장착된 타이어:코리안 타이어",car.getTierBrand());
}
@Test
public void 생성자주입_미국타이어_장착_자동차_테스트() {
Tire tire = new AmericaTire();
Car car = new Car(tire);
assertEquals("장착된 타이어:미국 타이어",car.getTierBrand());
}
}
Getter
을 이용해서 객체를 주입해보자.
운전자가 자동차를 생산한다.
운전자가 타이어를 생산한다.
운전자가 자동차에 타이어를 장착한다.
package expert001_03;
public class Car {
Tier tier;
public Tier getTire() {
return tier;
}
public void setTire(Tier tier) {
this.tier = tier;
}
public String getTierBrand(){
return "장착된 타이어:" + tier.getBrand();
}
}
package expert001_03;
public class Drive {
public static void main(String[] args) {
System.out.println("=============");
Tier koreaTier = new KoreaTier();
Car koreaCar = new Car();
koreaCar.setTire(koreaTier);
System.out.println(koreaCar.getTierBrand());
Tier americaTier = new AmericaTier();
Car americaCar = new Car();
americaCar.setTire(americaTier);
System.out.println(americaCar.getTierBrand());
}
}
실행화면
=============
장착된 타이어:코리안 타이어
장착된 타이어:미국 타이어
🤔 생성자 vs 속성
속성 주입은 중간에 변경이 가능하고, 생성자는 변경할 수 없다.
컨텍스트는 추상화된 인터페이스만 알면 된다.
구현 객체가 인터페이스만 잘 구현했다면 정상적으로 작동한다.
어떤 새로운 구현 객체를 만들더라도, 인터페이스만 따른다면, 컨텍스트의 코드를 변경하지 않고 사용할 수 있다. - IOP
새롭게 작성한 코드만 재컴파일하고, 다른 코드는 변경하지 않기 때문에 재컴파일하지 않아도 된다.
즉, 재배포가 간단해진다.
두구두구 드디어 스프링..?! 대단
운전자가 종합 쇼핑몰에서 타이어를 구매한다.
운전자가 종합 쇼핑몰에서 자동차를 구매한다.
운전자가 자동차에 타이어를 장착한다.
🤔 뭐가 달라졌을까...??
운전자가 직접 생산하는 것이 아닌 종합 쇼핑몰에서 구매하는 형식으로 바뀜
Driver
의 코드 변경종합쇼핑몰을 이용하기 위해서, 즉 스프링 프레임 워크의 정보를 가져와야한다.
package expert002;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main(String[] args) {
System.out.println("스프링 의존성 주입 - xml파일을 이용");
//왜 컨텍스트일까?
/* # 컨텍스트
전략 객체를 클라이언트로 주입받아 전략객체의 전략 메서드를 사용하는 컨택스트
전략 객체의 사용자, 소비자이다.
컨텍스트는 어떤 전략 객체가 들어올지 모른다.
*/
ApplicationContext context = new ClassPathXmlApplicationContext("expert002.xml");
//스프링 컨테이너에 등록된 빈을 사용
Car car = context.getBean("car", Car.class);
Tire tire = context.getBean("tire", Tire.class);
car.setTire(tire);
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="tire" class="expert002.KoreaTire"></bean>
<bean id="americaTire" class="expert002.AmericaTire"></bean>
<bean id="car" class="expert002.Car"></bean>
</beans>
⚒️
ApplicationContext
란?
스프링 컨테이너 : 자바 객체,빈을 등록하고 생명주기를 관리한다.
스프링 컨테이너의 종류 :BeanFactory
,ApplicationContext
일반적으로ApplicationContext
를 스프링 컨테이너라 한다.
자바코드에는 KoreaTire
인지, AmericaTire
인지 지칭하지 않는다. 즉 의존하지 않는다.
🤔 만약 AmericaTire
로 변경하고 싶으면, xml파일의 id만 변경하면 된다.
💡 이번에는 속성주입까지 스프링 설정 파일을 통해서 사용하자!
운전자가 종합 쇼핑몰에서 자동차를 구매요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합쇼핑몰은 자동차에 타이어를 장착한다.
종합쇼핑몰은 운전자에게 자동차를 전달한다.
<?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 id="americaTire" class="expert003.AmericaTire"/>
<bean id="car" class="expert003.Car">
<property name="tire" ref="koreaTire"/>
</bean>
</beans>
💡 property 태그를 사용해 속성을 주입한다.
<bean id="car" class="expert003.Car">
<property name="tire" ref="koreaTire"/>
</bean>
package expert003;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main(String[] args) {
System.out.println("스프링 의존성 주입 - 스프링 설정 파일에서 속성을 주입");
ApplicationContext context = new ClassPathXmlApplicationContext("expert003/expert003.xml");
//속성까지 모두 주입한 객체를 전달 받는다.
Car car = context.getBean("car", Car.class);
System.out.println(car.getTireBrand()); //장착된 타이어:코리안 타이어
}
}
@Autowired
를 통한 속성주입xml파일쓰기 귀찮네...? 속성도 자동 주입되면 편할텐데?
운전자가 종합 쇼핑몰에서 자동차를 구매요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합쇼핑몰은 자동차에 타이어를 장착한다.
종합쇼핑몰은 운전자에게 자동차를 전달한다.
🤔
@Autowired
는?의존관계를 자동으로 주입해준다.
Car
에서@Autowired
package expert004;
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.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config />
<bean id="tire" class="expert004.KoreaTire"></bean>
<bean id="tire2" class="expert004.AmericaTire"></bean>
<bean id="car" class="expert004.Car"></bean>
</beans>
실행화면
스프링 의존성 주입 - @Autowired를 통한 속성주입
장착된 타이어:미국 타이어
더 알아보기 : 속성에 쓰일 빈이 유일하고, id가 명확하지 않을 때
xml파일 수정
<bean class="expert004.AmericaTire"></bean> <bean id="car" class="expert004.Car"></bean> </beans>
실행결과
장착된 타이어:미국 타이어
👉 해당 type을 구현한 빈이 있고, 빈이 하나만 있을 때는 그 빈을 자동으로 할당해준다.
그러나 명확하게 id를 써주는 것이 좋다!
추가로, 빈을 등록할때는 id보다는 type을 먼저 검사한다.
@Resource
를 통한 속성주입@Resource
란?@Autowired
와 동일하게 작동한다. 그러나 @Resource
는 자바의 표준 어노테이션이다.@Resource
는 id와 type중에서 id가 더 우선순위가 높다.필자가 추천하는 방식
xml파일을 쓰고, 사용고려 객체를 모두 빈으로 등록하고, id값을 바꿔가면서 사용한다.
(그런데 아마도 요즘은 yml파일을 더 쓸수도...?)