본문 바로가기
👨‍💻 2. 웹개발_Back end/2-4 JSP & Servlet

[JSP & Servlet] 6장 미니 MVC 프레임워크 만들기 (3) DI를 이용한 빈 의존성 관리

by 달님🌙 2021. 10. 18.
반응형

 

6.3 DI를 이용한 빈 의존성 관리

 

1. 의존 객체

 

- 의미 : 특정 작업을 수행할 때 사용하는 객체

 

 

2. 의존 객체 관리

 

1) 사용할 때 마다 의존 객체 생성하기

- 문제점 : 많은 가비지(garbage) 생성, 실행시간 지연

 

2) 의존 객체를 미리 생성해 두었다가 필요할 때 꺼내쓰기

3) 필요한 의존 객체를 사용 전에 미리 주입해 두기 ★★★

- 의존성 주입(Dependency Injection; DI)

 

- 의존 객체 주입을 위한 코드 준비

- MemberDao 인스턴스 변수와 셋터 메서드 추가

public class ContextLoaderListener implements ServletContextListener {
 public void contextInitialized(ServletContextEvent event) {
 … 
 MemberDao memberDao = new MemberDao();
 … 
 sc.setAttribute("/member/list.do", 
 new MemberListController().setMemberDao(memberDao));

 

public class MemberListController implements Controller {
 MemberDao memberDao;
 
 public MemberListController setMemberDao(MemberDao memberDao) {
 this.memberDao = memberDao;
 return this;
 }
 public String execute(Map<String, Object> model) throws Exception {
 model.put("members", memberDao.selectList());
 return "/member/MemberList.jsp";
 }
}

 

 

3. 의존 객체와 느슨한 연대

 

- 인터페이스를 이용하여 대체가 용이하도록 만들자

1) 기존 방식: MemberDao가 클래스이다.

public class MemberDao {
 DataSource ds;
 public void setDataSource(DataSource ds) {
 this.ds = ds;
 }
 public List<Member> selectList() throws Exception { … }
…
}

 

 

- SQL문이 MySQL DBMS에 맞춰져 있다.

- DBMS 마다 DAO를 따로 만든다면, MemberDao 자리를 다른 DAO 클래스로 대체할 수 있는가? No!

- 서브 클래스가 아니면, 대체할 수 없다!

 

- 해결책? MemberDao를 인터페이스로 선언

- 기존의 MemberDao는 MySQL 전용 DAO로 만든다.

- 모든 DAO 클래스는 MemberDao 인터페이스를구현한다.

 

4. DI 실습 ( _26_MVC_DI_DAO )

 

1) ContextLoaderListener.java

package spms.listener;

import javax.naming.InitialContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;

import spms.controller.LoginController;
import spms.controller.LogoutController;
import spms.controller.MemberAddController;
import spms.controller.MemberDeleteController;
import spms.controller.MemberListController;
import spms.controller.MemberUpdateController;
import spms.dao.MemberDAO;

