티스토리 뷰

Spring

[Spring]의존성 주입

dev23 2024. 8. 16. 21:15
반응형

의존성 주입

- 의존성 주입이라는 것을 알기 전에는 어떤 클래스가 다른 클래스의 기능을 사용하려면 당연히 개발자가

 직접 코드에서 사용할 클래스의 생성자를 호출해서 사용했다.

- , 사용할 클래스와 사용될 클래스의 관계는 개발자에 의해 직접 코드에서 부여된다.

 

- 의존성 주입 이런 연관 관계를 개발자가 직접 코딩을 통해 컴포넌트(클래스) 부여하는 것이 아니라 

컨테이너가 연관 관계를 직접 규정하는 것이다.

- 코드에서 직접적인 연관 관계가 발생하지 않으므로 클래스들의 변경이 자유로워 진다.(약간 결합)

 

- 전체 애플리케이션은 각각의 기능을 담당하는 컴포넌트들로 이루어진다. 그리고 컴포넌트들은 다시

세부 기능을 수행하는 클래스들로 이루어진다.

 

- 다른 클래스의 기능을 사용하려면 어떻게 해야할까?

- 소스 코드에서 다른 클래스의 생성자를 호출해서 사용할 경우 기능을 구현하는 과정에서 다른 변경 사항이 

발생하면 빠르게 대처하기가 어렵다. 관련이 있는 모든 클래스들의 소스 코드를 수정해 주어야 하기 때문이다.

 

- 따라서 스프링 프레임워크에서는 클래스들의 연관 관계를 클래스들 사이에서 맺는 것이 아니라 스프링

프레임워크에서 설정을 통해 맺어줌으로써 클래스들이 연관 관계를 갖지 않게 구현했다.

 

의존성 주입 전의 게시판 기능

 - 클래스들의 기능을 보면 다른 클래스의 기능을 사용하기 위해 소스 코드에서 직접 다른 클래스 객체를

 생성 메서드를 호출하여 연동한다.

* BoardController.java

@WebServlet("/board/*")

public class BoardController extends HttpServlet {
	private static final String ARTICLE_IMAGE_REPO = "C:\\board\\article_image"; // 파일을 저장할 위치를 선언한다.
    
    BoardService service;
    ArticleVO vo;
    
    public void init() throws ServletException {
        service = new BoardService(); // BoardService 객체를 코드에서 직접 생성해 사용한다.
        vo = new ArticleVO();
	}
}

* BoardService.java

public class BoardService {
	BoardDAO dao;
	public BoardService() {
		dao = new BoardDAO(); // BoardDAO 객체를 코드에서 직접 생성해 DB와 연동한다.
	}
}

* BoardDAO.java

public class BoardDAO {
    private Connection con;
    private PreparedStatement pt;
    private DataSource ds;

    public BoardDAO (){
        try {
            Context ctx = new InitialContext();
            Context envCtx = (Context)ctx.lookup("java:/comp/env");
            ds = (DataSource)envCtx.lookup("jdbc/oracle");
        }catch(Exception e) {
        	e.printStackTrace();
	}
}

 

-  BoardDAO 클래스에서는 오라클과 연동해 게시판 기능을 구현하고 있다. 그런데 만약 중간에 오라클에서 MySQL DB 변경한다고 가정하면, 지금과 같은 경우에는 BoardDAO 클래스의 기능을 일일이 변경 주어야 한다.

- 그뿐만 아니라 경우에 따라서는 BoardDAO 클래스를 사용하는 BoardService 클래스의 기능도 변경해야 수도 있다.

 

- 지금까지는 클래스를 사용하려면 자바에서 배웠듯이 자바 코드에서 클래스 생성자를 호출해 객체를 생성했다.

- 하지만 프로젝트 규모가 점점 커지는 상황에서 자바 코드에 직접 객체를 생성해서 사용하는 것은 복잡한 문제를 일으킬 수도 있다.

- 다른 클래스의 변경 사항이 연속적으로 다른 부분에 영향을 미친다면 방법은 좋은 방법이 아니다.

 

인터페이스를 적용한 게시판 기능

- 앞에서 다룬 게시판 기능 구현 시의 문제점을 인터페이스를 사용해서 해결한다.

-각각의 클래스가 인터페이스를 구현하도록 한다.

* BoardServiceImpl.java

public class BoardServiceImpl implements BoardService{

