MVC - VO & Controller 자동화

jinkyung·2021년 1월 22일
0

JSP

목록 보기
18/20

VO 자동화

객체를 만들어야하는 update, add, login, delete 컨트롤러 코드수정

DataBinding interface

package spms.bind;

public interface DataBinding {
	// 자동으로 생성해야 할 VO객체 항목을 반환
	public Object[] getDataBinders();
}

MemberAddController

package spms.controls;

import java.util.Map;

import spms.bind.DataBinding;
import spms.dao.MemberDao;
import spms.vo.Member;

public class MemberAddController implements Controller, DataBinding {
	
	MemberDao memberDao = null;
	
	public MemberAddController setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
		return this;
	}
	
	@Override
	public Object[] getDataBinders() {
		
		// key값 이름, 자동으로 생성해야 할 클래스 타입 
		return new Object[] {
				"member", spms.vo.Member.class
		};
		
	}
	

	@Override
	public String execute(Map<String, Object> model) throws Exception {
//자동화때문에 객체를 무조건 만들기때문에 객체가 있냐 없냐로 판단하면 안된다. 
// 그래서 객체의 정보로 판단할 수 있도록 수정했다. 
//		if(model.get("member") == null) {
//			return "/member/MemberForm.jsp";
//		}
		
		
		Member member = (Member)model.get("member");
		
		if(member.getEmail() == null) {
			return "/member/MemberForm.jsp";
		}else {
			memberDao.insert(member);
			
			return "redirect:list.do";
		}
	}



}

LogInController

package spms.controls;

import java.util.Map;

import javax.servlet.http.HttpSession;

import spms.bind.DataBinding;
import spms.dao.MemberDao;
import spms.dao.MySqlMemberDao;
import spms.vo.Member;

public class LogInController implements Controller, DataBinding {
	
	MemberDao memberDao = null;
	
	public LogInController setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
		return this;
	}

	@Override
	public String execute(Map<String, Object> model) throws Exception {
		Member loginInfo = (Member)model.get("loginInfo");
		if(loginInfo.getEmail() == null) {
			return "/auth/LogInForm.jsp";
		}else {
			
			Member member = memberDao.exist(loginInfo.getEmail(), 
											loginInfo.getPassword());
			if(member != null) {
				HttpSession session = (HttpSession)model.get("session");
				session.setAttribute("member", member);
				return "redirect:../member/list.do";
			}else {
				return "/auth/LogInFail.jsp";
			}
		}
	}

	@Override
	public Object[] getDataBinders() {
		return new Object[] {
				"loginInfo", spms.vo.Member.class
		};
	}

}

MemberUpdateController

package spms.controls;

import java.util.Map;

import spms.bind.DataBinding;
import spms.dao.MemberDao;
import spms.vo.Member;

public class MemberUpdateController implements Controller, DataBinding {
	
	MemberDao memberDao = null;
	
	public MemberUpdateController setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
		return this;
	}

	@Override
	public String execute(Map<String, Object> model) throws Exception {
		Member member = (Member)model.get("member");
		
		if(member.getEmail() == null) {
			Integer no = (Integer)model.get("no");
			Member detailInfo = memberDao.selectOne(no);
			model.put("member", detailInfo);
			return "/member/MemberUpdateForm.jsp";
			
		}else {
			memberDao.update(member);
			return "redirect:list.do";
		}
	}

	@Override
	public Object[] getDataBinders() {
		
		//어떤 클래스가 생성될 것이냐 하는 정보를 return 
		return new Object[] {
				"no", Integer.class,
				"member", spms.vo.Member.class
		};
	}

}

MemberDeleteController

package spms.controls;

import java.util.Map;

import spms.bind.DataBinding;
import spms.dao.MemberDao;

public class MemberDeleteController implements Controller, DataBinding {
	
	MemberDao memberDao = null;
	
	public MemberDeleteController setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
		return this;
	}

	@Override
	public String execute(Map<String, Object> model) throws Exception {
		
		Integer no = (Integer)model.get("no");
		memberDao.delete(no);
		
		return "redirect:list.do";
	}

	@Override
	public Object[] getDataBinders() {
		return new Object[] {
				"no", Integer.class
		};
	}

}