public class ContextLoaderListener implements ServletContextListener {
	//datasource의 장점  커넥션풀을 톰캣에서 지원해주기 때문에 
	//개발자가 직접  connection pool 객체를 만들어 줄 필요가 없다
	//BasicDataSource ds;
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//웹 어플리케이션이 실행되면 자동으로 DB커넥션 생성 및 MemeberDAO객체 생성
		try {
			System.out.println("contextInitialized");
			ServletContext sc = sce.getServletContext();
			
			//톰캣 서버에서 자원(커넥션)을 찾기 위한 InitailContext 객체 생성
			InitialContext initialcontext = new InitialContext();
			/*
			 * JNDI 사용
			 * WAS의 자원에 대한 고유 이름 정의
			 * 어플리케이션에서 서버 리소스를 접근할 때 사용하는 명명 규칙(톰캣)
			 * 서버마다 명명 규칙은 다름 (제우스는 또다른 규칙이 있음)
			 * 
			 * 1) java:comp/env         	 - 응용 프로그램 환경 항목
			 * 2) java:comp/env/jdbc    	 - JDBC 데이터 소스
			 * 3) java:comp/ejb         	 - EJB 컴포넌트
			 * 4) java:comp/UserTransaction  - UserTransaction 객첵
			 * 5) java:comp/env/mail    	 - JavaMail 연결 객체
			 * 6) java:comp/env/url     	 - URL 정보
			 * 7) java:comp/env/jms     	 - JMS 연결 객체
			 * 
			 * */
			DataSource ds = (DataSource)initialcontext.lookup("java:comp/env/jdbc/studydb");
			
			/*
			 * ds = new BasicDataSource();
			 * ds.setDriverClassName(sc.getInitParameter("driver"));
			 * ds.setUrl(sc.getInitParameter("url"));
			 * ds.setUsername(sc.getInitParameter("username"));
			 * ds.setPassword(sc.getInitParameter("password"));
			 */
			
			MemberDAO memberDAO = new MemberDAO();
			memberDAO.setDataSource(ds);
			
			//생성된 MemberDAO객체를 ServletContext 데이터 보관소를 통해 서블릿끼리 공유
			//sc.setAttribute("memberDAO", memberDAO);
			//Controller 객체에 memberDAO 의존성 주입
			//Controller 객체들을 서블릿 컨텍스트 영역에 저장 (키 값 : 서블릿 요청 url, value : 해당 컨트롤러 )
			sc.setAttribute("/member/list.do", new MemberListController().setMemberDAO(memberDAO));
			sc.setAttribute("/auth/login.do", new LoginController().setMemberDAO(memberDAO));
			sc.setAttribute("/auth/login.do", new LogoutController());
			sc.setAttribute("/member/add.do", new MemberAddController().setMemberDAO(memberDAO));
			sc.setAttribute("/memberUpdate.do", new MemberUpdateController().setMemberDAO(memberDAO));
			sc.setAttribute("/member/delete.do", new MemberDeleteController().setMemberDAO(memberDAO));
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		try {
			System.out.println("contextDestroyed");
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

 

 

2) MemberListController.java

package spms.controller;

import java.util.Map;

import spms.dao.MemberDAO;

public class MemberListController implements Controller {
	/*
	 * DI(Dependency Injection)
	 * 1) 클래스간 결합도 낮춤
	 * 2) MemberDAO 인터페이스를 선언하고 상속구현하므로써 다른 DBMS로의 전환 용이
	 * 3) 나중에 변경할 자동화 작업에 사용
	 */
	
	MemberDAO memberDAO = null;
	
	public MemberListController setMemberDAO(MemberDAO memberDAO) {
		this.memberDAO = memberDAO;
		return this;
	}
	
	@Override
	public String execute(Map<String, Object> model) throws Exception {
		//리턴될 조회 결과 맵에 담음
		model.put("memberList", memberDAO.selectlist());
		
		return "/member/MemberList.jsp";
	}
}

 

3) MemberAddController, MemberDeleteController, MemberUpdateController 코드 수정

 

- 전부 memberDAO를 전역변수로 선언 (밖으로 빼줌)

public class MemberUpdateController implements Controller {
	MemberDAO memberDAO = null;
	
	public MemberUpdateController setMemberDAO(MemberDAO memberDAO) {
		this.memberDAO = memberDAO;
		return this;
	}
}

 

 

5. 실습 : DBMS 교체를 위한 인터페이스 생성 ( _27_MVC_Interface_DAO )

 

MySqlMemberDAO.java

package spms.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import spms.vo.Member;

public class MySqlMemberDAO implements MemberDAO {
	/*
	 * Connection connection;
	 * 
	 * //DAO객체는 servlet이 아니기 때문에 servletcontext에 있는 커넥션 직접 접근 불가능
	 * //memberlistServlet에서 커넥션을 객체를 DAO에 주입해줄 것 public void
	 * setConnection(Connection connection) { this.connection = connection; }
	 */
	
	DataSource ds;
	
	public void setDataSource(DataSource ds) {
		this.ds = ds;
	}
	
