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

[JSP & Servlet] 6장 미니 MVC 프레임워크 만들기 (5) 프로퍼티를 이용한 객체관리

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

 

6.5 프로퍼티를 이용한 객체 관리

 

1. 학습할 내용

 

2. 기존 방식

 

public void contextInitialized(ServletContextEvent event) {
 // 객체 준비
}

 

- 객체를 보관할 때 사용할 서블릿 컨텍스트 객체를 준비

ServletContext sc = event.getServletContext();

 

//톰캣 서버가 관리하는 객체를 얻기 위한 준비
InitialContext initialContext = new InitialContext();

//DAO가 사용할 DataSource 객체를 얻기
DataSource ds = (DataSource)initialContext.lookup("java:comp/env/jdbc/studydb");

 

//페이지 컨트롤러가 사용할 DAO 객체 준비
MySqlMemberDao memberDao = new MySqlMemberDao();
memberDao.setDataSource(ds);

//DAO 객체 생성 및 의존 객체(DataSource) 주입
MySqlMemberDao memberDao = new MySqlMemberDao();
memberDao.setDataSource(ds);

 

//페이지 컨트롤러 준비
sc.setAttribute("/auth/login.do", new LogInController().setMemberDao(memberDao));

//페이지 컨트롤러 객체 생성 및 의존 객체 주입
sc.setAttribute("/auth/login.do", //객체 생성 
	new LogInController().setMemberDao(memberDao)); //의존 객체 주입

 

//페이지 컨트롤러를 서블릿 컨텍스트에 보관
sc.setAttribute("/auth/login.do", new LogInController().setMemberDao(memberDao));
sc.setAttribute("/auth/logout.do", new LogOutController());
sc.setAttribute("/member/list.do", new MemberListController().setMemberDao(memberDao));

 

2. 기존 방식의 문제점과 해결책

 

1) 문제점

- 새 페이지 컨트롤러가 추가되면, ContextLoaderListener 클래스에 해당 코드를 추가해야 함. (계속 수정해야하는 단점)

MySqlBoardDao boardDao = new MySqlBoardDao();
boardDao.setDataSource(ds);
sc.setAttribute("/board/add.do”,
new BoardAddController().setBoardDao(boardDao));
…

 

2) 해결책

DAO나 페이지 컨트롤러가 추가되더라도 ContextLoaderListener 클래스를 변경하는 않는 방법? “구조 변경"

 

- 준비해야 할 객체 정보를 외부 파일(프로퍼티 파일)로 분리

 

- 객체 정보는 key=value 매핑되는 형태로 작성.

 

- .properties 파일에 작성된 내용에 따라 객체를 준비할 자동화 클래스 추가

 

- ContextLoaderListener는 객체 생성 및 관리를 ApplicationContext에게 위임.

public void contextInitialized(ServletContextEvent event) {
 try {
 ServletContext sc = event.getServletContext();
 
 String propertiesPath = sc.getRealPath(
 sc.getInitParameter("contextConfigLocation"));
 applicationContext = new ApplicationContext(propertiesPath);
 
 } catch(Throwable e) {
 e.printStackTrace();
 }
}

 

- 프런트 컨트롤러도 페이지 컨트롤러를 꺼낼 때 ApplicationContext를 사용.

 

3. 프로퍼티 실습

 

1) WEB-INF 폴더에 파일 추가 - application-context.properties 

#1) dataSource
jndi.dataSource=java:comp/env/jdbc/studydb
#2) MemberDAO
memberDAO=spms.dao.MySqlMemberDAO
#3) pageController
/auth/login.do=spms.controller.LoginController 
/auth/logout.do=spms.controller.LogoutController
/member/list.do=spms.controller.MemberListController
/member/add.do=spms.controller.MemberAddController
/member/delete.do=spms.controller.MemberDeleteController
/member/update.do=spms.controller.MemberUpdateController

* 문장 끝 공백 주의 -> 에러 발생

- 컨트롤러를 추가할 때 ContextLoaderListener 파일을 수정하지 않고도 application-context.properties에 추가하면 됨

- 키 값과 콘트롤러를 매핑만 해주면 됨

 

 

2) ApplicationContext.java

- 의존성 객체를 주입할 클래스를 하나 만들기

package spms.context;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;