ContextLoaderListener

package spms.listeners;

import java.sql.Connection;
import java.sql.DriverManager;

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.controls.*;
import spms.dao.MySqlMemberDao;
import spms.util.DBConnectionPool;

/* JNDI
 * WAS(Web Application Server)의 리소스(자원)에 대한 고유 이름 정의
 * 어플리케이션에서 서버의 리소스를 접근할 때 사용하는 명명 규칙
 * 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 연결 객체
 * */

public class ContextLoaderListener implements ServletContextListener {
	

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		try {
			System.out.println("contextDestroyed");
			// 우리가 DataSource 객체를 해제하지 않아도
			// tomcat 서버가 알아서 해제한다
		}catch(Exception e) {
			e.printStackTrace();
		}		

	}

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		try {
			System.out.println("contextInitialized");
			ServletContext sc = sce.getServletContext();

			// tomcat서버가 자동으로 생성하고 해제하는 DataSource객체를
			// 찾아서 가져온다
			InitialContext initialContext = new InitialContext();
			DataSource ds = (DataSource)initialContext.lookup(
					"java:comp/env/jdbc/studydb");
			
			MySqlMemberDao memberDao = new MySqlMemberDao();
			memberDao.setDataSource(ds);	
			
			// Controller 객체를 '서블릿 컨텍스트' 영역에 저장한다
			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));
			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();
		}
	}

}

DispatcherServlet

if 문 => 모두 객체를 만드는 것이었는데 이제는 자동으로 만들 것이니까 모두 지운다.


package spms.servlets;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import spms.bind.DataBinding;
import spms.bind.ServletRequestDataBinders;
import spms.controls.Controller;

@WebServlet("*.do")
@SuppressWarnings("serial")
public class DispatcherServlet extends HttpServlet {

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		response.setContentType("text/html;charset=UTF-8");

		// pageController에 분기 전송을 위해 '서블릿 경로'를 얻는다
		String servletPath = request.getServletPath();

		try {
			ServletContext sc = this.getServletContext();
			// '서블릿 경로'와 일치하는 객체를 '서블릿 컨텍스트'공유 공간에서 꺼낸다.
			Controller pageController = 
					(Controller)sc.getAttribute(servletPath);
			
			HashMap<String, Object> model = new HashMap<String, Object>();
			model.put("session", request.getSession());
			
			
			// pageController 객체가 DataBinding의 상속을 받았다면
			// => 만들어야할 객체가 존재하는 pageController라면 
			// 데이터 바인딩 타입이니? 혹은 데이터 바인딩을 상속받은 객체니?
			if(pageController instanceof DataBinding) {
				
				// pageController에 전달할 객체를 생성하여 
				// 브라우저가 보내는 Parameter를 객체에 자동 주입하여 
				// model 객체에 저장한다
				//=>브라우저가 요청하는 데이터를 담아서 model에 저장한다 
				preparedRequestData(request, model, (DataBinding)pageController);
			}

			String viewUrl = "";
			// POJO Page Controller
			if (pageController != null) {
				viewUrl = pageController.execute(model);
                // ex) update의 경우 업데이트된 member 객체를 request 공간에 set하여 정보를 가져다 쓸 수 있도록 한다.
				for (String key : model.keySet())
					request.setAttribute(key, model.get(key));
			}else {
				System.out.println("주소 대상 Controller가 없습니다!");
			}

			// 경로가 'redirect:'로 시작하면 바로 이동
			if (viewUrl.startsWith("redirect:")) {
				response.sendRedirect(viewUrl.substring(9));
				return;
			} else {
				RequestDispatcher rd = request.getRequestDispatcher(viewUrl);
				rd.include(request, response);
			}

		} catch (Exception e) {
			e.printStackTrace();

			request.setAttribute("error", e);
			RequestDispatcher rd = request.getRequestDispatcher("/Error.jsp");
			rd.forward(request, response);
		}
	}
	
	private void preparedRequestData(HttpServletRequest request,
									HashMap<String, Object> model,
									DataBinding dataBinding)
									throws Exception{
		//pageController마다 자기가 생성한 객체를 리턴 
		Object[] dataBinders = dataBinding.getDataBinders();
		String dataName = null; 	//key 이름 
		Class<?> dataType = null;	//생성할 객체의 클래스 정보 
		Object dataObj = null;		//생선한 객체 
		
		//넘겨주는 dataBinding에는 pageController의 객체가 담기는데, 2개가 짝이니까 i+2 
		for(int i=0; i<dataBinders.length; i+=2) {
			dataName = (String)dataBinders[i];		
			dataType = (Class<?>)dataBinders[i+1];
			
			/*
			request : 매개변수 추출을 위해 필요
			dataType : 객체를 생성할 클래스 타입 
			dataNmae : 매개변수 이름 
			*/
			
			//객체를 생성해준다 
			//request의 getparameter로 정보를 꺼내야 하기 때문에 매개변수로 주었다. 
			dataObj = ServletRequestDataBinders.bind(request, dataType, dataName);
			//만들어진 객체를 model에 저장한다 
			model.put(dataName,dataObj);
		}
	}
}