	BoardDAO boardDAO;
	public BoardService() {
		boardDAO = new BoardOracleDAOImpl(); // 인터페이스를 이용해 하위 클래스 객체를 생성한 후 오라클 DB와 연동한다.
	}
}

* BoardOracleDAOImpl.java

public class BoardOracleDAOImpl implements BoardDAO{
    private DataSource ds;
    Connection con;
    PreparedStatement pt;

	public BoardDAO() {
        try{
            Context ctx = new InitialContext();
            Context envCtx = (Context)ctx.lookup("java:/comp/env");
            ds = (DataSource) envCtx.lookup("jdbc/oracle");
        }catch(Exception e){
            e.printStackTrace();
        }
			...
    }
}

- 개발 중에 MySQL 연동하는 기능이 생겼다고 가정한다. 지금처럼 인터페이스로 구현한 경우에는

기존의 BoardOracleDAOImpl 클래스를 변경할 필요가 없다.

- BoardDAO 인터페이스를 구현한 다른 BoardMySqlDAOImpl 클래스를 구현한 BoardServiceImpl에서 사용하면 된다.

* BoardServiceImpl.java

public class BoardServiceImpl implements BoardService{
	BoardDAO boardDAO;
	public BoardService() {
		// boardDAO = new BoardOracleDAOImpl();
		boardDAO = new BoardMySqlDAOImpl(); // 인터페이스를 이용해 하위 객체를 생성한 후 MySql DB와 연동한다.
 }

 

- 인터페이스를 이용해 클래스를 구현한 클래스의 객체를 사용할 때는 인터페이스 타입으로 선언한 참조 변수로 접근해서 사용하면 된다. 그러면 완전하지는 않아도 앞의 경우보다 훨씬 클래스들 의존관계가 약해진다.

- 그러나 인터페이스를 사용해도 여전히 BoardServiceImpl 클래스 자체에는 소스 코드를 직접 수정해야 한다.

 

의존성 주입을 적용한 게시판 기능

- 앞의 게시판 예제를 통해 클래스들의 의존 관계가 강하게 결합되어 있으면 여러 가지 문제가 발생할 있음을 알았다.

- 이번에는 스프링의 의존성 주입 기능을 이용해 클래스들 사이의 의존 관계를 완전히 분리하는 작업을 알아본다.

- 의존성 주입 적용 시의 장점

  • 클래스들 간의 의존 관계를 최소화하여 코드를 단순화할 수 있다.
  • 애플리케이션을 더 쉽게 유지 및 관리할 수 있다.
  • 기존 구현 방법은 개발자가 직접 코드 안에서 객체의 생성과 소멸을 제어했지만, 의존성 주입은 객체의 생성,
    소멸과 객체 간의 의존 관계를 컨테이너가 제어한다.

- 의존성 역전을 구현하려면 XML이나 애너테이션을 이용해 객체를 주입하여 객체들의 의존 관계를 맺어주면 된다.

- , DI 사용하여 객체들 간의 의존 관계를 최소화함으로써 코드를 단순화하고 유지보수를 쉽게 있다.

 

- DI 객체의 생성, 소멸, 의존 관계를 개발자가 직접 설정하는 것이 아니라 XML이나 애너테이션 설정을 통해

경량 컨테이너에 해당하는 스프링 프레임워크가 제어한다.

- 따라서, 기존 코드에서는 개발자가 직접 객체를 제어했지만, 스프링 프레임워크에서는 객체의 제어를 스프링이 직접 
담당하므로 제어의 역전(Inversion of Control, IoC)이라고 한다.

- IoC 종류도 여러 가지이며, 일반적으로 스프링에서는 DI IoC의기능을 구현하므로 IoC보다는 DI라는 용어를 많이

