3. DI / 종속 객체 주입


DI란

  • Dependency Injection
  • 실제 어플리케이션에서는 두 개 이상의 클래스가 서로 협력하여 비즈니스 로직 수행
  • 이 때 각 객체는 협력하는 객체에 대한 레퍼런스(종속객체)를 얻어야 한다
  • 그 결과 결합도가 높아지고, 테스트하기 힘든 코드가 만들어지기 쉽다

    private dao;
    dao = new ProductDAO();
    ProductDAO가 강하게 결합된다

  • DI 이용
    • 각 객체를 조율하는 제 3자에 의해 생성시점에 종속객체가 부여된다
    • 객체는 종속객체를 생성하거나 얻지 않는다
    • 종속객체는 종속객체가 필요한 객체에 주입된다

      private dao;
      이후 dao는 조립기가 di 주입

    • 객체가 스스로 종속객체를 획득하는 것과는 반대로, 객체에 종속객체가 부여된다

스프링 설정파일을 이용한 의존관계 설정

  • 조립기가 종속객체를 자동으로 주입해주기 위해 객체 사이의 의존관계를 설정
  • 와이어링

    예시

public class ArticleServiceImpl{
  private ArticleDao articleDao;
  
  //setter, 주입기가 파라미터에 articleDao를 넣어줄 것이다
  public setArticleDao(ArticleDao articleDao){
    this.articleDao = articleDao;
  }
}

//위 클래스가 전달받게 될 의존객체
public class MySQLArticleDao implements ArticleDao{
  @Override
  public void insert(Article article{
    System.out.println("");
  }
}

의존관계 설정파일

  • applicationContext.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-3.0.xsd">

  <bean name=“articleService" class="mysite.spring.di.ArticleServiceImpl">
    <constructor-arg>
      <ref bean="articleDao" />
    </constructor-arg>
  </bean>

  <bean name="articleDao" class="mysite.spring.di.MySQLArticleDao">
  </bean>

</beans>
  • bean엘리먼트는 스프링에서 가장 기본적인 설정단위, 스프링에게 객체를 만들어달라는 의미
  • 스프링은 각 객체를 bean으로 관리한다
  • beans
    • 스프링 설정파일의 루트태그
  • bean
    • 스프링이 관리할 하나의 객체를 설정하는데 사용
    • name : 빈의 이름을 의미
    • class : 생성될 객체의 클래스 타입
  • constructor-arg
    • 빈 객체를 생성할 때 생성자에 전달할 파라미터를 명시하기 위해 사용

      MySqlArticleDao articleDao = new MySQLArticleDao();
      ArticleServiceImpl articleService = new ArticleServiceImpl(articleDao);

클래스패스 설정 및 빈 객체 사용