reflection 방식으로 객체 생성
해당 객체의 field목록과 method 목록도 얻어낼 수 있다.

ServletRequestDataBinders

invoke = 메서드 호출

package spms.bind;

import java.lang.reflect.Method;
import java.sql.Date;
import java.util.Set;

import javax.servlet.ServletRequest;

public class ServletRequestDataBinders {
	/* request : 매개변수 추출
	 * dataType : 클래스 타입으로 객체 생성
	 * dataName : 매개변수 이름
	 * */
	public static Object bind(ServletRequest request,
							 Class<?> dataType,
							 String dataName) throws Exception {
		// 생성해야 할 대상이 PrimitiveType일 경우
		if(isPrimitiveType(dataType)) {
			return createValueObject(dataType, 
						request.getParameter(dataName));
		}
		// 일반 vo객체일 경우
		else {
			// 브라우저가 보낸 매개변수들의 이름을 Set에 담는다.
			Set<String> paramNames = request.getParameterMap().keySet();
			// 클래스 타입대로 객체 생성
			Object dataObject = dataType.newInstance();
			// 브라우저가 보낸 매개변수를 객체의 필드를 찾아서 저장
			Method m = null;	// VO의 Setter를 찾아서 저장
			// 브라우저가 보낸 매개변수를 1개씩 접근한다
			for(String paramName : paramNames) {
				// 매개변수에 해당하는 Setter 얻기
				/* ex) no 매개변수 -> m은 setNo를 가리킨다
				 *     email -> m은 setEmail를 가리킨다
				 *     password -> m은 setPassword를 가리킨다
				 * */
				m = findSetter(dataType, paramName);
				if(m != null) {
					// dataObject객체의 m메서드를 호출한다
					// 첫번째 매개변수에 클라이언트의 매개변수값을 대입
					m.invoke(dataObject, 
							createValueObject(m.getParameterTypes()[0],
							request.getParameter(paramName)));
				}
			}
			// 브라우저가 보낸 변수값까지 객체에 저장한 후 생성한 객체를 리턴
			return dataObject;
		}		
	}
	
	private static Method findSetter(Class<?> type, String name) {
		// 해당 클래스 타입이 가진 모든 메서드를 추출
		Method[] methods = type.getMethods();
		
		String propName = null;
		for(Method m : methods) {
			// 메서드 시작이름이 set이 아니면 돌아가라
			if(!m.getName().startsWith("set"))
				continue;
			
			// set을 제외한 이름 ==> 프로퍼티
			propName = m.getName().substring(3);
			propName = propName.toLowerCase();	// 소문자로
			// name으로 넘겨준 이름과 같으면  => 해당 프로퍼티의 setter를 찾았다
			if(propName.equals(name.toLowerCase())) {
				return m;
			}
		}
		return null;
	}
	