public class ApplicationContext {
	//application-context.properties 파일을 읽어서 ke값과 value을 담아줄 Hashtable 생성
	//key : jndi.dataSource, memberDAO, /auth/login.do ....
	//value : java:comp/env/jdbc/studydb, spms.dao.MemberDAO, spms.controller.LoginController...
	Hashtable<String, Object> objTable = new Hashtable<String, Object>();
	
	//key 값에 해당하는 객체 반환하는 메소드
	public Object getBean(String key) {
		return objTable.get(key);
	}
	
	//생성자는 파일경로(application-context.properties)를 읽어서 프로퍼티를 생성해주는 역할
	public ApplicationContext(String propertiesPath) throws Exception {
		Properties props = new Properties();
		props.load(new FileReader(propertiesPath));
		
		//프로퍼티 파일을 읽어서 객체를 만들어 주는 메소드
		prepareObject(props); 
		//의존성 주입 메소드
		injectDependency();
	}
	
	public void prepareObject(Properties props) throws Exception {
		Context ctx = new InitialContext();
		String key =null;
		String value = null;
		
		//props.keySet : jndi.dataSource, memberDAO, /auth/login.do ....
		for(Object item : props.keySet()) {
			key = (String)item;
			//props.getProperty : java:comp/env/jdbc/studydb, spms.dao.MemberDAO, spms.controller.LoginController...
			value = props.getProperty(key);
			if(key.startsWith("jndi.")) {
				//key 값이 dataSource면 lookup
				objTable.put(key, ctx.lookup(value));
			} else {
				//dataSource 외에 객체들은 직접 객체 생성
				objTable.put(key, Class.forName(value).newInstance());
			}
		}
	}
	
	public void injectDependency() throws Exception {
		for(String key : objTable.keySet()) {
			//dataSource가 아니면 setter메소드를 찾아서 DAO 객체를 주입
			if(!key.startsWith("jndi.")) {
				//매개변수로는 key값에 해당하는 객체를 보내줌
				callSetter(objTable.get(key));
			}
		}
	}
	
	//setter메소드를 찾아서 실행해줌
	private void callSetter(Object obj) throws Exception {
		Object dependency = null;
		//객체에 있는 모든 메소드들을 호출해서 그 중에 setter 메소드를 찾음
		for(Method m : obj.getClass().getMethods()) {
			if(m.getName().startsWith("set")) {
				//setter메소드를 찾아서 setter메소드의 매개변수 타입으로 객체를 찾음
				//dependency = MySqlMemberDAO
				dependency = findObjectByType(m.getParameterTypes()[0]);
				if(dependency != null) {
					m.invoke(obj, dependency);
				}
			}
		}
	}
	
	private Object findObjectByType(Class<?> type) {
		for(Object obj : objTable.values()) {
			if(type.isInstance(obj)) {
				return obj;
			}
		}
		return null;
	}
}

 

3) ContextLoaderListener.java

package spms.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import spms.context.ApplicationContext;

public class ContextLoaderListener implements ServletContextListener {
	//datasource의 장점  커넥션풀을 톰캣에서 지원해주기 때문에 
	//개발자가 직접  connection pool 객체를 만들어 줄 필요가 없다
	//BasicDataSource ds;
	static ApplicationContext applicationContext;
	
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}
	
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//웹 어플리케이션이 실행되면 자동으로 DB커넥션 생성 및 MemeberDAO객체 생성
		try {
			System.out.println("contextInitialized");
			ServletContext sc = sce.getServletContext();
			
			String propertiesPath = sc.getRealPath(sc.getInitParameter("contextConfigLocation"));
			
			applicationContext = new ApplicationContext(propertiesPath);
			
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		try {
			System.out.println("contextDestroyed");
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

 

4) web.xml

  <!-- 컨텍스트 초기화 매개변수 -->
  <context-param>
  	<param-name>contextConfigLocation</param-name>
  	<param-value>/WEB-INF/application-context.properties</param-value>
  </context-param>

 

5) DispatcherServlet.java 수정

//ServletContext sc = this.getServletContext();를 아래 코드로 변경
ApplicationContext ctx = ContextLoaderListener.getApplicationContext();
 
//Controller pageController = (Controller)sc.getAttribute(servletPath);를 아래 코드로 변경
Controller pageController = (Controller)ctx.getBean(servletPath);

 

6) 실행 화면

- 로그인, 로그아웃, 회원등록, 회원수정, 회원삭제 잘 되는지 확인

 

반응형

댓글