1. CONCEPT
마틴 파울러는 2004년의글에서 제어의 어떤 측면이 역행되는 것인지에 대한 의문을 제기하고 의존하는 객체를 역행적으로 취득하는 것이라는 결론을 내렸다. 그는 그와 같은 정의에 기초하여 제어 역행이라는 용어에 좀더 참신한 ‘의존성 주입(DI,dependency injection)’이라는 이름을 지어줬다.
모든 어플리케이션은 비지니스 로직을 수행하기 위해 서로 협업하는 둘 또는 그 이상의 클래스들로 이뤄진다. 전통적으로 각 객체는 협업할 객체의 참조를 취득해야 하는 책임이 있다. 이것이 의존성이다. 이는 결합도가 높으며 테스트하기 어려운 코드를 만들어 낸다.
IoC를 적용함으로써 객체들은 시스템 내의 각 객체를 조정하는 어떤 외부의 존재에 의해 생성 시점에서 의존성을 부여 받는데, 의존성이 객체로 주입(inject)된다는 말이다. 따라서 IoC는 한 객체가 협업해야 하는 다른 객체의 참조를 취득하는 방법에 대한 제어의 역행이라는 의미를 갖는다. 일반적으로 IoC는 의존성주입(DI), 의존성 룩업(DL) 두개의 하위부류로 나눌 수 있으며 일반적으로 DI를 이야기 할 때는 IoC를 가리키지만 IoC를 이야기 할 때는 DI를 가리키는 것은 아니다. DI도 여러종류(세터주입,생성자주입,메소드주입)가 있지만 DL의 경우도 의존성풀과 컨텍스트화된 의존성룩업(CDL) 두 종류가 있다.
EJB를 통한 주문생성
• 온라인 쇼핑몰에서 무상태세션빈으로 만들어진 OrderService Bean이 있다고 하고 Application에서 OrderService Bean의 createOrder 메소드를 호출하는 예
private OrderService orderService;
public void doRequest(HttpServletRequest request) {
Order order = createOrder(request);
OrderService orderService = getOrderService();
orderService.createOrder();
}
Private OrderService getOrderService() throws CreateException {
Context initial = new InitialContext();
Context myEnv = (Context) initial.lookup(“java:comp/env”);
Object ref = myEnv.lookup(“ejb/OrderServiceHome”);
OrderServiceHome home = (OrderServiceHome) PortableRemoteObject.narrow(ref,
OrderService.class);
orderService = home.create();
return orderService;
}
• Spring의 Ioc를 통한 주문생성
private OrderService orderService;
public void doRequest(HttpServletRequest request) {
Order order = createOrder(request);
OrderService orderService = getOrderService();
orderService.createOrder();
}
Public void setOrderService(OrderService orderService){
this.orderService = orderService
}
OrderService에 대한 참조는 스프링 콘테이너에 의해 setOrderService를 통해 부여(setter-injection)된다.
스프링을 사용하면 의존성을 가져오는 문제에 대해서는 더이상 관여가 필요가 없다.
2. IoC 실습하기
오라클자바몰 운영 시나리오
“오라클자바몰”이라는 회사를 만들어서 내가 잘아는 분야인 차를 팔기로 정했다.
차량의 구매는 현대자동차와 계약을 해서 차량을 구매하기로 하고 돈을 지불하기로 했다.
Money.java
package ioc.ioc1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Money {
private int amount;
}
Car.java
package ioc.ioc1;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Car {
private String name;
}
HyndaiMaker.java
package ioc.ioc1;
public class HyundaiMaker {
/* 여기에 차를 팔기전에 해야할 일들이 필요하죠 */
public Car sell(Money money) {
System.out.println("I sold a car");
Car car = new Car("sonata");
return car;
}
}
OrderManager.java
package ioc.ioc1;
public class OrderManager {
private HyundaiMaker maker;
public OrderManager() {
maker = new HyundaiMaker();
}
public void order() {
Money money = new Money(1000);
Car car = maker.sell(money);
System.out.println(car.getName() + "를 " + money.getAmount() + "에 팔았음");
}
}
OrderManagerApp.java
package ioc.ioc1;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class OrderManagerApp {
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("src/main/resources/ioc1.xml"));
OrderManager manager = (OrderManager)factory.getBean("orderManager");
manager.order();
}
}
ioc1.xml
결과
* OrderManagerApp.java에서 구동시켜야합니다.
특징
• Orderanager와 HyundaiMaker은 서로 떼어 놓을 방법이 없다.
이 두 클래스는 정적으로 결합(coupling)되어 있다
• 하나의 버그를 고치면 그 결과 다른 새로운 버그가 생성되는 ‘두더지잡기(whack-a-mole)’ 버그를 만든다.
• 다른 한편으로는 완벽하게 분리된 코드는 아무일 도 못한다.
→ 결합도 낮추기
구현을 인터페이스 뒤로 숨기기
결합도를 낮추는 일반적인 기법은 구체적인 구현 내용을 인터페이스의 뒤로 숨김으로써, 클라이언트 클래스에 영향을 주지 않고도 실제 구현 클래스가 교체될 수 있도록 하는 것
CarMaker Interface 작성
package ioc.ioc2;
public interface CarMaker {
/* 무엇인가를 팔고 판돈을 반환한다. */
Car sell(Money money);
}
HyundaiMaker가 CarMaker 인터페이스 구현하도록 수정
package ioc.ioc2;
public class HyundaiMaker implements CarMaker{
public Car sell(Money money) {
/* 주문자의 주문에 대해 CarMaker는 car를 파는 것 */
System.out.println("I sold a car");
Car car = new Car("sonata");
return car;
}
}
OrderManager가 인터페이스를 사용하도록 수정
package ioc.ioc2;
public class OrderManager {
//만약 인터페이스를 사용하지 않는다면 HyundaiMaker 다른 메이커로 교체된다
//면 아래의 private CarMaker maker를 포함하여 소스코드가 수정되어야 하지만
//인터페이스를 사용한다면 new HyundaiMaker () 부분만 수정하면 된다.
private CarMaker maker;
public OrderManager() {
maker = new HyundaiMaker(); //나중에 다름 new DaewooMaker()로 수정하면 된다.
}
public void order() {
Money money = new Money(1500);
Car car = maker.sell(money);
System.out.println(car.getName() + "를 " + money.getAmount() + "에 팔았음");
}
}
수정된 코드에 대한 검토
• 다른 OrderManager를 사용할 수 없음
• 여전히 특정 Maker(HyundaiMaker) 에 한정
• 여전히 강한의존(Strong-coupled)
→ 인스턴스를 생성하는 방법의 문제
“오라클자바몰” 운영 시나리오 1
“오라클자바몰”이라는 회사를 만들어서 내가 잘아는 분야인 차를 팔기로 정했다. 차량의 구매는 현대자동차와 계약을 해서 차량을 구매하기로 하고 돈을 지불하기로 했다.
“오라클자바몰” 운영 시나리오 - 전략수정
나는 사업이 잘 안되었고 현대자동차도 매우 조건이 안 좋아서 대우자동차로 메이커를 변경하기로 했다.
KiaMaker.java 클래스 생성
package ioc.ioc2;
public class KiaMaker implements CarMaker{
/* 여기에 차를 팔기전에 해야할 일들이 필요하죠 */
public Car sell(Money money) {
System.out.println("I sold a car");
Car car = new Car("K5");
return car;
}
}
OrderManager 수정
package ioc.ioc2;
public class OrderManager {
//만약 인터페이스를 사용하지 않는다면 HyundaiMaker 다른 메이커로 교체된다
//면 아래의 private CarMaker maker를 포함하여 소스코드가 수정되어야 하지만
//인터페이스를 사용한다면 new HyundaiMaker () 부분만 수정하면 된다.
private CarMaker maker;
public OrderManager() {
// 변경된 코드
maker = new KiaMaker();
}
public void order() {
Money money = new Money(1500);
Car car = maker.sell(money);
System.out.println(car.getName() + "를 " + money.getAmount() + "에 팔았음");
}
}
수정할 클래스의 갯수는?
만일 CarMaker를 여러곳에서 의존하고 있다면?
마지막 수정
package ioc.ioc2;
import lombok.Setter;
// setter 메소드를 통해 CarMaker를 주입받는다.
@Setter
public class OrderManager {
private CarMaker maker;
public void order() {
Money money = new Money(1500);
Car car = maker.sell(money);
System.out.println(car.getName() + "를 " + money.getAmount() + "에 팔았음");
}
}
수정된 코드의 Class Diagram
결 론
• 자주 변경되는 구상클래스(Concrete class)에 의존하지 마라
• 어떤 클래스를 상속받아야 한다면 , 기반 클래스를 추상 클래스로 만들어라
• 어떤 클래스의 참조를 가져야 한다면 참조 대상이 되는 클래스를 추상 클래스로 하라
• 인터페이스를 만들어서 이 인터페이스에 의존하라
• DIP(Dependency Inversion Principle)
ioc2.xml
OrderManagerApp 작성 후 실행하자.
package ioc.ioc3;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class OrderManagerApp {
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("src/main/resources/ioc3.xml"));
OrderManager manager = (OrderManager)factory.getBean("orderManager");
manager.order();
}
}
"오라클자바몰" 운영 시나리오 2
"오라클자바몰"이라는 회사를 만들어서 내가 잘아는 분야인 차를 팔기로 정했다. 차량의 구매는 현대자동차/대우자동차와 계약을 해서 차량을 구매하기로 하고 돈을 지불하기로 했다. (XML기반코딩, Setter 주입)
↓
"오라클자바몰" 운영 시나리오 - 어노테이션 기반으로 소스 변경, Setter 주입
Money
package ioc.ioc4;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Money {
private int amount;
}
Car
package ioc.ioc4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@Component(value="car")
@NoArgsConstructor
// car라는 value를 꼭 주지 않아도됨
public class Car {
// @Value("K5")
private String name;
}
HyundaiMaker
package ioc.ioc4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.Setter;
@Service
public class HyundaiMaker implements CarMaker{
@Autowired @Setter
private Car car;
public Car sell(Money money) {
car.setName("sonata");
System.out.println("I sold a car");
return car;
}
}
KiaMaker
package ioc.ioc4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.Setter;
@Component
public class KiaMaker implements CarMaker{
@Autowired @Setter
private Car car;
public Car sell(Money money) {
car.setName("K5");
System.out.println("I sold a car");
return car;
}
}
OrderManager
package ioc.ioc4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import lombok.Setter;
@Setter
@Service
public class OrderManager {
@Autowired
@Qualifier("kiaMaker")
private CarMaker maker;
@Autowired
private Money money;
public void order() {
money.setAmount(10000);
Car car = maker.sell(money);
System.out.println(car.getName() + "를 " + money.getAmount() + "에 팔았음");
}
}
OrderManagerApp
package ioc.ioc4;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class OrderManagerApp {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("ioc4.xml");
OrderManager manager = ctx.getBean(OrderManager.class, "orderManager");
manager.order();
ctx.close();
}
}
ioc4.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: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.3.xsd">
<!-- 패키지 탐색, Namespace에서 context체크 하면 위에 코드가 추가로 생성 -->
<!-- 밑 코드 치면은 ico4에서 탐색을 실시 -->
<context:component-scan base-package="ioc.ioc4" />
contex
</beans>
"오라클 자바몰" 운영시나리오 3
"오라클 자바몰" 운영 시나리오 - 어노테이션 기반으로 소스 변경, Setter 주입
↓
"오라클자바몰" 운영 시나리오 - 어노테이션 기반으로 소스, 생성자 주입
메인메서드에서 getBean("orderManager") 하지 않고 생성자 주입을 통해 OrderManager의 객체 참조 취득하도록 수정
OrderManagerApp
package ioc.ioc4;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class OrderManagerApp {
public static void main(String[] args) {
// OrderManager manager = (OrderManager)factory.getBean("orderManager");
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("ioc4.xml");
OrderManager manager = ctx.getBean(OrderManager.class, "orderManager");
// OrderManager manager = ctx.getBean(OrderManager.class, "assdfafadorderManager"); // 버전에 따라 가능
// OrderManager manager = (OrderManager)ctx.getBean("asdffdfsefsorderManager"); // 안됨
manager.order();
ctx.close();
}
}
'Full Stack > Spring' 카테고리의 다른 글
[풀스택 과정] Spring Core : Spring IoC 및 DI(3) (0) | 2023.03.22 |
---|---|
[풀스택 과정] Spring Core : Spring IoC 및 DI(2) (0) | 2023.03.22 |
[풀스택 과정] Spring Core : Spring이란? (0) | 2023.03.22 |
[풀스택 과정] Spring 설치(STS 3.9.4 version) (1) | 2023.03.16 |