	private static boolean isPrimitiveType(Class<?> type) {
		if(type.getName().equals("int") || type==Integer.class ||
			type.getName().equals("long") || type==Long.class ||
			type.getName().equals("float") || type==Float.class ||
			type.getName().equals("double") || type==Double.class ||
			type.getName().equals("boolean") || type==Boolean.class ||
			type==Date.class || type==String.class) {
			return true;
		}
		return false;
	}
	
	private static Object createValueObject(Class<?> type, String value) {
		if(type.getName().equals("int") || type==Integer.class)
			return new Integer(value);
		else if(type.getName().equals("float") || type==Float.class)
			return new Float(value);
		else if(type.getName().equals("double") || type==Double.class)
			return new Double(value);
		else if(type.getName().equals("long") || type==Long.class)
			return new Long(value);
		else if(type.getName().equals("boolean") || type==Boolean.class)
			return new Boolean(value);
		else if(type==Date.class)
			return java.sql.Date.valueOf(value);
		else
			return value;
	}
}

controller 자동화

ex) key /auth/login.do = valueLogInController (이 객체가 담당한다)

#1. for ApplicationContext
# 1) [tomcat Object] 
jndi.dataSource=java:comp/env/jdbc/studydb
# 2) [General Object]
memberDao=spms.dao.MySqlMemberDao
# 3) [Page Controller Object]
/auth/login.do=spms.controls.LogInController
/auth/logout.do=spms.controls.LogOutController
/member/list.do=spms.controls.MemberListController
/member/add.do=spms.controls.MemberAddController
/member/update.do=spms.controls.MemberUpdateController
/member/delete.do=spms.controls.MemberDeleteController

ApplicationContext

dependency = findObjectByType(m.getParameterTypes()[0]);
: memberDao 타입으로 만들어진 obj=MemberSqlDao, ProjectDao이고 그것을 return하여 dependency에 넣는다.
m.invoke(obj, dependency); obj(Controller)에 MemberDao의 객체를 set시킨다.

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;


/*
application-context.properties 파일을 읽어서 
파일에 등록된 객체를 생성하는 역할 
*/

public class ApplicationContext {
	// url : 대응객체 Hashtable (이 주소로 들어오면 이 객체를 줘라 )
	// servlet 주소, pageController 객체 
	Hashtable<String, Object> objTable = new Hashtable<String, Object>();
	
	public Object getBean(String key) {
		//key(주소)에 대응하는 page컨트롤러 객체 리턴 
		return objTable.get(key);
	}
	
	//생성자 
	public ApplicationContext(String propertiesPath) throws Exception{
		
		// 프로퍼티 파일 목록 읽어오기 
		Properties props = new Properties();
		props.load(new FileReader(propertiesPath));
		
		prepareObjects(props);	//객체 생성 
		
		//listener에서 setMemberDao로 주입하던 것을 
		injectDependency();		//memberDao 객체를 pageController에 주입 
		
	}
	
	private void prepareObjects(Properties props) throws Exception{
		Context ctx = new InitialContext();
		String key = null;
		String value = null;
		
		for(Object item : props.keySet()) {
			key = (String)item;
			value = props.getProperty(key);
			
			// tomcat의 DataSource객체를 찾아서 저장 
			if(key.startsWith("jndi.")) {
            //key = jndi.dataSource / value = java:comp/env/jdbc/studydb
				objTable.put(key, ctx.lookup(value));
			} else{
				// 나머지 클래스들은 직접 객체를 생성한다 
				objTable.put(key, Class.forName(value).newInstance());
			}
		}
		
	}
	
	private void injectDependency() throws Exception{
		for(String key : objTable.keySet()) {
			if(!key.startsWith("jndi.")) {
				// 객체를 callSetter 
				callSetter(objTable.get(key));
			}
		}
	}
	