  • 설정파일로부터 bean factory를 생성, bean객체를 가져온다
package mysite.spring.di;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class Main {
  public static void main(String[] args) {
  
    Resource resource = new ClassPathResource("applicationContext.xml");
    BeanFactory beanFactory = new XmlBeanFactory(resource); // 스프링 컨텍스트 로드 (컨테이너)
    
    ArticleServiceImpl articleService = (ArticleServiceImpl) beanFactory.getBean(articleService"); //ArticleService 빈 얻기
    articleService.write(new Article()); //ArticleService 사용
  }
}
  • 또는
ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml"); // 스프링 컨텍스트 로드

ArticleServiceImpl articleService = (ArticleServiceImpl)context.getBean("articleService"); // ArticleService 빈 얻기

articleService.write(new Article()); // articleService 사용


흐름

  • main()메서드는 applicationContext.xml 파일을 기반으로 스프링 애플리케이션 컨텍스트를 생성
  • ID가 articleService인 빈을 조회하기 위해 백토리로 애플리케이션 컨텍스트 사용
  • ArticleService 객체에 대한 레퍼런스를 얻은 후 간단히 write()메서드로 호출해 작업 처리
  • 이 클래스에서는 ArticleDAO가 어떤 유형의 dao인지 알지 못하며, MySQLArticleDao를 다룬다는 사실도 알지 못한다
    • applicationContext.xml파일로만 알 수 있다

전체 파일 예시

Main.java

public static void main(String[] args) {

  /*ArticleServiceImpl1service = new ArticleServiceImpl1();
  Article article = new Article();
  article.setNo(1);
  article.setTitle("안녕");
  service.write(article); */
  
  
  //applicationContext.xml 스프링 설정파일에서 해당 빈 객체를 얻어온다
  //1. 스프링 컨텍스트 로드
  ApplicationContext context 
    =new ClassPathXmlApplicationContext("applicationContext.xml");
  //Resource resource = new ClassPathResource("applicationContext.xml");
  //BeanFactory context = new XmlBeanFactory(resource);
  
  
  //2. 빈(ArticleServiceImpl2) 얻어오기
  ArticleService articleService
    = (ArticleService)context.getBean("articleServiceImpl2");
  
  
  //3. 빈 사용하기
  Article article = new Article();
  article.setNo(2);
  article.setTitle("생성자에서 종속객체 주입 받는 경우");
  articleService.write(article);
  
  //3. setter에서 종속객체를 주입한 경우
  ApplicationContext context 
    = new ClassPathXmlApplicationContext("applicationContext.xml");
  ArticleService articleService3 
    = (ArticleService) context.getBean("articleServiceImpl3");
  Article bean = new Article();
  bean.setNo(3);
  bean.setTitle("setter 에서 종속객체 주입");
  articleService3.write(bean);
}

applicationContext.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-3.0.xsd">

   <bean name="articleServiceImpl2" class="mysite.spring.di.ArticleServiceImpl2">
    <constructor-arg>
      <ref bean="mySqlArticleDAO"/>
    </constructor-arg>
  </bean>

  <!-- <bean name="articleServiceImpl3" class="mysite.spring.di.ArticleServiceImpl3">
          <property name="articleDao" ref ="mySqlArticleDAO"/>
        </bean> -->

  <bean name="articleServiceImpl3" class="mysite.spring.di.ArticleServiceImpl3" p:articleDao-ref ="mySqlArticleDAO"/>
  <bean name="oracleArticleDAO" class="mysite.spring.di.OracleArticleDAO"></bean>
  <bean name="mySqlArticleDAO" class="mysite.spring.di.MySqlArticleDAO"></bean>

Article, ArticleDao

package mysite.spring.di;

public class Article {
  public int no;
  public String title;
  
  public int getNo() {
    return no;
  }

  public void setNo(int no) {
    this.no = no;
  }
}


public interface ArticleDao{
  void insert(Article article);
}

클래스들


package mysite.spring.di;

public class MySqlArticleDAO implements ArticleDAO{
  @Override
  public void insert(Article article) {
    System.out.println("MySql db에서 테이블에 insert한다 : mySqlDao.insert()");
  }
}

public class OracleArticleDAO implements ArticleDAO{
  @Override
  public void insert(Article article) {
    System.out.println("oracle db에 접속해서 테이블에 insert한다 : oracleDao.insert()");
  }
}

public class ArticleServiceImpl{ implements WriteArticleService{
  private ArticleDAO articleDao;
  //생성자
  public ArticleServiceImpl(){
    //dao 객체가 필요하므로 직접 객체 생성해서 사용함
    articleDao = new OracleArticleDAO();
  }
  @Override
  public void write(Article article) {
    System.out.println("articleServiceImpl.write() 메서드 호출");
    articleDao.insert(article);
  }
}

public class ArticleServiceImpl2{
  private ArticleDAO articleDao;
  //DI를 이용하여 dao객체를 직접 생성하지 않고, 외부 조립기가 주입하도록 한다
  //생성자 방식 - 생성자를 이용한 종속객체 주입
  public ArticleServiceImpl2(ArticleDAO articleDao){
  //생성자의 매개변수에 필요한 객체를 지정함
    this.articleDao = articleDao; 
  }
  @Override
  public void write(Article article) {
    System.out.println("articleServiceImpl2.write() 메서드 호출");
    articleDao.insert(article);
  }
}

public class ArticleServiceImpl3 {
  //멤버변수-프로퍼티
  private ArticleDAO articleDao;
  //DI를 이용하여 dao객체를 직접 생성하지 않고, 외부 조립기가 주입하도록 한다
  //프로퍼티 설정 방식 - setter를 이용한 종속객체 주입
  public void setArticleDao(ArticleDAO articleDao){
    //setter의 매개변수에 필요한 객체를 지정함
    this.articleDao = articleDao; 
  }
  @Override
  public void write(Article article) {
    System.out.println("articleServiceImpl3.write() 메서드 호출");
    articleDao.insert(article);
  }
}