 사용한다.

 

DI를 적용한 게시판 기능

public class BoardServiceImpl implements BoardService{
	BoardDAO boardDAO;
		public BoardService() {
		boardDAO = new BoardOracleDAOImpl(BoardDAO boardDAO){
			this.boardDAO = boardDAO;
		}

- BoardServiceImpl 클래스는 의존하는 BoardDAOImpl 객체를 전달받기 위해 new 키워드를 사용해 객체를

생성하지 않고  생성자를 호출할 때 외부에서 객체를 주입 받아 사용한다.

- new 사용해 객체를 생성하는 것이 아니라 BoardServiceImpl 생성자를 호출할 컨테이너에 의해 주입되는 객체로

boardDAO 변수를 초기화한 것이다.

 

- 스프링에서는 의존(dependency)하는 객체를 컨테이너 실행 주입(Injection)하기 때문에 DI라고 부른다.

- 클래스를 bean()이라고 부르는데, 이는 의존 관계를 설정하는 외부 XML 파일에서 각각의 객체를

<bean> 태그로 표시하기 때문이다.

 

Setter를 이용한 DI

- 프로젝트 하위에 XML 파일을 먼저 생성한다.

 

* person.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                             "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
   <bean id="personService" class="com.spring.PersonServiceImpl"> <!--  <bean> 태그를 이용해 PersonServiceImpl 객체를 생성한 후 빈 id를 personService로 지정한다. -->
      <property name="name"> <!-- PersonServiceImpl 객체의 속성 name 값을 <value> 태그를 이용해 '홍길동'으로 초기화한다. -->
         <value>홍길동</value>
      </property>
   </bean>
</beans>

- 실행 클래스 실행 <bean> 태그를 이용해 com.spring.PersonServiceImpl 클래스에 대한 빈을 생성한다. 

그리고 빈에 대해 접근할 있는 id personService 지정한 <property> 태그를 이용해

PersonServiceImpl 클래스 객체의 name 속성에 <value> 태그의 값으로 초기화한다.

- 소스 코드에서 new 이용해 직접 객체를 생성하던 것을 person.xml 설정한 것이다.

 

* PersonService.java

package com.spring;

public interface PersonService {
	public void sayHello();
}

 

* PersonServiceImpl.java

package com.spring;

public class PersonServiceImpl implements PersonService{
	
	private String name;
	private int age;

	public void setName(String name) { // XML 파일에서 설정한 <value> 태그의 값을 setter를 이용해 설정한다.
		this.name = name;
	}

	@Override
	public void sayHello() { 
		System.out.println("이름 : " + name);
		System.out.println("나이 : " + age);
	}
}

 

* PersonTest.java

- 실행 클래스이다. 라이브러리에서 제공하는 클래스를 이용해 XML 파일을 읽어와서 빈을 생성한다.

- 실행 클래스를 실행하면 스프링의 XmlBeanFactory 클래스를 이용해 person.xml 설정대로 PersonServiceImpl 빈을

메모리에 생성한다.

- 빈에 대한 id personService 접근하여 sayHello() 메서드를 호출한다.

package com.spring;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

public class PersonTest {
	public static void main(String[] args) {
		// 실행 시 person.xml을 읽어 들여 빈을 생성한다.
		BeanFactory factory = new XmlBeanFactory(new FileSystemResource("person.xml"));
		PersonService person = (PersonService) factory.getBean("personService");
		// PersonService person = new PersonServiceImpl(); // 더 이상 자바코드에서 객체를 직접 생성하지 않아도 된다.
		
		// 생성된 빈을 이용해 name 값을 출력한다.
		person.sayHello();
	}
}

 

- 해당 프로그램을 실행시켜 본다. 프로그램 실행 후 IDE의 콘솔을 보면 다음과 같이 출력된 것을 볼 수 있다.

xml 파일의 value 태그 값이 출력됨

 

생성자를 이용한 DI

* person.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                             "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
 	<!-- 인자가 한 개인 생성자로 id가 personService1인 빈을 생성한다. 생성자로 value인 이순신을 전달하여 속성 name을 초기화한다. -->
   <bean id="personService1" class="com.spring.PersonServiceImpl">
        <constructor-arg   value="이순신" />
   </bean>
   
   <!-- 인자가 두 개인 생성자로 id가 personService2인 빈을 생성한다. 생성자로 두 개의 값을 전달하여 name과 age를 초기화한다. -->
   <bean id="personService2" class="com.spring.PersonServiceImpl">
       <constructor-arg   value="손흥민"  />
       <constructor-arg   value="23"  />
   </bean>
</beans>

 

* PersonServiceImpl.java

package com.spring;

public class PersonServiceImpl implements PersonService{
	
	private String name;
	private int age;

	public PersonServiceImpl (String name) {
		this.name = name;
	}

	public PersonServiceImpl (String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public void sayHello() {
		System.out.println("이름  : " + name);
		System.out.println("나이 : " + age + "살");
	}
}

 

* PersonTest2.java

package com.spring;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

public class PersonTest2 {
	public static void main(String[] args) {
		// 실행 시 person.xml을 읽어 들려 빈을 생성한다.
		BeanFactory factory = new XmlBeanFactory(new FileSystemResource("person.xml"));
		// id가 personService1인 빈을 가져온다.
		PersonService person1 = (PersonService) factory.getBean("personService1");
		person1.sayHello();
		System.out.println();
		
		// id가 personService2인 빈을 가져온다.
		PersonService person2 = (PersonService) factory.getBean("personService2");
		person2.sayHello();
	}
}

- 코드 작성 후 프로그램을 실행시켜 본다. 프로그램 실행 후 IDE의 콘솔을 보면 다음과 같이 출력된 것을 볼 수 있다.

출력 결과

 

 

회원 기능을 이용한 DI

- 에서는 설정 파일에서 기본형 데이터를 빈의 속성 값으로 직접 주입해서 사용했다.

- 이번에는 빈에 주입되는 값이 의존 관계에 있는 다른 빈을 주입하는 경우를 살펴본다.

 

* member.xml

- 프로젝트 디렉터리 아래에 생성한다.

- 의존하는 빈을 주입할 때는 주입되는 타입이 기본형 데이터가 아닌 참조형 데이터일 경우 "ref" 속성을 이용해

주입해야 한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                             "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- MemberServiceImpl 클래스를 이용해 id가 memberService인 빈을 만든다. 빈을 만들면서 setter 방식으로 id가 memberDAO인 빈을 자신의 속성에 주입한다. -->
	<bean id="memberService" class="com.spring.MemberServiceImpl">
		<property name="memberDAO" ref="memberDAO" /> <!--  주입되는 데이터가 기본형이 아닌 참조형일 경우 'ref' 속성으로 설정한다. -->
	</bean>
	<bean id = "memberDAO" class="com.spring.MemberDAOImpl" />
</beans>

 

* MemberService.java

package com.spring;

public interface MemberService {
	public void listMembers();
}

* MemberServiceImpl.java

package com.spring;

public class MemberServiceImpl implements MemberService{
	private MemberDAO memberDAO;
	
	public void setMemberDAO(MemberDAO memberDAO) { // setter를 이용한 DI
		this.memberDAO = memberDAO;
	}
	
	@Override
	public void listMembers() {
		memberDAO.listMembers();
	}

}

 

* MemberDAO.java

package com.spring;

public interface MemberDAO {
	public void listMembers();
}

 

* MemberDAOImpl.java

 package com.spring;

public class MemberDAOImpl implements MemberDAO{

	@Override
	public void listMembers() {
		System.out.println("listMembers 메서드 호출");
		System.out.println("회원 정보를 조회합니다.");
	}
}

 

* MemberTest1.java

package com.spring;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

public class MemberTest1 {

	public static void main(String[] args) {
		BeanFactory factory = new XmlBeanFactory(new FileSystemResource("member.xml"));
		MemberService service = (MemberService) factory.getBean("memberService");
		service.listMembers();
	}

}

 

- 코드 작성 후 프로그램을 실행시켜 본다. 프로그램 실행 후 IDE의 콘솔을 보면 다음과 같이 출력된 것을 볼 수 있다.

출력 결과

- 예제에서 자바 코드로 어떤 클래스 객체도 생성하지 않았다. 오로지 스프링의 DI 기능을 이용해 빈을 생성했고,

의존 관계에 있는 빈을 속성에 주입한 빈의 기능을 사용했다.

반응형

'Spring' 카테고리의 다른 글

[Spring]스프링 애너테이션  (0) 2024.08.17
[Spring]MyBatis 연동하기  (0) 2024.08.17
[Spring]JDBC 연동  (0) 2024.08.17
[Spring]스프링 MVC  (0) 2024.08.17
[Spring]스프링이란?  (0) 2024.08.16
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함