	private void callSetter(Object obj) throws Exception{
		Object dependency = null;
		// obj에는 properties의 객체들이 저장되어 있음. 거기서 setter메서드를 찾기 
		for(Method m : obj.getClass().getMethods()) {
			//메서드 중에 Setter를 찾아라 
			/*DAO 객체는 set으로 시작하는 메서드가 없으므로 
			주입 대상이 되지 않는다 
			pageController 객체만 setter가 있다 
			*/
			if(m.getName().startsWith("set")) {
				/* 현재 objTable로부터 첫번째 매개변수에 해당하는 클래스 객체를 찾아라 
 					즉 DAO 객체를 찾아라 
					dependency == MemberDao 객체 
				*/
				dependency = findObjectByType(m.getParameterTypes()[0]);
				if(dependency != null) {
					// ex) 로그인컨트롤러, memberDao
				m.invoke(obj, dependency);
			}
		}
	}
	}
	
	private Object findObjectByType(Class<?> type) {
		for(Object obj : objTable.values()) {
//			type 클래스로 obj가 만들어진 것이라면 리턴 
			if(type.isInstance(obj))
				return obj;
		}
		return null;
	}
}
	

ContextLoaderListener

package spms.listeners;

import java.sql.Connection;
import java.sql.DriverManager;

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.context.ApplicationContext;
import spms.controls.*;
import spms.dao.MemberDao;
import spms.dao.MySqlMemberDao;
import spms.util.DBConnectionPool;

/* JNDI
 * WAS(Web Application Server)의 리소스(자원)에 대한 고유 이름 정의
 * 어플리케이션에서 서버의 리소스를 접근할 때 사용하는 명명 규칙
 * 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 연결 객체
 * */

public class ContextLoaderListener implements ServletContextListener {
	
	//외부에서 바로 가져다 쓸 수 있도록 static (편의성)
	static ApplicationContext applicationContext;
	
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		try {
			System.out.println("contextDestroyed");
			// 우리가 DataSource 객체를 해제하지 않아도
			// tomcat 서버가 알아서 해제한다
		}catch(Exception e) {
			e.printStackTrace();
		}		

	}

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		try {
			System.out.println("contextInitialized");
			ServletContext sc = sce.getServletContext();

			String propertiesPath = sc.getRealPath(
					sc.getInitParameter("contextConfigLocation"));
                    
			// propertiesPath : properties파일의 경로
			// 생성자에 넣음으로써 ApplicationContext에서는 파일에 등록된 객체들을 생성할 것이다. 
			applicationContext = new ApplicationContext(propertiesPath);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

web.xml

: contextConfigLocation 추가

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>_12_JDBCServlet_ServletInitParam</display-name>
  
  
  <!-- 컨텍스트 초기화 매개변수(모든 서블릿에서 공유가능) -->

  	<context-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>/WEB-INF/application-context.properties</param-value>
  	</context-param>
 
  	
<!-- 웹 어플리케이션 시작/종료 이벤트 리스너  -->

<listener>
	<listener-class>spms.listeners.ContextLoaderListener</listener-class>
</listener>


<!-- Servlet 2.4이전에는 '컨텍스트 초기화 매개변수' 다음에 위치해야 한다 
			 2.4 이상부터는 순서에 관계 없다  -->
  <!--  필터 설정  -->
  <filter>
  	<filter-name>CharacterEncodingFilter</filter-name>
  	<filter-class>spms.filters.CharacterEncodingFilter</filter-class>
  	<init-param>
  		<param-name>encoding</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  </filter>
 
 
 <!-- 모든 주소에 필터를 적용 --> 
  <filter-mapping>
  	<filter-name>CharacterEncodingFilter</filter-name>
  	<!-- 들어오는 모든 것에 이 필터를 적용 (특정 주소만 주면 그 주소만 ) -->
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <!-- tomcat 서버의 DataSource를 사용하기 위한 설정  -->
  <!-- DataSource 설정은 Servers/context.xml에 있음   -->
  <resource-ref>
  	<res-ref-name>jdbc/studydb</res-ref-name>
  	<res-type>javax.sql.DataSource</res-type>
  	<res-auth>Container</res-auth>
  </resource-ref>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>

controller, jsp 만들고 properties 만들기
class 만들 때 setter 메서드(Dao)는 꼭 만들어주기

0개의 댓글