본문 바로가기
교재 실습/자바 웹 개발 워크북

84. DI를 이용한 빈 의존성 관리 (3)

by Jint 2022. 8. 2.

7. 인터페이스를 활용하여 공급처를 다변화 하자

실무에서는 고객사들이 오라클을 사용할 수도 있고 MySQL이나 MS-SQL을 사용할 수도 있다. 그러므로 각 데이터베이스에 맞추어 DAO 클래스를 준비해야 한다. 문제는 데이터베이스를 바꿀 때마다 DAO 클래스를 사용하는 코드도 변경해야 하기 때문에 여간 번거로운 것이 아니다. 몇 개 정도라면 힘들더라도 해보겠지만, 몇십 개가 넘어간다면 재앙 수준이다. 이런 경우에 바로 인터페이스를 활용하면 손쉽게 해결할 수 있다.

다음 그림과 같이 의존 객체를 사용할 때 구체적으로 클래스 이름을 명시하는 대신에 인터페이스를 사용하면, 그 자리에 다양한 구현체(인터페이스를 구현한 클래스)를 놓을 수 있다. 즉 인터페이스라는 문법으로 DAO가 갖춰야 할 규격을 정의하고, 그 규격을 준수하는 클래스라면 어떤 클래스를 상속받았는지 따지지 않고 허용하는 것이다. 다음 그림을 본다(그림 1).

그림 1 (인터페이스를 활용한 결합도 감소 - 교체 용이)

실생활에서도 이런 사례를 흔히 볼 수 있다. 스마트폰을 예로 들어 본다. 대부분의 스마트폰은 마이크로 USB 포트가 있는데, 이 포트를 통해 데이터를 주고 받거나 충전을 한다. 마이크로 USB는 제품의 이름이 아니라 직렬 방식으로 데이터를 주고 받기 위해 고안된 입출력 규격이다. 자바 문법으로 보자면 '인터페이스'라 할 수 있다. 이렇게 규격을 정의해 두면, 제조 회사에 상관없이 이 규격을 준수하는 어떤 충전기라도 사용할 수 있어 제품 선택의 폭이 넓어진다.

프로그래밍도 마찬가지이다. 사용할 의존 객체에 대해 선택 폭을 넓히고 싶고 향후 확장을 고려하여 교체하기 쉽게 만들고 싶다면, 인터페이스 문법을 통해 사용 규칙을 정의한다. 그리고 그 규칙에 따라 호출하도록 코드를 작성하면 된다. 여기에다가 '의존성 주입' 방식을 적용한다면 금상첨화일 것이다.

 

8. MemberDao 인터페이스 정의

MemberDao 클래스에 인터페이스를 적용해 본다. 기존의 MemberDao 클래스를 MySqlMemberDao 클래스로 이름을 변경한다. spms.dao 패키지에 MemberDao 인터페이스를 생성한다.

package spms.dao;

import java.util.List;

import spms.vo.Member;

//MemberDao 인터페이스 정의
public interface MemberDao {
	List<Member> selectList() throws Exception;
	int insert(Member member) throws Exception;
	int delete(int no) throws Exception;
	Member selectOne(int no) throws Exception;
	int update(Member member) throws Exception;
	Member exist(String email, String password) throws Exception;
}

기존에 있던 MemberDao 클래스의 메서드 시그니처를 그대로 가져온 것이다.

 

- MySqlMemberDao 클래스를 MemberDao 규격에 맞추기

앞에서 이름을 변경한 spms.dao.MySqlMemberDao 클래스를 다음과 같이 MemberDao 인터페이스를 구현하도록 변경한다.

public class MySqlMemberDao implements MemberDao {//MemberDao 인터페이스 구현
    ...
}

MemberDao 인터페이스를 구현하는 코드만 추가하면 된다. 다른 부분은 그대로 둔다.

 

- ContextLoaderListener 클래스 변경

MemberDao는 이제 클래스가 아니다. 따라서 MemberDao 객체의 준비를 담당했던 spms.listeners.ContextLoaderListener 클래스를 변경한다.

@Override
public void contextInitialized(ServletContextEvent event) {
    try {
        //DB 커넥션 객체 준비
        ServletContext sc = event.getServletContext();

        //InitialContext 객체 생성 - 톰캣 서버에서 자원 찾기 위함
        InitialContext initialContext = new InitialContext();
        //lookup() 메서드로 JNDI 이름으로 등록된 서버 자원 찾을 수 있다.
        //lookup() 메서드 매개변수는 서버 자원의 JNDI 이름
        DataSource ds = (DataSource)initialContext.lookup("java:comp/env/jdbc/studydb");

        //MemberDao 객체 준비하여 ServletContext에 보관
        //MemberDao memberDao = new MemberDao();
        MySqlMemberDao memberDao = new MySqlMemberDao();

        memberDao.setDataSource(ds); //DAO에 DataSource 객체 주입
        //sc.setAttribute("memberDao", memberDao); //memberDao 객체를 별도로 꺼내서 사용할 일 없기 때문에 ServletContext에 저장하지 않는다.

        //페이지 컨트롤러 객체를 ServletContext에 저장시, 서블릿 요청 URL을 키값으로 하여 저장
        sc.setAttribute("/auth/login.do", new LogInController().setMemberDao(memberDao));
        ...
}

이제 MemberDao는 인터페이스이기 때문에 인스턴스를 생성할 수 없다. 그 코드를 제거한다.

//MemberDao memberDao = new MemberDao();
MySqlMemberDao memberDao = new MySqlMemberDao();

대신 MemberDao 인터페이스를 구현한 MySqlMemberDao 객체를 생성한다. 따라서 페이지 컨트롤러에 주입되는 것은 바로 이 MySqlMemberDao 객체이다.

톰캣 서버를 재시작한 후, 웹 브라우저에서 http://localhost:9999/web06/member/list.do를 요청한다. 회원 목록 외에 로그인, 로그아웃, 회원 등록, 변경, 삭제까지 모두 테스트 한다.

 

이번 절에서는 '의존성 주입' 기법으로 의존 객체를 관리하는 방법에 대해 알아보았다. 그러나 아직 개선할 점이 많다. DispatcherServlet 클래스의 경우 페이지 컨트롤러가 추가될 때마다 조건문을 변경해야 하는 문제가 남아 있고, ContextLoaderListener클래스도 Dao나 페이지 컨트롤러가 추가될 때마다 변경해야 한다. 이런 문제들은 앞으로 하나씩 해결해 나간다.

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr

댓글