	@Override
	public List<Member> selectlist() throws Exception {
		Connection connection = null;
		Statement stmt = null;
		ResultSet rs = null;
		
		String sqlSelect = "SELECT * FROM MEMBERS ORDER BY MNO ASC";
		
		try {
			connection = ds.getConnection();
			stmt = connection.createStatement();
			rs = stmt.executeQuery(sqlSelect);
			
			ArrayList<Member> members = new ArrayList<Member>();
			
			while(rs.next()) {
				members.add(new Member()
							.setNo(rs.getInt("MNO"))
							.setName(rs.getString("MNAME"))
							.setEmail(rs.getString("EMAIL"))
							.setCreateDate(rs.getDate("CRE_DATE")));	
			}
			return members;
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(rs != null) {
					rs.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	//MemberAddServlet에서 입력 폼으로 입력받은 데이터를 member객체로 담아서 
	//DAO로 전달할 예정
	@Override
	public int insert(Member member) throws Exception {
		int result = 0;
		Connection connection = null;
		PreparedStatement stmt = null;
		final String sqlInsert = "INSERT INTO MEMBERS(EMAIL, PWD, MNAME, CRE_DATE, MOD_DATE)" +
								"VALUES(?, ?, ?, NOW(), NOW())";
		
		try {
			connection = ds.getConnection();
			stmt = connection.prepareStatement(sqlInsert);
			stmt.setString(1, member.getEmail());
			stmt.setString(2, member.getPassword());
			stmt.setString(3, member.getName());
			//insert 성공하면 1 int 값 리턴
			result = stmt.executeUpdate();
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	@Override
	public int delete(int no) throws Exception {
		int result = 0;
		Connection connection = null;
		Statement stmt = null;
		final String sqlDelete = "DELETE FROM MEMBERS WHERE MNO=" + no;
		
		try {
			connection = ds.getConnection();
			stmt = connection.createStatement();
			result = stmt.executeUpdate(sqlDelete);
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	//해당 멤버 데이터 조회
	@Override
	public Member selectOne(int no) throws Exception {
		Member member = null;
		Connection connection = null;
		Statement stmt = null;
		ResultSet rs = null;
		
		final String sqlSelectOne = "SELECT * FROM MEMBERS WHERE MNO=" + no;
		
		try {
			connection = ds.getConnection();
			stmt = connection.createStatement();
			rs = stmt.executeQuery(sqlSelectOne);
			if(rs.next()) {
				member = new Member()
							 .setNo(rs.getInt("MNO"))
							 .setEmail(rs.getString("EMAIL"))
							 .setName(rs.getString("MNAME"))
							 .setCreateDate(rs.getDate("CRE_DATE"));
			} else {
				throw new Exception("해당 번호의 회원을 찾을 수 없습니다.");
			}
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return member;
	}
	
	//해당 멤버 데이터 수정
	@Override
	public int update(Member member) throws Exception {
		int result = 0;
		Connection connection = null;
		PreparedStatement stmt = null;
		
		final String sqlUpdate = "UPDATE MEMBERS SET EMAIL=?, MNAME=?, MOD_DATE=NOW() WHERE MNO=?";
		
		try {
			connection = ds.getConnection();
			stmt = connection.prepareStatement(sqlUpdate);
			stmt.setString(1, member.getEmail());
			stmt.setString(2, member.getName());
			stmt.setInt(3, member.getNo());
			result = stmt.executeUpdate();
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	@Override
	public Member exist(String email, String password) throws Exception {
		System.out.println(email);
		System.out.println(password);
		Member member = null;
		Connection connection = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		final String sqlExist = "SELECT * FROM MEMBERS WHERE EMAIL=? AND PWD=?";
		
		try {
			connection = ds.getConnection();
			stmt = connection.prepareStatement(sqlExist);
			stmt.setString(1, email);
			stmt.setString(2, password);
			rs = stmt.executeQuery();
			if(rs.next()) {
				member = new Member()
							 .setName(rs.getString("MNAME"))
							 .setEmail(rs.getString("EMAIL"));
			} else {
				return null;
			}
		} catch(Exception e) {
			throw e;
		} finally {
			try {
				if(stmt != null) {
					stmt.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
			
			try {
				if(connection != null) {
					connection.close();
				}
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		return member;
	}
}

 

MemberDAO.java

package spms.dao;

import java.util.List;

import spms.vo.Member;

/*
 * 인터페이스 생성 이유
 * 
 * Oracle, MySql, MSSql 등 DBMS 변경시 
 * 이 인터페이스를 상속받는 DAO 클래스를 정의하되
 * 각 DBMS 특성에 맞게 구현하여 DBMS 교체를 쉽게 함
 * 메서드의 역할과 리턴 값은 동일하게 처리
 * 
 * */
public interface MemberDAO {
	public List<Member> selectlist() throws Exception;
	public int insert(Member member) throws Exception;
	public int delete(int no) throws Exception;
	public Member selectOne(int no) throws Exception;
	public int update(Member member) throws Exception;
	public Member exist(String email, String password) throws Exception;
}

 

ContextLoaderListener.java

package spms.listener;

import javax.naming.InitialContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

import spms.controller.LoginController;
import spms.controller.LogoutController;
import spms.controller.MemberAddController;
import spms.controller.MemberDeleteController;
import spms.controller.MemberListController;
import spms.controller.MemberUpdateController;
import spms.dao.MySqlMemberDAO;

public class ContextLoaderListener implements ServletContextListener {
	//datasource의 장점  커넥션풀을 톰캣에서 지원해주기 때문에 
	//개발자가 직접  connection pool 객체를 만들어 줄 필요가 없다
	//BasicDataSource ds;
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//웹 어플리케이션이 실행되면 자동으로 DB커넥션 생성 및 MemeberDAO객체 생성
		try {
			System.out.println("contextInitialized");
			ServletContext sc = sce.getServletContext();
			
			//톰캣 서버에서 자원(커넥션)을 찾기 위한 InitailContext 객체 생성
			InitialContext initialcontext = new InitialContext();
			/*
			 * JNDI 사용
			 * WAS의 자원에 대한 고유 이름 정의
			 * 어플리케이션에서 서버 리소스를 접근할 때 사용하는 명명 규칙(톰캣)
			 * 
			 * 1) java:comp/env         	 - 응용 프로그램 환경 항목
			 * 2) java:comp/env/jdbc    	 - JDBC 데이터 소스
			 * 3) java:comp/ejb         	 - EJB 컴포넌트
			 * 4) java:comp/UserTransaction  - UserTransaction 객첵
			 * 5) java:comp/env/mail    	 - JavaMail 연결 객체
			 * 6) java:comp/env/url     	 - URL 정보
			 * 7) java:comp/env/jms     	 - JMS 연결 객체
			 * 
			 * */
			DataSource ds = (DataSource)initialcontext.lookup("java:comp/env/jdbc/studydb");
			
			/*
			 * ds = new BasicDataSource();
			 * ds.setDriverClassName(sc.getInitParameter("driver"));
			 * ds.setUrl(sc.getInitParameter("url"));
			 * ds.setUsername(sc.getInitParameter("username"));
			 * ds.setPassword(sc.getInitParameter("password"));
			 */
			
			MySqlMemberDAO memberDAO = new MySqlMemberDAO();
			memberDAO.setDataSource(ds);
			
			//생성된 MemberDAO객체를 ServletContext 데이터 보관소를 통해 서블릿끼리 공유
			/* sc.setAttribute("memberDAO", memberDAO); */
			//Controller 객체에 memberDAO 의존성 주입
			//Controller 객체들을 서블릿 컨텍스트 영역에 저장(키 값 : 서블릿 요청 url, value : 해당 컨트롤러(DAO 의존성 주입상태))
			sc.setAttribute("/member/list.do", new MemberListController().setMemberDAO(memberDAO));
			sc.setAttribute("/auth/login.do", new LoginController().setMemberDAO(memberDAO));
			sc.setAttribute("/auth/logout.do", new LogoutController());
			sc.setAttribute("/member/add.do", new MemberAddController().setMemberDAO(memberDAO));
			sc.setAttribute("/member/update.do", new MemberUpdateController().setMemberDAO(memberDAO));
			sc.setAttribute("/member/delete.do", new MemberDeleteController().setMemberDAO(memberDAO));
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		try {
			System.out.println("contextDestroyed");
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

 

 

반응형

댓글